图解Linux网络包接收过程( 四 )

第7步中,在igb_probe初始化过程中,还调用到了igb_alloc_q_vector 。他注册了一个NAPI机制所必须的poll函数,对于igb网卡驱动来说,这个函数就是igb_poll,如下代码所示 。
static int igb_alloc_q_vector(struct igb_adapter *adapter,                  int v_count, int v_idx,                  int txr_count, int txr_idx,                  int rxr_count, int rxr_idx){    ......    /* initialize NAPI */    netif_napi_add(adapter->netdev, &q_vector->napi,               igb_poll, 64);}2.5 启动网卡当上面的初始化都完成以后,就可以启动网卡了 。回忆前面网卡驱动初始化时,我们提到了驱动向内核注册了 structure net_device_ops 变量,它包含着网卡启用、发包、设置mac 地址等回调函数(函数指针) 。当启用一个网卡时(例如,通过 ifconfig eth0 up),net_device_ops 中的 igb_open方法会被调用 。它通常会做以下事情:

图解Linux网络包接收过程

文章插图
 
图7 启动网卡
//file: drivers/net/ethernet/intel/igb/igb_main.cstatic int __igb_open(struct net_device *netdev, bool resuming){    /* allocate transmit descriptors */    err = igb_setup_all_tx_resources(adapter);    /* allocate receive descriptors */    err = igb_setup_all_rx_resources(adapter);    /* 注册中断处理函数 */    err = igb_request_irq(adapter);    if (err)        goto err_req_irq;    /* 启用NAPI */    for (i = 0; i < adapter->num_q_vectors; i++)        napi_enable(&(adapter->q_vector[i]->napi));    ......}在上面__igb_open函数调用了igb_setup_all_tx_resources,和igb_setup_all_rx_resources 。在igb_setup_all_rx_resources这一步操作中,分配了RingBuffer,并建立内存和Rx队列的映射关系 。(Rx Tx 队列的数量和大小可以通过 ethtool 进行配置) 。我们再接着看中断函数注册igb_request_irq:
static int igb_request_irq(struct igb_adapter *adapter){    if (adapter->msix_entries) {        err = igb_request_msix(adapter);        if (!err)            goto request_done;        ......    }}static int igb_request_msix(struct igb_adapter *adapter){    ......    for (i = 0; i < adapter->num_q_vectors; i++) {        ...        err = request_irq(adapter->msix_entries[vector].vector,                  igb_msix_ring, 0, q_vector->name,    }在上面的代码中跟踪函数调用, __igb_open => igb_request_irq => igb_request_msix, 在igb_request_msix中我们看到了,对于多队列的网卡,为每一个队列都注册了中断,其对应的中断处理函数是igb_msix_ring(该函数也在drivers/net/ethernet/intel/igb/igb_main.c下) 。我们也可以看到,msix方式下,每个 RX 队列有独立的MSI-X 中断,从网卡硬件中断的层面就可以设置让收到的包被不同的 CPU处理 。(可以通过 irqbalance,或者修改 /proc/irq/IRQ_NUMBER/smp_affinity能够修改和CPU的绑定行为) 。
当做好以上准备工作以后,就可以开门迎客(数据包)了!

迎接数据的到来
3.1 硬中断处理首先当数据帧从网线到达网卡上的时候,第一站是网卡的接收队列 。网卡在分配给自己的RingBuffer中寻找可用的内存位置,找到后DMA引擎会把数据DMA到网卡之前关联的内存里,这个时候CPU都是无感的 。当DMA操作完成以后,网卡会向CPU发起一个硬中断,通知CPU有数据到达 。
图解Linux网络包接收过程

文章插图
 
图8 网卡数据硬中断处理过程
注意:当RingBuffer满的时候,新来的数据包将给丢弃 。ifconfig查看网卡的时候,可以里面有个overruns,表示因为环形队列满被丢弃的包 。如果发现有丢包,可能需要通过ethtool命令来加大环形队列的长度 。
在启动网卡一节,我们说到了网卡的硬中断注册的处理函数是igb_msix_ring 。
//file: drivers/net/ethernet/intel/igb/igb_main.cstatic irqreturn_t igb_msix_ring(int irq, void *data){    struct igb_q_vector *q_vector = data;    /* Write the ITR value calculated from the previous interrupt. */    igb_write_itr(q_vector);    napi_schedule(&q_vector->napi);    return IRQ_HANDLED;}


推荐阅读