Cuando se trata del control de la congestión de la red, es posible que esté familiarizado con los conceptos de "aumento aditivo", "disminución multiplicativa", "inicio lento", "evitación de la congestión", "retransmisión rápida" y "recuperación rápida" en ensayos estereotipados. Sí, esta es la teoría básica de un algoritmo de control de congestión de red clásico, pero en la implementación real, los diferentes algoritmos de control de congestión son muy diferentes. Este artículo aprende el marco de implementación específico del algoritmo de control de congestión de red del código fuente del kernel de Linux . Desde la perspectiva del desarrollo actual de los algoritmos de control de congestión de red, existen principalmente cuatro tipos de algoritmos de control de congestión de red :
- Algoritmos de control de congestión basados en pérdida de paquetes, que consideran la pérdida de paquetes como congestión de la red. Tome un método de detección lento, aumente gradualmente la ventana de congestión y reduzca la ventana de congestión cuando se produzca una pérdida de paquetes. Los algoritmos representativos incluyen Tahoe, Reno, NewReno, BIC y Cubic.
- Algoritmos de control de congestión basados en retrasos, este tipo de algoritmo considera el aumento del retraso como congestión de la red, reduce la ventana de congestión cuando aumenta el retraso y aumenta la ventana de congestión cuando disminuye el retraso.Algoritmos representativos incluyen Vegas y Westwood.
- El algoritmo de control de congestión basado en la capacidad del enlace, el algoritmo representativo es BBR, que adopta un método alternativo, ya no utiliza señales como la pérdida de paquetes y el retraso para medir si se produce congestión, sino que modela directamente la red para evitar y tratar la congestión real Red congestión.
- Algoritmos de control de congestión basados en aprendizaje, este tipo de algoritmo no tiene una señal de congestión específica, generalmente se basa en datos de entrenamiento y funciones de evaluación, y genera un modelo de estrategia de control de congestión de red a través de aprendizaje automático, los algoritmos representativos incluyen Remy, PCC, Aurora, DRL -CC, Orca espera.
Dado que el concepto central de cada tipo de algoritmo de control de congestión es muy diferente, la implementación y el principio de cada algoritmo se presentarán en artículos posteriores. Este artículo primero analiza y estudia aproximadamente los detalles de implementación y el marco general del control de congestión de red en el kernel de Linux . Antes de realizar un análisis formal , clasifiquemos brevemente el sentido común y los conceptos:
- ¿Qué es la congestión de la red ? La congestión de la red se refiere al fenómeno en el que la cantidad de datos transmitidos en la red excede la capacidad de procesamiento de los enlaces o nodos de la red, lo que resulta en un mayor retraso de la red, una mayor tasa de pérdida de paquetes y una menor utilización del ancho de banda.
- Ventana (Ventana) : El encabezado del protocolo TCP, como se muestra en la siguiente figura, ocupa 16 bits, que utiliza el extremo receptor para indicar al extremo emisor cuántos búferes están disponibles para recibir datos.
- Ventana deslizante, ventana de envío: el cuadro negro que se muestra en la siguiente figura representa la ventana de envío. La ventana deslizante es solo un nombre visual, es decir, la ventana de envío se mueve todo el tiempo para lograr el propósito de enviar nuevos datos.Como se muestra en la figura a continuación, cuando se recibe el paquete de datos ACK enviado por el extremo receptor, el envío ventana se mueve a la derecha. El recuadro gris de la figura representa los datos que han sido enviados y confirmados, y el rojo representa los datos que han sido enviados y recién confirmados, es precisamente porque se acaba de confirmar el dato de 5 bytes que se puede manejar la ventana de envío. para mover 5 unidades a la derecha, haciendo que el número de serie sea 52~ Los datos de 56 (recuadro verde, que representa los datos a enviar que se pueden enviar) pueden enviarse Cuando los datos están en el rango de 37~51 (cuadro azul, representa el paquete de datos enviado pero no confirmado) se puede confirmar, la ventana de envío se puede enviar a la derecha de Swipe. Los datos delante de la ventana de envío (cuadro amarillo, datos a enviar que no se pueden enviar) solo se pueden enviar después de esperar el intervalo de la ventana de envío. La ventana deslizante de TCP es dinámica, podemos imaginarla como un problema matemático común en la escuela primaria, una piscina con volumen V, entrada de agua V1 por hora y salida de agua V2. Cuando la piscina está llena, no se permite más inyección.Si hay un sistema hidráulico para controlar el tamaño de la piscina, entonces se puede controlar la velocidad y la cantidad de inyección de agua. Tal grupo es similar a una ventana TCP. De acuerdo con el cambio de su propia capacidad de procesamiento, la aplicación restringe el tráfico de la ventana de envío del extremo del par a través del control del tamaño de la ventana de recepción TCP del extremo local.
- Congestion window : El concepto de la ventana de envío se introdujo anteriormente.En el protocolo TCP, existe una variable que refleja la capacidad de transmisión de la red, llamada congestion window (ventana de congestión), denotada como cwnd. El tamaño real de la ventana de envío del remitente es en realidad el valor más pequeño de la ventana anunciada rwnd y la ventana de congestión cwnd para el receptor.
W=min(cwnd,rwnd)
A partir de los conceptos anteriores, podemos saber que la ventana de congestión puede reflejar indirectamente el estado de la red y luego limitar el tamaño de la ventana de envío. Como una de las variables centrales en el control de la congestión de la red, la ventana de congestión juega un papel clave en el control de la congestión de la red. La siguiente figura muestra las cuatro estructuras centrales. La relación entre las cuatro estructuras tiene características orientadas a objetos. A través de la herencia capa por capa, se realiza la reutilización de clases; muchas funciones relacionadas con la red en el kernel a menudo usan struct sock como parámetros, y las funciones se basan internamente en Diferente lógica comercial convierte struct sock en diferentes estructuras comerciales.
struct tcp_sock
struct inet_connection_sock
Heredados de la estructura, struct inet_connection_sock
se le añaden algunos campos relacionados con el protocolo tcp, como el protocolo de ventana deslizante, el algoritmo de congestión y otros atributos específicos de TCP. Debido a esta relación de herencia, se pueden convertir entre sí. Los siguientes dos métodos de conversión se dan como ejemplos. El primero es convertir struct sock en struct tcp_sock, y el segundo es convertir struct sock en struct inet_connection_sock. Expanda la estructura tcp_sock a continuación para ver los campos relacionados con el control de congestión de la red.
static inline struct tcp_sock *tcp_sk(const struct sock *sk)
{
return (struct tcp_sock *)sk;
}
static inline struct inet_connection_sock *inet_csk(const struct sock *sk)
{
return (struct inet_connection_sock *)sk;
}
Los campos relacionados con el control de congestión de la red definidos en la estructura tcp_sock son los siguientes:
struct tcp_sock {//在 inet_connection_sock 基础上增加了 滑动窗口 拥塞控制算法等tcp 专有 属性
__be32 pred_flags;/*首部预测标志 在接收到 syn 跟新窗口 等时设置此标志 ,
此标志和时间戳 序号等 用于判断执行 快速还是慢速路径*/
u64 bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived
* sum(delta(rcv_nxt)), or how many bytes
* were acked.
*/
u32 segs_in; /* RFC4898 tcpEStatsPerfSegsIn
* total number of segments in.
*/
u32 rcv_nxt; /* What we want to receive next 等待接收的下一个序列号 */
u32 copied_seq; /* Head of yet unread data */
/* rcv_nxt on last window update sent最早接收但没有确认的序号, 也就是接收窗口的左端,
在发送ack的时候, rcv_nxt更新 因此rcv_wup 更新比rcv_nxt 滞后一些 */
u32 rcv_wup;
u32 snd_nxt; /* Next sequence we send 等待发送的下一个序列号 */
u32 segs_out; /* RFC4898 tcpEStatsPerfSegsOut
* The total number of segments sent.
*/
u64 bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked
* sum(delta(snd_una)), or how many bytes
* were acked.
*/
struct u64_stats_sync syncp; /* protects 64bit vars (cf tcp_get_info()) */
u32 snd_una; /* First byte we want an ack for 最早一个未被确认的序号 */
u32 snd_sml; /* Last byte of the most recently transmitted small packet 最近发送一个小于mss的最后 一个字节序列号
在成功发送, 如果报文小于mss,跟新这个字段 主要用来判断是否启用 nagle 算法*/
u32 rcv_tstamp; /* timestamp of last received ACK (for keepalives) 最近一次收到ack的时间 用于 tcp 保活*/
u32 lsndtime; /* timestamp of last sent data packet (for restart window) 最近一次发送 数据包时间*/
u32 last_oow_ack_time; /* timestamp of last out-of-window ACK */
u32 tsoffset; /* timestamp offset */
struct list_head tsq_node; /* anchor in tsq_tasklet.head list */
unsigned long tsq_flags;
/* Data for direct copy to user cp 数据到用户进程的控制块 有用户缓存以及其长度 prequeue 队列 其内存*/
struct {
struct sk_buff_head prequeue // tcp 段 缓冲到此队列 知道进程主动读取才真正的处理;
struct task_struct *task;
struct msghdr *msg;
int memory;// prequeue 当前消耗的内存
int len;// 用户缓存中 当前可以使用的缓存大小
} ucopy;
u32 snd_wl1; /* Sequence for window update记录跟新发送窗口的那个ack 段号 用来判断是否 需要跟新窗口
如果后续收到ack大于snd_wll 则表示需要更新 窗口*/
u32 snd_wnd; /* The window we expect to receive 接收方 提供的窗口大小 也就是发送方窗口大小 */
u32 max_window; /* Maximal window ever seen from peer 接收方通告的最大窗口 */
u32 mss_cache; /* Cached effective mss, not including SACKS 发送方当前有效的mss*/
u32 window_clamp; /* Maximal window to advertise 滑动窗口最大值 */
u32 rcv_ssthresh; /* Current window clamp 当前接收窗口的阈值 */
......
u32 snd_ssthresh; /* Slow start size threshold 拥塞控制 满启动阈值 */
u32 snd_cwnd; /* Sending congestion window 当前拥塞窗口大小 ---发送的拥塞窗口 */
u32 snd_cwnd_cnt; /* Linear increase counter 自从上次调整拥塞窗口后 到目前位置接收到的
总ack段数 如果该字段为0 表示调整拥塞窗口但是没有收到ack,调整拥塞窗口之后 收到ack段就回让
snd_cwnd_cnt 加1 */
u32 snd_cwnd_clamp; /* Do not allow snd_cwnd to grow above this snd_cwnd 的最大值*/
u32 snd_cwnd_used;//记录已经从队列发送而没有被ack的段数
u32 snd_cwnd_stamp;//记录最近一次检验cwnd 的时间; 拥塞期间 每次会检验cwnd而调节拥塞窗口 ,
//在非拥塞期间,为了防止应用层序造成拥塞窗口失效 因此在发送后 有必要检测cwnd
u32 prior_cwnd; /* Congestion window at start of Recovery.在进入 Recovery 状态时的拥塞窗口 */
u32 prr_delivered; /* Number of newly delivered packets to在恢复阶段给接收者新发送包的数量
* receiver in Recovery. */
u32 prr_out; /* Total number of pkts sent during Recovery.在恢复阶段一共发送的包的数量 */
u32 rcv_wnd; /* Current receiver window 当前接收窗口的大小 */
u32 write_seq; /* Tail(+1) of data held in tcp send buffer 已加入发送队列中的最后一个字节序号*/
u32 notsent_lowat; /* TCP_NOTSENT_LOWAT */
u32 pushed_seq; /* Last pushed seq, required to talk to windows */
u32 lost_out; /* Lost packets丢失的数据报 */
u32 sacked_out; /* SACK'd packets启用 SACK 时,通过 SACK 的 TCP 选项标识已接收到的段的数量。
不启用 SACK 时,标识接收到的重复确认的次数,该值在接收到确认新数据段时被清除。 */
u32 fackets_out; /* FACK'd packets FACK'd packets 记录 SND.UNA 与 (SACK 选项中目前接收方收到的段中最高序号段) 之间的段数。FACK
用 SACK 选项来计算丢失在网络中上的段数 lost_out=fackets_out-sacked_out left_out=fackets_out */
/* from STCP, retrans queue hinting */
struct sk_buff* lost_skb_hint; /*在重传队列中, 缓存下次要标志的段*/
struct sk_buff *retransmit_skb_hint;/* 表示将要重传的起始包*/
/* OOO segments go in this list. Note that socket lock must be held,
* as we do not use sk_buff_head lock.
*/
struct sk_buff_head out_of_order_queue;
/* SACKs data, these 2 need to be together (see tcp_options_write) */
struct tcp_sack_block duplicate_sack[1]; /* D-SACK block */
struct tcp_sack_block selective_acks[4]; /* The SACKS themselves*/
struct tcp_sack_block recv_sack_cache[4];
struct sk_buff *highest_sack; /* skb just after the highest
* skb with SACKed bit set
* (validity guaranteed only if
* sacked_out > 0)
*/
int lost_cnt_hint;/* 已经标志了多少个段 */
u32 retransmit_high; /* L-bits may be on up to this seqno 表示将要重传的起始包 */
u32 prior_ssthresh; /* ssthresh saved at recovery start表示前一个snd_ssthresh得大小 */
u32 high_seq; /* snd_nxt at onset of congestion拥塞开始时,snd_nxt的大----开始拥塞的时候下一个要发送的序号字节*/
u32 retrans_stamp; /* Timestamp of the last retransmit,
* also used in SYN-SENT to remember stamp of
* the first SYN. */
u32 undo_marker; /* snd_una upon a new recovery episode. 在使用 F-RTO 算法进行发送超时处理,或进入 Recovery 进行重传,
或进入 Loss 开始慢启动时,记录当时 SND.UNA, 标记重传起始点。它是检测是否可以进行拥塞控制撤销的条件之一,一般在完成
拥塞撤销操作或进入拥塞控制 Loss 状态后会清零。*/
int undo_retrans; /* number of undoable retransmissions. 在恢复拥塞控制之前可进行撤销的重传段数。
在进入 FTRO 算法或 拥塞状态 Loss 时,清零,在重传时计数,是检测是否可以进行拥塞撤销的条件之一。*/
u32 total_retrans; /* Total retransmits for entire connection */
u32 urg_seq; /* Seq of received urgent pointer 紧急数据的序号 所在段的序号和紧急指针相加获得*/
unsigned int keepalive_time; /* time before keep alive takes place */
unsigned int keepalive_intvl; /* time interval between keep alive probes */
int linger2;
/* Receiver side RTT estimation */
struct {
u32 rtt;
u32 seq;
u32 time;
} rcv_rtt_est;
/* Receiver queue space */
struct {
int space;
u32 seq;
u32 time;
} rcvq_space;
/* TCP-specific MTU probe information. */
struct {
u32 probe_seq_start;
u32 probe_seq_end;
} mtu_probe;
u32 mtu_info; /* We received an ICMP_FRAG_NEEDED / ICMPV6_PKT_TOOBIG
* while socket was owned by user.
*/
#ifdef CONFIG_TCP_MD5SIG
const struct tcp_sock_af_ops *af_specific;
struct tcp_md5sig_info __rcu *md5sig_info;
#endif
struct tcp_fastopen_request *fastopen_req;
struct request_sock *fastopen_rsk;
u32 *saved_syn;
};
Información a través del tren: ruta de aprendizaje de la tecnología del código fuente del kernel de Linux + video tutorial del código fuente del kernel
Aprendizaje a través del entrenamiento: código fuente del kernel de Linux, ajuste de memoria, sistema de archivos, gestión de procesos, controlador de dispositivo/pila de protocolo de red
Veamos un marco particularmente importante, que también se puede llamar motor de control de congestión.Como se muestra en la siguiente estructura, tcp_congestion_ops describe las operaciones que un conjunto de algoritmos de control de congestión necesita admitir . Este marco define algunas funciones de enlace.Diferentes algoritmos de control de congestión en el kernel de Linux implementan las siguientes funciones de enlace de acuerdo con la idea del algoritmo y luego se registran para completar el diseño del algoritmo de control de congestión.
struct tcp_congestion_ops {
struct list_head list;
u32 key;
u32 flags;
/* initialize private data (optional) */
void (*init)(struct sock *sk);
/* cleanup private data (optional) */
void (*release)(struct sock *sk);
/* return slow start threshold (required) */
u32 (*ssthresh)(struct sock *sk);
/* do new cwnd calculation (required) */
void (*cong_avoid)(struct sock *sk, u32 ack, u32 acked);
/* call before changing ca_state (optional) */
void (*set_state)(struct sock *sk, u8 new_state);
/* call when cwnd event occurs (optional) */
void (*cwnd_event)(struct sock *sk, enum tcp_ca_event ev);
/* call when ack arrives (optional) */
void (*in_ack_event)(struct sock *sk, u32 flags);
/* new value of cwnd after loss (required) */
u32 (*undo_cwnd)(struct sock *sk);
/* hook for packet ack accounting (optional) */
void (*pkts_acked)(struct sock *sk, const struct ack_sample *sample);
/* suggest number of segments for each skb to transmit (optional) */
u32 (*tso_segs_goal)(struct sock *sk);
/* returns the multiplier used in tcp_sndbuf_expand (optional) */
u32 (*sndbuf_expand)(struct sock *sk);
/* call when packets are delivered to update cwnd and pacing rate,
* after all the ca_state processing. (optional)
*/
void (*cong_control)(struct sock *sk, const struct rate_sample *rs);
/* get info for inet_diag (optional) */
size_t (*get_info)(struct sock *sk, u32 ext, int *attr,
union tcp_cc_info *info);
char name[TCP_CA_NAME_MAX];
struct module *owner;
};
Los usuarios pueden implementar un algoritmo de control de congestión personalizado personalizando las funciones de gancho y el registro anteriores. Lo siguiente intercepta los fragmentos de código de implementación y registro de la interfaz del algoritmo de control de congestión cúbico. Se puede notar que cubic solo implementa parte de las funciones de enlace del motor de control de congestión tcp_congestion_ops, porque algunas funciones de enlace deben implementarse y algunas se seleccionan de acuerdo con el algoritmo.
static struct tcp_congestion_ops cubictcp __read_mostly = {
.init = bictcp_init,
.ssthresh = bictcp_recalc_ssthresh,
.cong_avoid = bictcp_cong_avoid,
.set_state = bictcp_state,
.undo_cwnd = tcp_reno_undo_cwnd,
.cwnd_event = bictcp_cwnd_event,
.pkts_acked = bictcp_acked,
.owner = THIS_MODULE,
.name = "cubic",
};
static int __init cubictcp_register(void)
{
BUILD_BUG_ON(sizeof(struct bictcp) > ICSK_CA_PRIV_SIZE);
beta_scale = 8*(BICTCP_BETA_SCALE+beta) / 3
/ (BICTCP_BETA_SCALE - beta);
cube_rtt_scale = (bic_scale * 10); /* 1024*c/rtt */
cube_factor = 1ull << (10+3*BICTCP_HZ); /* 2^40 */
/* divide by bic_scale and by constant Srtt (100ms) */
do_div(cube_factor, bic_scale * 10);
return tcp_register_congestion_control(&cubictcp);
}
static void __exit cubictcp_unregister(void)
{
tcp_unregister_congestion_control(&cubictcp);
}
module_init(cubictcp_register);
module_exit(cubictcp_unregister);
ley _ La siguiente tabla muestra los dos parámetros y sus significados.
parámetro | significado |
net.ipv4.tcp_congestion_control | El algoritmo de control de congestión actualmente en ejecución |
net.ipv4.tcp_disponible_congestion_control | Algoritmos de control de congestión admitidos actualmente |
Específicamente, como se muestra en la figura a continuación, puede ver el algoritmo de control de congestión admitido actualmente y el algoritmo de control de congestión actualmente utilizado a través de los parámetros. Se puede ver que el algoritmo de control de congestión admitido actualmente incluye el algoritmo bbr, y el algoritmo bbr es compatible a partir de la versión 4.9 del kernel.
Si presta atención, muchos algoritmos de control de congestión tradicionales se mencionaron al principio de este artículo, pero no los vio en el comando anterior. De hecho, hay muchos algoritmos de control de congestión que no se han instalado en Linux. El siguiente comando comprueba todos los implementados en el sistema Linux Módulo de algoritmo de control de congestión :
Si desea instalar un algoritmo de control de congestión específico, puede instalar el algoritmo de control de congestión especificado a través del comando modprobe. El algoritmo de control de congestión de Vegas se instala como se muestra a continuación. En este momento, verifique los algoritmos de control de congestión que se pueden usar en el sistema actual Hay un algoritmo Vegas adicional.
Además de ver dinámicamente los algoritmos de control de congestión disponibles para el sistema Linux actual y los algoritmos de control de congestión utilizados actualmente, también es posible cambiar dinámicamente los algoritmos de control de congestión. Cambie el algoritmo de control de congestión cúbico predeterminado al algoritmo de control de congestión bbr como se muestra a continuación.
Después del cambio, la verificación es la siguiente: el algoritmo de control de congestión actualmente en ejecución se cambia del algoritmo de control de congestión cúbico anterior al algoritmo de control de congestión bbr.