为什么中断分为上半部和下半部
1.
中断处理程序以异步方式执行并且它有可能会打断其他重要代码(甚至包括其他中断处理程序)的执行。因此,为了避免被打断的代码停止时间过长,中断处理程序应该执行得越快越好。
2.
如果当前有一个中断处理程序正在执行,在最好的情况下,与该中断同级的其他的同级中断会被屏蔽,在最坏的情况下,当前处理器上所有其他中断都会被屏蔽。因此,任然应该让他们执行得越快越好。
3.
由于中断处理程序往往需要对硬件进行操作,所以他们通常有很高的时限要求。
4.
中断处理程序不在进程上下文运行,所以他们不能阻塞。这限制了他们所做的事情。
人为把中断划分上下半部,
因为中断处理时间过长,
关中断,
占用
CPU。
类似 tcp
通信编程,服务器端程序多线程。
1.
监听-
accept 2.
处理-read
上半部:
接收中断,登记工作。
下半部:处理程序挂到发送中断请求的下半部来处理。
中断下半部实现有 2 种
1.tasklet 小任务
用软中断来实现是一种特殊运行模式。用内核线程
ksoftirqd。
中断上下文不能休眠,执行优先级非常高,适合很快执行完。
root@SOM-RK3399v2:/drv_code# ps -aux | grep ksoftirqdroot 3 0.0 0.0 0 0 ? S 08:44 0:00 [ksoftirqd/0]root 13 0.0 0.0 0 0 ? S 08:44 0:00 [ksoftirqd/1]root 18 0.0 0.0 0 0 ? S 08:44 0:00 [ksoftirqd/2]root 23 0.0 0.0 0 0 ? S 08:44 0:00 [ksoftirqd/3]root 28 0.0 0.0 0 0 ? S 08:44 0:00 [ksoftirqd/4]root 33 0.0 0.0 0 0 ? S 08:44 0:00 [ksoftirqd/5]
Tasklet 的实现
定义一个
tasklet
结构
struct tasklet_struct task;
定义一个处理函数
void my_tasklet_func(unsigned long data); //
下半部做的事
定义一个初始化函数
tasklet_init(&task,task_fun,data); //
将处理函数和
tasklet
对象绑定
调度
tasklet
执行
tasklet_schedule(&task);//
调度
2.workqueue 工作队列
交给一个内核线程
kworker
执行。
进程上下文中,可休眠。
root@SOM-RK3399v2:/drv_code# ps -aux | grep kworkerroot 5 0.0 0.0 0 0 ? S< 08:44 0:00 [kworker/0:0H]root 6 0.0 0.0 0 0 ? S 08:44 0:00 [kworker/u12:0]root 15 0.0 0.0 0 0 ? S< 08:44 0:00 [kworker/1:0H]root 20 0.0 0.0 0 0 ? S< 08:44 0:00 [kworker/2:0H]root 24 0.0 0.0 0 0 ? S 08:44 0:00 [kworker/3:0]root 25 0.0 0.0 0 0 ? S< 08:44 0:00 [kworker/3:0H]root 30 0.0 0.0 0 0 ? S< 08:44 0:00 [kworker/4:0H]root 35 0.0 0.0 0 0 ? S< 08:44 0:00 [kworker/5:0H]root 45 0.0 0.0 0 0 ? S 08:44 0:00 [kworker/4:1]
什么是工作队列
?
linux
中
workqueue
机制为了简化内核线程的创建,通过调用 workqueue
接口就能创建内核线程
工作队列可以把工作推迟后交由一个内核线程去执行,
也就是说
,
这个工作队列的下半部可在进程上下文执行。(可以重新调度甚至休眠
)
在一个独立的进程环境下运行的。
工作队列的实现
定义一个工作队列
struct work_struct my_wq;
定义一个处理函数
void my_wq_func(struct work_struct *work)
;
初始化工作队列并将其与处理函数
绑定
INIT_WORK(&my_wq, my_wq_func);
调度工作队列执行
schedule_work(&my_wq);
使用
create_singlethread_workqueue
函数
create_singlethread_workqueue
和
create_workqueue
函数
对于多处理器系统,
使用
siglethread
会在系统的第一个
cpu
上产生工作队列和工作线程;
而非
singlethread
则为系统每个处理器都产生一个工作队列和工作线程;
内核在初始化阶段创建了一个非
singlethread
的工作队列,
驱动程序可使用该队列,
也可调用
create_singlethread_workqueue
或
create_workqueue
函数创建属于自己的工作队列。
步骤如下:
定义一个工作队列 static struct workqueue_struct *my_workqueue = NULL; 定义一个工作任务 static struct work_struct my_work;//这个任务就是我们需要推后执行的动作 定义工作队列调用函数 void work_func(struct work_struct *work) { /*推后执行的操作,比如数据处理等*/ } 初始化工作队列 static int __init xxx_init(void) { my_workqueue= create_workqueue("my_workqueue"); /*创建工作队列workqueue_struct,该函数会为cpu创建内核线程*/ INIT_WORK(&my_work,work_func); /*初始化工作work_struct,指定工作函数*/ queue_work(test_wq, &work); /*将工作加入到队列中,最终唤醒内核线程(比较常见的使用场景是在中断上半部去唤醒内核线程)*/ return 0; } static void __exit xxx_exit(void) { if (my_workqueue) destroy_workqueue(my_workqueue); }