一个简单教程关于 TCP 协议在 linux 内核的实现linux
翻译:内核小王子 (欢迎订阅微信公众号)算法
原文:Helali Bhuiyan, Mark McGinley, Tao Li, Malathi Veeraraghavan University of Virginia微信
原文连接 TCP Implementation in Linux: A Brief Tutorial网络
本文档简要概述了如何在Linux中实现TCP。他可能并不全面,而且也不能保证彻底准确。数据结构
图1 和 图2 展现了 TCP/IP 协议栈在 Linux 内核中的实现,图1 展现了一个网络包经过物理网线到达应用程序的过程,Linux 内核使用一个名为 sk_buff 的数据结构来表示一个网络包。当一个网络包到达网卡时,会经过 DMA 引擎将这个 sk_buff 加入到一个叫 rx ring 的 ring buffer 中,当这个 ring buffer 已经满了的时候,的报文将被舍弃。当更高层的协议处理数据包的时候,报文保存在内核的内存中从而避免了额外的拷贝。socket
一旦成功接收到一个数据包,网卡会向 CPU 发送一个中断,中断处理函数将数据包传给 IP 层。 IP层处理完后,判断若是是 TCP 报文,就会将数据包发给 TCP 层处理,数据包通过 TCP 层一系列复杂的处理过程,会更新 TCP 的状态机,最后将数据包存储在 TCP 的 接收缓冲区中。tcp
TCP 调优的一个关键参数为接收端的 recv 缓冲区大小。TCP 发送方可以发送的数据包的数量为发送方的拥塞控制窗口 (cwnd) 和接收方的告知的接收窗口 (rwnd) 中的最小值。而接收方告知的接收窗口的最大值就是 recv 缓冲区大小。所以,若是 recv 缓冲区设置的比 BGP (带宽延迟积) 小,则网络的吞吐量将会很低。另外,一个大的 recv 缓冲区容许大量的数据包处于未完成状态,可能超过了双方能够维持的数据包数量。recv 缓冲区大小能够经过修改 /proc/sys/net/ipv4/tcp rmem变量来设置。它须要三个值,最大值,最小值,默认值。最小值定义了最小能够接收的缓冲区大小,即便操做系统处于硬件内存很小。默认值是接收缓冲区的默认大小,它与TCP滑动窗口比例一块儿用来计算实际公示的窗口大小。max 定义接收缓冲区的最大值。函数
此外在接收端,参数netdev max backlog 指示网卡设备上排队的最大数据包数,这些数据包等待TCP接收进程处理。若是一个新收到的数据包在添加到队列时会致使队列超过netdev max backlog,那么它将被丢弃。大数据
在发送端,如图 2 ,所示,用户程序经过系统调用 write() 将数据写入 TCP 的 send buffer,和接收端的缓冲区同样,send buffer 也是提供吞吐量很重要的参数。拥塞窗口的最大值和分配给 TCP socket 的 send buffer 空间大小相关,send buffer 保存了全部尚未确认的数据包,由于该数据包可能还须要重发,若是s end buffer 设置的过小,则拥塞窗口也会变小,将影响吞吐量。另外,一个大的 send buffer 可能致使拥塞窗口变大,若是没有经过 接收端的 recv buffer 来限制,未确认的报文数目会随着拥塞窗口的增长而变大,若是超过双方能够维持的最大包数目从而致使丢包。send buffer 的大小能够经过修改 /proc/sys/net/ipv4/tcp 的 wmem 变量值,一样须要配置最大最小值和默认值。ui
相似于接收端的 netdev max backlog 是发送者的网卡设备上排队的最大数据包数。TCP 层在数据到达 send buffer的时候会构建报文,当收到确认报文回复的时候也会更高数据包状态。构建好 TCP 报文后会将数据包推送到协议下层的 IP 层进行传输,IP 层将加数据包放入一个和网卡关联的输出队列。该队列的大小能够经过修改和网卡设备关联的 txqueuelen 变量值来设置。若是队列已满,会尝试将数据包排队生成一个阻塞事件传播到 TCP层。TCP 拥塞控制算法将减小拥塞窗口的状态变量,每有一个阻塞事件会将当前拥塞窗口的状态变量减半。当数据包成功加入到队列,则这个数据包的描述符 (sk buff) 将会放入到发送方的 ring buffer 中,以后设备驱动经过 DMA engine 将数据包传输到线路中。
上述参数展现了网络链接的流量控制,但拥塞控制行为也会对对吞吐量产生很大影响。TCP使用多种拥塞控制算法来匹配发送速率以适应有瓶颈的线路。在一个无链接的网络环境里,大量的TCP流和其余类型的流量共享同一个瓶颈链路,当链路上的数据包数量发生变化的时候,TCP 流的可用带宽也会变化。当TCP流的发送速率高于可用带宽时,数据包会丢失。另外一方面,因为带宽被保留,数据包不会由于与电路中其余流的竞争而丢失。但,当一个发送速率很快的发送端链接到一个速率较低的链路时,因为交换机的缓冲区溢出,数据包也可能会丢失。
当一个 TCP 完成链接创建后,发送方使用确认报文做为一个时钟从而将新的数据包加入网络,称为 ACK-clocking。因为 TCP 接收端发送 ACK 数据包的速度不能超过瓶颈链路速率,所以ACK 时钟下的 TCP 发送端传输速率与瓶颈链路速率匹配。为了启动 ACK 时钟,TCP 发送端使用慢速启动机制。在慢启动阶段,对于接收到的每一个 ACK 数据包,TCP发送端连续传输两个数据包。因为 ACK 数据包以瓶颈链路速率传输,发送方传输数据的速度基本上是瓶颈链路可以维持的速度的两倍。当拥塞窗口的大小超过 ssthresh 时,慢启动阶段结束。在许多拥塞控制算法中,如 bic,能够调整初始慢启动阈值(ssthresh),以及其余因素(如最大增量),使bic或多或少提升效率。可是,与经过sysctl函数更改缓冲区同样,这些是系统范围内的更改,可能会对其余正在进行的链接和未来的链接产生不利影响。TCP 发送端最多只能发送拥塞窗口和接收端公布的窗口中的最小值。所以,除非受接收端公示的窗口的限制,不然每一个往返时间内未完成数据包的数量将增长一倍。因为数据包是由瓶颈链路速率转发的,所以在每一个往返时间内,将未完成数据包的数量加倍也将使瓶颈交换机内的缓冲区占用率加倍。最后,一旦缓冲区溢出,瓶颈交换机内部就会有数据包丢失。
当发生数据包丢失后,TCP发送端进入拥塞控制阶段。在这期间,每收到一个回复报文拥塞窗口加一。当 ACK 数据包以瓶颈链路速率返回时,拥塞窗口和未完成数据包的数量都在不断增长。所以,一旦未完成数据包的数量超过瓶颈链路交换机中的缓冲区大小加上线路上的数据包数量,数据包将再次丢失。
还有许多与 Linux 中的 TCP 操做相关的其余参数,而且每一个参数都在发布的文档(documentation/networking/ip sysctl.txt)中进行了简要说明。TCP 实现可配置参数的一个例子是 rfc2861 拥塞窗口重启功能。若是发送方 空闲一段时间(一个 RTO),则RFC2861 Pro 将从新启动拥塞窗口,目的是确保拥塞窗口反映网络的当前状态。若是链接处于空闲状态,拥塞窗口可能反映网络的已通过时状态,须要进行重置。可使用 ysctl tcp slow start 在空闲后禁用此行为,但此更改会影响系统范围内的全部链接。
若是对 TCP 对流量控制和拥塞控制不是很理解,欢迎关注公众号 内核小王子 ,下周将分享 网络内核之如何实现c10m 深刻分析linux的网络模型