Linux 网络协议栈——从中断到上送协议栈

注: 内核代码是 4.9 版本

协议栈从报文接收说起,报文接收从网卡驱动说起。

两种方式,NAPI 和 非NAPI。

NAPI(New API) 是Linux内核针对网络数据传输做出的一个优化措施。
其目的是在大量数据传输时, 在收到硬件中断后,通过poll方式将传输过来的数据包统一处理, 通过禁止网络设备中断以减少硬件中断数量((Interrupt Mitigation),从而实现更高的数据传输。
 

其中要点:

1、硬件中断后开始处理报文。中断处理函数只是触发软中断准备接收报文;

2、软中断中通过pool方式处理报文。通过轮训的方式一次性处理多个报文;

3、禁止网络设备中断以减少硬件中断数量。同上,在软中断处理函数中,将禁止中断,处理完成后,开启中断,这样一次中断处理多个报文。

 

先从NAPI方式说起

以 Inter 的 e1000 的驱动为例,e1000在加载驱动并做设备初始化时会调用 e1000_probe 函数,完成设备的部分初始化工作,重要的是设备的napi结构 和 poll函数是在这个函数中设置的:

static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
    struct net_device *netdev;
    struct e1000_adapter *adapter;
    struct e1000_hw *hw;
......

    // 为网卡创建网络设备对象 net_device 结构,并完成组册
    netdev = alloc_etherdev(sizeof(struct e1000_adapter));
    if (!netdev)
        goto err_alloc_etherdev;

    SET_NETDEV_DEV(netdev, &pdev->dev);
        // 设置网卡私有数据
    pci_set_drvdata(pdev, netdev);
    adapter = netdev_priv(netdev);
    adapter->netdev = netdev;
    adapter->pdev = pdev;
    adapter->msg_enable = netif_msg_init(debug, DEFAULT_MSG_ENABLE);
    adapter->bars = bars;
    adapter->need_ioport = need_ioport;

    hw = &adapter->hw;
    hw->back = adapter;

    err = -EIO;
        // 映射寄存器IO区域(后面拷贝报文)
    hw->hw_addr = pci_ioremap_bar(pdev, BAR_0);
    if (!hw->hw_addr)
        goto err_ioremap;

    ......
        // 挂载网络设备操作接口
    netdev->netdev_ops = &e1000_netdev_ops;
    e1000_set_ethtool_ops(netdev);
    netdev->watchdog_timeo = 5 * HZ;
        // 初始化并挂载设备的NAPI接口,e1000_clean 是其poll函数,软中断中调用处理报文
    netif_napi_add(netdev, &adapter->napi, e1000_clean, 64);

    strncpy(netdev->name, pci_name(pdev), sizeof(netdev->name) - 1);

    ......
}
 

还有个重要的函数是e1000的网卡中断处理函数,其是在上面 e1000_netdev_ops 中的ndo_open函数(网卡UP后会调用)中,调用的 e1000_request_irq注册的 e1000_intr 函数。

然后是NAPI的接收流程。 在网卡中断之前,数据包到达网卡之后,就通过DMA直接将数据从网卡拷贝到内存的环形缓冲区了,成为 ring buffer,和非NAPI不同。

中断处理函数,如果没有在运行的NAPI任务,调度一个新的NAPI任务,会调用通用的NAPI处理函数,__napi_schedule,将设备的napi 挂载到当前CPU的 softnet_data 的待轮训设备列表poll_list中,并触发软中断。 napi_schedule_prep 中会检查并设置 napi_struct的 NAPI_STATE_SCHED位,并在流程结束后(软中断处理完报文)调用 __napi_complete,清楚状态位。

static irqreturn_t e1000_intr(int irq, void *data)
{
    struct net_device *netdev = data;
    struct e1000_adapter *adapter = netdev_priv(netdev);
    struct e1000_hw *hw = &adapter->hw;
......
        // 如果没有在运行的NAPI任务,调度一个新的NAPI任务
    if (likely(napi_schedule_prep(&adapter->napi))) {
        adapter->total_tx_bytes = 0;
        adapter->total_tx_packets = 0;
        adapter->total_rx_bytes = 0;
        adapter->total_rx_packets = 0;
                // 调用通用的NAPI处理函数
        __napi_schedule(&adapter->napi);
    } else {
        /* this really should not happen! if it does it is basically a
         * bug, but not a hard error, so enable ints and continue
         */
        if (!test_bit(__E1000_DOWN, &adapter->flags))
            e1000_irq_enable(adapter);
    }

    return IRQ_HANDLED;
}

void __napi_schedule(struct napi_struct *n)
{
    unsigned long flags;

    local_irq_save(flags);
    ____napi_schedule(this_cpu_ptr(&softnet_data), n);
    local_irq_restore(flags);
}

static inline void ____napi_schedule(struct softnet_data *sd,
                     struct napi_struct *napi)
{
        //  将设备的napi 挂载带了 sd 的poll_list中。
    list_add_tail(&napi->poll_list, &sd->poll_list);
       //  触发软中断
    __raise_softirq_irqoff(NET_RX_SOFTIRQ);
}
 

软中断接收处理函数是net_rx_action,在网络设备模块初始化时注册。

net_dev_init:

    open_softirq(NET_TX_SOFTIRQ, net_tx_action);
    open_softirq(NET_RX_SOFTIRQ, net_rx_action);
 

Linuxc/c++服务器开发高阶视频学习资料加群720209036获取

内容包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒体,P2P,K8S,Docker,TCP/IP,协程,DPDK多个高级知识点。

完整视频学习链接:https://ke.qq.com/course/417774?flowToken=1013189

关注VX公众号:Linux C后台服务器开发

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值