- 浏览: 1403598 次
- 性别:
- 来自: 火星
文章分类
最新评论
-
aidd:
内核处理time_wait状态详解 -
ahtest:
赞一下~~
一个简单的ruby Metaprogram的例子 -
itiProCareer:
简直胡说八道,误人子弟啊。。。。谁告诉你 Ruby 1.9 ...
ruby中的类变量与类实例变量 -
dear531:
还得补充一句,惊群了之后,数据打印显示,只有一个子线程继续接受 ...
linux已经不存在惊群现象 -
dear531:
我用select试验了,用的ubuntu12.10,内核3.5 ...
linux已经不存在惊群现象
我们来看tcp输入对于ack,段的处理。
先是ack的处理,在内核中,处理ack段是通过tcp_ack来进行的。
这个函数主要功能是:
1 update重传队列,并基于sack来设置skb的相关buf。
2 update发送窗口。
3 基于sack的信息或者重复ack来决定是否进入拥塞模式。
在看之前我们要知道tcp是累积确认的。为了解决带来的缺点,我们才需要sack的。
然后我们来看几个很重要的数据结构,先是tcp_skb_cb,它其实就是表示skb中所保存的tcp的控制信息。而他是保存在skb的cb中的(这个域可以看我前面的blog)。所以这里我们经常会用TCP_SKB_CB来存取这个结构。
这里还有一个inet_skb_parm,这个结构保存了ipoption的一些信息。
然后来看tcp_skb_cb:
下面就是flags所能取的值,可以看到也就是tcp头的控制位。
然后是sack/fack的状态标记:
这里要注意,当我们接收到正确的SACK后,这个域就会被初始化为sack所在的相对偏移(也就是相对于tcp头的偏移值,这样我们就能很容易得到sack option的位置).
然后是tcp_sock,这个结构保存了我们整个tcp层所需要得所有必要的信息(也就是从sock中提取出来).我们分两个部分来看这个结构,这里只看我们关注的两部分,第一部分是窗口相关的一些域。第二部分是拥塞控制的一些相关域。
先来看窗口相关的:
然后是拥塞部分,看这里之前还是需要取熟悉一下tcp拥塞控制的相关概念。
分析完相关的数据结构我们来看函数的实现。
来看tcp_ack的代码,函数比较大,因此我们分段来看,先来看一开始的一些校验部分。
这里有一个tcp_abc也就是proc下面的可以设置的东西,这个主要是看要不要每个ack都要进行拥塞控制。
packets_out这个表示已经发送还没有ack的数据段的字节数(这个值不会重复加的,比如重传的话不会增加这个值)。
sakced_out :sack了的字节数。
lost_out:丢失了的字节数。
retrans_out:重传的字节数。
现在我们就对这个函数的返回值很清楚了,它也就是包含了还没有到达对方的数据段的字节数。
接下来这一段主要是通过判断flag(slow还是fast)来进行一些窗口的操作。有关slow_path和fast_path的区别,可以看我前面的blog。
fast_path的话很简单,我们就更新相关的域以及snd_wl1(这个域主要是用于update窗口的时候).它这里会被赋值为我们这次的数据包的序列号。然后进行拥塞控制的操作。
snd_wl1是只要我们需要更新发送窗口的话,这个值是都会被更新的。
slow_path的话,我们就需要判断要不要update窗口的大小了。以及是否要处理sack等。
在看下面的代码之前,我们先来看传递进tcp_ack这个函数中的第三个参数flag,这里我们在函数中也还会修改这个值,这个flag也就是当前的skb的类型信息。看了注释后就清楚了。可疑看到好几个都是ack的类型。
然后我们来看代码,下面的代码会设置flag,也就是用上面的宏。
这里有一个很大的不同就是slow_path中,我们需要update窗口的大小,而在fast模式中,我们不需要,这个详细去看我前面的blog介绍的fast和slow的区别。fast就是最理想的情况,因此我们不需要update窗口。
接下来这段主要工作是:
1 清理重传队列中的已经ack的段。
2 处理F-RTO。
3 判断是否是零窗口探测的回复ack。
4 检测是否要进入拥塞处理。
ok,,接着来看上面略过的几个函数,先来看tcp_ack_is_dubious,这里的条件我们一个个来看
1 说明flag不能是 FLAG_NOT_DUP的, FLAG_NOT_DUP表示我们的ack不是重复的。
2 是flag是CA_ALERT,它的意思是我们是否在我们进入拥塞状态时被alert。
3 拥塞状态不能为TCP_CA_OPEN不为这个,就说明我们已经进入了拥塞状态。
可以看下面这几个宏的定义,就比较清楚了。
上面的任意一个为真。就说明ack是可疑的。这里起始也可以说我们就必须进入拥塞的处理了(tcp_fastretrans_alert)
然后是 tcp_may_raise_cwnd,这个函数用来判断是否需要增大拥塞窗口。
1 不能有ECE flag或者发送的拥塞窗口不能大于slow start的阀值。
3 拥塞状态为RECO或者CWR.
在看tcp_ack_update_window函数之前,我们先来看tcp_may_update_window,这个函数用来判断是否需要更新发送窗口。
1 新的数据已经被ack了。
2 当前的数据包的序列号大于当窗口更新的时候那个数据包的序列号。
3 当前的数据包的序列号等于窗口更新时的序列号并且新的窗口大小大于当前的发送窗口大小。这个说明对端可能已经增加了窗口的大小
然后是tcp_ack_update_window函数,这个主要用来更新发送窗口的大小。
然后是tcp_cong_avoid函数,这个函数用来实现慢开始和快重传的拥塞算法。
可以看到它主要是调用cong_avoid回调函数,而这个函数被初始化为tcp_reno_cong_avoid,我们来看这个函数,在看这个函数之前我们要知道一些慢开始和快回复的概念。这些东西随便介绍tcp的书上都有介绍的。
最后我们来看tcp_clean_rtx_queue函数,这个函数主要用于清理发送队列中已经被ack的数据段。函数比较大,我们来分段看。
这里有使用karn算法,也就是如果重传的段,则计算rto的话,不采样这次的值。
还有就是要判断是syn的ack回复,还是数据的ack回复。以及sack的判断。
首先是遍历部分:
剩下的部分就是计算rtt的部分,这里就不介绍了。
先是ack的处理,在内核中,处理ack段是通过tcp_ack来进行的。
这个函数主要功能是:
1 update重传队列,并基于sack来设置skb的相关buf。
2 update发送窗口。
3 基于sack的信息或者重复ack来决定是否进入拥塞模式。
在看之前我们要知道tcp是累积确认的。为了解决带来的缺点,我们才需要sack的。
然后我们来看几个很重要的数据结构,先是tcp_skb_cb,它其实就是表示skb中所保存的tcp的控制信息。而他是保存在skb的cb中的(这个域可以看我前面的blog)。所以这里我们经常会用TCP_SKB_CB来存取这个结构。
#define TCP_SKB_CB(__skb) ((struct tcp_skb_cb *)&((__skb)->cb[0]))
这里还有一个inet_skb_parm,这个结构保存了ipoption的一些信息。
struct inet_skb_parm { struct ip_options opt; /* Compiled IP options */ unsigned char flags; #define IPSKB_FORWARDED 1 #define IPSKB_XFRM_TUNNEL_SIZE 2 #define IPSKB_XFRM_TRANSFORMED 4 #define IPSKB_FRAG_COMPLETE 8 #define IPSKB_REROUTED 16 };
然后来看tcp_skb_cb:
struct tcp_skb_cb { union { struct inet_skb_parm h4; #if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE) struct inet6_skb_parm h6; #endif } header; /* For incoming frames */ //这个表示当前tcp包的序列号 __u32 seq; //这个表示结束序列号,也就是SEQ + FIN + SYN + datalen。 __u32 end_seq; ///主要用来计算rtt __u32 when; //tcp头的flag(比如syn,fin等),它能取的值,我们下面会介绍。 __u8 flags; //SACK/FACK的状态flag或者是sack option的偏移(相对于tcp头的)。我们下面会介绍 __u8 sacked; //ack的序列号。 __u32 ack_seq; };
下面就是flags所能取的值,可以看到也就是tcp头的控制位。
#define TCPCB_FLAG_FIN 0x01 #define TCPCB_FLAG_SYN 0x02 #define TCPCB_FLAG_RST 0x04 #define TCPCB_FLAG_PSH 0x08 #define TCPCB_FLAG_ACK 0x10 #define TCPCB_FLAG_URG 0x20 #define TCPCB_FLAG_ECE 0x40 #define TCPCB_FLAG_CWR 0x80
然后是sack/fack的状态标记:
//有这个域说明当前的tcpcb是被sack块确认的。 #define TCPCB_SACKED_ACKED 0x01 //表示重传的帧 #define TCPCB_SACKED_RETRANS 0x02 //丢失 #define TCPCB_LOST 0x04 #define TCPCB_TAGBITS 0x07 //重传的帧。 #define TCPCB_EVER_RETRANS 0x80 #define TCPCB_RETRANS (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)
这里要注意,当我们接收到正确的SACK后,这个域就会被初始化为sack所在的相对偏移(也就是相对于tcp头的偏移值,这样我们就能很容易得到sack option的位置).
然后是tcp_sock,这个结构保存了我们整个tcp层所需要得所有必要的信息(也就是从sock中提取出来).我们分两个部分来看这个结构,这里只看我们关注的两部分,第一部分是窗口相关的一些域。第二部分是拥塞控制的一些相关域。
先来看窗口相关的:
//我们期待从另一台设备接收的下一个数据字节的序列号。 u32 rcv_nxt; //还没有被读取的数据的序列号。 u32 copied_seq; //当最后一次窗口update被发送之前我们的rcv_nxt. u32 rcv_wup; //将要发送给另一台设备的下一个数据字节的序列号。 u32 snd_nxt; //已经发送但尚未被确认的第一个数据字节的序列号。 u32 snd_una; // u32 snd_sml; //最后一次接收到ack的时间戳,主要用于keepalive u32 rcv_tstamp; //最后一次发送数据包的时间戳。 u32 lsndtime; //发送窗口长度 u32 snd_wnd; //接收窗口长度。 u32 rcv_wnd //发送未确认的数据包的个数(或者字节数?) u32 packets_out; //重传的数据包的个数 u32 retrans_out;
然后是拥塞部分,看这里之前还是需要取熟悉一下tcp拥塞控制的相关概念。
//慢开始的阀值,也就是超过这个我们就要进入拥塞避免的阶段 u32 snd_ssthresh; //发送的拥塞窗口 u32 snd_cwnd; //这个应该是拥塞状态下所发松的数据字节数 u32 snd_cwnd_cnt; //这里也就是cwnd的最大值 u32 snd_cwnd_clamp; //这两个值不太理解什么意思。 u32 snd_cwnd_used; u32 snd_cwnd_stamp; ///接收窗口打消 u32 rcv_wnd; //tcp的发送buf数据的尾部序列号。 u32 write_seq; //最后一次push的数据的序列号 u32 pushed_seq; //丢失的数据包字节数 u32 lost_out; //sack的数据包的字节数 u32 sacked_out; //fack处理的数据包的字节数 u32 fackets_out; u32 tso_deferred; //计数 u32 bytes_acked;
分析完相关的数据结构我们来看函数的实现。
来看tcp_ack的代码,函数比较大,因此我们分段来看,先来看一开始的一些校验部分。
这里有一个tcp_abc也就是proc下面的可以设置的东西,这个主要是看要不要每个ack都要进行拥塞控制。
引用
Controls Appropriate Byte Count defined in RFC3465. If set to 0 then does congestion avoid once per ACK. 1 is conservative value, and 2 is more aggressive. The default value is 1.
struct inet_connection_sock *icsk = inet_csk(sk); struct tcp_sock *tp = tcp_sk(sk); ///等待ack,也就是发送未确认的序列号。 u32 prior_snd_una = tp->snd_una; u32 ack_seq = TCP_SKB_CB(skb)->seq; ///得到ack的序列号。 u32 ack = TCP_SKB_CB(skb)->ack_seq; u32 prior_in_flight; u32 prior_fackets; int prior_packets; int frto_cwnd = 0; ///如果ack的序列号小于发送未确认的,也就是说可能这个ack只是重传老的ack,因此我们忽略它。 if (before(ack, prior_snd_una)) goto old_ack; ///如果ack大于snd_nxt,也就是它确认了我们还没发送的数据段,因此我们discard这个段。 if (after(ack, tp->snd_nxt)) goto invalid_ack; //如果ack大于发送未确认,则设置flag if (after(ack, prior_snd_una)) flag |= FLAG_SND_UNA_ADVANCED; //是否设置tcp_abc,有设置的话,说明我们不需要每个ack都要拥塞避免,因此我们需要计算已经ack的字节数。 if (sysctl_tcp_abc) { if (icsk->icsk_ca_state < TCP_CA_CWR) tp->bytes_acked += ack - prior_snd_una; else if (icsk->icsk_ca_state == TCP_CA_Loss) tp->bytes_acked += min(ack - prior_snd_una,q tp->mss_cache); } ///得到fack的数据包的字节数 prior_fackets = tp->fackets_out; //计算还在传输的数据段的字节数,下面会详细分析这个函数。 prior_in_flight = tcp_packets_in_flight(tp);
packets_out这个表示已经发送还没有ack的数据段的字节数(这个值不会重复加的,比如重传的话不会增加这个值)。
sakced_out :sack了的字节数。
lost_out:丢失了的字节数。
retrans_out:重传的字节数。
现在我们就对这个函数的返回值很清楚了,它也就是包含了还没有到达对方的数据段的字节数。
static inline unsigned int tcp_left_out(const struct tcp_sock *tp) { return tp->sacked_out + tp->lost_out; } static inline unsigned int tcp_packets_in_flight(const struct tcp_sock *tp) { return tp->packets_out - tcp_left_out(tp) + tp->retrans_out; }
接下来这一段主要是通过判断flag(slow还是fast)来进行一些窗口的操作。有关slow_path和fast_path的区别,可以看我前面的blog。
fast_path的话很简单,我们就更新相关的域以及snd_wl1(这个域主要是用于update窗口的时候).它这里会被赋值为我们这次的数据包的序列号。然后进行拥塞控制的操作。
snd_wl1是只要我们需要更新发送窗口的话,这个值是都会被更新的。
slow_path的话,我们就需要判断要不要update窗口的大小了。以及是否要处理sack等。
在看下面的代码之前,我们先来看传递进tcp_ack这个函数中的第三个参数flag,这里我们在函数中也还会修改这个值,这个flag也就是当前的skb的类型信息。看了注释后就清楚了。可疑看到好几个都是ack的类型。
//这个说明当前的输入帧包含有数据。 #define FLAG_DATA 0x01 //这个说明当前的ack是一个窗口更新的ack #define FLAG_WIN_UPDATE 0x02 //这个ack确认了一些数据 #define FLAG_DATA_ACKED 0x04 //这个表示ack确认了一些我们重传的段。 #define FLAG_RETRANS_DATA_ACKED 0x08 //这个表示这个ack是对syn的回复。 #define FLAG_SYN_ACKED 0x10 //新的sack #define FLAG_DATA_SACKED 0x20 //ack中包含ECE #define FLAG_ECE 0x40 //sack检测到了数据丢失。 #define FLAG_DATA_LOST 0x80 //当更新窗口的时候不跳过RFC的检测。 #define FLAG_SLOWPATH 0x100 #define FLAG_ONLY_ORIG_SACKED 0x200 //snd_una被改变了。也就是更新了。 #define FLAG_SND_UNA_ADVANCED 0x400 //包含D-sack #define FLAG_DSACKING_ACK 0x800 //这个不太理解什么意思。 #define FLAG_NONHEAD_RETRANS_ACKED 0x1000 // #define FLAG_SACK_RENEGING 0x2000 //下面也就是一些组合。 #define FLAG_ACKED (FLAG_DATA_ACKED|FLAG_SYN_ACKED) #define FLAG_NOT_DUP (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED) #define FLAG_CA_ALERT (FLAG_DATA_SACKED|FLAG_ECE) #define FLAG_FORWARD_PROGRESS (FLAG_ACKED|FLAG_DATA_SACKED) #define FLAG_ANY_PROGRESS (FLAG_FORWARD_PROGRESS|FLAG_SND_UNA_ADVANCED)
然后我们来看代码,下面的代码会设置flag,也就是用上面的宏。
这里有一个很大的不同就是slow_path中,我们需要update窗口的大小,而在fast模式中,我们不需要,这个详细去看我前面的blog介绍的fast和slow的区别。fast就是最理想的情况,因此我们不需要update窗口。
///如果不是slowpath并且ack确实是正确的序列号(必须大于snd_una). if (!(flag & FLAG_SLOWPATH) && after(ack, prior_snd_una)) { //更新snd_wl1域为ack_seq; tcp_update_wl(tp, ack_seq); ///snd_una更新为ack也就是确认的序列号 tp->snd_una = ack; //更新flag域。 flag |= FLAG_WIN_UPDATE; //进入拥塞的操作。 tcp_ca_event(sk, CA_EVENT_FAST_ACK); ................................ } else { //这个判断主要是为了判断是否输入帧包含数据。也就是ack还包含了数据,如果有的话,我们设置标记然后后面会处理。 if (ack_seq != TCP_SKB_CB(skb)->end_seq) flag |= FLAG_DATA; else ..................................... //然后进入更新窗口的操作。 flag |= tcp_ack_update_window(sk, skb, ack, ack_seq); //然后判断是否有sack段,有的话,我们进入sack段的处理。 if (TCP_SKB_CB(skb)->sacked) flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una); //判断是否有ecn标记,如果有的话,设置ecn标记。 if (TCP_ECN_rcv_ecn_echo(tp, tcp_hdr(skb))) flag |= FLAG_ECE; //进入拥塞的处理。 tcp_ca_event(sk, CA_EVENT_SLOW_ACK); }
接下来这段主要工作是:
1 清理重传队列中的已经ack的段。
2 处理F-RTO。
3 判断是否是零窗口探测的回复ack。
4 检测是否要进入拥塞处理。
sk->sk_err_soft = 0; icsk->icsk_probes_out = 0; tp->rcv_tstamp = tcp_time_stamp; ///如果发送并且没有ack的数据段的值为0,则说明这个有可能是0窗口探测的回复,因此我们进入no_queue的处理,这个我们紧接着会详细介绍。 prior_packets = tp->packets_out; if (!prior_packets) goto no_queue; ///清理重传队列中的已经ack的数据段。 flag |= tcp_clean_rtx_queue(sk, prior_fackets, prior_snd_una); ///处理F-RTO if (tp->frto_counter) frto_cwnd = tcp_process_frto(sk, flag); if (before(tp->frto_highmark, tp->snd_una)) tp->frto_highmark = 0; //判断ack是否是可疑的。它主要是检测我们是否进入拥塞状态,或者已经处于拥塞状态。 if (tcp_ack_is_dubious(sk, flag)) { //检测flag以及是否需要update拥塞窗口的大小。 if ((flag & FLAG_DATA_ACKED) && !frto_cwnd && tcp_may_raise_cwnd(sk, flag)) ///如果都为真,则更新拥塞窗口。 tcp_cong_avoid(sk, ack, prior_in_flight); ///这里进入拥塞状态的处理(这个函数是一个非常关键的函数,等到后面详细分析拥塞的时候,会分析到)。 tcp_fastretrans_alert(sk, prior_packets - tp->packets_out,flag); } else { //这里可以看到和上面相比,没有tcp_may_raise_cwnd这个,我们紧接着就会分析到。 if ((flag & FLAG_DATA_ACKED) && !frto_cwnd) tcp_cong_avoid(sk, ack, prior_in_flight); } //是否更新neigh子系统。 if ((flag & FLAG_FORWARD_PROGRESS) || !(flag & FLAG_NOT_DUP)) dst_confirm(sk->sk_dst_cache); return 1; no_queue: //这里判断发送缓冲区是否为空,如果不为空,则我们进入判断需要重启keepalive定时器还是关闭定时器 if (tcp_send_head(sk)) tcp_ack_probe(sk); return 1;
ok,,接着来看上面略过的几个函数,先来看tcp_ack_is_dubious,这里的条件我们一个个来看
1 说明flag不能是 FLAG_NOT_DUP的, FLAG_NOT_DUP表示我们的ack不是重复的。
2 是flag是CA_ALERT,它的意思是我们是否在我们进入拥塞状态时被alert。
3 拥塞状态不能为TCP_CA_OPEN不为这个,就说明我们已经进入了拥塞状态。
可以看下面这几个宏的定义,就比较清楚了。
#define FLAG_ACKED (FLAG_DATA_ACKED|FLAG_SYN_ACKED) #define FLAG_NOT_DUP (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED) //收到sack则说明可能有的段丢失了。而ECE则是路由器提示我们有拥塞了。我们必须处理。 #define FLAG_CA_ALERT (FLAG_DATA_SACKED|FLAG_ECE)
上面的任意一个为真。就说明ack是可疑的。这里起始也可以说我们就必须进入拥塞的处理了(tcp_fastretrans_alert)
static inline int tcp_ack_is_dubious(const struct sock *sk, const int flag) { return (!(flag & FLAG_NOT_DUP) || (flag & FLAG_CA_ALERT) ||inet_csk(sk)->icsk_ca_state != TCP_CA_Open); }
然后是 tcp_may_raise_cwnd,这个函数用来判断是否需要增大拥塞窗口。
1 不能有ECE flag或者发送的拥塞窗口不能大于slow start的阀值。
3 拥塞状态为RECO或者CWR.
static inline int tcp_may_raise_cwnd(const struct sock *sk, const int flag) { const struct tcp_sock *tp = tcp_sk(sk); return (!(flag & FLAG_ECE) || tp->snd_cwnd < tp->snd_ssthresh) &&!((1 << inet_csk(sk)->icsk_ca_state) & (TCPF_CA_Recovery | TCPF_CA_CWR)); }
在看tcp_ack_update_window函数之前,我们先来看tcp_may_update_window,这个函数用来判断是否需要更新发送窗口。
1 新的数据已经被ack了。
2 当前的数据包的序列号大于当窗口更新的时候那个数据包的序列号。
3 当前的数据包的序列号等于窗口更新时的序列号并且新的窗口大小大于当前的发送窗口大小。这个说明对端可能已经增加了窗口的大小
static inline int tcp_may_update_window(const struct tcp_sock *tp,const u32 ack, const u32 ack_seq,const u32 nwin) { return (after(ack, tp->snd_una) || after(ack_seq, tp->snd_wl1) || (ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd)); }
然后是tcp_ack_update_window函数,这个主要用来更新发送窗口的大小。
static int tcp_ack_update_window(struct sock *sk, struct sk_buff *skb, u32 ack, u32 ack_seq) { struct tcp_sock *tp = tcp_sk(sk); int flag = 0; //得到窗口的大小。 u32 nwin = ntohs(tcp_hdr(skb)->window); if (likely(!tcp_hdr(skb)->syn)) nwin <<= tp->rx_opt.snd_wscale; //判断是否需要update窗口。 if (tcp_may_update_window(tp, ack, ack_seq, nwin)) { flag |= FLAG_WIN_UPDATE; //更新snd_wl1 tcp_update_wl(tp, ack_seq); //如果不等于,则说明我们需要更新窗口。 if (tp->snd_wnd != nwin) { tp->snd_wnd = nwin; ................................... } } tp->snd_una = ack; return flag; }
然后是tcp_cong_avoid函数,这个函数用来实现慢开始和快重传的拥塞算法。
static void tcp_cong_avoid(struct sock *sk, u32 ack, u32 in_flight) { const struct inet_connection_sock *icsk = inet_csk(sk); icsk->icsk_ca_ops->cong_avoid(sk, ack, in_flight); tcp_sk(sk)->snd_cwnd_stamp = tcp_time_stamp; }
可以看到它主要是调用cong_avoid回调函数,而这个函数被初始化为tcp_reno_cong_avoid,我们来看这个函数,在看这个函数之前我们要知道一些慢开始和快回复的概念。这些东西随便介绍tcp的书上都有介绍的。
void tcp_reno_cong_avoid(struct sock *sk, u32 ack, u32 in_flight) { struct tcp_sock *tp = tcp_sk(sk); //是否已经到达拥塞窗口的限制。 if (!tcp_is_cwnd_limited(sk, in_flight)) return; //如果拥塞窗口还没有到达慢开始的阈值,我们就进入慢开始处理。 if (tp->snd_cwnd <= tp->snd_ssthresh) tcp_slow_start(tp); //否则我们就要进入拥塞避免阶段。 else if (sysctl_tcp_abc) { //RFC3465,只有当当前的拥塞窗口的所有段都被ack了,窗口才被允许增加。 if (tp->bytes_acked >= tp->snd_cwnd*tp->mss_cache) { tp->bytes_acked -= tp->snd_cwnd*tp->mss_cache; if (tp->snd_cwnd < tp->snd_cwnd_clamp) tp->snd_cwnd++; } } else { //和上面处理方式类似。 tcp_cong_avoid_ai(tp, tp->snd_cwnd); } }
最后我们来看tcp_clean_rtx_queue函数,这个函数主要用于清理发送队列中已经被ack的数据段。函数比较大,我们来分段看。
这里有使用karn算法,也就是如果重传的段,则计算rto的话,不采样这次的值。
还有就是要判断是syn的ack回复,还是数据的ack回复。以及sack的判断。
首先是遍历部分:
while ((skb = tcp_write_queue_head(sk)) && skb != tcp_send_head(sk)) { struct tcp_skb_cb *scb = TCP_SKB_CB(skb); u32 acked_pcount; u8 sacked = scb->sacked; //这个说明当前的数据已经在发送未确认的段里面了。 if (after(scb->end_seq, tp->snd_una)) { //这边不是很懂。 if (tcp_skb_pcount(skb) == 1 || !after(tp->snd_una, scb->seq)) break; acked_pcount = tcp_tso_acked(sk, skb); if (!acked_pcount) break; fully_acked = 0; } else { acked_pcount = tcp_skb_pcount(skb); } //如果sack的状态有被设置重传,则我们会使用karn算法。 if (sacked & TCPCB_RETRANS) { ///如果标记为sack了重传段,则更新重传的计数。 if (sacked & TCPCB_SACKED_RETRANS) tp->retrans_out -= acked_pcount; flag |= FLAG_RETRANS_DATA_ACKED; //都为-1,也就是后面计算rtt,不会采样这次值。 ca_seq_rtt = -1; seq_rtt = -1; if ((flag & FLAG_DATA_ACKED) || (acked_pcount > 1)) flag |= FLAG_NONHEAD_RETRANS_ACKED; } else { //否则根据时间戳得到对应的rtt ca_seq_rtt = now - scb->when; last_ackt = skb->tstamp; if (seq_rtt < 0) { seq_rtt = ca_seq_rtt; } if (!(sacked & TCPCB_SACKED_ACKED)) reord = min(pkts_acked, reord); } //如果有sack的数据包被ack确认了,则我们需要减小sack的计数 if (sacked & TCPCB_SACKED_ACKED) tp->sacked_out -= acked_pcount; if (sacked & TCPCB_LOST) tp->lost_out -= acked_pcount; //总得发送为ack的数据字节计数更新。 tp->packets_out -= acked_pcount; pkts_acked += acked_pcount; //判断是否为syn的ack。 if (!(scb->flags & TCPCB_FLAG_SYN)) { flag |= FLAG_DATA_ACKED; } else { //如果是设置标记 flag |= FLAG_SYN_ACKED; tp->retrans_stamp = 0; } if (!fully_acked) break; //从写buf,unlink掉。 tcp_unlink_write_queue(skb, sk); //释放内存。 sk_wmem_free_skb(sk, skb); tp->scoreboard_skb_hint = NULL; if (skb == tp->retransmit_skb_hint) tp->retransmit_skb_hint = NULL; if (skb == tp->lost_skb_hint) tp->lost_skb_hint = NULL; }
剩下的部分就是计算rtt的部分,这里就不介绍了。
发表评论
-
Receive packet steering patch详解
2010-07-25 16:46 12156Receive packet steering简称rp ... -
内核中拥塞窗口初始值对http性能的影响分析
2010-07-11 00:20 9708这个是google的人提出的 ... -
linux 内核tcp拥塞处理(一)
2010-03-12 16:17 9579这次我们来分析tcp的拥塞控制,我们要知道协议栈都是很保守的, ... -
内核tcp协议栈SACK的处理
2010-01-24 21:13 12185上一篇处理ack的blog中我 ... -
内核处理time_wait状态详解
2010-01-10 17:39 6822这次来详细看内核的time_wait状态的实现,在前面介绍定时 ... -
tcp协议栈处理各种事件的分析
2009-12-30 01:29 13638首先我们来看socket如何将一些状态的变化通知给对应的进程, ... -
linux内核sk_buff的结构分析
2009-12-25 00:42 47921我看的内核版本是2.6.32. 在内核中sk_buff表示一 ... -
tcp的输入段的处理
2009-12-18 00:56 8362tcp是全双工的协议,因此每一端都会有流控。一个tcp段有可能 ... -
内核协议栈tcp层的内存管理
2009-11-28 17:13 12089我们先来看tcp内存管理相关的几个内核参数,这些都能通过pro ... -
linux内核定时器的实现
2009-10-31 01:44 10196由于linux还不是一个实时的操作系统,因此如果需要更高精度, ... -
linux内核中tcp连接的断开处理
2009-10-25 21:47 10342我们这次主要来分析相关的两个断开函数close和shotdow ... -
linux内核tcp的定时器管理(二)
2009-10-05 20:52 5432这次我们来看后面的3个定时器; 首先是keep alive定 ... -
linux内核tcp的定时器管理(一)
2009-10-04 23:29 9837在内核中tcp协议栈有6种 ... -
linux 内核tcp接收数据的实现
2009-09-26 20:24 14529相比于发送数据,接收数据更复杂一些。接收数据这里和3层的接口是 ... -
linux 内核tcp数据发送的实现
2009-09-10 01:41 19794在分析之前先来看下SO_RCVTIMEO和SO_SNDTIME ... -
tcp connection setup的实现(三)
2009-09-03 00:34 5206先来看下accept的实现. 其实accept的作用很简单, ... -
tcp connection setup的实现(二)
2009-09-01 00:46 8439首先来看下内核如何处理3次握手的半连接队列和accept队列( ... -
tcp connection setup的实现(一)
2009-08-23 04:10 5827bind的实现: 先来介绍几个地址结构. struct ... -
linux内核中socket的实现
2009-08-15 04:38 21109首先来看整个与socket相关的操作提供了一个统一的接口sys ... -
ip层和4层的接口实现分析
2009-08-08 03:50 6211首先来看一下基于3层的ipv4以及ipv6实现的一些4层的协议 ...
相关推荐
Linux内核中提供了详细的函数和状态转换过程来处理这些动作,如syn收到时构造并发送syn+ack,以及ack收到时发送ack确认包。 拥塞控制是TCP确保网络稳定性的关键技术之一。在Linux 4.4.0内核中,拥塞控制的实现包括...
《Linux内核TCP/IP协议栈源码分析》 在深入探讨Linux内核的TCP/IP协议栈之前,我们先理解一下TCP/IP协议栈的基本结构。TCP/IP协议栈是互联网通信的核心,它将网络通信分为四层:应用层、传输层、网络层和数据链路层...
在本资料"Linux内核TCP/IP协议栈分析"中,我们将深入探讨这个核心组件的工作原理。 TCP/IP协议栈分为四个主要层次:应用层、传输层、网络层和数据链路层。在Linux内核中,每一层都有相应的模块负责处理相关的协议和...
在这个场景中,我们将深入探讨“内核TCP的SYN包skbuffer构造发送模块”,以及它与SYN攻击和内核构造包的关联。 首先,让我们了解`skbuffer`(socket buffer)。`skbuffer`是Linux内核中用于处理网络数据包的数据...
《Linux内核TCP协议栈部分,下册》深入解析了Linux操作系统中TCP协议栈的实现细节,涵盖了TCP协议从启动到控制发送的全过程,以及不启用DSACK(duplicate SACK,重复确认选择)等相关主题。这本书是理解网络通信底层...
《Linux内核源码剖析 TCP/IP实现》是樊东东和莫澜合著的一本深入解析Linux内核网络协议栈的书籍,主要关注TCP/IP协议的实现细节。这本书上册的内容,将引领读者深入理解Linux操作系统如何处理网络通信,特别是TCP/IP...
在TCP源代码中,你可以看到滑动窗口机制、ACK确认、重传策略以及各种超时和重置情况的处理。 在TCP/IP协议栈中,IP和TCP的交互是通过一系列回调函数和结构体完成的。例如,TCP会调用IP的发送接口来将TCP段封装成IP...
6. 在`tcp_v4_rcv`中,内核会解析接收到的ACK包,并调用`tcp_rcv_state_process`处理对应的状态。如果是SYN+ACK包,内核会确认连接并发送一个对服务器的ACK包,以完成三次握手的最后一个步骤。 7. 对于非阻塞模式,...
1. **TCP连接建立与断开**:包括SYN/SYN-ACK/ACK的三次握手过程以及FIN/FIN-ACK/ACK的四次挥手过程,详细解析了这些过程在内核中的实现。 2. **TCP状态机**:介绍TCP连接的各种状态,如LISTEN、SYN_SENT、...
《TCP/IP协议内核源码分析》是一系列深入解析Linux内核中TCP/IP协议实现的文章。作者通过详尽的语言,逐步剖析了TCP/IP协议在Linux内核中的运作流程,特别是从应用程序到内核的交互过程。该系列文章旨在提供一份高...
读者会了解到如何通过socket接口与内核进行交互,以及内核如何处理TCP连接的状态转换,如SYN/SYN+ACK/ACK的三次握手过程,以及FIN/FIN+ACK/RST等用于关闭连接的序列。 在数据链路层,书中的内容可能涵盖以太网协议...
Linux内核协议栈中的TCP协议在处理连接关闭时,会进入一个特定的状态叫做time_wait。这个状态对于确保TCP连接的可靠性和避免旧连接与新连接混淆至关重要。在time_wait状态下,连接不会立即关闭,而是等待一段时间,...
在TCP/IP通信中,客户端首先通过SYN-SYN-ACK的三次握手建立连接,然后可以发送数据。服务器则在监听特定端口,一旦接收到客户端的连接请求,就会响应并建立连接。一旦连接建立,双方都可以通过TCP套接字发送和接收...
TCP内核参数对于网络通信的性能至关重要,尤其是在高并发、网络环境不稳定或遭受SYN攻击时。TCP的三次握手过程占据了HTTP请求平均时间的10%以上,因此优化这一过程对于提升系统性能具有显著效果。 三次握手是TCP...
3. `tcp_ack()`:处理接收到的ACK包,确认连接已建立并移除半连接队列中的相应条目。 4. `tcp_established()`:当连接完全建立后,调用此函数将套接字状态从SYN_SENT或SYN_RECV改为ESTABLISHED。 5. `tcp_v4_...
1. 连接管理:TCP连接的建立、维护和释放,包括SYN/SYN+ACK/ACK的三次握手和FIN/FIN+ACK/ACK的四次挥手过程。 2. 数据流控制:滑动窗口机制,通过接收端的通告窗口大小来控制发送端的发送速率,防止拥塞。 3. 拥塞...
8. **性能优化**:Linux内核为TCP提供了很多性能优化手段,如延迟ACK、快速ACK、TCP_tw_reuse等,以提升网络吞吐量和响应速度。 9. **TCP调试与分析**:Linux提供了一些工具,如`tcpdump`用于抓包分析,`ss`和`...
在Linux 2.6内核中,TCP(Transmission Control Protocol)是互联网协议栈中的关键组件,负责提供可靠的、面向连接的数据传输服务。本篇将深入探讨Linux 2.6版本下的TCP实现及其输出分析,帮助读者理解TCP在操作系统...
9. **性能调优**:在Linux中,可以通过修改内核参数如`/proc/sys/net/ipv4/tcp_*`来优化TCP性能,例如调整重传阈值、减小TIME_WAIT延迟等。 10. **TCP Server编程**:压缩包内的"tcpServer"可能是一个简单的TCP...