Linux内核网络栈深度实战:从数据包到达网卡到用户态应用的完整路径
\n\nLinux网络栈是内核中最复杂、代码量最大的子系统之一。本文将从网卡接收数据包开始,一路追踪到用户态应用程序读取数据的完整路径,涵盖链路层、网络层、传输层、Socket层以及驱动层的每一个关键函数和数据结构。读过本文后,你将对Linux内核网络有一个立体而清晰的理解。
\n\n一、数据包的生命周期总览
\n\n一个TCP数据包从网卡到达用户态应用,大致经历以下阶段:
\n\n- \n
- 1. 网卡接收:网卡通过DMA将数据包写入环形缓冲区(Ring Buffer),触发硬中断,唤醒NAPI软中断轮询 \n
- 2. 链路层处理:netif_receive_skb() 解剥以太网帧头,确定上层协议类型 \n
- 3. 网络层处理:ip_rcv() 校验IP头,经过Netfilter PREROUTING hook,路由决策后选择本地递交或转发 \n
- 4. 传输层处理:tcp_v4_rcv() 查连接状态机,将报文插入接收队列 \n
- 5. Socket层:用户态调用recv()/read(),从Socket接收队列拷贝数据 \n
发送路径则恰好相反:用户态调用send() -> tcp_sendmsg() -> ip_queue_xmit() -> dev_queue_xmit() -> 网卡硬发送。两条路径在sk_buff(Socket Buffer)的伴随下互为镜像。
\n\n二、核心数据结构:sk_buff
\n\nsk_buff是整个网络栈的灵魂。每个数据包在内核中都被封装为一个sk_buff实例,它在协议栈各层之间传递时,通过移动head/end/tail/data指针来\"解剥\"或\"添加\"头部,而无需拷贝数据。
\n\nstruct sk_buff {\n struct sk_buff *next; // 双向链表\n struct sk_buff *prev;\n struct sock *sk; // 归属Socket\n struct net_device *dev; // 关联网卡设备\n unsigned char *head; // 缓冲区起始\n unsigned char *data; // 当前层数据起始\n unsigned char *tail; // 当前层数据结束\n unsigned char *end; // 缓冲区结束\n unsigned int len; // 数据长度\n unsigned int truesize; // 总内存占用\n __u16 protocol; // 上层协议类型\n // ... 还有 dozens 个字段\n};\n\n\n一个典型的sk_buff在协议栈中的指针移动过程:网卡驱动调用skb_reserve(skb, 2) 对齐MAC头(14字节实际预留2字节对齐),然后填充MAC头(data前进14字节),调用skb_pull(skb, 14) 将data指向IP头,恢复以太网层同理,以此类推通过指针移动代替数据拷贝。
\n\nsk_buff通过引用计数(users字段)管理生命周期,kfree_skb()在引用归零时才真正释放缓冲区,实现了零拷贝。
\n\n三、链路层:数据包进入内核的大门
\n\n3.1 网卡驱动与NAPI
\n\n现代网卡使用NAPI(New API)机制来混合中断和轮询:网卡收到数据包后触发硬中断,中断处理函数关闭网卡的中断并调度NAPI(napi_schedule()),随后在软中断上下文(NET_RX_SOFTIRQ)中由net_rx_action()循环调用驱动注册的poll()函数批量处理数据包。
\n\n// 中断处理中\nstatic irqreturn_t igb_intr(int irq, void *data) {\n // 关闭中断,调度NAPI\n napi_schedule(

发表评论 取消回复