系统的中断资源是有限的,所以中断资源比较宝贵,在处理中断程序应该尽可能的快,不然会影响系统对其他设备响应,应为硬件中断不能打断的。网络设备接受数据包后就会产生中断通知CPU处理。一个数据包的处理过程比较漫长,不占用中断资源,网络设备的中断分为两部分
硬件中断处于中断上半场,以下时硬件中断的注册函数。
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
中断request_irq函数主要时定义中断处理函数。下面以e1000为例讲述硬件中断的注册流程:
static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
-->netdev->netdev_ops = &e1000_netdev_ops
-->e1000_open(struct net_device *netdev)
-->e1000_request_irq(struct e1000_adapter *adapter)
-->request_irq(unsigned int irq, irq_handler_t handler, unsigned
long flags,const char *name, void *dev)
硬件中断时在网卡驱动初始化的时候就定义好了,网卡驱动的初始化函数名称一般时xxx_probe函数,这个函数会初始化网卡的一些操作函数,比如打开网卡 网卡的发包函数等等,e1000的硬件初始化在open函数中定义。
系统监测是否有数据包的方式有三种:轮询 中断 napi
轮询处理:
以前的网络收报的流程时cpu会定期检查是否有数据包过来,如果有数据包过来就处理。当网络流量很少的时候,cpu定期检查有点浪费资源。流量很大的时候没影响。
中断方式:
中断方式就是来了一个数据包就产生一个中断通知系统去处理,这种方式可以高效快速响应数据包处理。但是当网络流量很频繁的时候,就会频繁产生大量的中断,大量中断也会消耗系统资源。
napi方式:
napi时轮询和中断方式的结合,当有数据包过来产生中断通知系统去处理,系统会一直处理直到没有数据包为止。在处理过程中不会在产生中断,可以避免大流量频繁中断处理,没有流量也就不会做什么,可以避免轮询白白消耗系统资源。
每个cpu都有自己的队列来管理输入数据帧,所以每个cpu都有自己的数据结构来管理网络数据的输入和输出流量,这样对数据的操作就不要加锁了。管理CPU队列的数据结构为struct sofnet_data.
struct softnet_data {
struct Qdisc *output_queue;
struct Qdisc **output_queue_tailp;
struct list_head poll_list;
struct sk_buff *completion_queue;
struct sk_buff_head process_queue;
......
unsigned int dropped;
struct sk_buff_head input_pkt_queue;
struct napi_struct backlog;
#ifdef CONFIG_NET_FLOW_LIMIT
struct sd_flow_limit __rcu *flow_limit;
#endif
};
output_queue:
网络输出帧放在专门的流量管理数据结构中,由流量控制子系统处理,不由软件中断和struct sofnet_data数据结构管理,但事后软件中断会负责清除已发送的socket Buffer,out_put_queue时流量控制子系统用于专门管理网络设备输出队列的数据结构。
input_pkt_queue:
每个CPU的输入队列,该队列是存放网络输入数据帧Socket Buffer 的链表,input_pkt_queue是输入队列链表的起始地址。
poll_list:
网络设备列表,在该链表中的网络设备是以NAPI方式结束网络数据的设备,或者是输入队列的处理函数。对于NAPI设备在自己的驱动程序中实现了poll函数,用来从网络设备接口中读入数据帧。他们的poll函数会在内核的调度网络子系统过的接受软件中断(NET_RX_SOFTIRQ)处理程序net_rx_action被调度的时候执行。poll函数的主要任务是将设备硬件缓冲区中的数据帧复制到内核地址空间,并放到CPU输入队列中。
completoin_queue:
这是Socket Buffer的列表,这些套接字缓冲区中的数据已经被成功发送或接受了,缓冲区可以释放了,对缓冲区的是否过程会在网络子系统发送软件中断(NET_TX_SOFTIRQ)的处理程序net_tx_action中进行。
Backlog:
NAPI的poll函数在软中断中处理负责将数据帧从硬件缓存区拷贝到内核空间,并添加到cpu输入队列。同时软中断还要处理从cpu输入队列中取出数据包交给上层协议栈处理。软中断net_rx_action函数会遍历poll_list链表中的处理函数。为了兼容这两者,此处设置为struct napi_struct结构,里面的poll函数初始化为process_baklog函数。process_backlog函数的主要任务是从cpu输入队列input_pkt_queue中取出数据帧交给上层协议栈处理。
static int __init net_dev_init(void)
{
.....
for_each_possible_cpu(i) { //遍历每一个cpu
//获取cpu的struct softnet_data结构
struct softnet_data *sd = &per_cpu(softnet_data, i);
//初始化数据帧输入队列
skb_queue_head_init(&sd->input_pkt_queue);
skb_queue_head_init(&sd->process_queue);
//初始化poll_list链表
INIT_LIST_HEAD(&sd->poll_list);
sd->output_queue_tailp = &sd->output_queue;
#ifdef CONFIG_RPS
sd->csd.func = rps_trigger_softirq;
sd->csd.info = sd;
sd->cpu = i;
#endif
//初始化数据帧出队处理函数process_backlog
sd->backlog.poll = process_backlog;
//process_backlog一次最多处理多少个数据帧,超过这值就退出
sd->backlog.weight = weight_p;
}
.....
}
每个CPU输入队列input_pkt_queue的最大长度由变量netdev_max_backlog的值定义,默认值是1000,即输入队列中最多可以容纳1000个数据帧,内核将该值通过proc文件系统,在/proc/sys/net/core目录下面的netdev_max_backlog文件中输出,用户也可以修改该值。
还有其他的一些限制:
int netdev_budget __read_mostly = 300 //软件中断一次可以处理最大数据帧数
int weight_p __read_mostly = 64 //poll函数可以从设备读取的数据帧数