《Linux内核的设计与实现》 第8章
8.1 下半部
8.1 划分上下半部的原因:
上半部,即中断处理程序,对时间反应迅速,及时响应;
下半部,具体处理对硬件中断反应需要做的事情,强调的是做更多的事情,是一种延后处理的机制。
8.1.2 下半部四种实现策略
分别是软中断、tasklet、工作队列workqueue、一个普通线程 (忽略BH,任务队列task queues)
8.2 软中断
软中断的实现 kernel/softirq.c
struct softirq_action {
void (*action)(struct softirq_action *);
};
系统中最多注册32个软中断:
static struct softirq_action softirq_vec[32]; //kernel/softirq.c
步骤:
1.注册一个软中断 //分配索引,设置优先级
2.写一个软中断处理程序softirq_handler
3.执行软中断do_softirq() //do_softirq的原理是检测当前pending位,不断判断移位,执行数组内softirq_action的已经注册的action函数。
软中断使用场景及原因:
对时间严格并且程序员能高效加锁处理同步,常用的地方是网络子系统。其他地方用的很少。
原因:软中断能响应中断,但不能休眠,也就是不能调度。缺点是多处理器时,一个cpu禁止了软中断,另一个cpu依旧可以触发软中断,有同步的问题,需要考虑加锁的问题。
8.3 tasklet
tasklet是基于软中断实现的一种机制,对锁的要求比较低。
优先级:HI_SOFTIRQ优先TASKLET_SOFTIRQ执行。
结构体:
struct tasklet_struct {
struct tasklet_struct *next; //链表连起来
unsigned long state; //当前状态,三种
atomic_t count; //计数器
void (*func)(unsigned long); //处理函数
unsigned long data; //传给处理函数的参数
};
state: 0, TASKLET_STATE_SCHED、TASKLET_STATE_RUN
count: 为0时,tasklet才被激活
//从这里可以看出tasklet的优点,它可以被调度(被打断的意思,有上下文保存与恢复)。
调度:
tasklet_schedule(), tasklet_hi_schedule(), 传入一个tasklet_struct指针。
调度实现步骤:
1.检查结构体state是否为TASKLET_STATE_SCHED,若是,则说明已经被调度了。
2.调用_tasklet_schedule() //封装了一层
3.保存中断状态,禁止本地中断。 //原理是softirq,呼应上文softirq可以在多处理上同时运行的缺点。禁止中断也是一种同步机制
4.tasklet加到vec链表表头。 //代表已经处理过
5.唤起TASKLET_SOFTIRQ或HI_SOFTIRQ软中断,下次执行do_softirq,执行该tasklet的处理函数。
6.恢复中断状态
总结:
这里很清晰看到tasklet是基于softirq实现的,就是在softirq的基础上加上同步机制,保存并恢复中断状态。
另一个感悟就是,为什么书上说基本都建议使用tasklet,因为tasklet就是带同步机制的softirq,就不需要程序员处理锁机制。
还有就是网络子系统,它是不要同步机制的,处理次数过多,会浪费很多时间,所以它要的是一种相对原始简单的结构,所以网络子系统选择的是softirq。
8.3.2 使用tasklet
1.声明tasklet
DECLARE_TASKLET(name, func, data); //此处都为静态创建
DECLARE_TASKLET_DISABLED(name, func, data);
例子:
DECLARE_TASKLET(my_tasklet, my_tasklet_handler, dev) ;
等价于
struct tasklet_struct my_tasklet = {NULL, 0, AOTMOCI_INIT{0}, my_tasklet_handler, dev}
动态创建:
tasklet_init(t, tasklet_handler, dev); //程序运行时把 tasklet 传给 t
2.写处理函数handler
void tasklet_handler(unsigned long data);
3.调度自己的tasklet
tasklet_schedule(&my_tasklet); //挂起tasklet, 有机会它自己会运行
8.4 工作队列
工作队列把下半部推后,交由一个内核线程去执行,在进程上下文中执行,所以可以重新调度和睡眠。
softirq: 无锁机制,不能调度,不能睡眠。
tasklet: 有锁机制,能调度,不能睡眠
工作队列:无锁机制,能调度,能睡眠
//这里书上也说了,工作队列可以用内核线程代替
8.4.2 使用工作队列
1.创建推后的工作 //想法:用宏函数,快速填写参数
//静态创建一个名为name, 处理函数为func, 参数为data的work_struct
DECLARE_WORK(name, void(*func)(void*), void* data);
//动态创建
INIT_WORK(struct work_struct *work, void(*func)(*void), void *data);
2.工作队列处理函数
void work_handler(void *data)
//书上说这个函数由一个工作者线程处理,运行在进程上下文,不持有锁,可以睡眠。
//
3.对工作队列进行调度
schedule_work(&work); //调度了不一定立马运行,只有其处理器上的工作者线程被唤醒,才会执行
schedule_delay_work(&work, delay); //调度,延迟执行
4.刷新操作
void flush_scheduled_work(void);
用于不确定队列中任务是否处理完了,刷新一下,直到所有对象被执行完才返回,用于卸载之前。(没执行完,表明会休眠)
5.取消延迟操作
int cancle_delayed_work(struct work_struct *work);
6.自己创建新的工作队列
struct workqueue_struct *create_workqueue(const char *name);
创建events队列:
struct workqueue_struct *keventd_wq = create_workqueue("events");
创建后自己调度: (传入第一个参数)
int queue_work(struct workqueue_struct *wq, struct_struct *work);
int queue_delay_work(struct workqueue_struct *wq, struct work_struct *work, unsigned long delay);
flush_workqueue(struct workqueue_struct *wq);
-----------------------------------------------------------------------------------------------------------------------------