Linux NAPI/非NAPI
本文主要介绍二层收包流程,包括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相关推荐
- linux 内核协议栈 NAPI机制与处理流程分析(图解)
目录 1 NAPI 机制 1.1 NAPI 缺陷 1.2 使用 NAPI 先决条件 1.3 非NAPI帧的接收 1.3.1 netif_rx - 将网卡中收到的数据包放到系统中的接收队列中 1.3.2 ...
- Linux BSP非标准HDMI分辨率
Linux BSP非标准HDMI分辨率 Intrinsyc公司发布了它的一个新的Linux BSP软件的发布 打开-Q™820 开发套件基于Linux内核版本.支持的软件功能包括HDMI输出,可以支持 ...
- linux c 文件键盘写入,linux - C非阻塞键盘输入
linux - C非阻塞键盘输入 我正在尝试用C语言编写一个程序(在Linux上)循环直到用户按下一个键,但不应该要求按键继续每个循环. 有一个简单的方法吗? 我想我可以用select()这样做,但这 ...
- linux grep 非_帮助非技术人员转向Linux的8条技巧
linux grep 非 早在2016年,我就取消了技术教练业务. 永久性. 还是我想. 这是一个有趣的经历,在很大程度上是因为大多数人根本不是技术专家. 他们知道如何使用计算机来完成他们需要做的事情 ...
- linux装软件需要root用户,Linux下非root用户安装软件的一般流程:
1. 获取源代码,一般是wget方式,ubuntu可以使用apt-get source来获取源代码. 2. 解压源代码,一般使用tar -zxvf xxx.tar.gz即可 3. 切换到解压后的目录, ...
- Linux下非交互式sshpass登录
摘要 在命令行 非交互的SSH登录的时候,一般我们可以借助于生成用户的公钥私钥对,然后把公钥添加到远程主机的authorized_keys文件,可以实现非交互无密码登录. 其实这里也可以有另外一种方式 ...
- C++笔记之linux下非阻塞多线程运行多个系统shell命令(popen方法)
参考博文:C++ linux 睡眠函数sleep和std::this_thread::sleep_for 参考博文:C++笔记之linux下运行系统shell命令(popen方法)函数封装 参考博文: ...
- Linux查hudi服务的进程,Linux查看非root运行的进程
Linux查看非root运行的进程 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ ps -U root -u root -N PID TTY TIME CMD ...
- linux是不是在根目录下安装的软件其它用户就可以使用,[转载]Linux下非root用户如何安装软件...
[转载]Linux下非root用户如何安装软件 这是本人遇到的实际问题,之前用到的所有机器,无论是自己的PC还是云服务器,root权限都是妥妥的,但是现在发现实验室的服务器原来自己并没有root权限2 ...
最新文章
- YUM安装多个(多实例) Mysql
- 看完这篇 HTTPS,和面试官扯皮就没问题了
- android模糊检索_【android学习笔记】ormlite学习之模糊搜索like
- SAP CRM BP contact detail - workAddress
- 【Python】Windows下Python3虚拟环境搭建
- 【MySQL】基于MySQL的SQL核心语法实战演练(二)
- MySQL5.7 安装(win)
- JSP Servlet | 错误统一处理
- 我国计算机网络发展水平,计算机网络发展
- 深度学习2.0-19.随机梯度下降之可视化与实战
- Java基础编程题(API阶段测试)
- 破解版editPlus
- 看一看Facebook工程师是怎么评价《第一行代码》的
- 利用EQSecure E盾预防流氓软体
- php7中require_once,php require_once的使用方法总结
- Fullcalendar 在vue中鼠标hover显示悬浮框(tippy.js插件)
- 只读挂载磁盘linux,linux挂载磁盘就变只读怎么解决
- DVWA靶机-存储型XSS漏洞(Stored)
- 2009上海最新“四金”及个人所得税计算(器)
- 房屋安全鉴定的建筑结构检测技术
热门文章
- Bootstrap3 栅格系统-媒体查询
- 优秀教程:创建基于 Ajax 的文件拖放上传功能
- Oracle:sqlplus查询出的中文是乱码问题的解决
- Python的if __name__ == ‘__main__‘:的作用
- STM32中关于串口通信的printf()函数重定向问题
- 根据经纬度批量计算多个点到多个点之间的距离
- 北京超级云计算GPU服务器的使用教程
- pytorch ctcloss 参数详解
- python whl大全
- Samba通过ad域进行认证并限制空间大小《转载》