Linux 网络协议栈开发代码分析篇之数据收发(一) —— netif_receive_skb()函数

本文详细介绍了Linux内核网络协议栈中数据接收的过程,重点关注netif_receive_skb函数的作用。从网络设备接收到报文,通过中断处理函数,再到netif_rx和netif_rx_schedule的调度,最终由net_rx_action处理。文章对比了老式和新式(NAPI)处理方式,并解释了如何通过软中断NET_RX_SOFTIRQ进行报文处理。netif_receive_skb函数处理报文并将其交给上层协议,如IP、ARP等。文章还探讨了设备驱动程序如何处理发送和接收报文的队列,以及流量控制和设备暂停状态的管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提到网络协议栈,数据的收发是核心,我们先来看下数据的接收函数 netif_receive_skb()

     先统一介绍下数据收发      

     对于linux内核来说,网络报文由网络设备来进行接收。设备驱动程序从网络设备中读取报文,通过内核提供的网络接口函数,将报文传递到内核中的网络协议栈。报文经过协议栈的处理,或转发、或丢弃、或被传送给某个进程。
      网络报文的发送与之相反,进程通过系统调用将数据送入网络协议栈,或者由网络协议栈自己发起报文的发送,然后协议栈通过调用网络接口函数来调度驱动程序,使其将报文传送给网络设备,从而发送出去。
      本文讨论的是网络接口层,它是网络设备驱动程序与网络协议栈交互的纽带。见下图中红色部分的netif。
3725884266736043489.jpg

报文的接收
      网络报文的接收源自网络设备。网络设备在接收到一个报文之后,通过中断告知CPU。网卡驱动程序需要注册对该中断事件的处理函数(参见《linux中断处理浅析》),以处理接收到的报文。
      在中断处理函数中,网络驱动程序有两种方法对报文进行处理(老式的方法,和新式的方法),我们先介绍老式的处理方式。在这种方式下,中断处理函数主要完成以下工作:
     分配一个skb结构(该结构用于保存一个报文)。操作设备,将设备收到的数据拷贝到这个skb结构对应的缓冲区中。设置skb的协议类型skb->protocol,该类型表明了网络协议栈的上层协议(下面我们将会看到)。然后调用内核提供的网络接口函数netif_rx;

netif_rx(skb);
      netif_rx函数对skb的如时间戳这样的附加信息进行初始化以后,将这个skb结构放入当前CPU的softdate_net结构的input_pkt_queue队列中。netif_rx会根据队列的长度,对设备的拥塞状况进行判断(队列过长则代表报文接收过快,以致于上层来不及处理)。如果设备已陷入拥塞,则收到的报文可能直接被丢弃。
     如果一切正常,netif_rx会调用网络接口函数netif_rx_schedule,以触发对接收报文的进一步处理;

netif_rx_schedule(dev);
     netif_rx使用softdate_net结构中内嵌的backlog_dev作为dev来调用netif_rx_schedule,后者将其加入到softdate_net结构的poll_list队列中(如果这个dev不在队列中的话),以使其等待被调度。

    相比老式的处理方式,新式的处理方式(称为NAPI)在中断处理函数中仅仅是以对应设备的dev结构为参数调用netif_rx_schedule函数即可。
     最后netif_rx_schedule函数会触发NET_RX_SOFTIRQ软中断,于是接下来对应的软中断处理函数net_rx_action将被调用;

net_rx_action();
     对于当前CPU对应的softdate_net结构的poll_list队列中的所有dev,调用dev->poll方法。该方法是由对应dev的驱动程序实现的,用于接收及处理报文(前面提到的backlog_dev除外)。
     net_rx_action每次运行都有一定的限度,并不一定要将所有报文都处理完。在处理完一定数量的报文配额、或处理过程超过一定时间后,net_rx_action便会返回。返回前触发一次NET_RX_SOFTIRQ软中断,等待下一次中断到来的时候继续被调度。

以上过程如图所示(摘自ULNI):
4511762401712180430.jpg

上面提到的softdate_net结构是用于进行报文收发调度的结构,内核为每个CPU维护一个这样的结构。在报文接收过程中用到了其中的三个成员:
1、poll_list,网络设备dev的队列。其中的设备接收到了报文,需要被处理;
2、input_pkt_queue,skb报文结构的队列,保存了已接收并需要被处理的报文;
3、backlog_dev,一个虚拟的网络设备dev结构;
后两个成员是专门为支持老式的处理方式而设置的,在这种方式下,接收到的skb被放入input_pkt_queue队列,然后backlog_dev被加入poll_list。而最后,自然backlog_dev->poll函数将对input_pkt_queue队列中的skb进行处理。backlog_dev->poll等于process_backlog函数;

process_backlog(backlog_dev, budget);
既然net_rx_action每次运行都有一个配额,它在调用dev->poll时也会传递当前剩余的配额值,即budget。
process_backlog会遍历input_pkt_queue队列中的skb,调用netif_receive_skb函数对其进行处理。
process_backlog函数有两种结局,一个是配额到或时间到,直接返回;另一个是处理完input_pkt_que

<think>我们正在讨论Linux内核中的跟踪点(tracepoint)`trace_netif_receive_skb_entry`。这个跟踪点用于追踪网络接口接收数据包的事件,具体是在数据包进入网络堆的接收路径时被触发。 ### 跟踪点`netif_receive_skb_entry`的作用 在Linux内核中,当网络设备接收到数据包(通常以`struct sk_buff *`的形式表示)并准备将其传递到网络协议栈的上层时,会调用`netif_receive_skb`函数。`trace_netif_receive_skb_entry`跟踪点就放置在这个函数的入口处,用于记录数据包被接收的事件。 这个跟踪点对于以下情况非常有用: - 调试网络数据包的接收路径 - 分析网络性能瓶颈 - 监控特定数据包的流动 ### 使用方式 要使用这个跟踪点,可以通过内核的ftrace接口或者使用像`perf`、`trace-cmd`这样的工具。 #### 1. 使用ftrace ```bash # 切换到ftrace目录 cd /sys/kernel/debug/tracing # 启用跟踪点 echo 1 > events/net/netif_receive_skb_entry/enable # 开始跟踪 echo 1 > tracing_on # ... 运行些网络操作 ... # 停止跟踪 echo 0 > tracing_on echo 0 > events/net/netif_receive_skb_entry/enable # 查看跟踪结果 cat trace ``` #### 2. 使用trace-cmd ```bash # 记录事件 trace-cmd record -e net:netif_receive_skb_entry # 查看报告 trace-cmd report ``` #### 3. 使用perf ```bash # 记录事件 perf record -e net:netif_receive_skb_entry -a # 查看报告 perf script ``` ### 跟踪点的输出信息 当这个跟踪点被触发时,它会记录以下信息(具体字段可能因内核版本而异): - 设备名(dev) - 数据包长度(skb->len) - 数据包的协议类型(skb->protocol) - 源IP和目的IP(如果可用) - 等等 这些信息可以帮助开发者了解数据包何时进入协议,以及经过哪些处理。 ### 示例输出 个典型的输出可能如下: ``` <idle>-0 [000] d.s. 12345.678901: netif_receive_skb_entry: dev=eth0 skbaddr=0xffff880123456789 len=1500 protocol=0x0800 ``` 这表示在eth0设备上接收到个长度为1500字节的IPv4协议数据包。 ### 注意事项 - 启用跟踪点会带来定的性能开销,特别是在高流量环境下。 - 需要root权限或相应的能力(CAP_SYS_ADMIN)来操作跟踪点。 - 内核必须配置了`CONFIG_TRACING`和`CONFIG_NETWORK_TRACING`选项。 ### 相关内核代码 在Linux内核源代码中,这个跟踪点通常定义在`include/trace/events/net.h`中,并在`net/core/dev.c`中的`netif_receive_skb`函数开头被调用。 例如,在`net/core/dev.c`中: ```c static int netif_receive_skb(struct sk_buff *skb) { trace_netif_receive_skb_entry(skb); // ... 其他处理 ... } ``` ### 总结 `trace_netif_receive_skb_entry`是个用于跟踪网络数据包接收事件的跟踪点,它对于网络开发和调试非常有用。通过ftrace、perf或trace-cmd等工具,开发者可以监控和分析数据包在内核协议中的流动。 §§相关问题§§ 1. Linux内核中还有哪些与网络接收相关的跟踪点? 2. 如何分析`netif_receive_skb_entry`跟踪点产生的数据? 3. 在高流量环境下使用跟踪点有什么注意事项? 4. 如何编写自定义的内核模块来扩展跟踪点的功能?</think>### Linux 内核跟踪点 `trace_netif_receive_skb_entry` 详解 #### **1. 作用与目的** `trace_netif_receive_skb_entry` 是 Linux 内核网络的关键跟踪点,用于监控数据包(`sk_buff`)**进入网络协议栈的初始阶段**。它在以下场景被触发: - 当 NIC 驱动通过 NAPI 轮询机制将数据包传递给内核时 - 在数据包进入 `netif_receive_skb()` 函数时立即记录[^1] - 主要用于调试**数据包接收路径**的延迟、丢包或异常行为 #### **2. 核心功能** ```c // 内核调用示例(简化) trace_netif_receive_skb_entry(skb); ``` 跟踪点记录的关键信息包括: - `skb` 的内存地址 - 接收设备名(如 eth0) - 数据包长度(`skb->len`) - 协议类型(IPv4/IPv6) - 时间戳(用于性能分析) #### **3. 典型使用场景** 1. **性能分析** 通过计算 `netif_receive_skb_entry` 和 `netif_receive_skb_exit` 的时间差,测量协议处理延迟: ```bash perf record -e net:netif_receive_skb_entry,net:netif_receive_skb_exit -a ``` 2. **丢包调试** 结合 `trace_netif_rx` 和 `trace_kfree_skb`,定位丢包位置: ```bash trace-cmd record -e netif* -e kfree_skb ``` 3. **CPU 负载均衡** 验证 RPS(Receive Packet Steering)是否生效[^2]: ```bash # 查看数据包被分配到哪个CPU trace-cmd report | grep "netif_receive_skb_entry.*cpu" ``` #### **4. 使用示例** **通过 ftrace 捕获数据包:** ```bash # 启用跟踪点 echo 1 > /sys/kernel/debug/tracing/events/net/netif_receive_skb_entry/enable # 捕获数据(持续5秒) cat /sys/kernel/debug/tracing/trace_pipe > trace.log & sleep 5 killall cat # 解析输出示例 # ksoftirqd/0-7 [000] ...1 1234.567: netif_receive_skb_entry: dev=eth0 skbaddr=0xffff888003aab700 len=1500 ``` #### **5. 关键注意事项** - **软中断上下文**:该跟踪点通常在 `ksoftirqd` 线程中触发[^3],需关注 CPU 亲和性 - **性能影响**:高频跟踪可能增加延迟,生产环境建议采样使用 - **依赖配置**:需内核启用 `CONFIG_TRACING` 和 `CONFIG_NETWORK_TRACING` - **与 `netif_rx` 的区别**:`netif_receive_skb_entry` 是 NAPI 路径,`netif_rx` 用于传统中断模式 #### **6. 高级调试技巧** - **关联堆**:启用 `stacktrace` 选项定位调用路径: ```bash echo stacktrace > /sys/kernel/debug/tracing/events/net/netif_receive_skb_entry/trigger ``` - **过滤设备**:仅监控 eth0: ```bash echo 'dev==1' > /sys/kernel/debug/tracing/events/net/netif_receive_skb_entry/filter # 设备ID通过 ifconfig eth0 获取 ``` > **提示**:完整接收路径涉及多个跟踪点: > `netif_receive_skb_entry` → `netif_receive_skb` → `__netif_receive_skb` → 协议层处理
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值