中断与异常(四)
这节主要说软中断,书中注释相对少点,理解困难点,后面有些问题需要大家帮忙,感激不尽。
2.4以前的内核采用bh(bottom half)机制,来避免中断关闭时间太长而丢失重要中断。
因为bh机制实际上是调用bh_base[32]中的某一项来完成,所以后文用bh_base来代替。
由于bh_base设计的是单cpu不可重入,在SMP系统中,即使其他CPU空闲也无法执行bh_base函数,造成瓶颈,因而引入了新的框架----“软中断”。
机制分层“软中断”-----“tasklet”-----“bh_base”。Bh_base设计最简单,因为不需要考虑多cpu,重入问题,而tasklet可以多cpu执行,不可重入,软中断则是多cpu,可重入。
2.4以后的linux则是通过一层层的封装来用软中断实现bh_base,这是比较麻烦的,看了好久。
从最上层的软中断说起
内核定义四种软中断
enum { HI_SOFTIRQ=0, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, TASKLET_SOFTIRQ };
软中断向量
struct softirq_action
{
void (*action)(struct softirq_action *); |
这就比较蛋疼了,结构体中的函数的参数就是当前的结构体的指针,因为书中所举例子没用这个参数,无法感觉这么设计的精妙之处啊,而且造成了理解障碍 |
void *data; |
通常是软中断号 |
};
软中断向量表softirq_vec
static struct softirq_action softirq_vec[32] __cacheline_aligned;
全局,32项,虽然内核只用了4项,还不推荐分配新的软中断
软中断控制/状态表irq_stat[NR_CPUS]
为了能多cpu处理软中断,所以每个cpu需要记录自己的硬中断、软中断个数、状态,软中断队列等信息,是一个irq_cpustat_t结构体数组
/* assembly code in softirq.h is sensitive to the offsets of these fields */ typedef struct { unsigned int __softirq_pending; unsigned int __local_irq_count; unsigned int __local_bh_count; unsigned int __syscall_count; struct task_struct * __ksoftirqd_task; /* waitqueue is too large */ unsigned int __nmi_count; /* arch dependent */ } ____cacheline_aligned irq_cpustat_t;
多cpu同时执行不同的tasklet就靠其中的一些相关数据来管理
多层封装实现
tasklet封装bh_base
因为tasklet可以允许多个cpu执行不同的tasklet,而bh_base有32个,同一时间允许一个cpu执行一个bh_base,所以内核新建了bh_task_vec[32]数组来进行封装。
来看看是怎么利用tasklet_struct架构巧妙的处理的。结构体定义
struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data; };
在初始化bh_task_vec[32]的过程中,将bh_action和i绑定到对应的bh_task_vec[i]上就完成了封装。
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data) { t->next = NULL; t->state = 0; atomic_set(&t->count, 0); t->func = func; t->data = data; } void __init softirq_init() { int i; for (i=0; i<32; i++) tasklet_init(bh_task_vec+i, bh_action, i); open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); }
此处将bh_task_vec[i]与bh_action、i绑定到了一起,如果你经验丰富也许就会发现,bh_action拿到了这个i不就可以去执行bh_base[i],linux确实是这样做的。
软中断封装tasklet
最终linux是希望所有的bottom half在新的软中断框架下运行的,所以还要进行一次封装。
linux将bh_task_vec封装到了0号软中断线上,也就是HI_SOFTIRQ,其主要实现的就是将软中断请求调度到一个可执行cpu上,让这个cpu去执行这个最本质的bn_base[i]。
这就是软中断的精髓,将软中断任务进行调度,因为不同的cpu有不同的软中断队列,自然就形成了多cpu同时执行软中断,这是软中断的最理想形态,但tasklet设计成不可重入,只需在tasklet进行调度之前进行判断,合法才进行调度,而bh_base则在执行之前加上全局锁,这样就很好的满足了多样化的需求。这些设计确实厉害,不得不服。
来看具体实现代码,从上一节硬中断调用do_softirq()说起
为了使do_irq()能够执行一个特定的bh_base函数,需要先申请0号软中断线,申请软中断线和软中断调度任务的代码是多cpu,可重入的,这就使得某个cpu申请软中断,其他cpu执行,软中断的基本框架就有了。
申请执行bh_base[nr]函数
回顾上面的do_irq()中0号软中断线执行h->action(h),实际上执行的是绑定在0号软中断线上的服务例程tasklet_hi_action
最后一个函数完成最终bh_base[nr]调用