Linux内核网络拥塞控制算法的实现框架(二)

描述

从上面的概念中可以得知,拥塞窗口可以间接反映网络的状况,进而去限制发送窗口的大小。拥塞窗口作为网络拥塞控制中核心变量之一,对网络拥塞控制起到关键作用。在Linux内核中,关于网络的核心结构体在:[Linux内核网络基础-TCP相关的几个关键结构体-小记]中进行了介绍,如下图是四个核心结构体,四个结构的关系具有面向对象的特征,通过层层继承,实现了类的复用;内核中网络相关的很多函数,参数往往都是struct sock,函数内部依照不同的业务逻辑,将struct sock转换为不同的业务结构。

控制算法

struct tcp_sockstruct inet_connection_sock结构体的基础上继承而来,在struct inet_connection_sock上增加了一些tcp协议相关的字段,如滑动窗口协议,拥塞算法等一些TCP专有的属性。由于这种继承关系,可以互相转换,如下举例两种转换方式,第一种是struct sock转换为struct tcp_sock,第二种是struct sock转换成struct inet_connection_sock。。下面将struct tcp_sock展开可以看到与网络拥塞控制相关的字段。

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;
}

struct tcp_sock中定义的关于网络拥塞控制相关的字段如下所示:

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;
};
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分