2. Linux内核协议栈概览:从socket到网卡的数据流路径

网络数据在Linux内核中的旅程,是一条从用户态Socket抽象层出发,穿越传输层、网络层、数据链路层,最终抵达物理网卡(NIC)的精密流水线。理解这条路径上的每一层职责、关键数据结构以及钩子点,是使用ftrace与perf进行性能定位的基石。

核心认知: 协议栈的每一层都引入了特定的处理开销(校验和、分段、队列调度)。性能瓶颈往往出现在层与层之间的上下文切换、锁竞争或内存拷贝上。

2.1 总体架构与分层模型

Linux网络协议栈严格遵循OSI模型的简化分层,但在实现上更贴近TCP/IP四层模型。数据在发送(Tx)路径上从上往下逐层封装头部,在接收(Rx)路径上从下往上逐层解析头部。

Linux内核协议栈分层架构 Linux 内核协议栈分层架构(发送路径) 用户态应用程序 (Socket API: sendmsg / write) 系统调用层 (sys_sendmsg → sock_sendmsg) 通用 Socket 层 (struct sock / struct socket) 传输层 (TCP: tcp_sendmsg / UDP: udp_sendmsg) 关键操作:分段(TCP GSO)、校验和计算、拥塞控制 网络层 (IP: ip_local_out / ip_output) 关键操作:路由查找(FIB)、分片、Netfilter钩子 数据链路层 (dev_queue_xmit / __dev_xmit_skb) 关键操作:排队规则(Qdisc)、TC分类、邻居子系统(ARP) 网卡驱动层 (ndo_start_xmit → DMA) ftrace 可追踪每一层的核心函数耗时;perf 可采样分析锁竞争与 cache miss

2.2 关键数据结构:sk_buff 与 sock

协议栈的运转围绕两个核心数据结构展开:struct sk_buff(报文缓冲区)和 struct sock(套接字控制块)。它们是ftrace追踪和perf分析中最常关注的“数据载体”。

数据结构 所在层 核心字段(性能相关) 说明
struct sk_buff 全栈 head/data/tail/end, len, dev, sk, cb[] 管理数据包的内存布局,cb[] 用于各层私有控制信息
struct sock 传输层 sk_receive_queue, sk_write_queue, sk_lock, sk_rcvbuf 管理连接状态、接收/发送缓冲区、拥塞控制参数
struct net_device 链路层 name, flags, mtu, tx_queue_len, features 描述网卡硬件能力,features 标志位影响协议栈卸载策略
struct Qdisc 链路层 enqueue, dequeue, qlen, bstats 流量控制排队规则,qlen 反映队列积压程度
ftrace 追踪技巧: 在发送路径上,追踪 __dev_queue_xmit 的 skb->len 和 skb->dev->name 可以快速定位大包是否触发了分段或TSO卸载异常。

2.3 发送路径(Tx)逐层详解

发送路径是性能优化的重点,因为它通常处于进程上下文,直接受应用程序行为影响。以下是标准发送路径的步骤分解:

  1. 系统调用入口: 应用程序调用 sendmsg(),进入内核 sys_sendmsg(),将用户态数据拷贝到内核态 sk_buff 中。
  2. Socket层: sock_sendmsg() 根据 socket 类型分发到 tcp_sendmsg() 或 udp_sendmsg()。
  3. TCP处理(面向连接): 将数据追加到 sk_write_queue,执行拥塞控制(如BBR/CUBIC),构造TCP头,计算校验和。如果启用了TSO,则在此层进行延迟分段。
  4. IP层: ip_local_out() 调用 __ip_local_out(),执行路由查找(FIB表),填充IP头。经过 NF_INET_LOCAL_OUT Netfilter钩子。
  5. 邻居子系统: 通过路由结果获取下一跳MAC地址,触发ARP/ND解析(如果缓存未命中)。
  6. 流量控制层: dev_queue_xmit() 将 sk_buff 入队到对应网卡的 Qdisc。默认pfifo_fast队列,或自定义HTB/TBF等。
  7. 驱动层: 出队后调用 ndo_start_xmit(),将 sk_buff 映射到DMA区域,触发网卡硬件发送。
内核协议栈发送路径详细流程 发送路径(Tx)关键函数调用链与性能热点 sys_sendmsg() tcp_sendmsg() __tcp_transmit_skb() ip_local_out() → ip_output() __dev_queue_xmit() sch_direct_xmit() → ndo_start_xmit() 性能热点标注(perf top 常见热点) ① copy_from_user() 用户态拷贝 ② tcp_sendmsg() 锁竞争 ③ qdisc_run() 队列调度 ④ skb_segment() TSO分段

2.4 接收路径(Rx)逐层详解

接收路径通常由硬件中断驱动,涉及中断上下文和软中断(SoftIRQ)的切换,是性能问题的多发区。

  • 硬件中断: 网卡收到数据包,通过DMA写入内存环形缓冲区(Ring Buffer),触发硬件中断(IRQ)。
  • NAPI轮询: 中断处理函数调度NAPI,关闭中断并进入轮询模式,调用 poll() 方法从Ring Buffer批量收取数据包。
  • 链路层处理: netif_receive_skb() 或 __netif_receive_skb_core(),处理VLAN标签、RPS/RFS分发、抓包点(tcpdump)。
  • IP层: ip_rcv() 校验IP头,经过 NF_INET_PRE_ROUTING Netfilter钩子,进行路由查找,决定是本地交付还是转发。
  • 传输层: 本地交付的包进入 tcp_v4_rcv() 或 udp_rcv(),查找对应的 sock,将数据放入 sk_receive_queue。
  • 唤醒用户进程: 数据入队后,唤醒等待在socket上的用户进程(epoll/poll/select),完成一次数据交付。
常见陷阱: 接收路径上的 softirq 占用过高(可通过 /proc/softirqs 观察),通常意味着单核处理能力达到瓶颈。此时应考虑RPS/RFS或XDP卸载。

2.5 协议栈卸载技术(Offload)

现代网卡支持将部分协议栈处理卸载到硬件,以降低CPU占用。这些卸载特性直接影响ftrace追踪到的函数调用模式。

卸载特性 缩写 作用层 对追踪的影响
TCP分段卸载 TSO 传输层 内核不再调用 skb_segment(),发送路径变短
通用分段卸载 GSO 传输层 软硬件分段回退机制,tcp_sendmsg 中延迟分段
接收侧合并 GRO 网络层 合并多个小包,减少 netif_receive_skb 调用次数
校验和卸载 TX/RX Checksum 传输层 内核跳过校验和计算,tcp_transmit_skb 中无checksum开销
大接收卸载 LRO 网络层 硬件合并TCP流,可能破坏监控精度(已逐渐被GRO取代)

2.6 性能定位切入点总结

基于上述数据流路径,我们可以将性能问题定位到具体层次:

  • 应用层/系统调用层: 关注 sys_sendmsg 和 sys_recvmsg 的耗时,通常与 copy_from_user 或 futex 锁有关。
  • 传输层: 关注 tcp_sendmsg 中的 sk_stream_wait_memory(发送缓冲区满)或 tcp_rcv_established 中的处理逻辑。
  • 网络层: 关注 ip_route_output_key 路由查找耗时,以及Netfilter规则匹配开销。
  • 链路层: 关注 __dev_queue_xmit 中的 qdisc_run 和 pfifo_fast_dequeue,队列锁竞争是常见瓶颈。
  • 驱动层: 关注 ndo_start_xmit 和NAPI poll 函数的执行时间,以及DMA映射开销。
ftrace 实战建议: 使用 function_graph 追踪器,设置 __dev_queue_xmit 为追踪函数,可以清晰看到从Qdisc入队到驱动发送的完整子函数调用栈,是诊断发送延迟的“黄金路径”。