Linux TCP/IP协议栈追踪
Linux TCP/IP协议栈函数分析
本文主要分析recv函数与send函数的实现细节,包括相关数据结构和函数调用层次。分析的方法是自顶向下,即从应用层到物理层进行函数的分析。
分层模型
OSI模型和TCP/IP模型
ISO(International Organization for Standardization,国际标准化组织)设计了一种参考 模型,定义了组成网络的各个层。该模型由7层组成,称为OSI(Open Systems Interconnection,开放 系统互连)模型,如下图所示:
Linux中的网络模型
Linux内核网络子系统的实现与上图的分层模型相似,相关的C语言代码划分为不同层次,各层次都有明确定义的任务,各个层次只能通过明确定义的 接口与上下紧邻的层次通信。这种做法的好处在于,可以组合使用各种设备、传输机制和协议。例如, 通常的以太网卡不仅可用于建立因特网(IP)连接,还可以在其上传输其他类型的协议,如Appletalk 或IPX,而无须对网卡的设备驱动程序做任何类型的修改。
从应用层分析
套接字将UNIX隐喻“万物皆文件”应用到了网络连接上。内核与用户空间套接字之间的接口实现 在C标准库中,使用了socketcall系统调用。
socketcall充当一个多路分解器,将各种任务分配由不同的过程执行,例如打开一个套接字、 绑定或发送数据。
Linux采用了内核套接字的概念,使得与用户空间中的套接字的通信尽可能简单。对程序使用的 每个套接字来说,都对应于一个socket结构和sock结构的实例。二者分别充当向下(到内核)和向 上的(到用户空间)接口。
socket数据结构
socket数据结构定义如下:
<net.h>
struct socket
{
socket_state state;
unsigned long flags;
const struct proto_ops* ops;
strduct file* file;
struct sock* sk;
short type;
}
-
type指定协议类型
-
state表示套接字连接状态,具体如下:
<net.h> typedef enum { SS_FREE=0; //未分配 SS_UNCONNECTED; //未连接到任何套接字 SS_CONNECTING; //处于连接过程中 SS_CONNECTED; //已经连接到另一个套接字 SS_DISCONNECTING; //处于断开连接过程中 } socket_state;
这里的状态与传输层协议在建立和关闭连接时使用的状态并无关系,只是单纯表示与应用程序相关的一般状态。
socketcall系统调用
由于socket并不能当成是传统的文件,需要拓展其bind、listen等功能,所以linux提供了socketcall系统调用。
17个套接字功能操作只对应到一个系统调用,sys_socketcall相当于一个萃取器(实现思想类似于C++中迭代器的不同tag,通过itertator_traits进行函数转发),根据不同参数选择最终要执行的函数。下图是sys_socketcall支持的17个套接字功能图:
接收数据(recv)
接收数据的函数(recvfrom,recv,readv,read)代码非常类似,处理过程中会合并起来,因此选择sys_recvfrom来讨论,代码流程图如下:
- fget_light根据task_struct描述符表,查找对应的file。sock_from_file确定与之关联的inode,并使用SOCKET_I找到相关的套接字。
- sock_recvvmsg调用接收数据的函数,TCP使用tcp_recvmsg,UDP使用_udp_recvmsg。
- move_addr_to_user将数据复制到用户空间。
发送数据(send)
同上,发送数据可以选择(sendto,send,write,writev)函数,这些函数在控制流中被合并为一个。
- 实现方法与recv函数相似,只不过mode_addr_to_kernel将用户态的数据复制到内核态。
传输层
这部分只分析TCP协议对函数细节的处理。
在网络层处理过分组之后,tcp_v4_rcv是TCP层的入口。
- 将首部中的信息复制到套接字缓冲区的控制快后,内核通过__inet_lookup函数查找等待该分组的套接字。如果找到目标套接字,则返回一个已连接的套接字。
接收分组
在控制流到达tcp_v4_do_rcv后,会选择一条快速路径(前提是连接已经存在)而不是到函数分配器。如果分组容易处理(是否包含不常见选项),则进入快速路径,否则进入低速路径。
发送分组
TCP分组的发送,由更高层网络协议对tcp_sendmsg调用开始。
可以看到,tcp_sendmsg函数的调用是先通过应用层的发送函数调用的。也就是说系统提供给用户的recv、send函数是最顶层抽象程度最高的函数,实现必须依赖下层协议一层一层调用解析。
网络层
ip_rcv函数是网络层的入口点。与上下层协议的关系如下:
接收分组
分片合并
如果分组目的地址是本地计算机,ip_local_deliver函数开始工作。
由于IP分组可能是分片的,因此会带来一些困难。不见得一定有一个完整的分组可用。该函数的 第一项任务,就是通过ip_defrag重新组合分片分组的各个部分。
- 内核会为每一个分组的各个分片建立一个分片缓存,同一个分组的各个分片都保存在一个独立的等待队列中,直至该分组的所有分片都到达。
- 所有分片都进入缓存后,ip_frag_reasm将各个分片重新组合。
交付传输层
这里会返回到ip_local_deliver继续执行,分组的分片合并完成后,调用netfilter恢复在ip_local_deliver_finish函数中的处理。根据分组协议标识符选择一个传输层函数,将分组传递给该函数。
从上图函数调用关系可以看到,ip分组到来后,先从ip_local_deliver函数执行,最后交付TCP层的入口函数tcp_v4_rcv,
发送分组
内核提供了几个通过IP层发送数据的函数,ip_queue_xmit是最常使用的。流程图如下:
- 首先查找合适的路由
- ip_send_check为分组产生校验和,通过netfilter调用dst_output函数。通常,该函数的指针指向ip_output。
从上图可以看出,IP层的发送操作是由TCP层发起的(入口函数tcp_v4_rcv)。
转移到数据链路层
上图给出了ip_output函数的流程图,函数会先对MTU的数值和分组的长度进行比较,根据是否需要分片分成两个执行路径。
上图给出了相关函数的运行流程。
数据链路层
在内核中,每个网络设备都表示为类型为net_device的结构体。结构体的详细内容就不再赘述了。
接收分组
分组到达内核的时间是不可预测的。所有现代的设备驱动程序都使用中断来通知内核(或系统)有分组到达。网络驱动程序对特定于设备的中断设置了一个处理例程,因此每当该中断被引发时(即分组到达),内核都调用该处理程序,将数据从网卡传输到物理内存,或通知内核在一定时间后进行处理。
几乎所有的网卡都支持DMA模式,能够自行将数据传输到物理内存。但这些数据仍然 需要解释和处理。
上图给出了分组到达网络适配器(网卡)后,通过内核到达网络层函数的路径。
- net_interrupt是设备驱动程序自定义的中断处理程序,负责确定中断是否是接收到分组发出的。
- net_rx特定于网卡,创建一个套接字缓冲区,分组的内容从网卡传输到缓冲区(物理内存),内核会对这些分组数据分析首部
- netif_rx将接收到的分组放置在CPU的等待队列上,退出中断上下文释放CPU。next_rx_action是软中断处理程序。
在经过软中断处理程序net_rx_action后,进入process_backlog函数,循环执行下列函数:
- __skb_dequeue函数从等待队列移除一个套接字缓冲区
- 有netif_receive_skb函数分析分组的类型,将分组传递给IP层
从上图可以看到,经过netif_receive_skb函数后,数据交给IP层的入口函数ip_rcv。
发送分组
网络层会通知链路层将分组发送,/net/core/dev.c中的dev_queue_xmit用于将分组发送到队列上,这是通过硬件设备驱动程序实现的,无需过多探究。
总结
Linux下网络层次相互联系的原理
通过上文可知:
- tcp_v4_rcv是传输层(TCP协议)的入口函数,负责与网络层和应用层联系
- ip_rcv是网络层的入口函数,负责与链路层和传输层联系
原文地址:https://www.cnblogs.com/ustcgerunze/p/14332716.html
- BZOJ 2456: mode(新生必做的水题)
- 【专知-PyTorch手把手深度学习教程07】NLP-基于字符级RNN的姓名分类
- Codeforces Round #301 (Div. 2)(A,【模拟】B,【贪心构造】C,【DFS】)
- 【专知-PyTorch手把手深度学习教程06】NLP-Word Embedding快速理解与PyTorch实现: 图文+代码
- [linux][memory]memcmp几种实现和性能对比
- 蒙特卡洛算法及其实现
- 【专知-PyTorch手把手深度学习教程05】Dropout快速理解与PyTorch实现: 图文+代码
- 【专知-PyTorch手把手深度学习教程04】GAN快速理解与PyTorch实现: 图文+代码
- 2017年中国大学生程序设计竞赛-中南地区赛暨第八届湘潭市大学生计算机程序设计大赛题解&源码(A.高斯消元,D,模拟,E,前缀和,F,LCS,H,Prim算法,I,胡搞,J,树状数组)
- 【专知中秋呈献-PyTorch手把手深度学习教程03】LSTM快速理解与PyTorch实现: 图文+代码
- BZOJ 3098: Hash Killer II(新生必做的水题)
- [接口测试 - 基础篇] 04 无法绕过的json解析
- 【专知-PyTorch手把手深度学习教程02】CNN快速理解与PyTorch实现: 图文+代码
- [接口测试 - 基础篇] 03 unittest测试框架了解多少才够?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 小程序生成二维码海报的组件-wxa-plugin-canvas
- kbone 是什么?这可能是最好的小程序开源框架
- jQuery根据填写的input的数值导出excel表格
- 解决多种版本python冲突问题
- 探索 App Clips
- ES索引模糊查询
- Dubbo定时任务时间轮(Time Wheel)算法详解
- Vue 中 data 为什么必须是一个函数
- Windows下制作nodejs后台程序的脚本-开机自启动
- Siamese Network & Triplet NetWork
- js常用函数集锦(持续更新)
- 《Java从入门到失业》第五章:继承与多态(5.8-5.10):多态与Object类
- 构建一个适合stm32mp157系列开发板的嵌入式Linux系统
- linux 达梦数据库 命令行 卸载
- Access Control: Database(数据库访问控制)最新解析及完整解决方案