第八章 中断处理下半部

中断下半部的处理

因为中断对时限的要求,也不能阻塞,因此整个中断处理流程被分成两部分,上半部分执行对时限要求高的、不能被中断的任务,很多能推迟执行的工作则被放在下半部分。

1. 下半部

下半部执行与中断处理密切相关但中断处理程序本身不执行的工作。中断处理程序会异步执行,并且在最好的情况下也会锁定当前的中断线。对上半部和下半部的划分,有一些提示:

  • 如果一个任务对时间非常敏感,将其放入中断处理程序
  • 如果一个任务和硬件相关,将其放入中断处理程序
  • 如果一个任务要保证不被其他中断(特别是相同中断)打断,将其放入中断处理程序
  • 其他任务考虑放入下半部执行

内核2.6以后,提供三种下半部实现机制:软中断(softirq)、tasklets、工作队列(work queues)

2.软中断

软中断的实现

tasklet是通过软中断实现的,首先了解软中断,相关代码在:kernel/softirq.c

软中断是在编译期间静态分配的,它不像tasklet可以动态注册或取消。软中断由结构softirq_action表示,定义在:linux/interrupt.h,一个软中断不会抢占另一个软中断。

1
2
3
4
struct softirq_action
{
void (*action)(struct softirq_action *);
};

kernel/softirq.c定义了包含该结构的数组

1
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

  • 1.软中断处理程序:软中断处理程序action的函数原型为:

    1
    void softirq_handler(struct softirq_action *)
  • 2.执行软中断:一个注册的软中断必须被标记,这被称为触发软中断(raising the softirq),通常中断处理程序会在返回前标记它的软中断,使其在稍后被执行,而软中断都在do_softirq()中执行。在下列地方,带处理软中断会被检查和执行:

    • 从一个硬件中断代码处返回时
    • 在ksoftirqd内核线程中
    • 在那些显式检查和执行待处理软中断的代码中,如网络子系统
使用软中断

目前只有网络和SCSI直接使用软中断,内核定时器和tasklet都是建立在软中断上。

  • 1.分配索引
    linux/interrupt.h的一个枚举类型静态声明软中断,内核用这些从0开始的索引表示一种相对优先级。

已有的tasklet类型

tasklet 优先级 描述
HI_SOFTIRQ 0 优先级高的tasklets
TIMER_SOFTIRQ 1 定时器的下半部
NET_TX_SOFTIRQ 2 发送网络数据包
NET_RX_SOFTIRQ 3 接收网络数据包
BLOCK_SOFTIRQ 4 BLOCK装置
TASKLET_SOFTIRQ 5 正常优先级的tasklets
SCHED_SOFTIRQ 6 调度程度
HRTIMER_SOFTIRQ 7 高分辨率定时器
RCU_SOFTIRQ 8 RCU锁定
  • 2.注册处理程序
    注册软中断处理程序通过调用open_softirq()完成,该函数两个参数:软中断索引号和处理函数
    1
    2
    3
    // 网络的例子
    open_softirq(NET_TX_SOFTIRQ, net_tx_action);
    open_softirq(NET_RX_SOFTIRQ, net_rx_action);

软中断处理程序执行时允许响应中断,但它自己不能休眠。

  • 3.触发软中断
    raise_softirq()函数挂起一个软中断,让它在下次调用do_softirq()函数时投入运行,该函数在触发一个软中断之前先禁止中断,触发后再恢复原来状态。
    1
    2
    3
    rasie_softirq(NET_TX_SOFTIRQ);
    //中断已经被禁止
    raise_softirq_irqoff(NET_TX_SOFTIRQ);

3. tasklet

tasklet是利用软中断实现的一种下半部机制,tasklet用途更广泛,通常应该使用它。

  • 1.tasklet实现
    tasklet由两种软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ,它们只有优先级的区别。tasklet由tasklet_struct结构表示:

    1
    2
    3
    4
    5
    6
    7
    8
    struct tasklet_struct
    {
    struct tasklet_struct *next; /* 链表中的下一个tasklet */
    unsigned long state; /* tasklet的状态 */
    atomic_t count; /* 引用计数器 */
    void (*func)(unsigned long); /* tasklet处理函数 */
    unsigned long data; /* 给tasklet处理函数的参数 */
    };
  • 2.调度tasklet
    已调度的tasklet存放在两个单处理器数据结构:tasklet_vet(普通tasklet)和tasklet_hi_vec(高优先级的tasklet),它们是由tasklet_struct结构体构成的链表。tasklet由tasklet_schedule()和tasklet_hi_schedule()函数进行调度。

  • 3.使用tasklet

    • 声明自己的tasklet,使用这两个
1
2
3
4
5
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
  • 编写自己的tasklet处理程序

    1
    void tasklet_handler(unsigned long data);
  • 调度自己的tasklet

1
2
3
4
5
6
tasklet_schedule(&my_tasklet);
tasklet_disable(&my_tasklet);
tasklet_enable(&my_tasklet);
tasklet_kill((=&my_tasklet);
  • ksoftirqd:辅助处理软中断的内核线程

4. 工作队列

工作队列可以将工作推后,交由一个内核线程去执行,因此这个下半部是执行在进程上下文的,它可以执行需要睡眠的任务:如获取大量内存、获取信号量、执行阻塞式I/O操作。
工作队列最基本的表现形式就是创建特定的通用线程来执行需要推后的任务,这种线程称为工作者线程,一个工作队列的缺省工作者线程叫做events/n,n是处理器编号,每个处理器对应一个线程。

工作线程用workqueue_struct表示,定义在kernel/workqueue.c

下半部比较

下半部 上下文 顺序执行保障
软中断 中断 没有
tasklet 中断 同类型不能同时执行
工作队列 进程 没有