Linux内核网络栈深度实战:从数据包到达网卡到用户态应用的完整路径

\n\n

Linux网络栈是内核中最复杂、代码量最大的子系统之一。本文将从网卡接收数据包开始,一路追踪到用户态应用程序读取数据的完整路径,涵盖链路层、网络层、传输层、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
\n\n

发送路径则恰好相反:用户态调用send() -> tcp_sendmsg() -> ip_queue_xmit() -> dev_queue_xmit() -> 网卡硬发送。两条路径在sk_buff(Socket Buffer)的伴随下互为镜像。

\n\n

二、核心数据结构:sk_buff

\n\n

sk_buff是整个网络栈的灵魂。每个数据包在内核中都被封装为一个sk_buff实例,它在协议栈各层之间传递时,通过移动head/end/tail/data指针来\"解剥\"或\"添加\"头部,而无需拷贝数据。

\n\n
struct 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\n

sk_buff通过引用计数(users字段)管理生命周期,kfree_skb()在引用归零时才真正释放缓冲区,实现了零拷贝。

\n\n

三、链路层:数据包进入内核的大门

\n\n

3.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(                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部