# Linux E 100 网卡 与 TCP 层原理 一

本文将详细解释从 e100 网卡驱动程序 到 TCP 层的数据处理过程。

# e100_init_module 函数

我们看到该函数较为简单,将在内核启动时自动调用,然后在该函数中将自身注册到 PCI 驱动列表中,当内核检测到PCI网卡设备时,将会遍历列表查找 e100_id_table 看看当前驱动是否匹配网卡,若匹配,则执行 e100_found1 函数。

module_init(e100_init_module); // 当 e 100 模块初始化时调用该函数static int __init e100_init_module(void)
{
    int ret;
    ret = pci_module_init(&e100_driver);  // 将当前PCI 驱动结构指针注册到内核中
    ...
    return ret;
}static struct pci_driver e100_driver = {
    .name         = "e100",
    .id_table     = e100_id_table, // 用于告诉内核当前驱动匹配哪些网卡信息
    .probe        = e100_found1, // 当内核匹配成功后,回调该函数
    .remove       = __devexit_p(e100_remove1),
};// 标识当前驱动支持的匹配网卡的 PCI 设备信息
static struct pci_device_id e100_id_table[] = {
    {0x8086, 0x1229, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x2449, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1059, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1209, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1029, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1030, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },   
    {0x8086, 0x1031, PCI_ANY_ID, PCI_ANY_ID, 0, 0, }, 
    {0x8086, 0x1032, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1033, PCI_ANY_ID, PCI_ANY_ID, 0, 0, }, 
    {0x8086, 0x1034, PCI_ANY_ID, PCI_ANY_ID, 0, 0, }, 
    {0x8086, 0x1038, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1039, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x103A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x103B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x103C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x103D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x103E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1050, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1051, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1052, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1053, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1054, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x1055, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x2459, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0x8086, 0x245D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
    {0,} /* This has to be the last entry*/
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# e100_found1 函数

该函数用于在e100 网卡被PCI设备检测到回调,在其中 创建网络设备 struct net_device,同时创建 e100 私有结构 struct e100_private,然后设置他们的成员变量并分配网卡使用内存,最后调用 e100_init 进一步初始化 struct e100_private 结构。

static int __devinit e100_found1(struct pci_dev *pcid, const struct pci_device_id *ent)
{
    struct net_device *dev; // 代表 Linux 中的一个网络设备
    struct e100_private *bdp = NULL; // 表示 e100 网卡自己的私有数据
    ...
    dev = alloc_etherdev(sizeof (struct e100_private)); // 分配代表 e100 的 struct net_device 结构,同时创建表示 e100 私有数据的 struct e100_private 结构,其中分配的内存空间包括 struct net_device 与 struct e100_private 结构,同时在 struct net_device 的 private 变量处 保留了指向  struct e100_private 的指针。同时该函数中将会初始化 以太网帧 的 mtu 以及 以太网头部长度 等等信息 (初始化代码在 ether_setup 函数中,这里了解即可)
    ...
    // 初始化 e100 网卡私有数据变量
    ...
    bdp->watchdog_timer.function = (void *) &e100_watchdog; // 看门狗函数,每两秒执行一次,进行数据静态分析和处理if ((rc = e100_pci_setup(pcid, bdp)) != 0) { // 安装 e100 PCI 适配器具体信息(比如:操作 PCI 的IO 映射等等)
        goto err_dev;
    }if ((rc = e100_alloc_space(bdp)) != 0) { // 分配 e100 网卡的放置私有数据的内存空间
        goto err_pci;
    }
    ...
    // 初始化网络设备 net_dev 的回调函数,当发生对应事件时进行回调
    ...
    dev->open = &e100_open; // 这里我们关注 e100 设备打开时的回调函数即可
    ...
    if ((rc = register_netdev(dev)) != 0) { // 将网络设备注册到内核中(挂入内核设备链表)
        goto err_dealloc;
    }
    ...
    if (!e100_init(bdp)) { // 执行 e100 的其他初始化
        printk(KERN_ERR "e100: Failed to initialize, instance #%d\n",
               e100nics);
        rc = -ENODEV;
        goto err_unregister_netdev;
    }
    ...
}unsigned char __devinit e100_alloc_space(struct e100_private *bdp)
{
    unsigned long off;
    if (!(bdp->dma_able =
          pci_alloc_consistent(bdp->pdev, sizeof (bd_dma_able_t),
                   &(bdp->dma_able_phys)))) { // 分配 e100 使用的 DMA 的内存
        goto err;
    }
    ...
}// E100 dma 表
typedef struct _bd_dma_able_t {
    char selftest[E100_SIZE_64A(self_test_t)];
    char stats_counters[E100_SIZE_64A(max_counters_t)];
} bd_dma_able_t;// 分配 DMA 使用的空间,返回虚拟地址
void * pci_alloc_consistent(struct pci_dev *hwdev, size_t size,
               dma_addr_t *dma_handle)
{
    void *ret;
    int gfp = GFP_ATOMIC;
    
    if (hwdev == NULL ||
        end_pfn > (hwdev->dma_mask>>PAGE_SHIFT) ||  /* XXX */
        (u32)hwdev->dma_mask < 0xffffffff)
        gfp |= GFP_DMA; // 标识优先分配 DMA 区域的内存
    ret = (void *)__get_free_pages(gfp, get_order(size)); // 分配内存
    if (ret != NULL) {
        memset(ret, 0, size);
        *dma_handle = virt_to_bus(ret); // 将虚拟内存映射为 PCI 总线地址
    }
    return ret;
}// 我们看 Intel 的即可,Intel 中物理地址即是总线地址
#define virt_to_bus virt_to_phys
static inline unsigned long virt_to_phys(volatile void * address){
    return __pa(address);
}// 在 I386 中很是简单,因为虚拟地址的高 1GB 便是 内核地址,其映射到 物理地址 0处,所以我们直接用内核地址 - 3GB 便是物理地址
#define __pa(x)         ((unsigned long)(x)-PAGE_OFFSET)
#define PAGE_OFFSET     ((unsigned long)__PAGE_OFFSET)
#define __PAGE_OFFSET       (0xC0000000UL) // 表示 3GB// 进一步初始化 e100 私有数据
static unsigned char __devinit e100_init(struct e100_private *bdp)
{
    ...
    e100_sw_init(bdp); // 初始化 e100 的内部结构(tx_thld 写阈值,e100 的 EPROM 大小信息,自旋锁等等信息)
    if (!e100_selftest(bdp, &st_timeout, &st_result)) { // 网卡自我检测,看看一切是否正常
            if (st_timeout) {
            printk(KERN_ERR "e100: selftest timeout\n");
        } else {
            printk(KERN_ERR "e100: selftest failed. Results: %x\n",
                    st_result);
        }
        return false;
    }
    else
        printk(KERN_DEBUG "e100: selftest OK.\n");
    
    e100_rd_eaddr(bdp);  // 从 e100 的 EPROM 中读取网卡的 mac 地址信息
    if (!is_valid_ether_addr(bdp->device->dev_addr)) {
        printk(KERN_ERR "e100: Invalid Ethernet address\n");
        return false;
    }
    
    // 读取 e100 网卡的 PWA (printed wired assembly) 信息
    e100_rd_pwa_no(bdp);if (!e100_hw_init(bdp)) // 初始化 e100 网卡的硬件状态信息
        return false;e100_disable_clear_intr(bdp); // 关闭 e100 的网卡中断 ,当 e100 打开后 将会重新开启中断
    return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

# e100_open 函数

该函数我们前面看到通过注册到 struct net_device 结构的回调函数中,该函数将会由内核在 PCI 网络设备初始化后,网络设备注册到内核中时进行调用。在该函数中将进一步对 e100 网卡的数据结构和占用内存分配(这里特别注意:TCB 个 RFD 两个循环队列的创建),然后开启 e100 中断,至此 该网卡将正式接受外部数据。

int e100_open(struct net_device *dev)
{
    struct e100_private *bdp;
    int rc = 0;
​
    bdp = dev->priv;if (!e100_alloc_tcb_pool(bdp)) { // 分配 TCB 循环队列,该队列用于发送数据时使用,将用于 DMA 发送到网卡的数据传输(队列由 struct tcb_t 结构描述,队列头部保存在 struct e100_private 结构的 tcb_pool 变量中,这个我们后面再详细分析)
        rc = -ENOMEM;
        goto err_exit;
    }
    ...
    e100_setup_tcb_pool((tcb_t *) bdp->tcb_pool.data,
                bdp->params.TxDescriptors, bdp); // 将上述分配的 TCB 循环队列的物理地址安装到 struct e100_private 中if (!e100_alloc_rfd_pool(bdp)) { // 分配 RFD 循环队列,该队列用于接受来自网卡的数据(队列由 struct rx_list_elem 结构描述,队列的头部保存在 struct e100_private 结构的 rx_struct_pool 变量中,这个我们后面再详细分析)
        rc = -ENOMEM;
        goto err_exit;
    }
    ... // 启动 网卡 控制单元、接收单元,初始化看门狗时钟
    if ((rc = request_irq(dev->irq, &e100intr, SA_SHIRQ, // 向内核注册网卡中断处理操作,当网卡发生中断时,将会回调 e100intr 函数 ,SA_SHIRQ 标志表示e100网卡的中断使用常规的 共享中断线,发生中断时,内核会根据 第一个参数 dev->irq 匹配中断号,从而调用 e100intr 函数,这个我们在后文再详细描述
                  dev->name, dev)) != 0) {
        del_timer_sync(&bdp->watchdog_timer);
        goto err_exit;
    }    
    e100_set_intr_mask(bdp);  // 开启 e100 中断,此时网卡正式进入运行状态
    return rc;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# e100_alloc_tcb_pool 函数

该函数用于创建网卡发送数据的循环队列。

int e100_alloc_tcb_pool(struct e100_private *bdp)
{
    int stcb = sizeof (tcb_t) * bdp->params.TxDescriptors; // 队列长度由配置的TX描述符个数来表示(在 e100_check_options 函数中设置 默认 #define E100_DEFAULT_TCB   64 个 )if (!(bdp->tcb_pool.data =
          pci_alloc_consistent(bdp->pdev, stcb, &bdp->tcb_phys))) // 调用内核分配函数分配内存
        return 0;memset(bdp->tcb_pool.data, 0x00, stcb);return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# e100_alloc_rfd_pool 函数

该函数用于创建网卡接收数据的循环队列。描述如下。

// 链表描述结构
struct rx_list_elem {
    struct list_head list_elem;
    dma_addr_t dma_addr;
    struct sk_buff *skb; 
};static int e100_alloc_rfd_pool(struct e100_private *bdp)
{
    struct rx_list_elem *rx_struct;
    int i;INIT_LIST_HEAD(&(bdp->active_rx_list));
    INIT_LIST_HEAD(&(bdp->rx_struct_pool));
    bdp->skb_req = bdp->params.RxDescriptors; // 同样在 e100_check_options 函数中配置 默认 #define E100_DEFAULT_RFD   64 个
    for (i = 0; i < bdp->skb_req; i++) { // 循环从 slab 分配器中分配 64个 struct rx_list_elem 结构放入 struct e100_private 的 rx_struct_pool 变量中
        rx_struct = kmalloc(sizeof (struct rx_list_elem), GFP_ATOMIC);
        list_add(&(rx_struct->list_elem), &(bdp->rx_struct_pool));
    }
    e100_alloc_skbs(bdp); // 然后分配skb用于保存网卡接收的数据
    return !list_empty(&(bdp->active_rx_list));
}
​
​
static inline void e100_alloc_skbs(struct e100_private *bdp)
{
    for (; bdp->skb_req > 0; bdp->skb_req--) { // 遍历所有上述函数分配的 struct rx_list_elem 队列,将它们从 rx_struct_pool 链表中摘除,设置好其中的 skb 后挂入 struct e100_private 的 active_rx_list 变量中,此时正是启用 该 rx_list_elem 结构来接收数据
        struct rx_list_elem *rx_struct;
        if ((rx_struct = e100_alloc_skb(bdp)) == NULL)
            return;
        e100_add_skb_to_end(bdp, rx_struct);
    }
}static inline struct rx_list_elem * e100_alloc_skb(struct e100_private *bdp)
{
    struct sk_buff *new_skb;
    u32 skb_size = sizeof (rfd_t); // rfd 表示 Receive Frame Descriptor  接收帧描述符
    struct rx_list_elem *rx_struct;
​
    new_skb = (struct sk_buff *) dev_alloc_skb(skb_size); // 首先分配一个 skb 结构(同时初始化其中的属性,这里了解即可),大小由 rfd_t 结构, 也即接收描述符 的大小来指定(读者可以看一下该方法:会将 rfd_t 的内容放置在 sk_buff 的数据指针(*data)指向的内存域中)
    if (new_skb) {
        skb_reserve(new_skb, 2); // 保留两个字节用于做IP数据对齐到 DWORD 四字,因为以太网头部 14byte,所以加上 两个 变为 16byte
        if ((rx_struct = e100_get_rx_struct(bdp)) == NULL) // 从rx_struct_pool链表中移出一个分配好的 struct rx_list_elem 结构
            goto err;
        rx_struct->skb = new_skb; // 保存 skb 引用
        rx_struct->dma_addr = pci_map_single(bdp->pdev, new_skb->data,
                             sizeof (rfd_t),
                             PCI_DMA_FROMDEVICE); // 将skb中用于存放数据的地址映射为 DMA 地址用于 DMA 传输(DMA 会将数据传输到 new_skb->data 中)
        if (!rx_struct->dma_addr)
            goto err;
        skb_reserve(new_skb, bdp->rfd_size); // 末尾保留 rfd 的大小,用于存放bdp设置的 rfd 描述符信息
        return rx_struct;
    } else {
        return NULL;
    }
err:
    dev_kfree_skb_irq(new_skb);
    return NULL;
}// 将分配好属性的 struct rx_list_elem 结构放入 active_rx_list 链表中
inline void e100_add_skb_to_end(struct e100_private *bdp, struct rx_list_elem *rx_struct)
{
    ...
    struct rx_list_elem *rx_struct_last;
    ... // 设置 rfd_t 的属性
    list_add_tail(&(rx_struct->list_elem), &(bdp->active_rx_list));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

# e100 intr 函数

该函数将由CPU在检测到注册到中断线上的中断发生时调用,将负责处理 RX(接受队列) 和 TX(传输队列) 。

irqreturn_t e100intr(int irq, void *dev_inst, struct pt_regs *regs)
{
    ...
    // 处理接受队列
    if (intr_status &
        (SCB_STATUS_ACK_FR | SCB_STATUS_ACK_RNR | SCB_STATUS_ACK_SWI)) 
        bdp->drv_stats.rx_intr_pkts += e100_rx_srv(bdp);
    // 处理发送队列
    if (intr_status & (SCB_STATUS_ACK_CNA | SCB_STATUS_ACK_CX))
        e100_tx_srv(bdp);
    ...
    return IRQ_HANDLED;
}// 处理接受队列函数
u32 e100_rx_srv(struct e100_private *bdp)
{
    struct rx_list_elem *rx_struct; // 表示当前处理的读描述符
    rfd_t *rfd; // 表示当前处理的接收帧描述符
    struct sk_buff *skb; // 表示当前接收的数据描述块
    ...
    for (i = 0; i < bdp->params.RxDescriptors; i++) { // 遍历所有读描述符( struct rx_list_elem)
        if (list_empty(&(bdp->active_rx_list))) { // 没有活动的读描述符,直接退出
            break;
        }
        rx_struct = list_entry(bdp->active_rx_list.next,
                       struct rx_list_elem, list_elem); // 获取第一个读描述符
        skb = rx_struct->skb;
        rfd = RFD_POINTER(skb, bdp); // #define RFD_POINTER(skb,bdp)      ((rfd_t *) (((unsigned char *)((skb)->data))-((bdp)->rfd_size))) 可以看到这里将 skb 的数据地址 - rfd 结构的大小即可获得其中的 rfd 读描述符,为何?因为 data 指针后面放置着 rfd 信息:rfd 信息 ----- data 指针// 与 CPU 的高速缓存同步 RFD 信息,使得 CPU 当前 dma_addr 地址的缓存数据为最新值
        pci_dma_sync_single(bdp->pdev, rx_struct->dma_addr,
                    bdp->rfd_size, PCI_DMA_FROMDEVICE);
        rfd_status = le16_to_cpu(rfd->rfd_header.cb_status);    // 检查当前 rfd 状态,如果状态不为RFD_STATUS_COMPLETE,表示当前接受队列的 rfd 及其之后都没有数据,那么结束循环 
        if (!(rfd_status & RFD_STATUS_COMPLETE))
            break;// 将当前 rx_struct 读描述符结构从  active_rx_list 循环队列中移出,方便我们后续处理接收到的数据
        list_del(&(rx_struct->list_elem));
        
        // 若当前 rfd 的状态 不为 RFD_STATUS_OK ,那么保留当前接收到错误数据包的 rx_struct 读描述符,复用它,所以这里将它重新放入 active_rx_list 循环队列(为什么重新调用 e100_add_skb_to_end 方法,答案很明显:重置rx_struct 读描述符 内部的数据)
        if (!(rfd_status & RFD_STATUS_OK)) {
            e100_add_skb_to_end(bdp, rx_struct);
            continue;
        }
        data_sz = min_t(u16, (le16_to_cpu(rfd->rfd_act_cnt) & 0x3fff),
                (sizeof (rfd_t) - bdp->rfd_size)); // 获取接收到的数据长度(rfd->rfd_act_cnt 中保存着接受到的数据帧长度)// 与 CPU 同步 所有DMA传输的数据
        pci_dma_sync_single(bdp->pdev, rx_struct->dma_addr,
                    (data_sz + bdp->rfd_size),
                    PCI_DMA_FROMDEVICE);
        pci_unmap_single(bdp->pdev, rx_struct->dma_addr,
                 sizeof (rfd_t), PCI_DMA_FROMDEVICE); // 将 DMA 映射区域中 rfd_t 的信息解除映射,此时该区域的空间将不再为 DMA 使用
        list_add(&(rx_struct->list_elem), &(bdp->rx_struct_pool)); // 将该 读描述符 struct rx_struct 放入struct rx_struct_pool 池中,在下一次调用  e100_get_rx_struct 函数时,复用该结构
        bdp->skb_req++; // 增加 skb 请求数
        e100_alloc_skbs(bdp);   // 由于上面的 skb 已经取下来准备递交给上层协议栈,所以这里需要新分配一个新的 sk_buff 用于接受下一个数据帧,此时将会复用前面 放入 struct rx_struct_pool 池中的 struct rx_struct 结构// 设置数据帧大小
        if ((bdp->flags & DF_CSUM_OFFLOAD)
            && (bdp->rev_id < D102_REV_ID))
            skb_put(skb, (int) data_sz - 2); // 如果存在校验和,那么去除末尾的 2字节 校验和数据
        else
            skb_put(skb, (int) data_sz); // 将 skb 的 tail 指针向后移动到最后一个数据字节,同时增加数据长度 length 属性值,打开一看便知// 设置当前数据帧的协议类型,通常来说我们用的都是 IP 协议
        skb->protocol = eth_type_trans(skb, dev);// 设置校验和信息
        if (bdp->flags & DF_CSUM_OFFLOAD) {
            if (bdp->rev_id >= D102_REV_ID) {
                skb->ip_summed = e100_D102_check_checksum(rfd);
            } else {
                skb->ip_summed = e100_D101M_checksum(bdp, skb);
            }
        } else {
            skb->ip_summed = CHECKSUM_NONE;
        }
​
        bdp->drv_stats.net_stats.rx_bytes += skb->len;
        if(bdp->vlgrp && (rfd_status & CB_STATUS_VLAN)) { // 处理vlan 虚拟局域网
            vlan_hwaccel_rx(skb, bdp->vlgrp, be16_to_cpu(rfd->vlanid));
        } else { // 我们这里   看正常包逻辑即可
            netif_rx(skb); // 将数据帧递交给上层协议
        }
        dev->last_rx = jiffies; // 记录最后一次处理接受数据帧的时间
        rfd_cnt++;
    }
    ...
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

# netif_rx 函数

该函数非常简单:将接收到的数据包放入 每 CPU 结构 struct softnet_data 的 input_pkt_queue 队列,等待中断下半部(软中断)处理。

nt netif_rx(struct sk_buff *skb)
{
    ...
    local_irq_save(flags); // 关闭当前 cpu 中断响应
    this_cpu = smp_processor_id(); 
    queue = &__get_cpu_var(softnet_data); // 获取当前 CPU 的 struct softnet_data 的结构,该结构每个CPU一个,用于存放 当前接收到的数据包
    __get_cpu_var(netdev_rx_stat).total++; // 增加接受到的数据包计数
    if (queue->input_pkt_queue.qlen <= netdev_max_backlog) { // int netdev_max_backlog = 300; 最大 300 个数据包
        if (queue->input_pkt_queue.qlen) { // 当前输入队列中存在数据帧
            if (queue->throttle) // 设置了不再接收任何数据帧,那么丢弃当前数据包
                goto drop;
enqueue: // 否则将其放入 input_pkt_queue 末尾
            dev_hold(skb->dev); // 增加 dev 设备数 计数,因为当前已经传递给了上层协议
            __skb_queue_tail(&queue->input_pkt_queue, skb);
            local_irq_restore(flags); // 开启CPU的中断响应
            return queue->cng_level;
        }// 执行到这里,说明输入队列中不存在任何数据,那么如果设置了不在接收任何数据帧,那么得将其打开 接收数据
        if (queue->throttle) {
            queue->throttle = 0;
        }
        netif_rx_schedule(&queue->backlog_dev); // 设置软中断标志位,通知当前已经收到数据包,等待硬中断处理完毕后,随后将会开启硬中断,然后执行软中断的中断下半部
        goto enqueue; // 打开 throttle 后,将数据包丢到 input_pkt_queue 队列中
    }if (!queue->throttle) { // 若上述开启了 throttle ,那么得将其关闭,等待数据包被软中断处理
        queue->throttle = 1;
        __get_cpu_var(netdev_rx_stat).throttled++;
    }
drop: // 丢弃当前数据包的流程处理
    __get_cpu_var(netdev_rx_stat).dropped++;
    local_irq_restore(flags);
    kfree_skb(skb);
    return NET_RX_DROP;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# netif_rx_schedule 函数

这里使用了 NAPI 的方式来对网卡中的数据包进行处理,所以在前面需要使用 per cpu 的 struct softnet_data 结构中的 struct net_device backlog_dev 设备来兼容旧的API方式处理数据包,所以先将数据包放入 input_pkt_queue 队列,然后在这里把 这个 backlog_dev 设备链入 struct softnet_data 结构中 poll_list 列表,随后将由 软中断 来处理 input_pkt_queue 队列 中的数据包。

同时如何保证进程与中断之间的操作安全?使用 local_irq_save 来关闭硬中断,此时不管是时钟还是网卡,都不响应中断,所以处理过程无需上锁,纯天然安全,处理完毕后使用 local_irq_restore 开启硬中断。

static inline void netif_rx_schedule(struct net_device *dev)
{
    if (netif_rx_schedule_prep(dev)) // 当前设备状态正常
        __netif_rx_schedule(dev);
}static inline void __netif_rx_schedule(struct net_device *dev)
{
    unsigned long flags;
    local_irq_save(flags);
    dev_hold(dev);
    list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list); // 将当前设备放入 poll 轮询列表等待处理
    // 设置处理队列中数据包的限额,超出限额后释放 CPU 资源,避免CPU一直执行软中断处理数据包
    if (dev->quota < 0)
        dev->quota += dev->weight;
    else
        dev->quota = dev->weight;
    __raise_softirq_irqoff(NET_RX_SOFTIRQ); // 设置接受数据包软中断,等待中断下半部完成 input_pkt_queue 队列的处理
    local_irq_restore(flags);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20