一文讲解LinuxTCP数据接收-快路径与慢路径
一、快路径与慢路径简介
在Linux内核的TCP/IP协议栈实现中,TCP数据接收分为快路径处理与慢路径进行处理,快路径用于处理预期的、理想情形的输入数段,TCP连接中最常见的情形应该被尽可能地检测并最优化处理,达到快速处理的目的。慢路径用于处理那些非预期、非理想情况下的数据段,如乱序数据段、socket内存管理和紧急数据等。
快路径与慢路径在Linux内核中的处理流程:tcp握手完成后,收到数据包后,调用路径为:
tcp_v4_rcv
->tcp_v4_do_rcv
->tcp_rcv_established,
在tcp_rcv_establisheed函数中处理TCP_ESTABLISHED状态的包 ,并根据pred_flags预测字段来选择着采用快路径或慢路径。
二、首部预测字段-pred_flags
预测字段存储在struct tcp_sock中,pred_flag为0表示关闭首部预测使用慢速路径,非0表示开启快速路径的前提,如果开启会对该变量进行设定。
可以看到pred_flags与网络传输时一致采用大端存储,其大小为32位。该32位的pred_flags和TCP首部的第3个32位字(第0个32位:16位源端口号,16位目的端口号;第1个32位:32位序列号;第2个32位:32位确认号;第3个32位:首部字段、标志、窗口大小等,即首部预测字段需要的字段)对应,**如下图1为TCP首部字段分布与第3个32位的细节,**pred_flag变量的赋值通过调用tcp_fast_path_on函数或tcp_fast_path_on函数(include\net\tcp.h)中进行设定,其中tcp_fast_path_on间接调用__tcp_fast_path_on,只不过是在调用__tcp_fast_path_on针对携带窗口扩大因子的TCP传输进行还原发送窗口的大小。
**pred_flags由三个部分组成:【首部长度、ACK标记、发送窗口大小】也就是与TCP首部字段(13-16字节)中相应的字段,******如图2 pred_flags图示,****内核中的解释如下:
进行快速路径判断的时候只需要将预测字段与TCP首部中对应的部分进行对比即可。
__tcp_fast_path_on函数中:tp->header_len<<26的解释如下:
1、关于header_len:是指TCP首部的字节数(TCP首部固定部分为20字节),在TCP头部对应的是4位的"首部长度"字段,TCP的首部字节数 header_len= 首部长度*4,要得到”首部长度“字段,需要将header_len右移两位,即除以4:header_len>>2
2、”首部长度“字段是4位,现在要得到的pred_flags应该与TCP首部的第4个32位的位置(第13-16字节)进行对应,所以4位的首部字段左移28位得到:32位初始化(仅包含首部长度字段)的pred_flags,即header_len右移两位后,再左移28位:header_len<<26,即XXXX0000000000000000000000000000
其中XXXX表示首部长度字段的值。
tp->header_len<<26得到了含首部长度字段的pred_flags,但pred_flags除了首部长度字段外还应含有ACK标记,发送窗口:
tp->tcp_header_len << 26) | ntohl(TCP_FLAG_ACK) | snd_wnd
其中TCP_FLAG_ACK定义如下:
【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)


TCP_FLAG_ACK字段为0x00100000,即对应ACK标志位为1。


三、首部预测字段的设定
首部预测字段的设定分为两种过程 :
1、直接调用__tcp_fast_path_on函数进行设定首部预测字段相应的值
2、直接调用tcp_fast_path_on函数进行设定首部预测字段相应的值
2、先进行条件检验,检验通过后调用__tcp_fast_path_on函数进行设定首部预测字段相应的值直接调用__tcp_fast_path_on 的时机:
客户端connect系统调用即将结束的时
在tcp_finish_connect中没有开启窗口扩大因子的时,调用__tcp_fast_path_on来设置快速路径条件,这时候客户端进入TCP_ESTABLISHED状态,服务端还在等待客户端最后一次ACK才能发送数据,因此不会收到服务端的数据,也就不用考虑快速路径。
直接调用tcp_fast_path_on的时机:
服务器在收到SYN请求后的处理过程中,如下tcp_rcv_state_process函数中
进行检验后调用__tcp_fast_path_on函数:
检验函数检查条件是否满足,满足后才能设置预测标记,条件:** **
条件1:乱序队列是否为空(没有乱序数据时设定)
条件2:接收窗口是否还有剩余空间(接收窗口不为0时设定)
条件3:接收内存是否受限(接收缓存未耗尽时设定)
条件4:是否有紧急数据需要传输(无紧急数据时设定)
检验函数是tcp_fast_path_check,逻辑主要是进行4个条件判断,决定是否设定首部预测字段,如下所示:
先通过tcp_fast_check函数检测后调用t cp_fast_path_on的时机:
1、读完紧急数据后:紧急数据是由慢路径处理的,在慢速路径上收完紧急数据后检查是否可用开启快速路径模式
2、当发送发收到ACK并调用tcp_ack_update_window更新窗口时,通告窗口发生了变化,则必须更新预测标记,以免后续的输入报文因为窗口不符而进入慢速路径:
3、当调用tcp_data_queue将数据放入接收队列时,这时可用的接收缓存大小发生变化,即将输入数据放入到接收队列后,更新了字节的内存占用量,tcp_fast_path_check会检查这俄格缓存的变化是否允许开启快速路径模式。只有当前包是非乱序包,且接收窗口非0的时候,才能调用tcp_fast_path_check尝试开启快速路径
四、进入快速路径与慢速路径处理
设置预测标记后,使用它是在处理已连接TCP数据段的唯一入口函数:tcp_rcv_estblished
是否能够执行快速路径,pred_flags匹配只是前提条件,还有一些其他的判断条件,在内核中有相关的定义(net\ipv4\tcp_input.c):
在tcp_rcv_established函数中,关于快速路径检查与执行部分如下:
其中:
tcp_flag_word(th)获取的是TCP首部的第13-16字节,也就是第3个32位(第0个32位:16位源端口号,16位目的端口号;第1个32位:32位序列号;第2个32位:32位确认号;第3个32位:首部字段、标志、窗口大小等,需要的首部预测字段)
得到TCP首部的第3个32位后,还不是首部预测字段,还要继续屏蔽掉PSH字段,即 tcp_flag_word(th) & TCP_HP_BITS
慢速路径处理:
