netif_rx函数由常规非NAPI网络设备驱动程序在接受中断将数据包从设备缓冲区拷贝到内核空间后调用,他的主要任务是把数据帧添加到CPU的输入队列input_pkt_queue中。随后标记软中断来处理后续上传数据帧给TCP/IP协议栈。
netif_rx函数调用场合有以下三种:
dev_cpu_callback函数是网络子系统注册到CPU时间通知链中的回调函数,在对称多任务系统SMP中,当一个CPU掉线时,会向其事件通知链发送时间消息,调用所有注册到CPU事件通知链中的回调函数来对时间做出反应。dev_cpu_callback函数就将掉线CPU的struct softnet_data数据结构实力中的发送数据帧完成队列,输出设备队列,input_pkt_queue加入到其他CPU的struct softnet_data数据结构实力相关队列中。将输入数据帧从掉线的输入队列转移到其他CPU输入队列也由netif_rx函数实现。
常规情况下,netif_rx函数在网络设备驱动程序的中断执行现场被调用。但是对于loopback设备,不存在中断执行现场,因为loopback设备不是真是的网络设备,所以开始执行netif_rx函数时要关闭本地CPU中断,等netif_rx执行完成后在开中断。
netif_rx可以在不同CPU上同时运行,每个CPU有自己的struct softnet_data数据结构实例来维护状态,所以对struct softnet_data数据结构实例的访问不会出现问题。
netif_rx主要做以下三件事情
netif_rx实际调用netif_rx_internal
int netif_rx(struct sk_buff *skb)
{
trace_netif_rx_entry(skb);
return netif_rx_internal(skb);
}
netif_rx_internal的输入参数只有一个:存放网络设备接受到的数据帧Socket Buffer。如果数据帧成功放入到CPU的输入队列就返回NET_RX_SUCCESS;如果数据帧被扔掉就返回NET_RX_DROP。
netif_rx_internal代码如下:
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); //取当前cpu的id
if (cpu < 0)
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;
}
enueue_to_backlog主要做以下事情:
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); //获取cpu的softnet_data
local_irq_save(flags); //禁止本地CPU中断,并保存本地中断信息
rps_lock(sd);
if (!netif_running(skb->dev))
goto drop;
qlen = skb_queue_len(&sd->input_pkt_queue);
if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) { //判断队列是否超过最大值
if (skb_queue_len(&sd->input_pkt_queue)) {
enqueue:
__skb_queue_tail(&sd->input_pkt_queue, skb); //将skb添加到cpu输入队列input_pkt_queue
input_queue_tail_incr_save(sd, qtail);
rps_unlock(sd);
local_irq_restore(flags); //恢复本地CPU中断
return NET_RX_SUCCESS;
}
/* Schedule NAPI for backlog device
* We can use non atomic operation since we own the queue lock
*/
if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
if (!rps_ipi_queued(sd))
____napi_schedule(sd, &sd->backlog); //如果NAPI_STATE_SCHED标志没有设置表示当前没有软中断在处理数据包,
} //将backlog添加到poll_list中,backlog也就是初始化的process_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;
}
如果NAPI_STATE_SCHED标志没有设置表示当前没有软中断在处理数据包,调用函数____napi_schedule将backlog添加到poll_list中,backlog也就是初始化的process_backlog。
static inline void ____napi_schedule(struct softnet_data *sd,
struct napi_struct *napi)
{
list_add_tail(&napi->poll_list, &sd->poll_list); //将软中断处理函数添加到CPU的poll_list
__raise_softirq_irqoff(NET_RX_SOFTIRQ); //标记软件中断
}