TCP拥塞控制算法内核实现剖析.docx
《TCP拥塞控制算法内核实现剖析.docx》由会员分享,可在线阅读,更多相关《TCP拥塞控制算法内核实现剖析.docx(42页珍藏版)》请在冰豆网上搜索。
TCP拥塞控制算法内核实现剖析
TCP拥塞控制算法内核实现剖析
分类:
LinuxKernel2011-12-0517:
10
内核版本:
2.6.37
主要源文件:
linux-2.6.37/net/ipv4/Tcp_cong.c
一、RENO及拥塞控制算法基础
1.1RENO拥塞控制算法
===========================================================
structsock*sk和structtcp_sock*tp的转换
[cpp] viewplaincopy
1.在include/ linux/ Tcp.h中,
2.static inline struct tcp_sock *tcp_sk(const struct sock *sk)
3.{
4. return (struct tcp_sock *)sk ;
5.}
6.
7.给出struct sock *sk,
8.struct tcp_sock *tp = tcp_sk(sk) ;
tcp_sock结构
[cpp] viewplaincopy
1.struct tcp_sock
2.{
3. ...
4. u32 window_clamp ; /* Maximal window to advertise */
5. u32 rcv_ssthresh ; /* Current window clamp */
6. u32 rcv_wnd ; /* Current receiver window */
7. ...
8. /* snd_wll 记录发送窗口更新时,造成窗口更新的那个数据报的第一个序号。
9. * 它主要用于在下一次判断是否需要更新发送窗口。
10. */
11. u32 snd_wll ; /* Sequence for window update */
12. u32 snd_wnd ; /* 发送窗口的大小,直接取值于来自对方的数据报的TCP首部 */
13. /* Maximal window ever seen from peer 记录来自对方通告的窗口的最大值 */
14. /* First byte we want an ack for 发送窗口的左边沿 */
15. u32 max_window ; u32 snd_una ;
16. ...
17. /*
18. * Slow start and congestion control
19. */
20. u32 snd_ssthresh ; /* Slow start size threshold */
21. u32 snd_cwnd ; /* Sending congestion window */
22. /*表示在当前的拥塞控制窗口中已经发送的数据段的个数*/
23. u32 snd_cwnd_cnt ; /* Linear increase counter */
24. u32 snd_cwnd_clamp ; /* Do not allow snd_cwnd to grow above this */
25. ...
26. u32 mss_cache ; /* cached effective mss , not including SACKS */
27. u32 bytes_acked ; /* Appropriate Byte Counting - RFC3465 */
28. ...
29.}
拥塞避免算法关键部分
[cpp] viewplaincopy
1./* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd ( or alternative w ) */
2.void tcp_cong_avoid_ai(struct tcp_sock *tp , u32 w)
3.{
4. if ( tp->snd_cwnd_cnt >= w) {
5. if ( tp->snd_cwnd < tp->snd_cwnd_clamp)
6. tp->snd_cwnd++ ;
7. tp->snd_cwnd_cnt = 0 ;
8. } else {
9. tp->snd_cwnd_cnt ++ ;
10. }
11.}
12.EXPORT_SYMBOL_GPL(tcp_cong_avoid_ai) ;
慢启动算法
tcp_max_ssthresh参数的含义
tcp_max_ssthresh参数是实现RFC3742时引入的,离现在已经有4年的时间了。
07年5月内核实现了这个RFC。
但是,tcp的这个参数在内核文档(ip-sysctl.txt)中找不到任何的说明。
慢启动阶段,就是当前拥塞窗口值比慢启动阈值(snd_ssthresh)小的时候,所处的阶段就叫做慢启动阶段。
当我们收到一个新的ACK时,则会调用tcp_slow_start()这个函数,并且为拥塞窗口增加1.
(Linux中拥塞窗口的值代表数据包的个数,而不是实际的发送字节数目。
实际可以发送的字节数等于可以发送的数据包个数*MSS。
)
直到慢启动阶段出现数据包的丢失。
而引入了tcp_max_ssthresh 这个参数后,则可以控制在慢启动阶段拥塞窗口增加的频度。
默认这个参数不打开,如果这个参数的值设置为1000,则当拥塞窗口值大于1000时,
则没收到一个ACK,并不再增加拥塞窗口一个单位了,而是约收到2个ACK才增加一个窗口单位。
注意:
收到2ACK并不是决定值!
!
需要根据当前的拥塞窗口值,tcp_max_ssthresh值进行判断。
参见tcp.txt(documentation/networking)
Thefollowingvariablesareusedinthetcp_sockforcongestioncontrol:
snd_cwndThesizeofthecongestionwindow
snd_ssthreshSlowstartthreshold.Weareinslowstartifsnd_cwndislessthanthis.
snd_cwnd_cntAcounterusedtoslowdowntherateofincreaseonceweexceedslowstartthreshold.
snd_cwnd_clampThisisthemaximumsizethatsnd_cwndcangrowto.
snd_cwnd_stampTimestampforwhencongestionwindowlastvalidated.
snd_cwnd_usedUsedasahighwatermarkforhowmuchofthecongestionwindowisinuse.Itisusedtoadjustsnd_cwnddownwhenthelinkislimitedbytheapplicationratherthanthenetwork.
1.void tcp_slow_start( struct tcp_sock *tp )
2.{
3. int cnt ; /* increase in packets */
4.
5./* RFC3465 :
ABC slow start
6. * Increase only after a full MSS of bytes is acked
7. *
8. * TCP sender SHOULD increase cwnd by the number of
9. * previously unacknowledged bytes ACKed by each incoming
10. * acknowledgment , provided the increase is not more than L
11. */
12. /* ack的数据少于MSS,tcp_abc默认关闭 */
13. if ( sysctl_tcp_abc && tp->bytes_acked < tp->mss_cached )
14. return ;
15.
16./* 当前拥塞窗口值大于sysctl_tcp_max_ssthresh.限制拥塞窗口增长值*/
17. if ( sysctl_tcp_max_ssthresh >0 && tcp->snd_cwnd >sysctl_tcp_max_ssthresh)
18. cnt = sysctl_tcp_max_ssthresh >> 1 ; /* limited slow start */
19. else
20. cnt = tp->snd_cwnd ; /* exponential increase */
21.
22./* RFC3465 :
ABC
23. * We MAY increase by 2 if discovered delayed ack
24. */
25./* 如果接收方启用了延时确认,此时收到的确认代表两个MSS数据报*/
26.if ( sysctl_tcp_abc >1 && tp->bytes_acked >= 2*tp->mss_cache )
27. cnt <<= 1 ;
28.
29.tp->bytes_acked = 0 ;
30.tp->snd_cwnd_cnt += cnt ; /* 此时snd_cwnd_cnt等于snd_cwnd或2*snd_cwnd */
31.
32./*最多增加1/2sysctl_tcp_max_ssthresh字节。
也就是说,至少2个ACK才能让拥塞窗口增加1.*/
33. while( tp->snd_cwnd_cnt >= tp->snd_cwnd ) {
34. tp->snd_cwnd_cnt -= tp->snd_cwnd ;
35. if( tp->snd_cwnd < tp->snd_cwnd_clamp )
36. tp->snd_cwnd++ ;
37. }
38.}
39.EXPORT_SYMBOL_GPL( tcp_slow_start ) ;
1.2拥塞控制算法基础
1.2.1代表拥塞算法的结构体
1.#define TCP_CA_NAME_MAX 16
2.struct tcp_congestion_ops {
3. struct list_head list ;
4. unsigned long flags ;
5. /* initialize private data (optional) */
6. void (*init) (struct sock *sk) ;
7. /* cleanup private data (optional) */
8. void (*release) (struct sock *sk) ;
9. /* return slow start threshold (required) */
10. u32 (*ssthresh) (struct sock *sk) ;
11. /* lower bound for congestion window (optional) */
12. u32 (*min_cwnd) (const struct sock *sk) ;
13. /* do new cwnd calculation (required) */
14. void (*cong_avoid) (struct sock *sk , u32 ack , u32 in_flight ) ;
15. /* call before changing ca_state (optional) */
16. void (*set_state) (struct sock *sk , u8 new_state) ;
17. /* call when cwnd event occurs (optional) */
18. void (*cwnd_event) (struct sock *sk , enum tcp_ca_event ev) ;
19. /* new value of cwnd after loss (optional) */
20. u32 (*undo_cwnd) (struct sock *sk) ;
21. /* hook for packet ack accounting (optional) */
22. void (*pkts_acked) (struct sock *sk , u32 num_acked , s32 rtt_us) ;
23. /* get info for inet_diag (optional) */
24. void (*get_info) (struct sock *sk , u32 ext , struct sk_buff *skb) ;
25. char name[TCP_CA_NAME_MAX] ;
26. struct module *owner ;
27.}
在Tcp_cong.c中,有全局变量:
intsysctl_tcp_max_ssthresh=0;
/*defineDEFINE_SPINLOCK(x)spinlock_tx=__SPIN_LOCK_UNLOCKED(x)*/
staticDEFINE_SPINLOCK(tcp_cong_list_lock);
staticLIST_HEAD(tcp_cong_list);//tcp拥塞控制算法链表,其元素为tcp_congestion_ops
/*
BUG_ON();如果BUG_ON中的条件为真就调用BUG,它输出一些信息,然后调用panic函数挂起系统。
char*strncpy(char*dest,char*src,size_tn);
它与strcpy不同之处在于复制n个字符,而不是把所有的字符拷贝(包括结尾'\0')。
当src的长度小于n时,dst内的未复制空间用'\0'填充。
否则,复制n个字符到dst,没有加'\0'。
这里就要注意在字符串dst结尾处理加'\0'的情况了。
rcu_read_lock()//读者在读取由RCU保护的共享数据时使用该函数标记它进入读端临界区。
rcu_read_unlock()//该函数与rcu_read_lock配对使用,用以标记读者退出读端临界区。
*/
1.2.2对拥塞控制算法的一些操作(读写增减注册等)
[cpp] viewplaincopy
1./* Get current default congestion control */
2.void tcp_get_default_congestion_control( char *name )
3.{
4. struct tcp_congestion_ops *ca ;
5. /* We will always have reno */
6. BUG_ON( list_empty( &tcp_cong_list) ) ;
7.
8. rcu_read_lock( ) ;
9. ca = list_entry( tcp_cong_list . next , struct tcp_congestion_ops , list ) ;
10. strncpy( name , ca->name , TCP_CA_NAME_MAX ) ;
11. rcu_read_unlock( ) ;
12.}
structsock——representationofsockets
structinet_sock——representationofINETsockets
structinet_connection_sock——INETconnectionorientedsockets
structtcp_sock——tcpsockets
以上几种socket越分越细,比如inet_connection_sock是在inet_sock上的扩展,具有自己特有的属性。
tcp_sock是TCP协议专用的一个socket表示,它是在structinet_connection_sock基础进行扩展,主要是增加了滑动窗口协议,避免拥塞算法等一些TCP专有属性。
[cpp] viewplaincopy
1.struct inet_connection_sock {
2. ...
3. // Pluggable congestion control hook
4. const struct tcp_congestion_ops *icsk_ca_ops ;
5. ...
6.
7. u32 icsk_ca_priv[16] ;
8.#define ICSK_CA_PRIV_SIZE (16*sizeof(u32))
9.}
举例:
//有一个初始化了得structsock*sk
structinet_connection_sock*icsk=inet_csk(sk);
printk(KERN_INFO"%s",icsk->icsk_ca_ops->name);//当前连接拥塞控制算法名称
1.struct inet_sock {
2. ...
3. /* Socket demultiplex comparisons on incoming packets */
4. __be32 inet_daddr ;
5. __be16 inet_dport ;
6. __be32 inet_saddr ;
7. __be16 inet_sport ;
8. __be16 inet_num ; // local port
9. __be32 inet_rcv_saddr ; // Bound local IPv4 addr
10. ...
11.}
1./* Built list of non-restricted congestion control values*/
2.void tcp_get_allowed_congestion_control( char *buf , size_t maxlen)
3.{
4. struct tcp_congestion_ops *ca ;
5. size_t offs = 0 ;
6. *buf = '\0' ; //有必要?
7. rcu_read_lock() ;
8. list_for_each_entry( ca , &tcp_cong_list , list ) {
9. if( !
( ca->flags & TCP_CONG_NON_RESTRICTED)) //排除有限制的。
限制和非限制区别?
10. continue;
11. offs += snprintf( buf+offs , maxlen-offs , "%s%s" , offs == 0?
"" :
" " , ca->name) ;
12. }
13. rcu_read_unlock() ;
14.}
1./* Simple linear search , don't expect many entries!
*/
2.static struct tcp_congestion_ops*tcp_ca_find( const char *name)
3.{
4. struct tcp_congestion_ops *e ;
5. list_for_each_entry_rcu( e , &tcp_cong_list , list ) {
6. if( strcmp(e->name , name)==0)
7. return e ;
8. }
9. return NULL ;
10.}
1./*
2. * Attach new congestion control algorith