Red del kernel de Linux - Serie de control de congestión (1)

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_sockstruct inet_connection_sockHeredados de la estructura, struct inet_connection_sockse 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.

 

Supongo que te gusta

Origin blog.csdn.net/youzhangjing_/article/details/131703758
Recomendado
Clasificación