中断与异常(四)

这节主要说软中断,书中注释相对少点,理解困难点,后面有些问题需要大家帮忙,感激不尽。

2.4以前的内核采用bhbottom 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_base32个,同一时间允许一个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_actioni绑定到对应的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_actioni绑定到了一起,如果你经验丰富也许就会发现,bh_action拿到了这个i不就可以去执行bh_base[i]linux确实是这样做的。

软中断封装tasklet

最终linux是希望所有的bottom half在新的软中断框架下运行的,所以还要进行一次封装。

linuxbh_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]调用

原文地址:https://www.cnblogs.com/hmxb/p/4906001.html