内核版本:linux-2.6.11
本文对Linux网络子系统的收发包的流程进行一个大致梳理,以流水账的形式记录从应用层write一个socket开始到这些数据被应用层read出来的这个过程中linux的网络子系统怎么运作的。
PS:不能保证所有内容都是正确的,同时欢迎指出问题。
发包流程
1)用户进程对一个网络套接字执行write或者send后,该进程通过系统调用进入内核态。
2)在socket这层最终会通过调用sendmsg这个函数将数据传给传输层(这里涉及到使用一个多态的手法来实现不同协议的调用)。
3)接着自顶向下走协议栈的处理流程,以TCP协议为例,tcp_sendmsg作为这个函数接受到发送数据以后将其包装成sk_buff并进行协议处理,包括分组、添加头部、启动定时器等等。
4)传输层处理完以后的包会进入netfilter的NF_IP_LOCAL_OUT hook点。
5)然后将一个个sk_buff传给网络层,如果是IP协议,会进行分片、添加头部、路由判决等操作。同时会进入netfilter的NF_IP_POST_ROUTING点
6)最后,网络层通过调用dev_queue_xmit将处理完并包装好的sk_buff发给驱动程序。
此时,无论是阻塞还是非阻塞模式,write系统调用都会返回,该进程的任务结束。
7)驱动程序在接受到一个待发送的skb后,会选择将其直接从网卡发出或者加入发送队列。
8)最终,软中断NET_TX_SOFTIRQ的处理程序会将发送队列里的包由网卡发出。
收包流程
1)网卡接收到一个数据包后,会向CPU发起一个硬件中断,该中断处理程序由网卡驱动实现。
2)中断上半部会为这个新来的数据包分配合适大小的skb,并将这个数据包从网卡的缓存中拷贝到内存。最后触发中断下半部继续处理。
3)网卡中断的下半部由软中断softirq实现,NET_RX_SOFTIRQ的处理程序将会被触发,这个处理程序会开始自底向上的协议栈处理。
4)网络层收到包后会先判断是否有原始套接字的监听,然后进入netfilter的NF_IP_PRE_ROUTEING hook点。
(libpcap等抓包程序一般都是使用SOCK_PACKET套接字实现,在NF_IP_PREROUTEING点之前就被获取了的。然而libpcap对收到的包只能监测不能修改,netfilter可以对其做生死判决)
5)进行路由判决,如果是目的是本机,进入netfilter的NF_IP_LOCAL_IN hook点,如果目的不是本地且需要对其进行转发,并进入netfilter的NF_IP_FORWARDING hook点。
6)目的为本机的包将继续向上传给传输层,如果是TCP包,将进行重组,并将数据存入socket的缓冲区。
7)此时如果有一个应用进程正在read这个socket或者阻塞在read这个socket上,将会将其唤醒并把缓冲区的数据从内核拷贝到用户态的buff里。read系统调用由此返回。
补充一点细节:
当进行大流量的网络通信的时候,发包软中断和收包软中断本身还可以重新触发自己以得到再次执行。
但是由于这样会导致用户态进程饥饿,于是作为改进,当大量软中断出现的时候,内核会唤醒内核线程ksoftirqd来处理这些负载。
发包中断和收包中断的处理程序有可能在在软中断触发执行的任何地方执行。包括中断返回时、网络子系统主动触发或者被ksoftirqd强制线程化。