以前分析了linux 软中断,最近看了软中断与网络接收函数的关系,特记下,以免忘记。
一、linux网络接收函数与软中断的关系及初始化。
关于软中断的原理就不依依介绍了,本节主要介绍网络数据处理对软中断的使用(关于软中断的工作原理可参看 linux中断底半部之 softirq 原理与代码分析)。
网络数据处理的软中断的注册是在函数net_dev_init进行初始化,主要是通过调用函数open_softirq,将rx、tx的处理函数注册到数组softirq_vec中相应软中断号对应的action指针中。这样,当设备产生中断接收一个网络数据时,在中断处理函数中通过调用函数raise_softirq_irqoff将NET_RX_SOFTIRQ对应的flag位置1,这样在中断函数处理结束,通过函数irq_exit中调用invoke_softirq,实现软中断处理。
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
以上就是网络协议栈中对于软中断的使用与初始化。
二、LINUX NAPI与netif_rx之间的关系
NAPI:混合使用中断与轮询,而不是使用单一的中断驱动机制,这样就提高了系统的性能。当设备产生一个数据接收中断后,新机制的软中断处理函数就会轮询设备的入口队列,直到入口队列中没有数据了,再开启中断,好多芯片厂家会同时使用DMA与NAPI机制,通过DMA机制收取switch接收到的数据,然后再通过NAPI机制,由协议栈再对数据进行处理。
Netif_rx:单一的中断驱动处理机制,每产生一个设备接收中断,就会在中断结束时唤醒该软中断,调用该软中断的处理函数。
由于NAIP相对于netif_rx是新的机制,所以需要做兼容,而对于现有设备来说netif_rx机制也是需要的。因为对于现在的网关设备来说都会支持vlan功能,而好多厂家的vlan实现中,会在原有接口的基础上,虚拟出一个接口作为vlan接口。既然虚拟出了接口,那也就有相应的数据收发流程,而对于虚拟接口来说收发数据时是没有相应的中断的,此时可以使用函数netif_rx就可以实现数据从真实接口处理转变为虚拟接口的处理,而且虚拟接口的处理也是走的标准的协议栈,所以netif_rx机制也还是有用处的。
由于这两种机制都有存在的理由,那就需要进行兼容。那如何进行兼容呢?
1、数据队列的考虑
由于NAPI设备都有自己的私有队列,所以其队列处理函数就要有相应的设备驱动程序来进行处理。
对于netif来说,使用的是内核统一的数据帧接收队列,而其处理函数也是唯一的。
2、如何将网络设备与软中断联系起来,
对于netif_rx来说各设备不需要与softnet_data联系,直接调用netif_rx的软中断处理函数,从内核接收队列中取出数据包,然后交给协议栈处理函数即可;
而不同的napi设备的私有队列是不同的,则其接收处理函数就应该不同。
下面分析内核的实现流程。
首先看sftnet_data的定义,
struct softnet_data {
struct Qdisc *output_queue;
struct sk_buff_head input_pkt_queue;//不使用napi设备存储入口数据的队列
struct list_head poll_list;//napi链表,将所有需要软中断的napi设备链接在一起
struct sk_buff *completion_queue;//对于已不需要的数据,可以移到该队列中,被释放调用
struct napi_struct backlog;//不使用napi设备的入口数据处理函数。
};
该结构体兼容了napi与netiif,对于napi设备,需要将napi设备链接到poll_list中;对于不支持napi的设备,可以用input_pkt_queue与backlog实现入口数据的存储与处理。
对于napi设备,其对应的数据结构体如下:
struct napi_struct {
/* The poll_list must only be managed by the entity which
* changes the state of the NAPI_STATE_SCHED bit. This means
* whoever atomically sets that bit can add this napi_struct
* to the per-cpu poll_list, and whoever clears that bit
* can remove from the list right before clearing the bit.
*/
struct list_head poll_list;//链接到softnet_data->poll_list
unsigned long state;
int weight;//一次可以从队列中取出的数据个数
int (*poll)(struct napi_struct *, int);//poll处理函数,负责从队列中取出数据,并调用函数netif_receive_skb将数据交给协议栈进行处理
#ifdef CONFIG_NETPOLL
spinlock_t poll_lock;
int poll_owner;
#endif
unsigned int gro_count;
struct net_device *dev;//对应的网络设备
struct list_head dev_list;//将该结构体链接到网络设备的list链表中。
struct sk_buff *gro_list;
struct sk_buff *skb;
};
通过poll_list就实现了 将该napi结构与softnet_data的关联。
三、实现及调用
1、napi
对于napi设备,可以通过调用netif_napi_add实现napi结构的初始化以及与设备的关联。而调用napi_schedule,实现将napi添加到当前cpu对应的softnet_data->poll_list链表中,然后置位NET_RX_SOFTIRQ,实现软中断的调用。
对于每一个napi设备,如果使用软中断,则需要调用以上两个函数
2、netif_rx
对于netif_rx,由于其是统一的处理,所以该机制已经默认写入到dev的初始化函数中,在net_dev_init初始化函数中,对于每一个cpu,会执行以下代码段
for_each_possible_cpu(i) {
struct softnet_data *queue;
queue = &per_cpu(softnet_data, i);
skb_queue_head_init(&queue->input_pkt_queue);//入口队列初始化,
queue->completion_queue = NULL;
INIT_LIST_HEAD(&queue->poll_list);//napi链表初始化
/*netif_rx的napi结构初始化*/
queue->backlog.poll = process_backlog;//netif_rx入口队列的处理函数。
queue->backlog.weight = weight_p;
queue->backlog.gro_list = NULL;
queue->backlog.gro_count = 0;
}
关于netif_rx的调用,如果想使用netif_rx的napi机制时,只需要将softnet_data->backlog添加到链表softnet_data->poll_list中,并置位NET_RX_SOFTIRQ,实现软中断的调用即可,所以其调用函数依然是napi_schedule。
至此,完成napi 与netif_rx的分析