Linux内核 TCP源码详解一:拥塞控制算法(tcp.h tcp_cong.c)

2620阅读 0评论2016-01-29 jiaojinawei
分类:LINUX

关于TCP内核实现的文章和书籍不少,讲解侧重点不一。在内核源码中,注释率普遍在20%以上,大约能看懂。

但是有些关键变量解释的不够详细,对源码流程也没有文档给出。【笔者按:文档应该是有的,没找到】

http://blog.csdn.net/zhangskd/article/details/7043071写的一系列文章都很好,值得佩服。这里在他文章的基础上,做些分析和扩展。

先从数据结构讲起,tcp_sock在整个TCP实现中,所占位置极其重要,因为该结构体包含拥塞窗口、阈值等一系列变量。

在 include/linux/tcp.h中:

  1. static inline struct tcp_sock *tcp_sk(const struct sock *sk)     
  2. {    
  3.         return (struct tcp_sock *)sk ;    
  4. }    
  5.    
这里对传入的sock结构体进行了强制转换,这里做的工作只是将数据类型转换为相应源码便于处理的类型,关于sock的定义,可以查阅sock.h源码。

tcp_sock结构体的关键变量定义:

  1. u32 snd_wl1;    /* Sequence for window update       */  
  2. u32 snd_wnd;    /* The window we expect to receive  */  
  3. u32 max_window; /* Maximal window ever seen from peer   */  
  4. u32 mss_cache;  /* Cached effective mss, not including SACKS */  
  5.   
  6. u32 window_clamp;   /* Maximal window to advertise      */  
  7. u32 rcv_ssthresh;   /* Current window clamp         */  
  1. ·································  
  1.     /* 
  2.    *   Slow start and congestion control (see also Nagle, and Karn & Partridge) 
  3.    */  
  4.    u32 snd_ssthresh;   /* Slow start size threshold        */  
  5.    u32 snd_cwnd;   /* Sending congestion window        */  
  6.     u32 snd_cwnd_cnt;   /* Linear increase counter      */  
  7.     u32 snd_cwnd_clamp; /* Do not allow snd_cwnd to grow above this */  
  8.     u32 snd_cwnd_used;  
  9.     u32 snd_cwnd_stamp;  
  10.   
  11.   
  12.    u32 rcv_wnd;    /* Current receiver window      */  
  13.     u32 write_seq;  /* Tail(+1) of data held in tcp send buffer */  
  14.     u32 pushed_seq; /* Last pushed seq, required to talk to windows */  
  15.     u32 lost_out;   /* Lost packets         */  
  16.     u32 sacked_out; /* SACK'd packets           */  
  17.     u32 fackets_out;    /* FACK'd packets           */  
  18.     u32 tso_deferred;  
  19.     u32 bytes_acked;    /* Appropriate Byte Counting - RFC3465 */  
  1.   

这里有几个对于拥塞控制算法,十分重要的变量:

snd_cwnd//拥塞窗口

snd_ssthresh//慢启动阈值

snd_cwnd_clamp//拥塞窗口夹子

snd_cwnd就是发送端的拥塞窗口,用于发送端的流量控制,相应,接收方有一个接收窗口,用于接收端的流量控制。snd_ssthresh定义了慢启动和拥塞控制的分界点,也就是指数增长和线性增长的分界点。实际上,慢启动和拥塞控制的算法是在一起实现的,详情参见《TCP/IP详解卷一:协议》和我的上一篇文章 http://blog.csdn.net/hanrui90/article/details/8457863

关于snd_cwnd_clamp变量,在《linux内核源码剖析——TCP/IP实现(下册)》p717,讲到:snd_cwnd_clamp是允许的拥塞窗口最大值,初始值为65535,之后再接收SYN和ACK段时,会根据条件确定是否从路由配置项读取信息更新该字段,最后在TCP连接复位前,将更新后的值根据某种算法计算后再更新回相对应的路由配置项中,便于连接使用。

慢启动算法关键:

  1. /* 
  2.  * Slow start is used when congestion window is less than slow start 
  3.  * threshold. This version implements the basic RFC2581 version 
  4.  * and optionally supports: 
  5.  *  RFC3742 Limited Slow Start        - growth limited to max_ssthresh 
  6.  *  RFC3465 Appropriate Byte Counting - growth limited by bytes acknowledged 
  7.  */  
  8. void tcp_slow_start(struct tcp_sock *tp)  
  9. {  
  10.     int cnt; /* increase in packets */  
  11.   
  12.     /* RFC3465: ABC Slow start 
  13.      * Increase only after a full MSS of bytes is acked 
  14.      * 
  15.      * TCP sender SHOULD increase cwnd by the number of 
  16.      * previously unacknowledged bytes ACKed by each incoming 
  17.      * acknowledgment, provided the increase is not more than L 
  18.      */  
  19.     if (sysctl_tcp_abc && tp->bytes_acked < tp->mss_cache)  
  20.         return;  
  21.   
  22.     if (sysctl_tcp_max_ssthresh > 0 && tp->snd_cwnd > sysctl_tcp_max_ssthresh)  
  23.         cnt = sysctl_tcp_max_ssthresh >> 1;   /* limited slow start */  
  24.     else  
  25.         cnt = tp->snd_cwnd;          /* exponential increase */  
  26.   
  27.     /* RFC3465: ABC 
  28.      * We MAY increase by 2 if discovered delayed ack 
  29.      */  
  30.     if (sysctl_tcp_abc > 1 && tp->bytes_acked >= 2*tp->mss_cache)  
  31.         cnt <<= 1;  
  32.     tp->bytes_acked = 0;  
  33.   
  34.     tp->snd_cwnd_cnt += cnt;  
  35.     while (tp->snd_cwnd_cnt >= tp->snd_cwnd) {  
  36.         tp->snd_cwnd_cnt -= tp->snd_cwnd;  
  37.         if (tp->snd_cwnd < tp->snd_cwnd_clamp)  
  38.             tp->snd_cwnd++;  
  39.     }  
  40. }  
  41. EXPORT_SYMBOL_GPL(tcp_slow_start);  
这里着重解释一下,cwnd的指数增长是如何进行的,所有的文献中都会提到,cwnd在一个RTT内会翻倍,这里看到的源码似乎不是这样,而是接收一个ACK就加一。这里疑惑了一下,接收一个ACK的时间不就是RTT吗?其实不是的,一个cwnd内的包是一起发送的,之间相关的时间很短,只和带宽有关,ACK也是连续返回的,一个cwnd内的所有ACK都返回了才算是一个RTT。每返回一个ACK,cwnd就加一,那等所有ACK都返回了,cwnd也就翻倍了。【笔者按:自己的理解,有疑问可以回复交流】

参考文献:

[1] http://blog.csdn.net/zhangskd/article/details/7043071,写的文章很好。

[2] 《TCP/IP详解卷一:协议》

[3] http://blog.csdn.net/hanrui90/article/details/8457863

[4] kernel.org linux-2.6.35.13源码

[5] 《linux内核源码剖析——TCP/IP实现(下册)》p717

上一篇:CentOS安装Git
下一篇:pcre使用例子