- 浏览: 1386549 次
- 性别:
- 来自: 火星
文章分类
最新评论
-
aidd:
内核处理time_wait状态详解 -
ahtest:
赞一下~~
一个简单的ruby Metaprogram的例子 -
itiProCareer:
简直胡说八道,误人子弟啊。。。。谁告诉你 Ruby 1.9 ...
ruby中的类变量与类实例变量 -
dear531:
还得补充一句,惊群了之后,数据打印显示,只有一个子线程继续接受 ...
linux已经不存在惊群现象 -
dear531:
我用select试验了,用的ubuntu12.10,内核3.5 ...
linux已经不存在惊群现象
这个是google的人提出的概念,那就是对tcp的拥塞窗口的初始值进行增大可以显著的提高http的性能,这个主要是针对tcp的slow start(我前面的blog有介绍)的.
下面是相关的paper和ppt:
paper: http://code.google.com/speed/articles/tcp_initcwnd_paper.pdf
ppt:http://www.ietf.org/proceedings/10mar/slides/iccrg-4.pdf
先来看什么叫做拥塞窗口:
拥塞窗口也就是tcp的发送端所估计的如果没有包的丢失(发生拥塞)时的窗口的大小,他主要是用于tcp的拥塞控制算法,以及发送滑动窗口。
当前内核默认实现是拥塞窗口默认为2个段(也就是两个mss的大小),也就是slow start开始的初始窗口大小只是2个段,这里google 的paper通过一些数据来正明最合适的大小为10(也就是15k),不过这里我认为这里的初始段的大小还是应该根据自己网站的平均页面大小来进行设置。
当前的的http,大部分都是短链接,因此很多情况下slow start还没有结束(进入拥塞避免状态),可是连接已经断开了,并且根据google的paper,网页的平均大小是300多k,而每次都是slow start,这个时候其实slow start会导致性能的下降,所以说这种情况下,我们其实可以加速slow start,而在内核中我们就可以通过设置initcwnd来进行控制。由于拥塞窗口的扩大,slow start将不会起作用,因为就算发送的数据包没有ack,可是由于我们的拥塞窗口本身就比较大,因此还是能够发送比较多的数据而不需要等待ack到来之后来增大拥塞窗口.
更具体的就要去看我上面给出的ppt和paper了。
接下来我们来看源码如何实现的。
首先要知道initcwnd必须通过iproute来设置,而且版本必须大于等于 2.6.34。
通过ip route的源码我们能够看到它是通过netlink 与内核进行通讯,从而达到修改这个值,而对应的数组位置就是RTAX_INITCWND。
这里ip route的源码就不详细分析了,主要我们来看内核的代码。
我们要知道dst_entry这个结构,这个结构我们不详细的分析,他有一个很重要的域我们这里会用到,就是metrics,这个域是一个数组,主要是保存了很多和对端通信时所需要的数值,比如initcwnd,比如max_mtu等等。这里我们只需要关注initcwnd.
下面就是它所保存的数值类型:
可以看到我们如果需要取得某个值的话,只需要从数组里面取得对应位置的数值就可以了。
下面就是读取方法:
拥塞窗口的初始化是在当tcp连接建立之后进行的,我们来看相关代码,这里是当状态处于TCP_SYN_RECV,然后接收到ack之后,进行初始化拥塞窗口。
相关代码在tcp_rcv_state_process中,来看代码片断:
然后来看tcp_init_metrics的代码,这里我们只需要看最重要的一部分,也就是初始化snd_cwnd.
然后就是最重要的一个函数,初始化snd_cwnd,这个函数主要就是调用dst_metric得到对应的INITCWND的值(如果dst存在),然后选择snd_cwnd_clamp与cwnd的最小值,这里snd_cwnd_clamp表示snd_cwnd的最大值,因此我们设置的值不能大于这个值。
一开始我们就知道ip route initcwnd设置的就是 RTAX_INITCWND对应的值,因此这里就改变了拥塞窗口的值。
ok,然后我们来看拥塞窗口变大后,内核发送数据段时如何被影响。
内核4层发送数据是通过tcp_write_xmit函数进行的,我们来看这个的代码片断,这个函数的详细分析我前面的blog 有分析,有需要的可以看我前面的blog。
下面就是我们最关心的代码片断。
紧接着就是tcp_cwnd_test,这个函数里面用到了几个数据包的概念,比如in flight,这些我前面的blog都有分析,可以去看我前面的blog。
它通过拥塞窗口来计算还可惜发送几个段的数据包。
通过上面的代码我们可以看到如果cwnd也就是拥塞默认窗口变大之后,则我们每次可以多发送一些数据段。
这里要注意的是就算initcwnd增大,slow start也是不可避免的,只不过现在拥塞窗口变大导致发送的时候能够多发送数据段。而也只有当发送的都ack之后拥塞窗口才会变大,而有可能等不到全部的ack,连接已经断开。
在2.6.33之后还多出来了一个RTAX_INITRWND,这个值主要是针对默认的接收窗口进行设置。
下面是相关的paper和ppt:
paper: http://code.google.com/speed/articles/tcp_initcwnd_paper.pdf
ppt:http://www.ietf.org/proceedings/10mar/slides/iccrg-4.pdf
先来看什么叫做拥塞窗口:
拥塞窗口也就是tcp的发送端所估计的如果没有包的丢失(发生拥塞)时的窗口的大小,他主要是用于tcp的拥塞控制算法,以及发送滑动窗口。
当前内核默认实现是拥塞窗口默认为2个段(也就是两个mss的大小),也就是slow start开始的初始窗口大小只是2个段,这里google 的paper通过一些数据来正明最合适的大小为10(也就是15k),不过这里我认为这里的初始段的大小还是应该根据自己网站的平均页面大小来进行设置。
当前的的http,大部分都是短链接,因此很多情况下slow start还没有结束(进入拥塞避免状态),可是连接已经断开了,并且根据google的paper,网页的平均大小是300多k,而每次都是slow start,这个时候其实slow start会导致性能的下降,所以说这种情况下,我们其实可以加速slow start,而在内核中我们就可以通过设置initcwnd来进行控制。由于拥塞窗口的扩大,slow start将不会起作用,因为就算发送的数据包没有ack,可是由于我们的拥塞窗口本身就比较大,因此还是能够发送比较多的数据而不需要等待ack到来之后来增大拥塞窗口.
更具体的就要去看我上面给出的ppt和paper了。
接下来我们来看源码如何实现的。
首先要知道initcwnd必须通过iproute来设置,而且版本必须大于等于 2.6.34。
通过ip route的源码我们能够看到它是通过netlink 与内核进行通讯,从而达到修改这个值,而对应的数组位置就是RTAX_INITCWND。
这里ip route的源码就不详细分析了,主要我们来看内核的代码。
我们要知道dst_entry这个结构,这个结构我们不详细的分析,他有一个很重要的域我们这里会用到,就是metrics,这个域是一个数组,主要是保存了很多和对端通信时所需要的数值,比如initcwnd,比如max_mtu等等。这里我们只需要关注initcwnd.
下面就是它所保存的数值类型:
enum { RTAX_UNSPEC, #define RTAX_UNSPEC RTAX_UNSPEC RTAX_LOCK, #define RTAX_LOCK RTAX_LOCK RTAX_MTU, #define RTAX_MTU RTAX_MTU RTAX_WINDOW, #define RTAX_WINDOW RTAX_WINDOW RTAX_RTT, #define RTAX_RTT RTAX_RTT RTAX_RTTVAR, #define RTAX_RTTVAR RTAX_RTTVAR RTAX_SSTHRESH, #define RTAX_SSTHRESH RTAX_SSTHRESH RTAX_CWND, #define RTAX_CWND RTAX_CWND RTAX_ADVMSS, #define RTAX_ADVMSS RTAX_ADVMSS RTAX_REORDERING, #define RTAX_REORDERING RTAX_REORDERING RTAX_HOPLIMIT, #define RTAX_HOPLIMIT RTAX_HOPLIMIT RTAX_INITCWND, #define RTAX_INITCWND RTAX_INITCWND RTAX_FEATURES, #define RTAX_FEATURES RTAX_FEATURES RTAX_RTO_MIN, #define RTAX_RTO_MIN RTAX_RTO_MIN RTAX_INITRWND, #define RTAX_INITRWND RTAX_INITRWND __RTAX_MAX };
可以看到我们如果需要取得某个值的话,只需要从数组里面取得对应位置的数值就可以了。
下面就是读取方法:
dst_entry static inline u32 dst_metric(const struct dst_entry *dst, int metric) { //根据传递进来的位置,返回对应的数值。 return dst->metrics[metric-1]; }
拥塞窗口的初始化是在当tcp连接建立之后进行的,我们来看相关代码,这里是当状态处于TCP_SYN_RECV,然后接收到ack之后,进行初始化拥塞窗口。
相关代码在tcp_rcv_state_process中,来看代码片断:
if (th->ack) { int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH) > 0; switch (sk->sk_state) { case TCP_SYN_RECV: if (acceptable) { .............................................. //初始化对应序列号 tp->snd_una = TCP_SKB_CB(skb)->ack_seq; tp->snd_wnd = ntohs(th->window) << tp->rx_opt.snd_wscale; ........................................... //这个函数里面将会初始化上面所介绍的那些类型metrics,包括initcwnd tcp_init_metrics(sk); tcp_init_congestion_control(sk); ............................................................
然后来看tcp_init_metrics的代码,这里我们只需要看最重要的一部分,也就是初始化snd_cwnd.
static void tcp_init_metrics(struct sock *sk) { ....................................... cwnd: //初始化snd_cwnd tp->snd_cwnd = tcp_init_cwnd(tp, dst); tp->snd_cwnd_stamp = tcp_time_stamp; return; .......................... }
然后就是最重要的一个函数,初始化snd_cwnd,这个函数主要就是调用dst_metric得到对应的INITCWND的值(如果dst存在),然后选择snd_cwnd_clamp与cwnd的最小值,这里snd_cwnd_clamp表示snd_cwnd的最大值,因此我们设置的值不能大于这个值。
__u32 tcp_init_cwnd(struct tcp_sock *tp, struct dst_entry *dst) { //取得对应类型的值 __u32 cwnd = (dst ? dst_metric(dst, RTAX_INITCWND) : 0); if (!cwnd) { if (tp->mss_cache > 1460) cwnd = 2; else cwnd = (tp->mss_cache > 1095) ? 3 : 4; } //返回两个之间的相对较小的值。 return min_t(__u32, cwnd, tp->snd_cwnd_clamp); }
一开始我们就知道ip route initcwnd设置的就是 RTAX_INITCWND对应的值,因此这里就改变了拥塞窗口的值。
ok,然后我们来看拥塞窗口变大后,内核发送数据段时如何被影响。
内核4层发送数据是通过tcp_write_xmit函数进行的,我们来看这个的代码片断,这个函数的详细分析我前面的blog 有分析,有需要的可以看我前面的blog。
下面就是我们最关心的代码片断。
while ((skb = tcp_send_head(sk))) { unsigned int limit; tso_segs = tcp_init_tso_segs(sk, skb, mss_now); BUG_ON(!tso_segs); //这个函数将会返回还允许发送几个数据包,也就是说这个函数就是配合慢开始的。 cwnd_quota = tcp_cwnd_test(tp, skb); //如果没有则跳出循环,也就是不再发送数据。 if (!cwnd_quota) break;
紧接着就是tcp_cwnd_test,这个函数里面用到了几个数据包的概念,比如in flight,这些我前面的blog都有分析,可以去看我前面的blog。
它通过拥塞窗口来计算还可惜发送几个段的数据包。
static inline unsigned int tcp_cwnd_test(struct tcp_sock *tp, struct sk_buff *skb) { u32 in_flight, cwnd; /* Don't be strict about the congestion window for the final FIN. */ if ((TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN) && tcp_skb_pcount(skb) == 1) return 1; //计算发送还没有确认的数据包 in_flight = tcp_packets_in_flight(tp); //取得发送拥塞窗口 cwnd = tp->snd_cwnd; //如果发送还没确认的数据包小于拥塞窗口,则说明我们还能发送数据 if (in_flight < cwnd) //返回还能发送的数据段个数,默认的cwnd变大则将会返回更大的可以发送的数据段。 return (cwnd - in_flight); //否则返回0 return 0; }
通过上面的代码我们可以看到如果cwnd也就是拥塞默认窗口变大之后,则我们每次可以多发送一些数据段。
这里要注意的是就算initcwnd增大,slow start也是不可避免的,只不过现在拥塞窗口变大导致发送的时候能够多发送数据段。而也只有当发送的都ack之后拥塞窗口才会变大,而有可能等不到全部的ack,连接已经断开。
在2.6.33之后还多出来了一个RTAX_INITRWND,这个值主要是针对默认的接收窗口进行设置。
发表评论
-
Receive packet steering patch详解
2010-07-25 16:46 11956Receive packet steering简称rp ... -
linux 内核tcp拥塞处理(一)
2010-03-12 16:17 9491这次我们来分析tcp的拥塞控制,我们要知道协议栈都是很保守的, ... -
内核tcp协议栈SACK的处理
2010-01-24 21:13 12071上一篇处理ack的blog中我 ... -
内核tcp的ack的处理
2010-01-17 03:06 11069我们来看tcp输入对于ack,段的处理。 先是ack的处理, ... -
内核处理time_wait状态详解
2010-01-10 17:39 6706这次来详细看内核的time_wait状态的实现,在前面介绍定时 ... -
tcp协议栈处理各种事件的分析
2009-12-30 01:29 13560首先我们来看socket如何将一些状态的变化通知给对应的进程, ... -
linux内核sk_buff的结构分析
2009-12-25 00:42 47813我看的内核版本是2.6.32. 在内核中sk_buff表示一 ... -
tcp的输入段的处理
2009-12-18 00:56 8269tcp是全双工的协议,因此每一端都会有流控。一个tcp段有可能 ... -
内核协议栈tcp层的内存管理
2009-11-28 17:13 11920我们先来看tcp内存管理相关的几个内核参数,这些都能通过pro ... -
linux内核定时器的实现
2009-10-31 01:44 10122由于linux还不是一个实时的操作系统,因此如果需要更高精度, ... -
linux内核中tcp连接的断开处理
2009-10-25 21:47 10213我们这次主要来分析相关的两个断开函数close和shotdow ... -
linux内核tcp的定时器管理(二)
2009-10-05 20:52 5333这次我们来看后面的3个定时器; 首先是keep alive定 ... -
linux内核tcp的定时器管理(一)
2009-10-04 23:29 9764在内核中tcp协议栈有6种 ... -
linux 内核tcp接收数据的实现
2009-09-26 20:24 14387相比于发送数据,接收数据更复杂一些。接收数据这里和3层的接口是 ... -
linux 内核tcp数据发送的实现
2009-09-10 01:41 19661在分析之前先来看下SO_RCVTIMEO和SO_SNDTIME ... -
tcp connection setup的实现(三)
2009-09-03 00:34 5129先来看下accept的实现. 其实accept的作用很简单, ... -
tcp connection setup的实现(二)
2009-09-01 00:46 8377首先来看下内核如何处理3次握手的半连接队列和accept队列( ... -
tcp connection setup的实现(一)
2009-08-23 04:10 5745bind的实现: 先来介绍几个地址结构. struct ... -
linux内核中socket的实现
2009-08-15 04:38 21001首先来看整个与socket相关的操作提供了一个统一的接口sys ... -
ip层和4层的接口实现分析
2009-08-08 03:50 6125首先来看一下基于3层的ipv4以及ipv6实现的一些4层的协议 ...
相关推荐
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...
1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 1.7 网络实现概述 6 1.8 描述符 7 1.9 mbuf与输出处理 11 1.9.1 包含插口地址结构的mbuf 11 ...
1.2.1 将拥塞窗口设置为1 1.2.2 印刷约定 1.3 历史 1.4 应用编程接口 1.5 程序示例 1.6 系统调用和库函数 1.7 网络实现概述 1.8 描述符 1.9 mbuf与输出处理 1.9.1 包含插口地址结构的mbuf 1.9.2 包含数据的mbuf ...
1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 1.7 网络实现概述 6 1.8 描述符 7 1.9 mbuf与输出处理 11 1.9.1 包含插口地址结构的mbuf 11 ...
1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 1.7 网络实现概述 6 1.8 描述符 7 1.9 mbuf与输出处理 11 1.9.1 包含插口地址结构的mbuf 11 ...
1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 1.7 网络实现概述 6 1.8 描述符 7 1.9 mbuf与输出处理 11 1.9.1 包含插口地址结构的mbuf 11 ...
1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 1.7 网络实现概述 6 1.8 描述符 7 1.9 mbuf与输出处理 11 1.9.1 包含插口地址结构的mbuf 11 ...