操作系统必须有一个快速、异步、简单的机制负责对硬件作出迅速响应并完成那些时间要求很严格的操作,然后对其他对时间要求相对宽松的任务,应该推后到中断被激活以后再去运行,因此整个中断处理流程分为了两个部分。
目的是:缩短中断被屏蔽的时间,提升系统的响应能力和性能。
下半部的方法
- 软中断
- tasklet
- 工作队列
软中断
软中断是在编译器静态分配的,由softirq_action结构表示,定义再linux/interrupt.h中
kernel/softirq.c中定义了一个包含32个该结构体的数组
每个被注册的软中断都占据该数组的一项,因此最多可能有32个软中断,当前版本的内核中,用到了10个
软中断流程.png
tasklet
tasklet也是利用软中断来实现的,但是提供了比软中断更好用的接口
tasklet对应的结构体:
struct tasklet_struct
{
struct tasklet_struct *next; /* 链表中的下一个tasklet */
unsigned long state; /* tasklet状态 */
atomic_t count; /* 引用计数器 */
void (*func)(unsigned long); /* tasklet处理函数 */
unsigned long data; /* tasklet处理函数的参数 */
};
tasklet状态只有3种值:
- 值 0 表示该tasklet没有被调度
- 值 TASKLET_STATE_SCHED 表示该tasklet已经被调度
- 值 TASKLET_STATE_RUN 表示该tasklet已经运行
引用计数器count 的值不为0,表示该tasklet被禁止。
tasklet使用流程如下
- 声明tasklet (参见<linux/interrupt.h>)
/* 静态声明一个tasklet */
#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 传递一个tasklet_struct指针给初始化函数 */
extern void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
- 编写处理程序
参照tasklet处理函数的原型来写自己的处理逻辑
void tasklet_handler(unsigned long date)
- 调度tasklet
中断的上半部处理完后调度tasklet,在适当时候进行下半部的处理
tasklet_schedule(&my_tasklet) /* my_tasklet就是之前声明的tasklet_struct */
工作队列
工作队列子系统是一个用于创建内核线程的接口,通过它可以创建一个工作者线程来专门处理中断的下半部工作。
工作队列和tasklet不一样,不是基于软中断来实现的。工作队列允许重新调度甚至睡眠。
缺省的工作者线程名称是 events/n (n对应处理器号)。
了解3个结构体:
/* 在 include/linux/workqueue.h 文件中定义 */
struct work_struct {
atomic_long_t data; /* 这个并不是处理函数的参数,而是表示此work是否pending等状态的flag */
#define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
struct list_head entry; /* 中断下半部处理函数的链表 */
work_func_t func; /* 处理中断下半部工作的函数 */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
/* 在 kernel/workqueue.c文件中定义
* 每个工作者线程对应一个 cpu_workqueue_struct ,其中包含要处理的工作的链表
* (即 work_struct 的链表,当此链表不空时,唤醒工作者线程来进行处理)
*/
/*
* The per-CPU workqueue (if single thread, we always use the first
* possible cpu).
*/
struct cpu_workqueue_struct {
spinlock_t lock; /* 锁保护这种结构 */
struct list_head worklist; /* 工作队列头节点 */
wait_queue_head_t more_work;
struct work_struct *current_work;
struct workqueue_struct *wq; /* 关联工作队列结构 */
struct task_struct *thread; /* 关联线程 */
} ____cacheline_aligned;
/* 也是在 kernel/workqueue.c 文件中定义的
* 每个 workqueue_struct 表示一种工作者类型,系统默认的就是 events 工作者类型
* 每个工作者类型一般对应n个工作者线程,n就是处理器的个数
*/
/*
* The externally visible workqueue abstraction is an array of
* per-CPU workqueues:
*/
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq; /* 工作者线程 */
struct list_head list;
const char *name;
int singlethread;
int freezeable; /* Freeze threads during suspend */
int rt;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
work、工作队列、工作者线程之间的关系.jpg
使用工作者队列的流程:
工作队列执行流程.png
下半部机制的选择
下半部 | 上下文 | 顺序执行保障 |
---|---|---|
软中断 | 中断 | 没有 |
tasklet | 中断 | 同类型不能同时执行 |
|工作队列|进程|没有(和进程上下文一样被调度) |