分类: LINUX
2014-10-16 16:58:41
(1)、对报文的处理都是放在软中断中处理。
(2)、两者都有存储报文的队列,NAPI的队列是由网卡驱动来管理的(一般是有网卡DMA到内存中,网卡驱动管理着存储报文的队列),而旧接口的队列是由Linux内核管理的,由网卡驱动负责往队列里加报文。
每个NAPI设备都有一个轮询函数来由收包软中断调用,来进行轮询处理报文。我们可以建立一个虚拟的NAPI设备(backlog),让她的轮询函数来轮询的处理旧接口的报文队列。收包软中断处理函数只是进行调用相应NAPI的轮询函数进行处理,并不关心是虚拟NAPI还是非虚拟NAPI。具体轮询细节由相关NAPI的轮询函数自己实现。这样旧接口和NAPI就可以很好的融合在一起来实现了。
如上所述,我们现在可以知道每个CPU上需要有如下几个元素:
1、一个报文的接收队列,由旧接口来使用。
2、一个虚拟的NAPI设备,来有Linux协议栈自己创建并处理旧接口使用的队列中的报文。
3、一个NAPI的链表,上面挂着有报文需要处理的NAPI设备,由收包软中断来遍历该链表,顺序执行每个NAPI的轮询函数。
Linux内核具体实现:
结构体定义如下
structsoftnet_data
{
struct sk_buff_head input_pkt_queue;//旧接口的输入队列
struct list_head poll_list;//有需要处理报文的NAPI设备链表
struct napi_struct backlog;//虚拟的NAPI设备 backlog
};
定义一个per_cpu变量 softnet_data
DECLARE_PER_CPU(struct softnet_data,softnet_data);
softnet_data 的初始化如下:
static int __init net_dev_init(void)
{
int i, rc = -ENOMEM;
/*
* Initialise the packet receive queues.
*/
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;
/*初始化pool 链表头*/
INIT_LIST_HEAD(&queue->poll_list);
/*把backlog的轮询函数初始化为 process_backlog
该函数来处理传统接口使用的输入队列的报文*/
queue->backlog.poll = process_backlog;
/*backlog 轮询函数一次可以处理的报文上限个数*/
queue->backlog.weight = weight_p;
queue->backlog.gro_list = NULL;
queue->backlog.gro_count = 0;
}
}
NAPI的数据结构:
每个NAPI需要如下几个元素:
1、一个轮询函数,来轮询处理报文。
2、需要有一个状态,来标识NAPI的调度状态,是正在被调度还是没有被调度。
3、每个NAPI的一次轮询能处理的报文个数应该有一个上限,不能无限制的处理下去,防止独占CPU资源导致其他进程被饿死。
4、每个NAPI应该和一个网络设备进行关联。
5、NAPI设计时考虑了多队列的情况。一个网络设备可以有多个报文队列,这样一个网络设备可以关联多个NAPI,每个NAPI只处理特定的队列的报文。这样在多核情况下,多个CPU核可以并行的进行报文的处理。一般专用的网络多核处理器存在这种情况。
6、每个NAPI在有报文的情况下应该挂到softnet_data的pool_list上去。
如上所述,NAPI的结构体定义如下
struct napi_struct
{
struct list_head poll_list;//挂到softnet_data的pool_list上
unsigned long state;//NAPI的调度状态
int weight;//一次轮询的最大处理报文数
int (*poll)(struct napi_struct *, int);//轮询函数
struct net_device *dev;//指向关联的网络设备
struct list_head dev_list;//对应的网络设备上关联的NAPI链表节点
/*其他字段是gso功能用,这里先不讨论*/
};
NAPI的调度状态:
NAPI_STATE_SCHED设置时表示该NAPI有报文 需要接收。即把NAPI挂到softnet_data 时要设置该状态,处理完从softnet_data 上摘除该NAPI时要清除该状态。
一些NAPI的函数详解:
1、netif_napi_add(),把网络设备net_device 和NAPI结构相绑定。
void netif_napi_add(struct net_device *dev,
struct napi_struct *napi,
int (*poll)(struct napi_struct *, int), int weight)
{
INIT_LIST_HEAD(&napi->poll_list);
napi->poll = poll;
napi->weight = weight;
/*把NAPI加入到网络设备相关联的NAPI链表上去。*/
list_add(&napi->dev_list, &dev->napi_list);
napi->dev = dev;
/*绑定时设置NAPI是已调度状态,禁用该NAPI,以后手动的来清除该标识来使
能NAPI.*/
set_bit(NAPI_STATE_SCHED, &napi->state);
}
2、使能和禁用NAPI:
static inline void napi_disable(struct napi_struct *n)
{
/*先设置NAPI状态为DISABLE*/
set_bit(NAPI_STATE_DISABLE, &n->state);
/*循环的等待NAPI被调度完成,变成可用的,设置成SCHED状态*/
while (test_and_set_bit(NAPI_STATE_SCHED, &n->state))
msleep(1);
/*清除DISABLE状态*/
clear_bit(NAPI_STATE_DISABLE, &n->state);
}
3、调度NAPI