本文主要介绍二层收包流程,包括NAPI与非NAPI方式;

NAPI:数据包到来,第一个数据包产生硬件中断,中断处理程序将设备的napi_struct结构挂在当前cpu的待收包设备链表softnet_data->poll_list中,并触发软中断,软中断执行过程中,遍历softnet_data->poll_list中的所有设备,依次调用其收包函数napi_sturct->poll,处理收包过程;

非NAPI:每个数据包到来,都会产生硬件中断,中断处理程序将收到的包放入当前cpu的收包队列softnet_data->input_pkt_queue中,并且将非napi设备对应的虚拟设备napi结构softnet->backlog结构挂在当前cpu的待收包设备链表softnet_data->poll_list中,并触发软中断,软中断处理过程中,会调用backlog的回调处理函数process_backlog,将收包队列input_pkt_queue合并到softdata->process_queue后面,并依次处理该队列中的数据包;

源码分析:

Kernel:4.12.6

NAPI:

上半部:

以e100为例:

e100_intr(中断处理程序)-->__napi_schedule-->____napi_schedule(将设备对应的napi结构加入到当前cpu的待收包处理队列softnet_data->poll_list中,并触发软中断)

数据包到来,第一包产生中断,中断处理程序得到执行,其中关键步骤为调用__napi_schedule(&nic->napi)将设备对应的napi加入到当前cpu的softnet_data->poll_list中;

 1 static irqreturn_t e100_intr(int irq, void *dev_id)
 2 {
 3     struct net_device *netdev = dev_id;
 4     struct nic *nic = netdev_priv(netdev);
 5     u8 stat_ack = ioread8(&nic->csr->scb.stat_ack);
 6
 7     netif_printk(nic, intr, KERN_DEBUG, nic->netdev,
 8              "stat_ack = 0x%02X\n", stat_ack);
 9
10     if (stat_ack == stat_ack_not_ours ||    /* Not our interrupt */
11        stat_ack == stat_ack_not_present)    /* Hardware is ejected */
12         return IRQ_NONE;
13
14     /* Ack interrupt(s) */
15     iowrite8(stat_ack, &nic->csr->scb.stat_ack);
16
17     /* We hit Receive No Resource (RNR); restart RU after cleaning */
18     if (stat_ack & stat_ack_rnr)
19         nic->ru_running = RU_SUSPENDED;
20
21     if (likely(napi_schedule_prep(&nic->napi))) {
22         e100_disable_irq(nic);
23         //将该网络设备加入到sd的poll_list中
24         __napi_schedule(&nic->napi);
25     }
26
27     return IRQ_HANDLED;
28 }

将设备对应的napi结构加入到当前cpu的softnet_data->poll_list中,并触发收包软中断;

 1 void __napi_schedule(struct napi_struct *n)
 2 {
 3     unsigned long flags;
 4
 5     local_irq_save(flags);
 6     ____napi_schedule(this_cpu_ptr(&softnet_data), n);
 7     local_irq_restore(flags);
 8 }
 9
10
11 //添加设备到poll_list,激活接收报文软中断
12 static inline void ____napi_schedule(struct softnet_data *sd,
13                      struct napi_struct *napi)
14 {
15     list_add_tail(&napi->poll_list, &sd->poll_list);
16     __raise_softirq_irqoff(NET_RX_SOFTIRQ);
17 }

下半部:

net_rx_action(软中断收包处理程序)-->napi_poll(执行设备包处理回调napi_struct->poll)

收包软中断处理程序,软中断触发,说明有设备的数据包到达,此时本处理程序遍历softnet_data->poll_list中的待收包设备,并执行napi中的poll调度,关键代码napi_poll(n, &repoll);

 1 static __latent_entropy void net_rx_action(struct softirq_action *h)
 2 {
 3     struct softnet_data *sd = this_cpu_ptr(&softnet_data);
 4     unsigned long time_limit = jiffies +
 5         usecs_to_jiffies(netdev_budget_usecs);
 6     int budget = netdev_budget;
 7     LIST_HEAD(list);
 8     LIST_HEAD(repoll);
 9
10     //禁止本地中断
11     local_irq_disable();
12
13     //将poll_list合并到list,重新初始化poll_list
14     list_splice_init(&sd->poll_list, &list);
15
16     //开启本地中断
17     local_irq_enable();
18
19     for (;;) {
20         struct napi_struct *n;
21
22         //如果list为空,结束循环
23         if (list_empty(&list)) {
24             //alextodo
25             if (!sd_has_rps_ipi_waiting(sd) && list_empty(&repoll))
26                 goto out;
27             break;
28         }
29
30         //取链表头
31         n = list_first_entry(&list, struct napi_struct, poll_list);
32
33         //调用设备n的poll处理,若未处理完成,则设备挂在repoll上
34         budget -= napi_poll(n, &repoll);
35
36         /* If softirq window is exhausted then punt.
37          * Allow this to run for 2 jiffies since which will allow
38          * an average latency of 1.5/HZ.
39          */
40         //总配额用尽,或者中断时间窗口用尽,记录之,结束循环
41         if (unlikely(budget <= 0 ||
42                  time_after_eq(jiffies, time_limit))) {
43             sd->time_squeeze++;
44             break;
45         }
46     }
47
48     //禁止本地中断
49     local_irq_disable();
50
51     //整理poll_list设备链表
52     list_splice_tail_init(&sd->poll_list, &list);
53     list_splice_tail(&repoll, &list);
54     list_splice(&list, &sd->poll_list);
55
56     //如果设备列表不为空,则触发下一次收包软中断
57     if (!list_empty(&sd->poll_list))
58         __raise_softirq_irqoff(NET_RX_SOFTIRQ);
59
60     //开启本地中断
61     //alextodo
62     net_rps_action_and_irq_enable(sd);
63 out:
64     //刷新skb缓冲区
65     //在网卡驱动的tx_clean操作中调用napi_consume_skb缓存了skb
66     //alextodo 为什么缓存
67     __kfree_skb_flush();
68 }

调用设备对应的napi_struct->poll回调接收数据包,接收数量要根据配额进行限制,关键代码为 work = n->poll(n, weight);

 1 static int napi_poll(struct napi_struct *n, struct list_head *repoll)
 2 {
 3     void *have;
 4     int work, weight;
 5
 6     //从链表中拿掉n
 7     list_del_init(&n->poll_list);
 8
 9     have = netpoll_poll_lock(n);
10
11     //读取配额
12     weight = n->weight;
13
14     /* This NAPI_STATE_SCHED test is for avoiding a race
15      * with netpoll's poll_napi().  Only the entity which
16      * obtains the lock and sees NAPI_STATE_SCHED set will
17      * actually make the ->poll() call.  Therefore we avoid
18      * accidentally calling ->poll() when NAPI is not scheduled.
19      */
20     work = 0;
21     //如果napi poll被调度状态
22     if (test_bit(NAPI_STATE_SCHED, &n->state)) {
23         //执行当前设备n的poll回调
24         work = n->poll(n, weight);
25         trace_napi_poll(n, work, weight);
26     }
27
28     WARN_ON_ONCE(work > weight);
29
30     //读取小于配额,全部读出,退出
31     if (likely(work < weight))
32         goto out_unlock;
33
34
35     //读取数等于配额尚未读完
36
37
38     /* Drivers must not modify the NAPI state if they
39      * consume the entire weight.  In such cases this code
40      * still "owns" the NAPI instance and therefore can
41      * move the instance around on the list at-will.
42      */
43     //如果napi状态为disable,则执行完成项
44     if (unlikely(napi_disable_pending(n))) {
45         napi_complete(n);
46         goto out_unlock;
47     }
48
49     //如果等待合并的skb链表存在,清理过时的节点
50     if (n->gro_list) {
51         /* flush too old packets
52          * If HZ < 1000, flush all packets.
53          */
54         napi_gro_flush(n, HZ >= 1000);
55     }
56
57     /* Some drivers may have called napi_schedule
58      * prior to exhausting their budget.
59      */
60     //alextodo
61     if (unlikely(!list_empty(&n->poll_list))) {
62         pr_warn_once("%s: Budget exhausted after napi rescheduled\n",
63                  n->dev ? n->dev->name : "backlog");
64         goto out_unlock;
65     }
66
67     //将当前设备加入到链表尾部
68     list_add_tail(&n->poll_list, repoll);
69
70 out_unlock:
71     netpoll_poll_unlock(have);
72
73     return work;
74 }

其中poll回调的处理流程如下,以e100为例:

 1 static int e100_poll(struct napi_struct *napi, int budget)
 2 {
 3     struct nic *nic = container_of(napi, struct nic, napi);
 4     unsigned int work_done = 0;
 5
 6     //从网络设备中读取数据包
 7     e100_rx_clean(nic, &work_done, budget);
 8
 9     //清理发包队列
10     e100_tx_clean(nic);
11
12     /* If budget not fully consumed, exit the polling mode */
13     //处理数小于配额,退出poll模式
14     if (work_done < budget) {
15         napi_complete_done(napi, work_done);
16         e100_enable_irq(nic);
17     }
18
19     //返回处理数
20     return work_done;
21 }

具体数据包的接收流程,不再继续深入讨论;

非NAPI:

上半部:

netif_rx(中断处理程序最终会调用次函数处理收到的包)->netif_rx_internal->enqueue_to_backlog(将收到的包加入到当前cpu的softnet->input_pkt_queue中,并将默认设备backlog加入到softnet_data结构的poll_list链表)

中断处理程序会调用netif_rx来将数据包加入到收包队列中,关键代码:enqueue_to_backlog(skb, get_cpu(), &qtail); 注意数每包都会中断;

1 int netif_rx(struct sk_buff *skb)
2 {
3     trace_netif_rx_entry(skb);
4
5     return netif_rx_internal(skb);
6 }

 1 static int netif_rx_internal(struct sk_buff *skb)
 2 {
 3     int ret;
 4
 5     net_timestamp_check(netdev_tstamp_prequeue, skb);
 6
 7     trace_netif_rx(skb);
 8
 9     //alextodo
10 #ifdef CONFIG_RPS
11     if (static_key_false(&rps_needed)) {
12         struct rps_dev_flow voidflow, *rflow = &voidflow;
13         int cpu;
14
15         preempt_disable();
16         rcu_read_lock();
17
18         cpu = get_rps_cpu(skb->dev, skb, &rflow);
19         if (cpu < 0)
20             cpu = smp_processor_id();
21
22         ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
23
24         rcu_read_unlock();
25         preempt_enable();
26     } else
27 #endif
28     {
29         unsigned int qtail;
30
31         ret = enqueue_to_backlog(skb, get_cpu(), &qtail);
32         put_cpu();
33     }
34     return ret;
35 }

将skb加入到当前cpu的softnet_data->input_pkt_queue中,并将softnet_data->backlog结构加入到softnet_data->poll_list链表中,并触发收包软中断;

 1 static int enqueue_to_backlog(struct sk_buff *skb, int cpu,
 2                   unsigned int *qtail)
 3 {
 4     struct softnet_data *sd;
 5     unsigned long flags;
 6     unsigned int qlen;
 7
 8     sd = &per_cpu(softnet_data, cpu);
 9
10     local_irq_save(flags);
11
12     rps_lock(sd);
13
14     //检查设备状态
15     if (!netif_running(skb->dev))
16         goto drop;
17
18     //获取队列长度
19     qlen = skb_queue_len(&sd->input_pkt_queue);
20
21     //如果队列未满&& 未达到skb流限制
22     if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) {
23
24         //长度不为空,设备已经得到了调度
25         if (qlen) {
26 enqueue:
27             //skb入队
28             __skb_queue_tail(&sd->input_pkt_queue, skb);
29             input_queue_tail_incr_save(sd, qtail);
30             rps_unlock(sd);
31             local_irq_restore(flags);
32             return NET_RX_SUCCESS;
33         }
34
35         /* Schedule NAPI for backlog device
36          * We can use non atomic operation since we own the queue lock
37          */
38         //为空,则设置napi调度
39         if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
40
41             //alextodo
42             if (!rps_ipi_queued(sd))
43                 ____napi_schedule(sd, &sd->backlog);
44         }
45
46         //设置调度之后,入队
47         goto enqueue;
48     }
49
50 //丢包
51 drop:
52     sd->dropped++;
53     rps_unlock(sd);
54
55     local_irq_restore(flags);
56
57     atomic_long_inc(&skb->dev->rx_dropped);
58     kfree_skb(skb);
59     return NET_RX_DROP;
60 }

下半部:

net_rx_action(软中断收包处理程序)-->napi_poll(执行非napi回调函数process_backlog)

net_rx_action与napi方式相同,这里略过,主要看下其poll回调函数:

 1 static int process_backlog(struct napi_struct *napi, int quota)
 2 {
 3     struct softnet_data *sd = container_of(napi, struct softnet_data, backlog);
 4     bool again = true;
 5     int work = 0;
 6
 7     /* Check if we have pending ipi, its better to send them now,
 8      * not waiting net_rx_action() end.
 9      */
10     if (sd_has_rps_ipi_waiting(sd)) {
11         local_irq_disable();
12         net_rps_action_and_irq_enable(sd);
13     }
14
15     //设置设备接收配额
16     napi->weight = dev_rx_weight;
17     while (again) {
18         struct sk_buff *skb;
19
20         //从队列中取skb向上层输入
21         while ((skb = __skb_dequeue(&sd->process_queue))) {
22             rcu_read_lock();
23             __netif_receive_skb(skb);
24             rcu_read_unlock();
25             input_queue_head_incr(sd);
26
27             //如果达到配额,则完成
28             if (++work >= quota)
29                 return work;
30
31         }
32
33         local_irq_disable();
34         rps_lock(sd);
35
36         //如果输入队列为空,没有需要处理
37         if (skb_queue_empty(&sd->input_pkt_queue)) {
38             /*
39              * Inline a custom version of __napi_complete().
40              * only current cpu owns and manipulates this napi,
41              * and NAPI_STATE_SCHED is the only possible flag set
42              * on backlog.
43              * We can use a plain write instead of clear_bit(),
44              * and we dont need an smp_mb() memory barrier.
45              */
46
47             //重置状态,处理完毕
48             napi->state = 0;
49             again = false;
50         } else {
51             //合并输入队列到处理队列,继续走循环处理
52             skb_queue_splice_tail_init(&sd->input_pkt_queue,
53                            &sd->process_queue);
54         }
55         rps_unlock(sd);
56         local_irq_enable();
57     }
58
59     //返回实际处理的包数
60     return work;
61 }

具体数据包的接收流程,不再继续深入讨论;

转载于:https://www.cnblogs.com/wanpengcoder/p/7419143.html

Linux NAPI/非NAPI相关推荐

  1. linux 内核协议栈 NAPI机制与处理流程分析(图解)

    目录 1 NAPI 机制 1.1 NAPI 缺陷 1.2 使用 NAPI 先决条件 1.3 非NAPI帧的接收 1.3.1 netif_rx - 将网卡中收到的数据包放到系统中的接收队列中 1.3.2 ...

  2. Linux BSP非标准HDMI分辨率

    Linux BSP非标准HDMI分辨率 Intrinsyc公司发布了它的一个新的Linux BSP软件的发布 打开-Q™820 开发套件基于Linux内核版本.支持的软件功能包括HDMI输出,可以支持 ...

  3. linux c 文件键盘写入,linux - C非阻塞键盘输入

    linux - C非阻塞键盘输入 我正在尝试用C语言编写一个程序(在Linux上)循环直到用户按下一个键,但不应该要求按键继续每个循环. 有一个简单的方法吗? 我想我可以用select()这样做,但这 ...

  4. linux grep 非_帮助非技术人员转向Linux的8条技巧

    linux grep 非 早在2016年,我就取消了技术教练业务. 永久性. 还是我想. 这是一个有趣的经历,在很大程度上是因为大多数人根本不是技术专家. 他们知道如何使用计算机来完成他们需要做的事情 ...

  5. linux装软件需要root用户,Linux下非root用户安装软件的一般流程:

    1. 获取源代码,一般是wget方式,ubuntu可以使用apt-get source来获取源代码. 2. 解压源代码,一般使用tar -zxvf xxx.tar.gz即可 3. 切换到解压后的目录, ...

  6. Linux下非交互式sshpass登录

    摘要 在命令行 非交互的SSH登录的时候,一般我们可以借助于生成用户的公钥私钥对,然后把公钥添加到远程主机的authorized_keys文件,可以实现非交互无密码登录. 其实这里也可以有另外一种方式 ...

  7. C++笔记之linux下非阻塞多线程运行多个系统shell命令(popen方法)

    参考博文:C++ linux 睡眠函数sleep和std::this_thread::sleep_for 参考博文:C++笔记之linux下运行系统shell命令(popen方法)函数封装 参考博文: ...

  8. Linux查hudi服务的进程,Linux查看非root运行的进程

    Linux查看非root运行的进程 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ ps -U root -u root -N PID TTY TIME CMD ...

  9. linux是不是在根目录下安装的软件其它用户就可以使用,[转载]Linux下非root用户如何安装软件...

    [转载]Linux下非root用户如何安装软件 这是本人遇到的实际问题,之前用到的所有机器,无论是自己的PC还是云服务器,root权限都是妥妥的,但是现在发现实验室的服务器原来自己并没有root权限2 ...

最新文章

  1. YUM安装多个(多实例) Mysql
  2. 看完这篇 HTTPS,和面试官扯皮就没问题了
  3. android模糊检索_【android学习笔记】ormlite学习之模糊搜索like
  4. SAP CRM BP contact detail - workAddress
  5. 【Python】Windows下Python3虚拟环境搭建
  6. 【MySQL】基于MySQL的SQL核心语法实战演练(二)
  7. MySQL5.7 安装(win)
  8. JSP Servlet | 错误统一处理
  9. 我国计算机网络发展水平,计算机网络发展
  10. 深度学习2.0-19.随机梯度下降之可视化与实战
  11. Java基础编程题(API阶段测试)
  12. 破解版editPlus
  13. 看一看Facebook工程师是怎么评价《第一行代码》的
  14. 利用EQSecure E盾预防流氓软体
  15. php7中require_once,php require_once的使用方法总结
  16. Fullcalendar 在vue中鼠标hover显示悬浮框(tippy.js插件)
  17. 只读挂载磁盘linux,linux挂载磁盘就变只读怎么解决
  18. DVWA靶机-存储型XSS漏洞(Stored)
  19. 2009上海最新“四金”及个人所得税计算(器)
  20. 房屋安全鉴定的建筑结构检测技术

热门文章

  1. Bootstrap3 栅格系统-媒体查询
  2. 优秀教程:创建基于 Ajax 的文件拖放上传功能
  3. Oracle:sqlplus查询出的中文是乱码问题的解决
  4. Python的if __name__ == ‘__main__‘:的作用
  5. STM32中关于串口通信的printf()函数重定向问题
  6. 根据经纬度批量计算多个点到多个点之间的距离
  7. 北京超级云计算GPU服务器的使用教程
  8. pytorch ctcloss 参数详解
  9. python whl大全
  10. Samba通过ad域进行认证并限制空间大小《转载》