1 netif_rx简介
netif_rx函数由常规非NAPI网络设备驱动程序在接受中断将数据包从设备缓冲区拷贝到内核空间后调用,他的主要任务是把数据帧添加到CPU的输入队列input_pkt_queue中。随后标记软中断来处理后续上传数据帧给TCP/IP协议栈。
netif_rx函数调用场合有以下三种:
- 网络设备驱动程序接受中断的执行现场
- 处理CPU掉线时间的回调函数dev_cpu_callback中
- loopback设备的接受数据帧函数
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数据结构实例的访问不会出现问题。
2 netif_rx详情
netif_rx主要做以下三件事情
- 初始化skb_buff数据结构的某些数据域,如接受数据帧的时间
- 将接受到的数据帧放入CPU的私有输入队列,通过标记通知内核有数据帧到达
- 更新CPU的接受统计信息
netif_rx实际调用netif_rx_internal
int netif_rx(struct sk_buff *skb)
{
trace_netif_rx_entry(skb);
return netif_rx_internal(skb);
}
2. 1 netif_rx_internal
netif_rx_internal的输入参数只有一个:存放网络设备接受到的数据帧Socket Buffer。如果数据帧成功放入到CPU的输入队列就返回NET_RX_SUCCESS;如果数据帧被扔掉就返回NET_RX_DROP。
netif_rx_internal代码如下:
- 首先设置skb的接受时间
- 获取当前CPU的标识ID
- 调用enqueue_to_backlog继续处理
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;
}
2.2?enqueue_to_backlog
enueue_to_backlog主要做以下事情:
- 获取CPU的softnet_data
- 保存本地中断信息,并禁止本地中断
- 将skb添加到CPU的输入队列input_pkt_queue
- 恢复本地中断
- 如果CPU的输入队列input_pkt_queu为空,说明当前时收到第一个数据帧,应该标记软件中断,并且将backlog添加到poll_list中,backlog也就是初始化的process_backlog。如果CPU输入队列不为空说明当前已经标记过软件中断,只需要将数据帧加入到CPU的输入队列中。
- 如果当前CPU输入队列已经满了,那么netif_rx就会扔掉数据帧,释放缓冲区只能用的内存空间,更行CPU的扔包统计信息。
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); //标记软件中断
}
|