美文网首页Linux Kernel Net
Linux Kernel Net pkt processing

Linux Kernel Net pkt processing

作者: SnC_ | 来源:发表于2021-10-11 14:57 被阅读0次

    原文

    1. 网卡驱动

    在内核中,一个PCI设备,使用struct pci_driver结构来描述。因为在系统引导的时候,PCI设备已经被识别,当内核发现一个已经检测到的设备同驱动注册的id_table中的信息相匹配时,它就会触发驱动的probe函数。
    如ixgbe驱动

    static struct pci_driver ixgb_driver = {
        .name     = ixgb_driver_name,
        .id_table = ixgb_pci_tbl,
        // 系统探测到ixgbe网卡后调用ixgbe_probe()
        .probe    = ixgb_probe,
        .remove   = ixgb_remove,
        .err_handler = &ixgb_err_handler
    };
    
    static int __init ixgbe_init_module(void)
    {
        ...
        ret = pci_register_driver(&ixgbe_driver); // 注册ixgbe_driver
        ...
    }
    

    probe函数被调用,证明已经发现了我们所支持的网卡,这样,就可以调用register_netdev函数向内核注册网络设备了,注册之前,一般会调用alloc_etherdev(或者alloc_etherdev_mq)分配一个net_device,然后初始化它的重要成员。

    /* ixgbe_probe   */
    struct net_device *netdev;
    struct pci_dev *pdev;
    pci_enable_device_mem(pdev);
    pci_request_mem_regions(pdev, ixgbe_driver_name);
    pci_set_master(pdev);
    pci_save_state(pdev);
    // 这里分配struct net_device
    netdev = alloc_etherdev_mq(sizeof(struct ixgbe_adapter), indices);
    SET_NETDEV_DEV(netdev, &pdev->dev);
    adapter = netdev_priv(netdev);
    
    // alloc_etherdev_mq() -> ether_setup()
    void ether_setup(struct net_device *dev)
    {
        dev->header_ops     = &eth_header_ops;
        dev->type       = ARPHRD_ETHER;
        dev->hard_header_len    = ETH_HLEN;
        dev->min_header_len = ETH_HLEN;
        dev->mtu        = ETH_DATA_LEN;
        dev->addr_len       = ETH_ALEN;
        dev->tx_queue_len   = 1000; /* Ethernet wants good queues */
        dev->flags      = IFF_BROADCAST|IFF_MULTICAST;
        dev->priv_flags     |= IFF_TX_SKB_SHARING;
    
        eth_broadcast_addr(dev->broadcast);
    }
    EXPORT_SYMBOL(ether_setup);
    

    2. 中断注册

    enum
    {
        HI_SOFTIRQ=0,
        TIMER_SOFTIRQ,
        NET_TX_SOFTIRQ,
        NET_RX_SOFTIRQ,
        BLOCK_SOFTIRQ,
        IRQ_POLL_SOFTIRQ,
        TASKLET_SOFTIRQ,
        SCHED_SOFTIRQ,
        HRTIMER_SOFTIRQ,
        RCU_SOFTIRQ,
        NR_SOFTIRQS
    };
    

    内核初始化期间,softirq_init会注册TASKLET_SOFTIRQ以及HI_SOFTIRQ相关联的处理函数。

    void __init softirq_init(void)
    {
        ......
     
        open_softirq(TASKLET_SOFTIRQ, tasklet_action);
        open_softirq(HI_SOFTIRQ, tasklet_hi_action);
    }
    

    网络子系统分两种soft IRQ。NET_TX_SOFTIRQNET_RX_SOFTIRQ,分别处理发送数据包和接收数据包。这两个soft IRQ在net_dev_init函数(net/core/dev.c)中注册:

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

    收发数据包的软中断处理函数被注册为net_tx_actionnet_rx_action
    其中open_softirq实现为

    void open_softirq(int nr, void (*action)(struct softirq_action *))
    {
        softirq_vec[nr].action = action;
    }
    

    3. 重要数据结构体初始化

    每个cpu都有队列来处理接收到的帧,都有其数据结构来处理入口和出口流量。
    此队列数据结构为softnet_data(定义在include/linux/netdevice.h中):

    /*
     * Incoming packets are placed on per-cpu queues so that
     * no locking is needed.
     */
    struct softnet_data
    {
        struct Qdisc *output_queue; 
    
        //有数据要传输的设备列表
        struct sk_buff_head input_pkt_queue;
        //双向链表,其中的设备有输入帧等着被处理
        struct list_head poll_list; 
        //缓冲区列表,其中缓冲区已成功传输,可以释放掉
        struct sk_buff *completion_queue;
    
        struct napi_struct backlog;
    }
    

    softnet_data 是在start_kernel 中创建的, 并且每个cpu一个 softnet_data 变量。
    这个变量中,最重要的是poll_list , 每当收到数据包时,网络设备驱动会把自己的napi_struct挂到CPU私有变量softnet_data->poll_list上,这样在软中断时,net_rx_action会遍历cpu私有变量的softnet_data->poll_list,执行上面所挂的napi_struct结构的poll钩子函数,将数据包从驱动传到网络协议栈。

    4. 收发包过程

    ixgbe_adapter包含ixgbe_q_vector数组,ixgbe_q_vector (一个ixgbe_q_vector对应一个中断) 包含napi_struct:

    • 硬中断函数把napi_struct加入CPU的poll_list,软中断函数net_rx_action()遍历poll_list,执行poll函数.

    发包过程

    1. 网卡驱动创建tx descriptor ring(一致性DMA内存),将tx descriptor ring的地址写入网卡寄存器
    2. 协议栈通过dev_queue_xmit()将sk_buff下送网卡驱动
    3. 网卡驱动将sk_buff放入tx descriptor ring,更新queue tail pointer (TDT)
    4. DMA感知到TDT的改变后,找到tx descriptor ring中下一个将要使用的descriptor
    5. DMA通过PCI总线将descriptor的数据缓存区复制到Tx FIFO
    6. 复制完后,网卡将数据包发送出去
    7. 发送完后,启动一个硬中断通知driver释放数据缓存区中的数据包

    收包过程

    1. (准备工作) 网卡驱动创建rx descriptor ring(一致性DMA内存),将rx descriptor ring的地址写入网卡寄存器
    2. (准备工作) 网卡驱动为每个descriptor分配sk_buff和数据缓存区
    3. 网卡接收数据包,将数据包写入Rx FIFO
    4. DMA找到rx descriptor ring中下一个将要使用的descriptor
    5. 整个数据包写入Rx FIFO后,DMA通过PCI总线将Rx FIFO中的数据包复制到descriptor的数据缓存区
    6. 复制完后,网卡启动硬中断通知CPU数据缓存区中已经有新的数据包了,CPU执行硬中断函数
      • NAPI(以e1000网卡为例):e1000_intr() -> __napi_schedule() -> __raise_softirq_irqoff(NET_RX_SOFTIRQ)
      • 非NAPI(以dm9000网卡为例):dm9000_interrupt() -> dm9000_rx() -> netif_rx() -> napi_schedule() -> __napi_schedule() -> __raise_softirq_irqoff(NET_RX_SOFTIRQ)
    7. 执行软中断函数net_rx_action():
      • NAPI(以e1000网卡为例):net_rx_action() -> e1000_clean() -> e1000_clean_rx_irq() -> e1000_receive_skb() -> netif_receive_skb()
      • 非NAPI(以dm9000网卡为例):net_rx_action() -> process_backlog() -> netif_receive_skb()
    8. 网卡驱动通过netif_receive_skb()将sk_buff上送协议栈

    相关文章

      网友评论

        本文标题:Linux Kernel Net pkt processing

        本文链接:https://www.haomeiwen.com/subject/qjdholtx.html