TCP/IP协议栈在Linux内核中的运行时序分析
1.1 TCP/IP概念
互联网协议套件(Internet Protocol Suite,缩写IPS)是网络通信模型,以及整个网络传输协议家族,为网际网络的基础通信架构。它常通称为TCP/IP协议族,简称TCP/IP,因为该协议家族的两个核心协议:TCP(传输控制协议)和IP(网际协议),为该家族中最早通过的标准。由于在网络通讯协议普遍采用分层的结构,当多个层次的协议共同工作时,类似计算机科学中的堆栈,因此又称为TCP/IP协议栈。这些协议最早发源于美国国防部(缩写为DoD)的ARPA网项目,因此也称作DoD模型(DoD Model)。这个协议族由互联网工程任务组负责维护。
TCP/IP提供了点对点链接的机制,将资料应该如何封装、寻址、传输、路由以及在目的地如何接收,都加以标准化。它将软件通信过程抽象化为四个抽象层,采取协议堆栈的方式,分别实现出不同通信协议。协议族下的各种协议,依其功能不同,分别归属到这四个层次结构之中,常视为是简化的七层OSI模型。
1.2 TCP/IP协议栈组成
整个通信网络的任务,可以划分成不同的功能区块,即所谓的层级(layer),用于互联网的协议可以比照TCP/IP参考模型进行分类。TCP/IP协议栈起始于第三层协议IP(网际协议)。所有这些协议都在相应的RFC文档中讨论及标准化。重要的协议在相应的RFC文档中均标记状态:“必须”(required),“推荐”(recommended),“可选”(selective)。其他的协议还可能有“试验”(experimental)或“历史”(historic)的状态。”
1.3 OSI模型和TCP/IP模型
| OSI模型 | 协议或功能物件 |
|---|---|
| 7.应用层 | 例如HTTP、SMTP、SNMP、FTP、Telnet、SIP、SSH、NFS、RTSP、XMPP、Whois、ENRP、TLS |
| 6.表示层 | 例如XDR、ASN.1、NCP、TLS、ASCII |
| 5.会话层 | 例如ASAP、ISO 8327 / CCITT X.225、RPC、NetBIOS、Winsock、BSD sockets、SOCKS、密码验证协议 |
| 4.传输层 | 例如TCP、UDP、RTP、SCTP、SPX、ATP、IL |
| 3.网络层 | 例如IP、ICMP、IPX、BGP、OSPF、RIP、IGRP、EIGRP、ARP、RARP、X.25 |
| 2.数据链路层 | 例如以太网、令牌环、HDLC、帧中继、ISDN、ATM、IEEE 802.11、FDDI、PPP |
| 1.物理层 | 例如调制解调器、无线电、光纤 |
| TCP/IP模型 | 协议或功能物件 |
|---|---|
| 4.应用层 | 例如HTTP、FTP、DNS(如BGP和RIP这样的路由协议,尽管由于各种各样的原因它们分别运行在TCP和UDP上,仍然可以将它们看作网络层的一部分) |
| 3.传输层 | 例如TCP、UDP、RTP、SCTP<(如OSPF这样的路由协议,尽管运行在IP上也可以看作是网络层的一部分) |
| 2.网际层 | 对于TCP/IP来说这是因特网协议(IP)(如ICMP和IGMP这样的必须协议尽管运行在IP上,也仍然可以看作是网络互连层的一部分;ARP不运行在IP上) |
| 1.网络访问层 | 例如以太网、Wi-Fi、MPLS等。 |

通常人们认为OSI模型的最上面三层(应用层、表示层和会话层)在TCP/IP组中是一个应用层。由于TCP/IP有一个相对较弱的会话层,由TCP和RTP下的打开和关闭连接组成,并且在TCP和UDP下的各种应用提供不同的端口号,这些功能能够由单个的应用程序(或者那些应用程序所使用的库)增加。与此相似的是,IP是按照将它下面的网络当作一个黑盒子的思想设计的,这样在讨论TCP/IP的时候就可以把它当作一个独立的层。
1)应用层
该层包括所有和应用程序协同工作,利用基础网络交换应用程序专用的数据的协议。 应用层是大多数普通与网络相关的程序为了通过网络与其他程序通信所使用的层。这个层的处理过程是应用特有的;数据从网络相关的程序以这种应用内部使用的格式进行传送,然后编码成标准协议的格式。
一些特定的程序视为在此层运行。它们提供服务直接支持用户应用。这些程序和它们对应的协议包括HTTP(万维网服务)、FTP(文件传输)、SMTP(电子邮件)、SSH(安全远程登录)、DNS(名称⇔IP地址寻找)以及许多其他协议。
每一个应用层(TCP/IP参考模型的最高层)协议一般都会使用到两个传输层协议之一: 面向连接的TCP传输控制协议和无连接的包传输的UDP用户数据报文协议。
2)传输层
传输层(transport layer)的协议,能够解决诸如端到端可靠性(“数据是否已经到达目的地?”)和保证数据按照正确的顺序到达这样的问题。在TCP/IP协议组中,传输协议也包括所给数据应该送给哪个应用程序。 在TCP/IP协议组中技术上位于这个层的动态路由协议通常认为是网络层的一部分;一个例子就是OSPF(IP协议89)。
TCP(IP协议6)是一个“可靠的”、面向链接的传输机制,它提供一种可靠的字节流保证数据完整、无损并且按顺序到达。TCP尽量连续不断地测试网络的负载并且控制发送数据的速度以避免网络过载。另外,TCP试图将数据按照规定的顺序发送。这是它与UDP不同之处,这在实时数据流或者路由高网络层丢失率应用的时候可能成为一个缺陷。 较新的SCTP也是一个“可靠的”、面向链接的传输机制。它是面向记录而不是面向字节的,它在一个单独的链接上提供通过多路复用提供的多个子流。它也提供多路自寻址支持,其中链接终端能够以多个IP地址表示(代表多个实体接口),这样的话即使其中一个连接失败了也不中断。它最初是为电话应用开发的(在IP上传输SS7),但是也可以用于其他的应用。
UDP(IP协议号17)是一个无链接的数据报协议。它是一个“尽力传递”(best effort)或者说“不可靠”协议——不是因为它特别不可靠,而是因为它不检查数据包是否已经到达目的地,并且不保证它们按顺序到达。如果一个应用程序需要这些特性,那它必须自行检测和判断,或者使用TCP协议。 UDP的典型性应用是如流媒体(音频和视频等)这样按时到达比可靠性更重要的应用,或者如DNS查找这样的简单查询/响应应用,如果创建可靠的链接所作的额外工作将是不成比例地大。 DCCP目前正由IETF开发。它提供TCP流动控制语义,但对于用户来说保留UDP的数据报服务模型。
3)网际层
TCP/IP协议族中的网际层(internet layer)在OSI模型中叫做网络层(network layer)。正如最初所定义的,网络层解决在一个单一网络上传输数据包的问题。类似的协议有X.25和ARPANET的Host/IMP Protocol。 随着因特网思想的出现,在这个层上添加附加的功能,也就是将数据从源网络传输到目的网络。这就牵涉到在网络组成的网上选择路径将数据包传输,也就是因特网。 在因特网协议组中,IP完成数据从源发送到目的的基本任务。IP能够承载多种不同的高层协议的数据;这些协议使用一个唯一的IP协议号进行标识。ICMP和IGMP分别是1和2。 一些IP承载的协议,如ICMP(用来发送关于IP发送的诊断信息)和IGMP(用来管理多播数据),它们位于IP层之上但是完成网络层的功能,这表明因特网和OSI模型之间的不兼容性。所有的路由协议,如BGP、OSPF、和RIP实际上也是网络层的一部分,尽管它们似乎应该属于更高的协议栈。
4)网络访问层
网络访问层实际上并不是因特网协议组中的一部分,但是它是数据包从一个设备的网络层传输到另外一个设备的网络层的方法。这个过程能够在网卡的软件驱动程序中控制,也可以在韧体或者专用芯片中控制。这将完成如添加报头准备发送、通过实体介质实际发送这样一些数据链路功能。另一端,链路层将完成数据帧接收、去除报头并且将接收到的包传到网络层。 然而,链路层并不经常这样简单。它也可能是一个虚拟专有网络(VPN)或者隧道,在这里从网络层来的包使用隧道协议和其他(或者同样的)协议组发送而不是发送到实体的接口上。VPN和通道通常预先建好,并且它们有一些直接发送到实体接口所没有的特殊特点(例如,它可以加密经过它的数据)。由于现在链路“层”是一个完整的网络,这种协议组的递归使用可能引起混淆。但是它是一个实现常见复杂功能的一个优秀方法。(尽管需要注意预防一个已经封装并且经隧道发送下去的数据包进行再次地封装和发送)。
2 Socket编程概述
2.1简介
•独立于具体协议的网络编程接口
•在OSI模型中,主要位于会话层和传输层之间
•BSD Socket(伯克利套接字)是通过标准的UNIX文件描述符和其它程序通讯的一个方法,目前已经被广泛移植到各个平台。
2.2 Socket API函数
•socket 创建套接字
•connect 建立连接
•bind 绑定本机端口
•listen 监听端口
•accept 接受连接
•recv, recvfrom 数据接收
•send, sendto 数据发送
close, shutdown 关闭套接字
2.3UDP数据通信的过程
UDP数据通信的过程如下所示:

2.4TCP数据通信的过程
TCP数据通信的过程如下图所示:

2.5Socket系统调用
1.sys_socketcall
在Linux系统中,所有的socket系统的调用总入口都为sys_socketcall,其函数原型为:

函数的两个形参含义如下:
*call:每个数字代表一个操作码,一共17种,具体操作码对应情况如下:

对一些常用操作的说明:
SYS_SOCKET: 创建一个套接口,若创建成功,返回一个打开的文件描述符
SYS_BIND:将套接字地址与套接字号相绑定
SYS_CONNECT: 建立连接
SYS_LISTEN: 仅在TCP服务器端调用,将套接字转换到LISTEN状态
SYS_ACCEPT: 用于面向对象的连接器,用于接受新的连接
SYS_SEND:见下分析
SYS_RECV:见下分析
*args参数:为一个指针,指向数组,其可以根据不同的操作码要求,从用户态复制相应长度的数据,结构如下:

3 send和recv过程中TCP/IP协议栈相关的运行任务实体及相互协作的时序分析
本次调研的代码如下
服务器代码
1 #include <stdio.h> /* perror */ 2 #include <stdlib.h> /* exit */ 3 #include <string.h> /* memset */ 4 #include <sys/wait.h> /* waitpid */ 5 #include"socketwrapper.h" 6 7 #define true 1 8 #define false 0 9 10 #define MYPORT 3490 /* 监听的端口 */ 11 #define BACKLOG 10 /* listen的请求接收队列长度 */ 12 #define MAXDATASIZE 100 /*最大接受客户端字节数*/ 13 14 int main() 15 { 16 int sockfd, new_fd; /* 监听端口,数据端口 */ 17 struct sockaddr_in sa; /* 自身的地址信息 */ 18 struct sockaddr_in their_addr; /* 连接对方的地址信息 */ 19 unsigned int sin_size; 20 21 char buf[MAXDATASIZE]; 22 int n; 23 24 if ((sockfd = Socket(PF_INET, SOCK_STREAM, 0)) == -1) 25 { 26 perror("socket"); 27 exit(1); 28 } 29 30 sa.sin_family = AF_INET; 31 sa.sin_port = Htons(MYPORT); /* 网络字节顺序 */ 32 sa.sin_addr.s_addr = INADDR_ANY; /* 自动填本机IP */ 33 memset(&(sa.sin_zero), 0, 8); /* 其余部分置0 */ 34 35 if (bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)) == -1) 36 { 37 perror("bind"); 38 exit(1); 39 } 40 41 if (Listen(sockfd, BACKLOG) == -1) 42 { 43 perror("listen"); 44 exit(1); 45 } 46 47 /* 主循环 */ 48 while (1) 49 { 50 sin_size = sizeof(struct sockaddr_in); 51 new_fd = Accept(sockfd, 52 (struct sockaddr *)&their_addr, &sin_size); 53 if (new_fd == -1) 54 { 55 perror("accept"); 56 continue; 57 } 58 59 printf("Got connection from %s\n", 60 inet_ntoa(their_addr.sin_addr)); 61 62 n=Recv(new_fd,buf,MAXDATASIZE) 63 if (fork() == 0) 64 { 65 /* 子进程 */ 66 if (Send(new_fd, "Hello, world!\n", 14, 0) == -1) 67 perror("send"); 68 Close(new_fd); 69 exit(0); 70 } 71 buf[n] = '\0'; 72 printf("recv msg from client: %s\n", buf); 73 74 Close(new_fd); 75 76 /*清除所有子进程 */ 77 while (waitpid(-1, NULL, WNOHANG) > 0) 78 ; 79 } 80 Close(sockfd); 81 return true; 82 }
客户端代码
1 #include <stdio.h> /* perror */ 2 #include <stdlib.h> /* exit */ 3 #include <string.h> /* memset */ 4 #include <sys/wait.h> /* waitpid */ 5 #include"socketwrapper.h" 6 7 #define true 1 8 #define false 0 9 10 #define PORT 3490 /* Server的端口 */ 11 #define MAXDATASIZE 100 /* 一次可以读的最大字节数 */ 12 13 int main(int argc, char *argv[]) 14 { 15 int sockfd, numbytes; 16 char buf[MAXDATASIZE]; 17 struct hostent *he; /* 主机信息 */ 18 struct sockaddr_in their_addr; /* 对方地址信息 */ 19 if (argc != 2) 20 { 21 fprintf(stderr, "usage: client hostname\n"); 22 exit(1); 23 } 24 25 /* get the host info */ 26 if ((he = Gethostbyname(argv[1])) == NULL) 27 { 28 /* 注意:获取DNS信息时,显示出错需要用herror而不是perror */ 29 /* herror 在新的版本中会出现警告,已经建议不要使用了 */ 30 perror("gethostbyname"); 31 exit(1); 32 } 33 34 if ((sockfd = Socket(PF_INET, SOCK_STREAM, 0)) == -1) 35 { 36 perror("socket"); 37 exit(1); 38 } 39 40 their_addr.sin_family = AF_INET; 41 their_addr.sin_port = Htons(PORT); /* short, NBO */ 42 their_addr.sin_addr = *((struct in_addr *)he->h_addr_list[0]); 43 memset(&(their_addr.sin_zero), 0, 8); /* 其余部分设成0 */ 44 45 if (Connect(sockfd, (struct sockaddr *)&their_addr, 46 sizeof(struct sockaddr)) == -1) 47 { 48 perror("connect"); 49 exit(1); 50 } 51 52 if( Send(sockfd, "Hi, server\n", 9, 0) < 0) 53 { 54 printf("send msg error: %s(errno: %d)\n", strerror(errno), errno); 55 exit(0); 56 } 57 58 if ((numbytes = Recv(sockfd, buf, MAXDATASIZE, 0)) == -1) 59 { 60 perror("recv"); 61 exit(1); 62 } 63 64 buf[numbytes] = '\0'; 65 printf("Received: %s", buf); 66 Close(sockfd); 67 68 return true; 69 }
客户端和服务器端交互具体流程如下:

3.1 send和recv应用层流程
应用层的各种网络应用程序基本上都是通过 Linux Socket 编程接口来和内核空间的网络协议栈通信的。Linux Socket 是Linux 操作系统的重要组成部分之一,是网络应用程序的基础。从层次上来说,它位于应用层,位于传输层协议之上,屏蔽了不同网络协议之间的差异,同时也是网络编程的入口,它提供了大量的系统调用,构成了网络程序的主体。
在Linux系统中,socket 属于文件系统的一部分,网络通信可以被看作是对文件的读取,使得对网络的控制和对文件的控制一样方便。
3.1.1 socket的创建
在内核中与socket对应的系统调用是sys_soceket,所谓的创建套接口,就是在sockfs这个文件系统中创建一个节点,从Linux/Unix的角度来看,该节点是一个文件,不过这个文件具有非普通文件的属性,于是起了--个独特的名字socket。由于sockfs文件系统是系统初始化时就保存在全局指针sock_mnt中的,所以申请一个inode的过程便以sock_mnt为参数。
socket函数本身,经过glibc库对其封装,它将通过int 0x80产生-一个软件中断(注意不是软中断),由内核导向执行sys_socket,基本上参数会原封不动地传入内核,它们分别是(1) int family,(2) int type, (3) int protocol。
其调用树如下图:
在sock_create中会继续调用sock_alloc,该函数创建了struct socket{}结构。

通过调用树可知sock_alloc的作用就是分配和初始化属于网络类型的inode。
inode结构中的大部分字段只是对真正的文件系统重要,只有一部分由socket使用。Socket_alloc类型的Inode结构如下:

这里socket socket 部分就是特定文件的数据。Socket{}结构就表示这是一个和网络有关的文件描述符,是INET层和应用层打开的文件描述一一对应的实体,每一次调用socket都会在INET中保存这么一个实体。
3.1.2 socket的发送
在创建完socket之后,应用程序会使用send发送数据。sys_send只是对sys_sendto函数的简单封装,在用户态调用系统接口send、sendto或者sendmsg都是在调用sock_sendmsg
1 SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len, 2 unsigned int, flags, struct sockaddr __user *, addr, 3 int, addr_len) 4 { 5 return __sys_sendto(fd, buff, len, flags, addr, addr_len); 6 } 7 8 /* 9 * Send a datagram down a socket. 10 */ 11 12 SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len, 13 unsigned int, flags) 14 { 15 return __sys_sendto(fd, buff, len, flags, NULL, 0); 16 }
1 int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags, 2 struct sockaddr __user *addr, int addr_len) 3 { 4 struct socket *sock; 5 struct sockaddr_storage address; 6 int err; 7 struct msghdr msg; 8 struct iovec iov; 9 int fput_needed; 10 11 err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter); 12 if (unlikely(err)) 13 return err; 14 sock = sockfd_lookup_light(fd, &err, &fput_needed); 15 if (!sock) 16 goto out; 17 18 msg.msg_name = NULL; 19 msg.msg_control = NULL; 20 msg.msg_controllen = 0; 21 msg.msg_namelen = 0; 22 if (addr) { 23 err = move_addr_to_kernel(addr, addr_len, &address); 24 if (err < 0) 25 goto out_put; 26 msg.msg_name = (struct sockaddr *)&address; 27 msg.msg_namelen = addr_len; 28 } 29 if (sock->file->f_flags & O_NONBLOCK) 30 flags |= MSG_DONTWAIT; 31 msg.msg_flags = flags; 32 err = sock_sendmsg(sock, &msg); 33 34 out_put: 35 fput_light(sock->file, fput_needed); 36 out: 37 return err; 38 }
再来看看它们调用的 sock_sendmsg
1 int sock_sendmsg(struct socket *sock, struct msghdr *msg) 2 { 3 int err = security_socket_sendmsg(sock, msg, 4 msg_data_left(msg)); 5 6 return err ?: sock_sendmsg_nosec(sock, msg); 7 } 8 EXPORT_SYMBOL(sock_sendmsg);
1 static inline int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg) 2 { 3 int ret = INDIRECT_CALL_INET(sock->ops->sendmsg, inet6_sendmsg, 4 inet_sendmsg, sock, msg, 5 msg_data_left(msg)); 6 BUG_ON(ret == -EIOCBQUEUED); 7 return ret; 8 }
继续追踪这个函数,会看到最终调用的是inet_sendmsg
1 int inet_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) 2 { 3 struct sock *sk = sock->sk; 4 5 if (unlikely(inet_send_prepare(sk))) 6 return -EAGAIN; 7 8 return INDIRECT_CALL_2(sk->sk_prot->sendmsg, tcp_sendmsg, udp_sendmsg, 9 sk, msg, size); 10 } 11 EXPORT_SYMBOL(inet_sendmsg);
通过上述源码的分析,我们大致可以画出sendmsg函数的系统调用过程

3.1.3 socket的接收
对于recv的系统调用,仔细分析源码,可以发现,与send的调用过程非常的相似:
1 SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size, 2 unsigned int, flags, struct sockaddr __user *, addr, 3 int __user *, addr_len) 4 { 5 return __sys_recvfrom(fd, ubuf, size, flags, addr, addr_len); 6 }
1 int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags, 2 struct sockaddr __user *addr, int __user *addr_len) 3 { 4 struct socket *sock; 5 struct iovec iov; 6 struct msghdr msg; 7 struct sockaddr_storage address; 8 int err, err2; 9 int fput_needed; 10 11 err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter); 12 if (unlikely(err)) 13 return err; 14 sock = sockfd_lookup_light(fd, &err, &fput_needed); 15 if (!sock) 16 goto out; 17 18 msg.msg_control = NULL; 19 msg.msg_controllen = 0; 20 /* Save some cycles and don't copy the address if not needed */ 21 msg.msg_name = addr ? (struct sockaddr *)&address : NULL; 22 /* We assume all kernel code knows the size of sockaddr_storage */ 23 msg.msg_namelen = 0; 24 msg.msg_iocb = NULL; 25 msg.msg_flags = 0; 26 if (sock->file->f_flags & O_NONBLOCK) 27 flags |= MSG_DONTWAIT; 28 err = sock_recvmsg(sock, &msg, flags); 29 30 if (err >= 0 && addr != NULL) { 31 err2 = move_addr_to_user(&address, 32 msg.msg_namelen, addr, addr_len); 33 if (err2 < 0) 34 err = err2; 35 } 36 37 fput_light(sock->file, fput_needed); 38 out: 39 return err; 40 }
1 int sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags) 2 { 3 int err = security_socket_recvmsg(sock, msg, msg_data_left(msg), flags); 4 5 return err ?: sock_recvmsg_nosec(sock, msg, flags); 6 }
1 static inline int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg, 2 int flags) 3 { 4 return INDIRECT_CALL_INET(sock->ops->recvmsg, inet6_recvmsg, 5 inet_recvmsg, sock, msg, msg_data_left(msg), 6 flags); 7 }
sock->ops->recvmsg即inet_recvmsg,最后在inet_recvmsg中调用的是tcp_recvmsg
1 int inet_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, 2 int flags) 3 { 4 struct sock *sk = sock->sk; 5 int addr_len = 0; 6 int err; 7 8 if (likely(!(flags & MSG_ERRQUEUE))) 9 sock_rps_record_flow(sk); 10 11 err = INDIRECT_CALL_2(sk->sk_prot->recvmsg, tcp_recvmsg, udp_recvmsg, 12 sk, msg, size, flags & MSG_DONTWAIT, 13 flags & ~MSG_DONTWAIT, &addr_len); 14 if (err >= 0) 15 msg->msg_namelen = addr_len; 16 return err; 17 }
直接给出调用过程图如下:

然后就是进入传输层的调用。
3.2send和recv传输层分析
传输层向网际层方向:
大致经历了以下几个步骤:
调用Tcp_sendmsg函数检查链接状态,并同时获取链接的MSS。创建该数据包的 sk_buffer 数据结构实例 skb,从 userspace buffer 中拷贝 packet 的数据到 skb 的 buffer。构造数据包头部,接而计算 TCP 校验和(ack)和顺序号(seq)。最后调用ip_queue_xmit函数将数据包传输到网际层进行处理。
这里主要对Tcp_sendmsg函数的调用逻辑进行补充分析,该函数只要检查已经建立的 TCP connection 的状态,然后获取有效的 MSS,Tcp_sendmsg函数的内部调用顺序如下:

各调用函数作用说明:
1.Tcp_sendmsg分析:sendmsg系统调用在TCP层的实现
2.lock_sock():获取套接口的锁
3.sock_sndtimeo()根据标志计算阻塞超时时间
4.sk_stream_wait_connect():对于不能发送信息状态须等待连接正确建立,超时
5.tcp_current_mss():获得有效的MSS
3.2.1 tcp发送数据
1 int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) 2 { 3 int ret; 4 5 lock_sock(sk); 6 ret = tcp_sendmsg_locked(sk, msg, size); 7 release_sock(sk); 8 9 return ret; 10 }
tcp_sendmsg实际上调用的是int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
1 int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size) 2 { 3 struct tcp_sock *tp = tcp_sk(sk); 4 struct ubuf_info *uarg = NULL; 5 struct sk_buff *skb; 6 struct sockcm_cookie sockc; 7 int flags, err, copied = 0; 8 int mss_now = 0, size_goal, copied_syn = 0; 9 int process_backlog = 0; 10 bool zc = false; 11 long timeo; 12 13 flags = msg->msg_flags;
1 if (copied) 2 tcp_push(sk, flags & ~MSG_MORE, mss_now, 3 TCP_NAGLE_PUSH, size_goal);
在tcp_sendmsg_locked中,完成的是将所有的数据组织成发送队列,这个发送队列是struct sock结构中的一个域sk_write_queue,这个队列的每一个元素是一个skb,里面存放的就是待发送的数据。然后调用了tcp_push()函数。

在tcp协议的头部有几个标志字段:URG、ACK、RSH、RST、SYN、FIN,tcp_push中会判断这个skb的元素是否需要push,如果需要就将tcp头部字段的push置一,置一的过程如下:
1 static inline void tcp_mark_push(struct tcp_sock *tp, struct sk_buff *skb) 2 { 3 TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_PSH; 4 tp->pushed_seq = tp->write_seq; 5 }
1 static void tcp_push(struct sock *sk, int flags, int mss_now, 2 int nonagle, int size_goal) 3 { 4 struct tcp_sock *tp = tcp_sk(sk); 5 struct sk_buff *skb; 6 7 skb = tcp_write_queue_tail(sk); 8 if (!skb) 9 return; 10 if (!(flags & MSG_MORE) || forced_push(tp)) 11 tcp_mark_push(tp, skb); 12 13 tcp_mark_urg(tp, flags); 14 15 if (tcp_should_autocork(sk, skb, size_goal)) { 16 17 /* avoid atomic op if TSQ_THROTTLED bit is already set */ 18 if (!test_bit(TSQ_THROTTLED, &sk->sk_tsq_flags)) { 19 NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTOCORKING); 20 set_bit(TSQ_THROTTLED, &sk->sk_tsq_flags); 21 } 22 /* It is possible TX completion already happened 23 * before we set TSQ_THROTTLED. 24 */ 25 if (refcount_read(&sk->sk_wmem_alloc) > skb->truesize) 26 return; 27 } 28 29 if (flags & MSG_MORE) 30 nonagle = TCP_NAGLE_CORK; 31 32 __tcp_push_pending_frames(sk, mss_now, nonagle); 33 }
然后,tcp_push调用了__tcp_push_pending_frames(sk, mss_now, nonagle);函数发送数据:
1 void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss, 2 int nonagle) 3 { 4 /* If we are closed, the bytes will have to remain here. 5 * In time closedown will finish, we empty the write queue and 6 * all will be happy. 7 */ 8 if (unlikely(sk->sk_state == TCP_CLOSE)) 9 return; 10 11 if (tcp_write_xmit(sk, cur_mss, nonagle, 0, 12 sk_gfp_mask(sk, GFP_ATOMIC))) 13 tcp_check_probe_timer(sk); 14 }
随后又调用了tcp_write_xmit来发送数据:
1 static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, 2 int push_one, gfp_t gfp) 3 { 4 struct tcp_sock *tp = tcp_sk(sk); 5 struct sk_buff *skb; 6 unsigned int tso_segs, sent_pkts; 7 int cwnd_quota; 8 int result; 9 bool is_cwnd_limited = false, is_rwnd_limited = false; 10 u32 max_segs; 11 12 sent_pkts = 0;
若发送队列未满,则准备发送报文
1 while ((skb = tcp_send_head(sk))) { 2 unsigned int limit; 3 4 if (unlikely(tp->repair) && tp->repair_queue == TCP_SEND_QUEUE) { 5 /* "skb_mstamp_ns" is used as a start point for the retransmit timer */ 6 skb->skb_mstamp_ns = tp->tcp_wstamp_ns = tp->tcp_clock_cache; 7 list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue); 8 tcp_init_tso_segs(skb, mss_now); 9 goto repair; /* Skip network transmission */ 10 } 11 12 if (tcp_pacing_check(sk)) 13 break; 14 15 tso_segs = tcp_init_tso_segs(skb, mss_now); 16 BUG_ON(!tso_segs);
检查发送窗口的大小
cwnd_quota = tcp_cwnd_test(tp, skb); if (!cwnd_quota) { if (push_one == 2) /* Force out a loss probe pkt. */ cwnd_quota = 1; else break; }
tcp_write_xmit位于tcpoutput.c中,它实现了tcp的拥塞控制,然后调用了tcp_transmit_skb(sk, skb, 1, gfp)传输数据,实际上调用的是__tcp_transmit_skb


构建TCP头部和校验和


tcp_transmit_skb是tcp发送数据位于传输层的最后一步,这里首先对TCP数据段的头部进行了处理,然后调用了网络层提供的发送接口icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);实现了数据的发送,自此,数据离开了传输层,传输层的任务也就结束了。
网际层向传输层方向:

各调用函数作用说明:
1.tcp_v4_rcv():充当网络层与传输层的接口,传输层报文处理入口函数
2.__inet_lookup_v4_lookup():在ehash或者bhask中查找传输控制块,若无找到则进行退出, 并通过tcp_v4_send_reset(skb)发送RST段给对方,如果报文被损坏则无法发送rst,直接丢包
3.xfrm4_policy_check():进行安全检查
4.sk_filter():看是否符合过滤器规则
5.tcp_v4_do_rcv():传输层处理TCP段的主入口
6.tcp_rcv_established():当连接已经建立时,用快速路径处理报文
7. tcp_v4_hnd_req():为侦听套口,处理半连接状态的ACK消息
8. tcp_child_process():不是侦听套接字,说明已经建立了半连接。调用此函数初始化子传输控制块,如果失败则向客户端发送rst段,即tcp_v4_send_reset()
3.2.2 tcp接收数据
接收函数比发送函数要复杂得多,因为数据接收不仅仅只是接收,tcp的三次握手也是在接收函数实现的,所以收到数据后要判断当前的状态,是否正在建立连接等,根据发来的信息考虑状态是否要改变,在这里仅仅考虑在连接建立后数据的接收。
首先从上向下分析,即上一层中调用了tcp_recvmsg。
该函数完成从接收队列中读取数据复制到用户空间的任务;函数在执行过程中会锁定控制块,避免软中断在tcp层的影响;函数会涉及从接收队列receive_queue和后备队列backlog中读取数据;其中从backlog中读取的数据,还需要经过sk_backlog_rcv回调,该回调的实现为tcp_v4_do_rcv,实际上是先缓存到队列中,然后需要读取的时候,才进入协议栈处理,此时,是在进程上下文执行的,因为会设置tp->ucopy.task=current,在协议栈处理过程中,会直接将数据复制到用户空间。
这里的copied表示已经复制了多少字节,target表示目标是多少字节。
1 int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock, 2 int flags, int *addr_len) 3 { 4 struct tcp_sock *tp = tcp_sk(sk); 5 int copied = 0; 6 u32 peek_seq; 7 u32 *seq; 8 unsigned long used; 9 int err, inq; 10 int target; /* Read at least this many bytes */ 11 long timeo; 12 struct sk_buff *skb, *last; 13 u32 urg_hole = 0; 14 struct scm_timestamping_internal tss; 15 int cmsg_flags; 16 17 if (unlikely(flags & MSG_ERRQUEUE)) 18 return inet_recv_error(sk, msg, len, addr_len); 19 20 if (sk_can_busy_loop(sk) && skb_queue_empty_lockless(&sk->sk_receive_queue) && 21 (sk->sk_state == TCP_ESTABLISHED)) 22 sk_busy_loop(sk, nonblock); 23 24 lock_sock(sk);
在连接建立后,若没有数据到来,接收队列为空,进程会在sk_busy_loop函数内循环等待。Lock_sock()传输层上锁,避免软中断影响 。
1 if (unlikely(tp->repair)) { 2 err = -EPERM; 3 if (!(flags & MSG_PEEK)) 4 goto out; 5 6 if (tp->repair_queue == TCP_SEND_QUEUE) 7 goto recv_sndq; 8 9 err = -EINVAL; 10 if (tp->repair_queue == TCP_NO_QUEUE) 11 goto out; 12 13 /* 'common' recv queue MSG_PEEK-ing */ 14 }
1 last = skb_peek_tail(&sk->sk_receive_queue); 2 skb_queue_walk(&sk->sk_receive_queue, skb) { 3 last = skb;
获得数据后,遍历接收队列,找到满足读取的skb
1 if (!(flags & MSG_TRUNC)) { 2 err = skb_copy_datagram_msg(skb, offset, msg, used); 3 if (err) { 4 /* Exception. Bailout! */ 5 if (!copied) 6 copied = -EFAULT; 7 break; 8 } 9 } 10 11 WRITE_ONCE(*seq, *seq + used); 12 copied += used; 13 len -= used; 14 15 tcp_rcv_space_adjust(sk);
并调用函数skb_copy_datagram_msg将接收到的数据拷贝到用户态,实际调用的是__skb_datagram_iter,这里同样用了struct msghdr *msg来实现。


以上是对数据进行拷贝。

如果copied>0,即读取到数据则继续,否则的话,也就是没有读到想要的数据,[当设置了nonblock时,(表现在timeo=0)],就返回-EAGAIN,也就是非阻塞方式。
1 if (copied) { 2 if (sk->sk_err || 3 sk->sk_state == TCP_CLOSE || 4 (sk->sk_shutdown & RCV_SHUTDOWN) || 5 !timeo || 6 signal_pending(current)) 7 break; 8 } else { 9 if (sock_flag(sk, SOCK_DONE)) 10 break; 11 12 if (sk->sk_err) { 13 copied = sock_error(sk); 14 break; 15 } 16 17 if (sk->sk_shutdown & RCV_SHUTDOWN) 18 break; 19 20 if (sk->sk_state == TCP_CLOSE) { 21 /* This occurs when user tries to read 22 * from never connected socket. 23 */ 24 copied = -ENOTCONN; 25 break; 26 } 27 28 if (!timeo) { 29 copied = -EAGAIN; 30 break; 31 }
如果目标数据读取完,则处理后备队列。但是如果没有设置nonblock,同时也没有出现copied >= target的情况,也就是没有读到足够多的数据,则调用sk_wait_data将当前进程等待。也就是我们希望的阻塞方式。阻塞函数sk_wait_data所做的事情就是让出CPU,等数据来了或者设定超时之后再恢复运行。
然后从下向上分析,即tcp层是如何接收来自ip的数据并且插入相应队列的。
tcp_v4_rcv函数为TCP的总入口,数据包从IP层传递上来,进入该函数;其协议操作函数结构如下所示,其中handler即为IP层向TCP传递数据包的回调函数,设置为tcp_v4_rcv;
static struct net_protocol tcp_protocol = {
.early_demux = tcp_v4_early_demux,
.early_demux_handler = tcp_v4_early_demux,
.handler = tcp_v4_rcv,
.err_handler = tcp_v4_err,
.no_policy = 1,
.netns_ok = 1,
.icmp_strict_tag_validation = 1,
};
在IP层处理本地数据包时,会获取到上述结构的实例,并且调用实例的handler回调,也就是调用了tcp_v4_rcv;
tcp_v4_rcv函数只要做以下几个工作:(1) 设置TCP_CB (2) 查找控制块 (3)根据控制块状态做不同处理,包括TCP_TIME_WAIT状态处理,TCP_NEW_SYN_RECV状态处理,TCP_LISTEN状态处理 (4) 接收TCP段;
1 int tcp_v4_rcv(struct sk_buff *skb) 2 { 3 struct net *net = dev_net(skb->dev); 4 struct sk_buff *skb_to_free; 5 int sdif = inet_sdif(skb); 6 const struct iphdr *iph; 7 const struct tcphdr *th; 8 bool refcounted; 9 struct sock *sk; 10 int ret;
1 if (sk->sk_state == TCP_LISTEN) { 2 ret = tcp_v4_do_rcv(sk, skb); 3 goto put_and_return; 4 } 5 6 sk_incoming_cpu_update(sk); 7 8 bh_lock_sock_nested(sk); 9 tcp_segs_in(tcp_sk(sk), skb); 10 ret = 0; 11 if (!sock_owned_by_user(sk)) { 12 skb_to_free = sk->sk_rx_skb_cache; 13 sk->sk_rx_skb_cache = NULL; 14 ret = tcp_v4_do_rcv(sk, skb); 15 } else { 16 if (tcp_add_backlog(sk, skb)) 17 goto discard_and_relse; 18 skb_to_free = NULL; 19 } 20 bh_unlock_sock(sk);
tcp_v4_rcv判断状态为listen时会直接调用tcp_v4_do_rcv;如果是其他状态,将TCP包投递到目的套接字进行接收处理。如果套接字未被上锁则调用tcp_v4_do_rcv。当套接字正被用户锁定,TCP包将暂时排入该套接字的后备队列(sk_add_backlog)。
1 int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb) 2 { 3 struct sock *rsk; 4 5 if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */ 6 struct dst_entry *dst = sk->sk_rx_dst; 7 8 sock_rps_save_rxhash(sk, skb); 9 sk_mark_napi_id(sk, skb); 10 if (dst) { 11 if (inet_sk(sk)->rx_dst_ifindex != skb->skb_iif || 12 !dst->ops->check(dst, 0)) { 13 dst_release(dst); 14 sk->sk_rx_dst = NULL; 15 } 16 } 17 tcp_rcv_established(sk, skb); 18 return 0; 19 }
Tcp_v4_do_ecv检查状态如果是established,就调用tcp_rcv_established函数。
1 void tcp_rcv_established(struct sock *sk, struct sk_buff *skb) 2 { 3 const struct tcphdr *th = (const struct tcphdr *)skb->data; 4 struct tcp_sock *tp = tcp_sk(sk); 5 unsigned int len = skb->len; 6 7 /* TCP congestion window tracking */ 8 trace_tcp_probe(sk, skb); 9 10 tcp_mstamp_refresh(tp); 11 if (unlikely(!sk->sk_rx_dst)) 12 inet_csk(sk)->icsk_af_ops->sk_rx_dst_set(sk, skb); 13 /* 14 * Header prediction. 15 * The code loosely follows the one in the famous 16 * "30 instruction TCP receive" Van Jacobson mail. 17 * 18 * Van's trick is to deposit buffers into socket queue 19 * on a device interrupt, to call tcp_recv function 20 * on the receive process context and checksum and copy 21 * the buffer to user space. smart... 22 * 23 * Our current scheme is not silly either but we take the 24 * extra cost of the net_bh soft interrupt processing... 25 * We do checksum and copy also but from device to kernel. 26 */ 27 28 tp->rx_opt.saw_tstamp = 0; 29 30 /* pred_flags is 0xS?10 << 16 + snd_wnd 31 * if header_prediction is to be made 32 * 'S' will always be tp->tcp_header_len >> 2 33 * '?' will be 0 for the fast path, otherwise pred_flags is 0 to 34 * turn it off (when there are holes in the receive 35 * space for instance) 36 * PSH flag is ignored. 37 */ 38 39 if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags && 40 TCP_SKB_CB(skb)->seq == tp->rcv_nxt && 41 !after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) { 42 int tcp_header_len = tp->tcp_header_len; 43 44 /* Timestamp header prediction: tcp_header_len 45 * is automatically equal to th->doff*4 due to pred_flags 46 * match. 47 */ 48 49 /* Check timestamp */ 50 if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) { 51 /* No? Slow path! */ 52 if (!tcp_parse_aligned_timestamp(tp, th)) 53 goto slow_path; 54 55 /* If PAWS failed, check it more carefully in slow path */ 56 if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0) 57 goto slow_path;
tcp_rcv_established用于处理已连接状态下的输入,处理过程根据首部预测字段分为快速路径和慢速路径。
在快路中,若无数据,则处理输入ack,释放该skb,检查是否有数据发送,有则发送
if (len <= tcp_header_len) { /* Bulk data transfer: sender */ if (len == tcp_header_len) { /* Predicted packet is in window by definition. * seq == rcv_nxt and rcv_wup <= rcv_nxt. * Hence, check seq<=rcv_wup reduces to: */ if (tcp_header_len == (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) && tp->rcv_nxt == tp->rcv_wup) tcp_store_ts_recent(tp); /* We know that such packets are checksummed * on entry. */ tcp_ack(sk, skb, 0); __kfree_skb(skb); tcp_data_snd_check(sk); /* When receiving pure ack in fast path, update * last ts ecr directly instead of calling * tcp_rcv_rtt_measure_ts() */ tp->rcv_rtt_last_tsecr = tp->rx_opt.rcv_tsecr; return; } else { /* Header too small */ TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS); goto discard; }
;
if (tcp_checksum_complete(skb)) goto csum_error; if ((int)skb->truesize > sk->sk_forward_alloc) goto step5; /* Predicted packet is in window by definition. * seq == rcv_nxt and rcv_wup <= rcv_nxt. * Hence, check seq<=rcv_wup reduces to: */ if (tcp_header_len == (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) && tp->rcv_nxt == tp->rcv_wup) tcp_store_ts_recent(tp); tcp_rcv_rtt_measure_ts(sk, skb); NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPHPHITS); /* Bulk data transfer: receiver */ __skb_pull(skb, tcp_header_len); eaten = tcp_queue_rcv(sk, skb, &fragstolen); tcp_event_data_recv(sk, skb);
若有数据,则使用tcp_queue_rcv()将数据加入到接收队列中。
1 static int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, 2 bool *fragstolen) 3 { 4 int eaten; 5 struct sk_buff *tail = skb_peek_tail(&sk->sk_receive_queue); 6 7 eaten = (tail && 8 tcp_try_coalesce(sk, tail, 9 skb, fragstolen)) ? 1 : 0; 10 tcp_rcv_nxt_update(tcp_sk(sk), TCP_SKB_CB(skb)->end_seq); 11 if (!eaten) { 12 __skb_queue_tail(&sk->sk_receive_queue, skb); 13 skb_set_owner_r(skb, sk); 14 } 15 return eaten; 16 }
加入方式包括合并到已有数据段,或者加入队列尾部。
1 if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) { 2 /* Well, only one small jumplet in fast path... */ 3 tcp_ack(sk, skb, FLAG_DATA); 4 tcp_data_snd_check(sk); 5 if (!inet_csk_ack_scheduled(sk)) 6 goto no_ack; 7 } 8 9 __tcp_ack_snd_check(sk, 0); 10 no_ack: 11 if (eaten) 12 kfree_skb_partial(skb, fragstolen); 13 tcp_data_ready(sk); 14 return; 15 } 16 }
回到快路中继续进行tcp_ack()处理ack , tcp_data_snd_check(sk)检查是否有数据要发送,需要则发送,__tcp_ack_snd_check(sk, 0)检查是否有ack要发送,需要则发送.
kfree_skb_partial(skb, fragstolen) skb已经复制到用户空间,则释放之。
1 void tcp_data_ready(struct sock *sk) 2 { 3 const struct tcp_sock *tp = tcp_sk(sk); 4 int avail = tp->rcv_nxt - tp->copied_seq; 5 6 if (avail < sk->sk_rcvlowat && !sock_flag(sk, SOCK_DONE)) 7 return; 8 9 sk->sk_data_ready(sk); 10 }
唤醒用户进程通知有数据可读。
slow_path: if (len < (th->doff << 2) || tcp_checksum_complete(skb)) goto csum_error; if (!th->ack && !th->rst && !th->syn) goto discard; /* * Standard slow path. */ if (!tcp_validate_incoming(sk, skb, th, 1)) return; step5: if (tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) < 0) goto discard; tcp_rcv_rtt_measure_ts(sk, skb); /* Process urgent data. */ tcp_urg(sk, skb, th); /* step 7: process the segment text */ tcp_data_queue(sk, skb); tcp_data_snd_check(sk); tcp_ack_snd_check(sk); return; csum_error: TCP_INC_STATS(sock_net(sk), TCP_MIB_CSUMERRORS); TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS); discard: tcp_drop(sk, skb); }
在慢路中,会进行更详细的校验,然后处理ack,处理紧急数据,接收数据段。
1 static void tcp_data_queue(struct sock *sk, struct sk_buff *skb) 2 { 3 struct tcp_sock *tp = tcp_sk(sk); 4 bool fragstolen; 5 int eaten; 6 7 if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq) { 8 __kfree_skb(skb); 9 return; 10 } 11 skb_dst_drop(skb); 12 __skb_pull(skb, tcp_hdr(skb)->doff * 4); 13 14 tcp_ecn_accept_cwr(sk, skb); 15 16 tp->rx_opt.dsack = 0; 17 18 /* Queue data for delivery to the user. 19 * Packets in sequence go to the receive queue. 20 * Out of sequence packets to the out_of_order_queue. 21 */ 22 if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) { 23 if (tcp_receive_window(tp) == 0) { 24 NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPZEROWINDOWDROP); 25 goto out_of_window; 26 } 27 28 /* Ok. In sequence. In window. */ 29 queue_and_out: 30 if (skb_queue_len(&sk->sk_receive_queue) == 0) 31 sk_forced_mem_schedule(sk, skb->truesize); 32 else if (tcp_try_rmem_schedule(sk, skb, skb->truesize)) { 33 NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPRCVQDROP); 34 goto drop; 35 } 36 37 eaten = tcp_queue_rcv(sk, skb, &fragstolen); 38 if (skb->len) 39 tcp_event_data_recv(sk, skb); 40 if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) 41 tcp_fin(sk); 42 43 if (!RB_EMPTY_ROOT(&tp->out_of_order_queue)) { 44 tcp_ofo_queue(sk); 45 46 /* RFC5681. 4.2. SHOULD send immediate ACK, when 47 * gap in queue is filled. 48 */ 49 if (RB_EMPTY_ROOT(&tp->out_of_order_queue)) 50 inet_csk(sk)->icsk_ack.pending |= ICSK_ACK_NOW; 51 } 52 53 if (tp->rx_opt.num_sacks) 54 tcp_sack_remove(tp); 55 56 tcp_fast_path_check(sk); 57 58 if (eaten > 0) 59 kfree_skb_partial(skb, fragstolen); 60 if (!sock_flag(sk, SOCK_DEAD)) 61 tcp_data_ready(sk); 62 return; 63 }
其中数据段可能包含乱序的情况,如果他是有序的就调用tcp_queue_rcv()将数据加入到接收队列中,无序的就放入无序队列中tcp_ofo_queue。最后tcp_data_ready唤醒用户进程通知有数据可读。
3.3send和recv网际层分析
网际层到网络访问层方向:

各调用函数作用说明:
Ip_queue_xmit:将TCP端打包成IP数据报
Dst_output:封装了输出数据报目的路由缓存项中的输出端口(分为两类:单播,组播)
Ip_output: 处理单播数据报,设置数据报的输出网络设备以及网络层协议类型参数。
Ip_finish_output:观察数据报长度是否大于MTU,若大于,则调用ip_fragment分片,否则调用ip_finish_output2输出;
Ip_ finish_output2: 对skb的头部空间进行检查,看是否能够容纳下二层头部,若空间不足,则需要重新申请skb;然后,获取邻居子系统,并通过邻居子系统输出
3.3.1 ip发送数据
ip_queue_xmit是ip层提供给tcp层发送回调,大多数tcp发送都会使用这个回调,tcp层使用tcp_transmit_skb封装了tcp头之后调用该函数。
1 static inline int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, 2 struct flowi *fl) 3 { 4 return __ip_queue_xmit(sk, skb, fl, inet_sk(sk)->tos); 5 }
Ip_queue_xmit实际上是调用__ip_queue_xmit。
1 int __ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl, 2 __u8 tos) 3 { 4 struct inet_sock *inet = inet_sk(sk); 5 struct net *net = sock_net(sk); 6 struct ip_options_rcu *inet_opt; 7 struct flowi4 *fl4; 8 struct rtable *rt; 9 struct iphdr *iph; 10 int res; 11 12 /* Skip all of this if the packet is already routed, 13 * f.e. by something like SCTP. 14 */ 15 rcu_read_lock(); 16 inet_opt = rcu_dereference(inet->inet_opt); 17 fl4 = &fl->u.ip4; 18 rt = skb_rtable(skb); 19 if (rt) 20 goto packet_routed;
Skb_rtable(skb)获取skb中的路由缓存,然后判断是否有缓存,如果有缓存就直接进行packet_routed。
1 rt = (struct rtable *)__sk_dst_check(sk, 0); 2 if (!rt) { 3 __be32 daddr; 4 5 /* Use correct destination address if we have options. */ 6 daddr = inet->inet_daddr; 7 if (inet_opt && inet_opt->opt.srr) 8 daddr = inet_opt->opt.faddr; 9 10 /* If this fails, retransmit mechanism of transport layer will 11 * keep trying until route appears or the connection times 12 * itself out. 13 */ 14 rt = ip_route_output_ports(net, fl4, sk, 15 daddr, inet->inet_saddr, 16 inet->inet_dport, 17 inet->inet_sport, 18 sk->sk_protocol, 19 RT_CONN_FLAGS_TOS(sk, tos), 20 sk->sk_bound_dev_if); 21 if (IS_ERR(rt)) 22 goto no_route; 23 sk_setup_caps(sk, &rt->dst); 24 } 25 skb_dst_set_noref(skb, &rt->dst);
如果没有路由缓存就ip_route_output_ports查找路由缓存,在之后封装ip头和ip选项的功能。
1 if (inet_opt && inet_opt->opt.optlen) { 2 iph->ihl += inet_opt->opt.optlen >> 2; 3 ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0); 4 } 5 6 ip_select_ident_segs(net, skb, sk, 7 skb_shinfo(skb)->gso_segs ?: 1); 8 9 /* TODO : should we use skb->sk here instead of sk ? */ 10 skb->priority = sk->sk_priority; 11 skb->mark = sk->sk_mark; 12 13 res = ip_local_out(net, sk, skb); 14 rcu_read_unlock(); 15 return res;
最后调用ip_local_out发送数据包
1 int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb) 2 { 3 int err; 4 5 err = __ip_local_out(net, sk, skb); 6 if (likely(err == 1)) 7 err = dst_output(net, sk, skb); 8 9 return err; 10 }
调用__ip_local_out。
1 int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb) 2 { 3 struct iphdr *iph = ip_hdr(skb); 4 5 iph->tot_len = htons(skb->len); 6 ip_send_check(iph); 7 8 /* if egress device is enslaved to an L3 master device pass the 9 * skb to its handler for processing 10 */ 11 skb = l3mdev_ip_out(sk, skb); 12 if (unlikely(!skb)) 13 return 0; 14 15 skb->protocol = htons(ETH_P_IP); 16 17 return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, 18 net, sk, skb, NULL, skb_dst(skb)->dev, 19 dst_output); 20 }
经过netfilter的LOCAL_OUT钩子点进行检查过滤,如果通过,则调用dst_output函数,实际上调用的是ip数据包输出函数ip_output。
1 static int ip_finish_output(struct net *net, struct sock *sk, struct sk_buff *skb) 2 { 3 int ret; 4 5 ret = BPF_CGROUP_RUN_PROG_INET_EGRESS(sk, skb); 6 switch (ret) { 7 case NET_XMIT_SUCCESS: 8 return __ip_finish_output(net, sk, skb); 9 case NET_XMIT_CN: 10 return __ip_finish_output(net, sk, skb) ? : ret; 11 default: 12 kfree_skb(skb); 13 return ret; 14 } 15 }
里面调用ip_finish_output。
1 static int __ip_finish_output(struct net *net, struct sock *sk, struct sk_buff *skb) 2 { 3 unsigned int mtu; 4 5 #if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM) 6 /* Policy lookup after SNAT yielded a new policy */ 7 if (skb_dst(skb)->xfrm) { 8 IPCB(skb)->flags |= IPSKB_REROUTED; 9 return dst_output(net, sk, skb); 10 } 11 #endif 12 mtu = ip_skb_dst_mtu(sk, skb); 13 if (skb_is_gso(skb)) 14 return ip_finish_output_gso(net, sk, skb, mtu); 15 16 if (skb->len > mtu || (IPCB(skb)->flags & IPSKB_FRAG_PMTU)) 17 return ip_fragment(net, sk, skb, mtu, ip_finish_output2); 18 19 return ip_finish_output2(net, sk, skb); 20 }
实际上调用的是__ip_finish_output,如果需要分片就调用ip_fragment,否则直接调用ip_finish_output2。
1 static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *skb) 2 { 3 struct dst_entry *dst = skb_dst(skb); 4 struct rtable *rt = (struct rtable *)dst; 5 struct net_device *dev = dst->dev; 6 unsigned int hh_len = LL_RESERVED_SPACE(dev); 7 struct neighbour *neigh; 8 bool is_v6gw = false; 9 10 if (rt->rt_type == RTN_MULTICAST) { 11 IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTMCAST, skb->len); 12 } else if (rt->rt_type == RTN_BROADCAST) 13 IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTBCAST, skb->len); 14 15 /* Be paranoid, rather than too clever. */ 16 if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) { 17 struct sk_buff *skb2; 18 19 skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev)); 20 if (!skb2) { 21 kfree_skb(skb); 22 return -ENOMEM; 23 } 24 if (skb->sk) 25 skb_set_owner_w(skb2, skb->sk); 26 consume_skb(skb); 27 skb = skb2; 28 } 29 30 if (lwtunnel_xmit_redirect(dst->lwtstate)) { 31 int res = lwtunnel_xmit(skb); 32 33 if (res < 0 || res == LWTUNNEL_XMIT_DONE) 34 return res; 35 } 36 37 rcu_read_lock_bh(); 38 neigh = ip_neigh_for_gw(rt, skb, &is_v6gw); 39 if (!IS_ERR(neigh)) { 40 int res; 41 42 sock_confirm_neigh(skb, neigh); 43 /* if crossing protocols, can not use the cached header */ 44 res = neigh_output(neigh, skb, is_v6gw); 45 rcu_read_unlock_bh(); 46 return res; 47 } 48 rcu_read_unlock_bh(); 49 50 net_dbg_ratelimited("%s: No header cache and no neighbour!\n", 51 __func__); 52 kfree_skb(skb); 53 return -EINVAL; 54 }
在构造好ip头,检查完分片之后,会调用邻居子系统的输出函数neigh_output进行输出。
1 static inline int neigh_output(struct neighbour *n, struct sk_buff *skb, 2 bool skip_cache) 3 { 4 const struct hh_cache *hh = &n->hh; 5 6 if ((n->nud_state & NUD_CONNECTED) && hh->hh_len && !skip_cache) 7 return neigh_hh_output(hh, skb); 8 else 9 return n->output(n, skb); 10 }
输出分为有二层头缓存和没有两种情况,有缓存时调用neigh_hh_output进行快速输出,没有缓存时,则调用邻居子系统的输出回调函数进行慢速输出。
1 static inline int neigh_hh_output(const struct hh_cache *hh, struct sk_buff *skb) 2 { 3 unsigned int hh_alen = 0; 4 unsigned int seq; 5 unsigned int hh_len; 6 7 do { 8 seq = read_seqbegin(&hh->hh_lock); 9 hh_len = READ_ONCE(hh->hh_len); 10 if (likely(hh_len <= HH_DATA_MOD)) { 11 hh_alen = HH_DATA_MOD; 12 13 /* skb_push() would proceed silently if we have room for 14 * the unaligned size but not for the aligned size: 15 * check headroom explicitly. 16 */ 17 if (likely(skb_headroom(skb) >= HH_DATA_MOD)) { 18 /* this is inlined by gcc */ 19 memcpy(skb->data - HH_DATA_MOD, hh->hh_data, 20 HH_DATA_MOD); 21 } 22 } else { 23 hh_alen = HH_DATA_ALIGN(hh_len); 24 25 if (likely(skb_headroom(skb) >= hh_alen)) { 26 memcpy(skb->data - hh_alen, hh->hh_data, 27 hh_alen); 28 } 29 } 30 } while (read_seqretry(&hh->hh_lock, seq)); 31 32 if (WARN_ON_ONCE(skb_headroom(skb) < hh_alen)) { 33 kfree_skb(skb); 34 return NET_XMIT_DROP; 35 } 36 37 __skb_push(skb, hh_len); 38 return dev_queue_xmit(skb); 39 }
最后调用dev_queue_xmit向链路层发送数据包。
3.3.2 ip接收数据
IP 层的入口函数在 ip_rcv 函数。
1 int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, 2 struct net_device *orig_dev) 3 { 4 struct net *net = dev_net(dev); 5 6 skb = ip_rcv_core(skb, net); 7 if (skb == NULL) 8 return NET_RX_DROP; 9 10 return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, 11 net, NULL, skb, dev, NULL, 12 ip_rcv_finish); 13 }
然后调用已经注册的 Pre-routing netfilter hook ,完成后最终到达 ip_rcv_finish 函数。
1 static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb) 2 { 3 struct net_device *dev = skb->dev; 4 int ret; 5 6 /* if ingress device is enslaved to an L3 master device pass the 7 * skb to its handler for processing 8 */ 9 skb = l3mdev_ip_rcv(skb); 10 if (!skb) 11 return NET_RX_SUCCESS; 12 13 ret = ip_rcv_finish_core(net, sk, skb, dev); 14 if (ret != NET_RX_DROP) 15 ret = dst_input(skb); 16 return ret; 17 }
如果是发到本机就调用dst_input,里面由ip_local_deliver函数。
1 int ip_local_deliver(struct sk_buff *skb) 2 { 3 /* 4 * Reassemble IP fragments. 5 */ 6 struct net *net = dev_net(skb->dev); 7 8 if (ip_is_fragment(ip_hdr(skb))) { 9 if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER)) 10 return 0; 11 } 12 13 return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, 14 net, NULL, skb, skb->dev, NULL, 15 ip_local_deliver_finish); 16 }
判断是否分片,如果有分片就ip_defrag()进行合并多个数据包的操作,没有分片就调用ip_local_deliver_finish()。
1 static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb) 2 { 3 __skb_pull(skb, skb_network_header_len(skb)); 4 5 rcu_read_lock(); 6 ip_protocol_deliver_rcu(net, skb, ip_hdr(skb)->protocol); 7 rcu_read_unlock(); 8 9 return 0; 10 }
进一步调用ip_protocol_deliver_rcu,该函数根据 package 的下一个处理层的 protocal number,调用下一层接口,包括 tcp_v4_rcv (TCP), udp_rcv (UDP)。对于 TCP 来说,函数 tcp_v4_rcv 函数会被调用,从而处理流程进入 TCP 栈。
1 void ip_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int protocol) 2 { 3 const struct net_protocol *ipprot; 4 int raw, ret; 5 6 resubmit: 7 raw = raw_local_deliver(skb, protocol); 8 9 ipprot = rcu_dereference(inet_protos[protocol]); 10 if (ipprot) { 11 if (!ipprot->no_policy) { 12 if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { 13 kfree_skb(skb); 14 return; 15 } 16 nf_reset_ct(skb); 17 } 18 ret = INDIRECT_CALL_2(ipprot->handler, tcp_v4_rcv, udp_rcv, 19 skb); 20 if (ret < 0) { 21 protocol = -ret; 22 goto resubmit; 23 } 24 __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS); 25 } else { 26 if (!raw) { 27 if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { 28 __IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS); 29 icmp_send(skb, ICMP_DEST_UNREACH, 30 ICMP_PROT_UNREACH, 0); 31 } 32 kfree_skb(skb); 33 } else { 34 __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS); 35 consume_skb(skb); 36 } 37 } 38 }
调试验证:

3.4send和recv链路层和物理层流程
3.4.1 发送数据
上层调用dev_queue_xmit进入链路层的处理流程,实际上调用的是__dev_queue_xmit
1 static int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev) 2 { 3 struct net_device *dev = skb->dev; 4 struct netdev_queue *txq; 5 struct Qdisc *q; 6 int rc = -ENOMEM; 7 bool again = false; 8 9 skb_reset_mac_header(skb); 10 11 if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_SCHED_TSTAMP)) 12 __skb_tstamp_tx(skb, NULL, skb->sk, SCM_TSTAMP_SCHED); 13 14 /* Disable soft irqs for various locks below. Also 15 * stops preemption for RCU. 16 */ 17 rcu_read_lock_bh(); 18 19 skb_update_prio(skb); 20 21 qdisc_pkt_len_init(skb); 22 #ifdef CONFIG_NET_CLS_ACT 23 skb->tc_at_ingress = 0; 24 # ifdef CONFIG_NET_EGRESS 25 if (static_branch_unlikely(&egress_needed_key)) { 26 skb = sch_handle_egress(skb, &rc, dev); 27 if (!skb) 28 goto out; 29 } 30 # endif 31 #endif 32 /* If device/qdisc don't need skb->dst, release it right now while 33 * its hot in this cpu cache. 34 */ 35 if (dev->priv_flags & IFF_XMIT_DST_RELEASE) 36 skb_dst_drop(skb); 37 else 38 skb_dst_force(skb); 39 40 txq = netdev_core_pick_tx(dev, skb, sb_dev); 41 q = rcu_dereference_bh(txq->qdisc); 42 43 trace_net_dev_queue(skb); 44 if (q->enqueue) { 45 rc = __dev_xmit_skb(skb, q, dev, txq); 46 goto out; 47 } 48 49 /* The device has no queue. Common case for software devices: 50 * loopback, all the sorts of tunnels... 51 52 * Really, it is unlikely that netif_tx_lock protection is necessary 53 * here. (f.e. loopback and IP tunnels are clean ignoring statistics 54 * counters.) 55 * However, it is possible, that they rely on protection 56 * made by us here. 57 58 * Check this and shot the lock. It is not prone from deadlocks. 59 *Either shot noqueue qdisc, it is even simpler 8) 60 */ 61 if (dev->flags & IFF_UP) { 62 int cpu = smp_processor_id(); /* ok because BHs are off */ 63 64 if (txq->xmit_lock_owner != cpu) { 65 if (dev_xmit_recursion()) 66 goto recursion_alert; 67 68 skb = validate_xmit_skb(skb, dev, &again); 69 if (!skb) 70 goto out; 71 72 HARD_TX_LOCK(dev, txq, cpu); 73 74 if (!netif_xmit_stopped(txq)) { 75 dev_xmit_recursion_inc(); 76 skb = dev_hard_start_xmit(skb, dev, txq, &rc); 77 dev_xmit_recursion_dec(); 78 if (dev_xmit_complete(rc)) { 79 HARD_TX_UNLOCK(dev, txq); 80 goto out; 81 } 82 } 83 HARD_TX_UNLOCK(dev, txq); 84 net_crit_ratelimited("Virtual device %s asks to queue packet!\n", 85 dev->name); 86 } else { 87 /* Recursion is detected! It is possible, 88 * unfortunately 89 */
调用dev_hard_start_xmit
1 struct sk_buff *dev_hard_start_xmit(struct sk_buff *first, struct net_device *dev, 2 struct netdev_queue *txq, int *ret) 3 { 4 struct sk_buff *skb = first; 5 int rc = NETDEV_TX_OK; 6 7 while (skb) { 8 struct sk_buff *next = skb->next; 9 10 skb_mark_not_on_list(skb); 11 rc = xmit_one(skb, dev, txq, next != NULL); 12 if (unlikely(!dev_xmit_complete(rc))) { 13 skb->next = next; 14 goto out; 15 } 16 17 skb = next; 18 if (netif_tx_queue_stopped(txq) && skb) { 19 rc = NETDEV_TX_BUSY; 20 break; 21 } 22 } 23 24 out: 25 *ret = rc; 26 return skb; 27 }
然后调用 xmit_one
1 static int xmit_one(struct sk_buff *skb, struct net_device *dev, 2 struct netdev_queue *txq, bool more) 3 { 4 unsigned int len; 5 int rc; 6 7 if (dev_nit_active(dev)) 8 dev_queue_xmit_nit(skb, dev); 9 10 len = skb->len; 11 trace_net_dev_start_xmit(skb, dev); 12 rc = netdev_start_xmit(skb, dev, txq, more); 13 trace_net_dev_xmit(skb, rc, dev, len); 14 15 return rc; 16 }
调用netdev_start_xmit,实际上是调用__netdev_start_xmit
1 __netdev_start_xmit(const struct net_device_ops *ops, 2 struct sk_buff *skb, struct net_device *dev, 3 bool more) 4 { 5 __this_cpu_write(softnet_data.xmit.more, more); 6 return ops->ndo_start_xmit(skb, dev); 7 }
调用各网络设备实现的ndo_start_xmit回调函数指针,其为数据结构struct net_device,从而把数据发送给网卡,物理层在收到发送请求之后,通过 DMA 将该主存中的数据拷贝至内部RAM(buffer)之中。在数据拷贝中,同时加入符合以太网协议的相关header,IFG、前导符和CRC。对于以太网网络,物理层发送采用CSMA/CD,即在发送过程中侦听链路冲突。
一旦网卡完成报文发送,将产生中断通知CPU,然后驱动层中的中断处理程序就可以删除保存的 skb 了。
调试验证:

5.2 接受数据
这层的数据接收要涉及到一些中断和硬件层面的东西。
1: 数据包从外面的网络进入物理网卡。如果目的地址不是该网卡,且该网卡没有开启混杂模式,该包会被网卡丢弃。
2: 网卡将数据包通过DMA的方式写入到指定的内存地址,该地址由网卡驱动分配并初始化。注: 老的网卡可能不支持DMA,不过新的网卡一般都支持。
3: 网卡通过硬件中断(IRQ)通知CPU,告诉它有数据来了
4: CPU根据中断表,调用已经注册的中断函数,这个中断函数会调到驱动程序(NIC Driver)中相应的函数
5: 驱动先禁用网卡的中断,表示驱动程序已经知道内存中有数据了,告诉网卡下次再收到数据包直接写内存就可以了,不要再通知CPU了,这样可以提高效率,避免CPU不停的被中断。
6: 启动软中断。这步结束后,硬件中断处理函数就结束返回了。由于硬中断处理程序执行的过程中不能被中断,所以如果它执行时间过长,会导致CPU没法响应其它硬件的中断,于是内核引入软中断,这样可以将硬中断处理函数中耗时的部分移到软中断处理函数里面来慢慢处理。
软中断会触发内核网络模块中的软中断处理函数,内核中的ksoftirqd进程专门负责软中断的处理,当它收到软中断后,就会调用相应软中断所对应的处理函数,对于上面第6步中是网卡驱动模块抛出的软中断,ksoftirqd会调用网络模块的net_rx_action函数。
1 static __latent_entropy void net_rx_action(struct softirq_action *h) 2 { 3 struct softnet_data *sd = this_cpu_ptr(&softnet_data); 4 unsigned long time_limit = jiffies + 5 usecs_to_jiffies(netdev_budget_usecs); 6 int budget = netdev_budget; 7 LIST_HEAD(list); 8 LIST_HEAD(repoll); 9 10 local_irq_disable(); 11 list_splice_init(&sd->poll_list, &list); 12 local_irq_enable(); 13 14 for (;;) { 15 struct napi_struct *n; 16 17 if (list_empty(&list)) { 18 if (!sd_has_rps_ipi_waiting(sd) && list_empty(&repoll)) 19 goto out; 20 break; 21 } 22 23 n = list_first_entry(&list, struct napi_struct, poll_list); 24 budget -= napi_poll(n, &repoll); 25 26 /* If softirq window is exhausted then punt. 27 * Allow this to run for 2 jiffies since which will allow 28 * an average latency of 1.5/HZ. 29 */ 30 if (unlikely(budget <= 0 || 31 time_after_eq(jiffies, time_limit))) { 32 sd->time_squeeze++; 33 break; 34 } 35 } 36 37 local_irq_disable(); 38 39 list_splice_tail_init(&sd->poll_list, &list); 40 list_splice_tail(&repoll, &list); 41 list_splice(&list, &sd->poll_list); 42 if (!list_empty(&sd->poll_list)) 43 __raise_softirq_irqoff(NET_RX_SOFTIRQ); 44 45 net_rps_action_and_irq_enable(sd); 46 out: 47 __kfree_skb_flush(); 48 }
net_rx_action调用网卡驱动里的naqi_poll函数来一个一个的处理数据包。在poll函数中,驱动会一个接一个的读取网卡写到内存中的数据包,内存中数据包的格式只有驱动知道。驱动程序将内存中的数据包转换成内核网络模块能识别的skb格式,然后调用napi_gro_receive函数。
1 gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb) 2 { 3 gro_result_t ret; 4 5 skb_mark_napi_id(skb, napi); 6 trace_napi_gro_receive_entry(skb); 7 8 skb_gro_reset_offset(skb); 9 10 ret = napi_skb_finish(dev_gro_receive(napi, skb), skb); 11 trace_napi_gro_receive_exit(ret); 12 13 return ret; 14 } 15 EXPORT_SYMBOL(napi_gro_receive);
napi_gro_receive会直接调用netif_receive_skb_core。
1 int netif_receive_skb_core(struct sk_buff *skb) 2 { 3 int ret; 4 5 rcu_read_lock(); 6 ret = __netif_receive_skb_one_core(skb, false); 7 rcu_read_unlock(); 8 9 return ret; 10 } 11 EXPORT_SYMBOL(netif_receive_skb_core);
netif_receive_skb_core调用 __netif_receive_skb_one_core,将数据包交给上层ip_rcv进行处理。
1 static int __netif_receive_skb_one_core(struct sk_buff *skb, bool pfmemalloc) 2 { 3 struct net_device *orig_dev = skb->dev; 4 struct packet_type *pt_prev = NULL; 5 int ret; 6 7 ret = __netif_receive_skb_core(skb, pfmemalloc, &pt_prev); 8 if (pt_prev) 9 ret = INDIRECT_CALL_INET(pt_prev->func, ipv6_rcv, ip_rcv, skb, 10 skb->dev, pt_prev, orig_dev); 11 return ret; 12 }
待内存中的所有数据包被处理完成后(即poll函数执行完成),启用网卡的硬中断,这样下次网卡再收到数据的时候就会通知CPU。
4.时序图

浙公网安备 33010602011771号