Linux网络包接收过程:深入理解高性能网络编程的基石
深入理解Linux底层,尤其是网络包接收过程,是解决线上性能瓶颈的必备技能。本文将以图解的方式,带你深入Linux内核,揭秘网络包从网卡到达recvfrom的整个旅程。
1. Linux网络收包总览
在TCP/IP分层模型中,Linux内核主要实现了链路层、网络层和传输层。网卡驱动负责链路层,内核协议栈实现网络层和传输层,并通过socket接口向上层应用提供服务。
下图展示了Linux视角的网络协议栈:

graph LR
subgraph 用户空间
A["应用层 (Nginx, FTP等)"]
end
subgraph 内核空间
B[Socket接口]
C["传输层 (TCP, UDP)"]
D["网络层 (IP, ARP, ICMP)"]
E["链路层 (网卡驱动)"]
end
subgraph 硬件
F[网卡]
G[网线]
end
A -- 数据交互 --> B
B -- 数据包 --> C
C -- 数据包 --> D
D -- 数据帧 --> E
E -- 电信号 --> F
F -- 物理信号 --> G
应用层: 常见的网络应用,如 Nginx、FTP 等。
Socket 接口: 内核提供给用户空间的编程接口。
传输层**:** TCP 和 UDP 协议的实现。
网络层**:** IP、ARP 和 ICMP 等协议的实现。
链路层**:** 网卡驱动程序,负责与网卡硬件交互。
网卡**:** 接收和发送网络数据的硬件设备。
网线: 连接网卡和网络的物理介质。
当网络数据包到达网卡后,Linux内核的处理流程大致如下:
(图2) Linux内核****网络收包总览
graph LR
subgraph 网络硬件
A1["网卡"]
A2["网线"]
end
subgraph 网卡驱动
B1["网卡驱动程序"]
B2["RingBuffer"]
B3["DMA引擎"]
B4["硬中断处理函数"]
B5["NAPI机制"]
A1 --> B1
B1 --> B2
B1 --> B3
B1 --> B4
B1 --> B5
end
subgraph Linux内核
subgraph 中断处理
C1["硬中断"]
C2["软中断"]
B4 --> C1
C1 --> C2
end
subgraph ksoftirqd线程
D1["ksoftirqd线程"]
C2 --> D1
end
subgraph 网络协议栈
E1["net_rx_action"]
E2["igb_poll"]
E3["netif_receive_skb"]
E4["ip_rcv"]
E5["udp_rcv"]
D1 --> E1
E1 --> E2
E2 --> E3
E3 --> E4
E4 --> E5
end
subgraph Socket层
F1["Socket接收队列"]
E5 --> F1
end
end
subgraph 用户空间
G1["用户进程"]
G2["recvfrom系统调用"]
F1 --> G2
G2 --> G1
end
链路层(网卡驱动) --> Linux内核
Linux内核 --> 用户空间
网卡****接收数据: 网卡通过 DMA 方式将数据帧写入内存的 RingBuffer。
硬中断: 网卡触发硬中断通知 CPU。
硬中断处理函数: 快速响应,发起软中断请求。
ksoftirqd线程: 处理软中断,调用驱动的 poll 函数。
poll函数: 轮询收包,送入协议栈。
协议栈处理: 数据包逐层处理(net_rx_action, igb_poll, netif_receive_skb, ip_rcv, udp_rcv)。
Socket接收队列: 数据包最终放入 Socket 接收队列。
recvfrom系统调用: 用户进程从 Socket 接收队列获取数据。
2. Linux启动:收包前的准备工作
在接收网络包之前,Linux内核需要进行一系列初始化工作,包括:
2.1 创建ksoftirqd内核线程
Linux的软中断处理在专门的内核线程(ksoftirqd)中进行。系统初始化时,通过smpboot_register_percpu_thread调用spawn_ksoftirqd创建ksoftirqd线程。
(图3) 创建ksoftirqd内核线程
graph LR
A[系统初始化] --> B[kernel/smpboot.c]
B --> C[smpboot_register_percpu_thread]
C --> D[kernel/softirq.c]
D --> E[spawn_ksoftirqd]
E --> F[创建ksoftirqd进程]
F --> G[线程循环函数]
G --> H[ksoftirqd_should_run]
G --> I[run_ksoftirqd]
H --> J[判断是否有软中断]
I --> K[处理软中断]
ksoftirqd_should_run:判断是否有软中断需要处理。run_ksoftirqd:处理软中断。
2.2 网络子系统初始化
通过subsys_initcall调用net_dev_init函数初始化网络子系统。
(图4) 网络子系统初始化
graph LR
A[内核初始化] --> B[subsys_initcall]
B --> C[net/core/dev.c]
C --> D[net_dev_init]
D --> E[为每个CPU分配softnet_data]
E --> F[初始化poll_list和数据队列]
F --> G[注册NET_TX_SOFTIRQ和NET_RX_SOFTIRQ处理函数]
G --> H["open_softirq(NET_TX_SOFTIRQ, net_tx_action)"]
G --> I["open_softirq(NET_RX_SOFTIRQ, net_rx_action)"]
为每个CPU分配
softnet_data结构。初始化
poll_list和数据队列。注册
NET_TX_SOFTIRQ和NET_RX_SOFTIRQ的处理函数(net_tx_action和net_rx_action)。
2.3 协议栈注册
通过fs_initcall调用inet_init函数注册网络协议栈处理函数。
(图5) AF_INET协议栈注册
graph LR
A[内核初始化] --> B[fs_initcall]
B --> C[net/ipv4/af_inet.c]
C --> D[inet_init]
D --> E[注册IP协议处理函数ip_rcv]
E --> F["dev_add_pack(&ip_packet_type)"]
F --> G[注册到ptype_base]
D --> H[注册TCP协议处理函数tcp_v4_rcv]
H --> I["inet_add_protocol(&tcp_protocol, IPPROTO_TCP)"]
I --> J[注册到inet_protos]
D --> K[注册UDP协议处理函数udp_rcv]
K --> L["inet_add_protocol(&udp_protocol, IPPROTO_UDP)"]
L --> J
D --> M[注册ICMP协议处理函数icmp_protocol]
M --> N["inet_add_protocol(&icmp_protocol, IPPROTO_ICMP)"]
N --> J
注册UDP协议处理函数
udp_rcv。注册TCP协议处理函数
tcp_v4_rcv。注册IP协议处理函数
ip_rcv。将协议处理函数注册到
inet_protos(用于传输层)和ptype_base(用于网络层)数据结构中。
2.4 网卡驱动初始化
驱动程序通过module_init向内核注册初始化函数。以igb网卡驱动为例,通过pci_register_driver注册igb_driver。
(图6) 网卡驱动初始化
graph LR
A[驱动加载] --> B[module_init]
B --> C[drivers/net/ethernet/intel/igb/igb_main.c]
C --> D["pci_register_driver(&igb_driver)"]
D --> E[内核识别igb网卡]
E --> F[调用igb_probe]
F --> G[注册ethtool接口]
G --> H[drivers/net/ethernet/intel/igb/igb_ethtool.c]
F --> I[注册net_device_ops接口]
I --> J[igb_open等函数]
F --> K[注册NAPI poll函数]
K --> L[igb_poll]
注册
ethtool接口。注册
net_device_ops接口(包含igb_open等函数)。注册NAPI poll函数
igb_poll。
2.5 启动网卡
通过ifconfig eth0 up等命令启动网卡时,会调用net_device_ops中的igb_open方法。
(图7) 启动网卡
graph LR
A[ifconfig eth0 up] --> B[net_device_ops]
B --> C[igb_open]
C --> D["分配和设置发送/接收描述符 (RingBuffer)"]
D --> E[igb_setup_all_tx_resources]
D --> F[igb_setup_all_rx_resources]
C --> G[注册硬中断处理函数]
G --> H[igb_request_irq]
H --> I[igb_request_msix]
I --> J[igb_msix_ring]
C --> K[启用NAPI]
K --> L[napi_enable]
分配和设置发送/接收描述符(RingBuffer)。
注册硬中断处理函数
igb_msix_ring。启用NAPI。
3. 迎接数据的到来
3.1 硬中断处理
当数据帧到达网卡时,首先进入网卡的接收队列。
(图8) 网卡数据硬中断处理过程
graph LR
A[数据帧到达] --> B[网卡接收队列]
B --> C[DMA到RingBuffer]
C --> D[DMA操作完成]
D --> E[网卡触发硬中断]
E --> F[CPU响应中断]
F --> G[调用硬中断处理函数]
G --> H[igb_msix_ring]
H --> I["记录中断频率(igb_write_itr)"]
H --> J["调度NAPI软中断(napi_schedule)"]
网卡DMA数据到RingBuffer: 网卡DMA引擎将数据包写入内存RingBuffer,CPU无感知。
触发硬中断: 网卡向CPU发送硬中断信号。
硬中断处理函数:
调用
igb_write_itr记录中断频率。调用
napi_schedule调度NAPI软中断。
3.2 ksoftirqd内核线程处理软中断
(图9) ksoftirqd内核线程
graph LR
A[硬中断触发软中断] --> B[ksoftirqd内核线程]
B --> C[检查软中断请求]
C --> D[ksoftirqd_should_run]
D --> E["读取local_softirq_pending()标记"]
E -- 有NET_RX_SOFTIRQ --> F[执行软中断处理]
F --> G[run_ksoftirqd]
G --> H[调用__do_softirq]
H --> I[根据软中断类型调用action]
I -- NET_RX_SOFTIRQ --> J[net_rx_action]
检查软中断请求:
ksoftirqd_should_run检查是否有NET_RX_SOFTIRQ软中断待处理。执行软中断处理:
run_ksoftirqd调用__do_softirq。NET_RX_SOFTIRQ处理: 根据软中断类型调用
net_rx_action。NAPI poll函数收包:
net_rx_action调用网卡驱动注册的igb_poll函数。处理接收队列:
igb_poll调用igb_clean_rx_irq从RingBuffer取出数据包。GRO处理和SKB传递:
igb_clean_rx_irq调用napi_gro_receive进行GRO处理,然后调用netif_receive_skb传递SKB到协议栈。
3.3 网络协议栈处理
(图10) 网络协议栈处理
graph LR
A[igb_clean_rx_irq] --> B[napi_gro_receive]
B --> C[GRO处理]
C --> D[netif_receive_skb]
D --> E[__netif_receive_skb_core]
E --> F[遍历ptype_base哈希表]
F --> G[找到IP协议处理函数ip_rcv]
G --> H[ip_rcv]
H --> I[Netfilter PRE_ROUTING钩子]
I --> J[ip_rcv_finish]
J --> K[路由查找]
K --> L[ip_local_deliver]
L --> M[IP分片重组]
M --> N[Netfilter LOCAL_IN钩子]
N --> O[ip_local_deliver_finish]
O --> P[查找UDP/TCP处理函数]
P --> Q[udp_rcv/tcp_v4_rcv]
Q --> R[Socket接收队列]
SKB接收:
netif_receive_skb接收SKB并分发到协议栈。协议类型判断和分发:
__netif_receive_skb_core根据协议类型分发SKB,找到IP协议处理函数ip_rcv并调用。
3.4 IP协议层处理
graph LR
A[__netif_receive_skb_core] --> B[ip_rcv]
B --> C[Netfilter PRE_ROUTING钩子]
C --> D[ip_rcv_finish]
D --> E[ip_route_input_noref/ip_route_input_mc 路由查找]
E --> F[设置dst->input为ip_local_deliver]
F --> G["调用dst_input (实际调用ip_local_deliver)"]
G --> H[ip_local_deliver]
H --> I[IP分片重组]
H --> J[Netfilter LOCAL_IN钩子]
H --> K[ip_local_deliver_finish]
K --> L[从inet_protos数组查找UDP/TCP处理函数]
L --> M["调用ipprot->handler (UDP调用udp_rcv)"]
IP协议层接收处理:
ip_rcv进行Netfilter PRE_ROUTING钩子处理,然后调用ip_rcv_finish。IP路由和本地交付:
ip_rcv_finish进行路由查找,设置dst->input为ip_local_deliver,然后调用dst_input(实际调用ip_local_deliver)。IP本地交付:
ip_local_deliver进行IP分片重组,Netfilter LOCAL_IN钩子处理,然后调用ip_local_deliver_finish。IP协议分发到上层协议:
ip_local_deliver_finish根据IP包协议字段分发到上层协议,从inet_protos数组查找UDP/TCP处理函数,然后调用ipprot->handler(UDP调用udp_rcv)。
3.5 UDP协议层处理
graph LR
A[ip_local_deliver_finish] --> B[udp_rcv]
B --> C[__udp4_lib_rcv]
C --> D[__udp4_lib_lookup_skb查找Socket]
D --> E[udp_queue_rcv_skb将SKB入队到Socket接收队列]
UDP协议层接收处理:
udp_rcv调用__udp4_lib_rcv。UDP查找Socket和入队:
__udp4_lib_rcv通过__udp4_lib_lookup_skb查找Socket,然后udp_queue_rcv_skb将SKB入队到Socket接收队列。
4. recvfrom系统调用
(图11) socket内核数据机构
graph LR
A[用户进程] --> B["recvfrom (glibc库函数)"]
B --> C[系统调用]
C --> D[陷入内核态]
D --> E["sys_recvfrom (系统调用入口)"]
E --> F[inet_recvmsg]
F --> G["sk->sk_prot->recvmsg (UDP调用udp_recvmsg)"]
G --> H[udp_recvmsg]
H --> I[__skb_recv_datagram]
I --> J[访问sk->sk_receive_queue]
J --> K[如果队列为空且允许等待,则睡眠]
J --> L["copy_to_user (内核空间数据拷贝到用户空间)"]
L --> M[recvfrom返回]
M --> N[返回读取的字节数]
N --> O[进程切换回用户态]
subgraph Socket数据结构
P[socket]
P --> Q[const struct proto_ops inet_dgram_ops]
Q --> R[.recvmsg = inet_recvmsg]
P --> S[struct sock *sk]
S --> T["sk_prot (struct proto udp_prot)"]
T --> U[.recvmsg = udp_recvmsg]
S --> V["sk_receive_queue (接收队列)"]
end
(图 12) recvfrom函数内部实现过程
graph LR
A[用户进程] --> B["recvfrom (glibc库函数)"]
B --> C[系统调用]
C --> D[陷入内核态]
D --> E["sys_recvfrom (系统调用入口)"]
E --> F[inet_recvmsg]
F --> G["sk->sk_prot->recvmsg (UDP调用udp_recvmsg)"]
G --> H[udp_recvmsg]
H --> I["__skb_recv_datagram"]
I --> J[访问sk->sk_receive_queue]
J --> K[如果队列为空且允许等待,则睡眠]
J --> L["copy_to_user (内核空间数据拷贝到用户空间)"]
L --> M[recvfrom返回]
M --> N[返回读取的字节数]
N --> O[进程切换回用户态]
**
recvfrom**系统调用: 用户进程调用recvfrom,陷入内核态。系统调用入口:
sys_recvfrom调用inet_recvmsg。inet_recvmsg: 调用
sk->sk_prot->recvmsg(UDP调用udp_recvmsg)。UDP接收消息处理:
udp_recvmsg调用__skb_recv_datagram。从Socket接收队列读取数据:
__skb_recv_datagram访问sk->sk_receive_queue。数据拷贝到用户空间: 内核将数据拷贝到用户空间。
recvfrom返回: 系统调用返回,进程切换回用户态。
5. 总结
通过以上图解和分析,我们可以清晰地看到Linux内核接收网络包的完整过程。从网卡接收数据,到硬中断、软中断、协议栈处理,再到最终用户进程通过recvfrom获取数据,每一步都充满了细节和精妙的设计。
