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后面,并依次处理该队列中的数据包;
NAPI方式收包流程
中断上半部
以e100为例:
e100_intr(中断处理程序)–>__napi_schedule–>____napi_schedule(将设备对应的napi结构加入到当前cpu的待收包处理队列softnet_data->poll_list中,并触发软中断)
数据包到来,第一包产生中断,中断处理程序得到执行,其中关键步骤为调用__napi_schedule(&nic->napi)将设备对应的napi加入到当前cpu的softnet_data->poll_list中;
static irqreturn_t e100_intr(int irq, void *dev_id)
{
struct net_device *netdev = dev_id;
struct nic *nic = netdev_priv(netdev);
u8 stat_ack = ioread8(&nic->csr->scb.stat_ack); netif_printk(nic, intr, KERN_DEBUG, nic->netdev,
"stat_ack = 0x%02X\n", stat_ack); if (stat_ack == stat_ack_not_ours || /* Not our interrupt */
stat_ack == stat_ack_not_present) /* Hardware is ejected */
return IRQ_NONE; /* Ack interrupt(s) */
iowrite8(stat_ack, &nic->csr->scb.stat_ack); /* We hit Receive No Resource (RNR); restart RU after cleaning */
if (stat_ack & stat_ack_rnr)
nic->ru_running = RU_SUSPENDED; if (likely(napi_schedule_prep(&nic->napi))) {
e100_disable_irq(nic);
//将该网络设备加入到sd的poll_list中
__napi_schedule(&nic->napi);
} return IRQ_HANDLED;
}
将设备对应的napi结构加入到当前cpu的softnet_data->poll_list中,并触发收包软中断;
void __napi_schedule(struct napi_struct *n)
{
unsigned long flags; local_irq_save(flags);
____napi_schedule(this_cpu_ptr(&softnet_data), n);
local_irq_restore(flags);
} //添加设备到poll_list,激活接收报文软中断
static inline void ____napi_schedule(struct softnet_data *sd,
struct napi_struct *napi)
{
list_add_tail(&napi->poll_list, &sd->poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}
中断下半部
net_rx_action(软中断收包处理程序)–>napi_poll(执行设备包处理回调napi_struct->poll)
收包软中断处理程序,软中断触发,说明有设备的数据包到达,此时本处理程序遍历softnet_data->poll_list中的待收包设备,并执行napi中的poll调度,关键代码napi_poll(n, &repoll);
/* 收包软中断处理程序 */
static __latent_entropy void net_rx_action(struct softirq_action *h)
{
struct softnet_data *sd = this_cpu_ptr(&softnet_data);
unsigned long time_limit = jiffies +
usecs_to_jiffies(netdev_budget_usecs);
int budget = netdev_budget;
LIST_HEAD(list);
LIST_HEAD(repoll); /*
将当前cpu的待收包设备列表poll_list合并到list,
并且重新初始化poll_list
*/
local_irq_disable();
list_splice_init(&sd->poll_list, &list);
local_irq_enable(); /* 遍历列表 */
for (;;) {
struct napi_struct *n; /* 列表为空,则跳出 */
if (list_empty(&list)) {
if (!sd_has_rps_ipi_waiting(sd) && list_empty(&repoll))
goto out;
break;
} /* 取链表头napi节点 */
n = list_first_entry(&list, struct napi_struct, poll_list); /*
调用该节点的poll函数收包 ,
若未处理完,则挂到repoll上
*/
budget -= napi_poll(n, &repoll); /* If softirq window is exhausted then punt.
* Allow this to run for 2 jiffies since which will allow
* an average latency of 1.5/HZ.
*/
/* 总配额用尽,或者中断时间窗口用尽,跳出 */
if (unlikely(budget <= ||
time_after_eq(jiffies, time_limit))) {
sd->time_squeeze++;
break;
}
} /* 禁用中断 */
local_irq_disable(); /* 整合poll_list链表,包括新产成的,未完成的,未完成的在前 */
list_splice_tail_init(&sd->poll_list, &list);
list_splice_tail(&repoll, &list);
list_splice(&list, &sd->poll_list); /* 如果poll_list不为空,则触发下一次收包中断 */
if (!list_empty(&sd->poll_list))
__raise_softirq_irqoff(NET_RX_SOFTIRQ); /* 启用中断 */
net_rps_action_and_irq_enable(sd);
out:
__kfree_skb_flush();
} struct netdev_adjacent {
struct net_device *dev; /* upper master flag, there can only be one master device per list */
bool master; /* counter for the number of times this device was added to us */
u16 ref_nr; /* private field for the users */
void *private; struct list_head list;
struct rcu_head rcu;
};
调用设备对应的napi_struct->poll回调接收数据包,接收数量要根据配额进行限制,关键代码为 work = n->poll(n, weight);
static int napi_poll(struct napi_struct *n, struct list_head *repoll)
{
void *have;
int work, weight; /* 将napi从链表中拿掉 */
list_del_init(&n->poll_list); have = netpoll_poll_lock(n); /* 读取配额 */
weight = n->weight; /* This NAPI_STATE_SCHED test is for avoiding a race
* with netpoll's poll_napi(). Only the entity which
* obtains the lock and sees NAPI_STATE_SCHED set will
* actually make the ->poll() call. Therefore we avoid
* accidentally calling ->poll() when NAPI is not scheduled.
*/
work = ; /* napi在调度状态 */
if (test_bit(NAPI_STATE_SCHED, &n->state)) {
/* 执行设备napi的poll回调进行收包 */
work = n->poll(n, weight);
trace_napi_poll(n, work, weight);
} WARN_ON_ONCE(work > weight); /* 收包数量小于配额,全部读完 */
if (likely(work < weight))
goto out_unlock; /* 以下未读完 */ /* Drivers must not modify the NAPI state if they
* consume the entire weight. In such cases this code
* still "owns" the NAPI instance and therefore can
* move the instance around on the list at-will.
*/
/* napi在禁用状态 */
if (unlikely(napi_disable_pending(n))) {
/* 执行完成项 */
napi_complete(n);
goto out_unlock;
} if (n->gro_list) {
/* flush too old packets
* If HZ < 1000, flush all packets.
*/
napi_gro_flush(n, HZ >= );
} /* Some drivers may have called napi_schedule
* prior to exhausting their budget.
*/
if (unlikely(!list_empty(&n->poll_list))) {
pr_warn_once("%s: Budget exhausted after napi rescheduled\n",
n->dev ? n->dev->name : "backlog");
goto out_unlock;
} /* 将为处理完的挂到repoll上 */
list_add_tail(&n->poll_list, repoll); out_unlock:
netpoll_poll_unlock(have); return work;
}
非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); 注意数每包都会中断;
int netif_rx(struct sk_buff *skb)
{
trace_netif_rx_entry(skb); return netif_rx_internal(skb);
}
static int netif_rx_internal(struct sk_buff *skb)
{
int ret; net_timestamp_check(netdev_tstamp_prequeue, skb); trace_netif_rx(skb); #ifdef CONFIG_RPS
if (static_key_false(&rps_needed)) {
struct rps_dev_flow voidflow, *rflow = &voidflow;
int cpu; preempt_disable();
rcu_read_lock(); cpu = get_rps_cpu(skb->dev, skb, &rflow);
if (cpu < )
cpu = smp_processor_id(); ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail); rcu_read_unlock();
preempt_enable();
} else
#endif
{
unsigned int qtail; ret = enqueue_to_backlog(skb, get_cpu(), &qtail);
put_cpu();
}
return ret;
}
enqueue_to_backlog将skb加入到当前cpu的softnet_data->input_pkt_queue中,并将softnet_data->backlog结构加入到softnet_data->poll_list链表中,并触发收包软中断;
static int enqueue_to_backlog(struct sk_buff *skb, int cpu,
unsigned int *qtail)
{
struct softnet_data *sd;
unsigned long flags;
unsigned int qlen; sd = &per_cpu(softnet_data, cpu); local_irq_save(flags); rps_lock(sd); //检查设备状态
if (!netif_running(skb->dev))
goto drop; //获取队列长度
qlen = skb_queue_len(&sd->input_pkt_queue); //如果队列未满&& 未达到skb流限制
if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) { //长度不为空,设备已经得到了调度
if (qlen) {
enqueue:
//skb入队
__skb_queue_tail(&sd->input_pkt_queue, skb);
input_queue_tail_incr_save(sd, qtail);
rps_unlock(sd);
local_irq_restore(flags);
return NET_RX_SUCCESS;
} /* Schedule NAPI for backlog device
* We can use non atomic operation since we own the queue lock
*/
//为空,则设置napi调度
if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) { //alextodo
if (!rps_ipi_queued(sd))
____napi_schedule(sd, &sd->backlog);
} //设置调度之后,入队
goto enqueue;
} //丢包
drop:
sd->dropped++;
rps_unlock(sd); local_irq_restore(flags); atomic_long_inc(&skb->dev->rx_dropped);
kfree_skb(skb);
return NET_RX_DROP;
}
中断下半部
net_rx_action(软中断收包处理程序)–>napi_poll(执行非napi回调函数process_backlog)
net_rx_action与napi方式相同,这里略过,主要看下其poll回调函数,其将数据包从队列中移出,调用__netif_receive_skb传递到上层,后续介绍传递流程,此处略过:
static int process_backlog(struct napi_struct *napi, int quota)
{
struct softnet_data *sd = container_of(napi, struct softnet_data, backlog);
bool again = true;
int work = ; /* Check if we have pending ipi, its better to send them now,
* not waiting net_rx_action() end.
*/
if (sd_has_rps_ipi_waiting(sd)) {
local_irq_disable();
net_rps_action_and_irq_enable(sd);
} //设置设备接收配额
napi->weight = dev_rx_weight;
while (again) {
struct sk_buff *skb; //从队列中取skb向上层输入
while ((skb = __skb_dequeue(&sd->process_queue))) {
rcu_read_lock();
__netif_receive_skb(skb);
rcu_read_unlock();
input_queue_head_incr(sd); //如果达到配额,则完成
if (++work >= quota)
return work; } local_irq_disable();
rps_lock(sd); //如果输入队列为空,没有需要处理
if (skb_queue_empty(&sd->input_pkt_queue)) {
/*
* Inline a custom version of __napi_complete().
* only current cpu owns and manipulates this napi,
* and NAPI_STATE_SCHED is the only possible flag set
* on backlog.
* We can use a plain write instead of clear_bit(),
* and we dont need an smp_mb() memory barrier.
*/ //重置状态,处理完毕
napi->state = ;
again = false;
} else {
//合并输入队列到处理队列,继续走循环处理
skb_queue_splice_tail_init(&sd->input_pkt_queue,
&sd->process_queue);
}
rps_unlock(sd);
local_irq_enable();
} //返回实际处理的包数
return work;
}
Linux NAPI/非NAPI的更多相关文章
- LINux网络的NAPI机制详解一
在查看NAPI机制的时候发现一篇介绍NAPI引入初衷的文章写的很好,通俗易懂,就想要分享下,重要的是博主还做了可以在他基础上任意修改,而并不用注明出处的声明,着实令我敬佩,不过还是附上原文链接! ht ...
- Linux下非root用户如何安装软件
Linux下非root用户如何安装软件 从windows转移到Linux的用户最开始总会有各种不适,因为这种不适最终放弃linux的不在少数.对于这类人只能说可惜,还没有领略到linux的美好就过早放 ...
- Linux查看非root流程执行
Linux查看非root流程执行 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ ps -U root -u root -N PID TTY TIME CMD ...
- Linux 下非root用户使用docker
Linux 下非root用户使用docker 通常我们使用linux系统的时候,最好是不要直接使用root账号,但是使用Docker的时候,默认又是不能使用非root用户的,关于原因,官方说法如下: ...
- Linux查看非root运行的进程
Linux查看非root运行的进程 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ ps -U root -u root -N PID TTY TIME CMD ...
- Linux下,非Docker启动Elasticsearch 6.3.0,安装ik分词器插件,以及使用Kibana测试Elasticsearch,
Linux下,非Docker启动Elasticsearch 6.3.0 查看java版本,需要1.8版本 java -version yum -y install java 创建用户,因为elasti ...
- [转载]Linux下非root用户如何安装软件
[转载]Linux下非root用户如何安装软件 来源:https://tlanyan.me/work-with-linux-without-root-permission/ 这是本人遇到的实际问题,之 ...
- Linux BSP非标准HDMI分辨率
Linux BSP非标准HDMI分辨率 Intrinsyc公司发布了它的一个新的Linux BSP软件的发布 打开-Q820 开发套件基于Linux内核版本.支持的软件功能包括HDMI输出,可以支持标 ...
- linux系统非ROOT用户80端口不能启动tomcat问题的变通办法——通过Iptables端口转发
2010-07-17 13:21:42 org.apache.tomcat.util.digester.SetPropertiesRule begin 警告: [SetPropertiesRule]{ ...
随机推荐
- [OS] 生产者-消费者问题(有限缓冲问题)
·最简单的情形--(一个生产者 + 一个消费者 + 一个大小为1的有限缓冲) 首先来分析其中的同步关系: ·必须在生产者放入一个产品之后,消费者才能够从缓冲中取出产品来消费.·只有在消费者从缓冲区中取 ...
- 【.Net】浅谈C#中的值类型和引用类型
在C#中,值类型和引用类型是相当重要的两个概念,必须在设计类型的时候就决定类型实例的行为.如果在编写代码时不能理解引用类型和值类型的区别,那么将会给代码带来不必要的异常.很多人就是因为没有弄清楚这两个 ...
- 【原创】宿主机远程登录虚拟机(windows server 2003系统)
转载请注明,谢谢合作 1.虚拟机网络设置为 “桥接模式”如图 2.系统装好并登陆后 右键点击我的电脑,点击属性,然后在弹出来的选择框中勾选远程桌面-->启用这台计算机的远程桌面 然后点添加-- ...
- 【题解】Atcoder ARC#76 F-Exhausted?
第一次用霍尔定理做题..简单的来说,就是判断一张二分图上是否存在完美匹配,只需要证明对于 \(a\) 集合中的任意 \(k\) 个点来说,都与 \(b\) 集合中的 \(k\) 个点有边相连.如果不满 ...
- [清华集训2017]无限之环(infinityloop)
description 题面 solution 一开始的思路是插头\(DP\),然而复杂度太高 考虑将网格图黑白染色后跑费用流 流量为接口数,费用为操作次数 把一个方格拆成五个点,如何连边请自行脑补 ...
- [洛谷P2495][SDOI2011]消耗战
题目大意:有一棵$n(n\leqslant2.5\times10^5)$个节点的带边权的树,$m$个询问,每次询问给出$k(\sum\limits_{i=1}^mk_i\leqslant5\times ...
- Spring-Boot基于配置按条件装Bean
背景 同一个接口有多种实现,项目启动时按某种规则来选择性的启用其中一种实现,再具体一点,比如Controller初始化的时候,根据配置文件的指定的实现类前缀,来记载具体Service,不同Servic ...
- zabbix调优PPT
http://www.slideshare.net/xsbr/alexei-vladishev-zabbixperformancetuning# http://zabbixzone.com/zabbi ...
- 一个完整的upstart脚本分析
基本概念可以了解 http://www.mike.org.cn/articles/understand-upstart/ http://blog.fens.me/linux-upstart/ http ...
- udhcpd源码分析4--获取client报文及发包动作
1:重要的结构体 获取的报文是UDP的payload部分,结构体struct dhcpMessage描述了dhcp报文的结构. /* packet.h */ struct dhcpMessage { ...