设备与内核(可理解为内核中的驱动程序)交换数据的两种方式:
轮询:
中断:
当接收一个帧时,设备产生一个硬件中断通知内核,然后驱动程序将该帧拷贝到内核可以访问的输入队列,之后,内核将该帧传给相关协议(如IP)专用的处理函数。
中断处理函数分为上半部(top handler)和下半部(bottom handler),上半部时需要立即处理的一些事,而下半部不是特别紧迫。这么做是由于中断是非可抢占的,且在中断期间关闭了中断请求,但不是整个中断期间都是如此。因此,为了尽可能地提高性能,将中断处理过程分为上下两部分,上部分不可抢占,关闭中断,下半部分则不需要如此。
内核2.2的下半部函数:
下半部函数被划分为一大群类型:
enum{
...
NET_BH;//网络相关
...
}
每种类型都通过init_bh而关联一个下半部处理函数。每当中断处理函数想触发下半部执行时,调用mark_bn标记一下。内核检查到这个标记后,就会执行函数do_bottom_half来执行下半部函数。
内核2.4之后的下半部函数引入了软IRQ,可以看作是多线程版的下半部函数。
新的软IRQ有6种类型:
enum{
HI_SOFTIRQ;//用于实现高优先级的微任务
HT_SOFTIRQ;//旧版本的下半部类型都以HT_SOFTIRQ重新实现
NET_TX_SOFTIRQ;//网络代码使用
NET_RX_SOFTIRQ;//网络代码使用
SCSI_SOFTIRQ;
TASKLET_SOFTIRQ;//用于实现低优先级的微任务
}
每种软IRQ都维护一个softnet_data结构数组,而每个cpu都有一个softnet_data数据来存储当前软IRQ的信息。调用open_softirq来注册软irq函数。为了执行软IRQ,需要_ _raise_softirq_irqoff函数来标记一下。
内核2.2的多数下半部函数都已经转化为软IRQ或微任务,微任务是函数,可以延迟中断或其他任务。微任务建立在软IRQ之上,通常由中断处理函数发出。
下面介绍一下softnet_data结构:
每个cpu都有队列,用于处理接收的帧。该队列的数据结构就是softnet_data。
struct softnet_data{
int throttle;//bool值,表示cpu是否窒息,通常可以理解为队列是否已满
int cng_level;//拥塞等级
int avg_blog;//平均队列长度。以上三个参数有拥塞管理算法使用
struct sk_buff_head input_pkt_queue;//该队列用来保存进来的帧(被驱动处理前)
struct list_head poll_list;//双向链表
struct net_device *output_queue;//设备列表,其中的列表有数据要传输
struct sk_buff *completion_queue;//缓冲区列表,其中的数据已经传输,可以释放掉
sruct net_device backlog_dev;//表示该设备已在相关联的cpu上为net_rx_action调度以准备执行。
}
吐槽一下:本章关于中断里的下半部函数,软IRQ,微任务的诸多细节我都没有记录(偷懒了,反正我又不搞驱动开发)。要是完整记录感觉恨不得把整章都抄下来,还是太菜了,理解不够,不会提炼。总之,中断处理分为上个两个部分,上半部分是必须即使处理的(如将帧拷贝到sk_buff等),且处理过程中无法被中断,cpu无法被抢占,而下半部分则没有这些要求,为了进一步的改进下半部处理过程,又出现了软IRQ和微任务。