中断的上半部和下半部

参阅《linux内核设计与实现》
中断处理分为上半部和下半部
中断处理的上半部和下半部都是不允许出现睡眠和阻塞的。但是对于下半部,并不是一刀切,不同下半部的实现方式有的不允许睡眠和阻塞(软中断和tasklet),有的是可以的(工作队列)
上半部:一般中断的中断处理函数为上半部,需要立即执行且耗时少的操作(时间太长,且如果该中断的标志是IRQF_DISABLED的话,会禁掉所有本地中断,这个函数时间长的话会对系统的性能早成严重影响)
下半部:由于上半部只能执行耗时少的操作,所以耗时长的操作就放在下半部,两个的界限并不是很明显,取决于我们要将哪个操作放在上半部还是下半部

##上半部:
中断的上半部是中断处理函数,要求做耗时少的动作,尽量迅速,一定不能休眠和阻塞
##下半部:
参阅《linux内核设计与实现》
==以下如未特殊申明,则中断均为硬件中断,软中断即为软中断

目前中断下半部的实现机制有
1:软中断2.tasklets3.工作队列
其中1,2应该同属于软中断,因为tasklets的实现也是使用软中段实现的,其中使用了软中断的0号中断号,优先级很高
3.的实现原理是内核线程

以前的认知是下半部一定是不可以睡眠和阻塞的
但是实质上中断的下半部的处理函数中是根据不同的实现方式来决定允不允许使用睡眠和可以阻塞的程序的.
其中软中断和tasklet是不可以睡眠和阻塞的。
原因:
1,软中断的处理程序允许本身被一个处理器执行的时候,另外一个处理器也也可以运行(允许响应中断)这个处理程序,如果有睡眠和阻塞的话,另外一个就运行不了了。
这也牵扯到一个软中段处理程序数据共享的问题
2:tasklet是使用了软中断而来,软中断本身就不能睡眠和阻塞,因此tasklet也不可以有睡眠和阻塞
工作队列是可以睡眠和阻塞的。
原因:
工作队列的本质是使用了一个内核线程,也就是利用了进程的上下文,而进程的上下文是允许有阻塞和睡眠的

软中断:
软中断机制的起源是来源于中断的下半部(即需要推后执行的事情,中断的上半部是中断处理函数)
软中断的使用:
1:注册软中断,绑定软中断的软中断处理函数
2:触发;这里的触发并不是直接运行软中断的处理函数,而是对这个软中断号进行置位标记
3:执行
软中断的使用场景
1:在中断的下半部是用软中断中的软中断处理函数去处理耗时的操作
对于这种场景,假设已经注册了一个软中断,一般在中断的中断处理函数中进行该软中断号的一个触发(只是标记,并不执行)。当中断处理函数返回后,系统会调用do_softirq()
去执行软中断的中断处理函数,然后清零该软中断号的标记

2:直接当成另外一种中断的机制去使用,不过这里的注册,触发都ok,但是执行软中断的操作do_softirq()可不可以自己调用还没有尝试过,不过原理上就这样

不过,软中断是的资源比较稀缺(软中断号只有32个)而且一部分系统的模块已经占用可一些软中断号,并且由于对锁的要求比较高,所以应该慎重使用,因为同样的功能,使用
tasklet更方便安全,tasklet是对软中断的一个更好的封装使用

tasklet:
tasklet的实质是利用了软中断的0号,5号软中断号来进行的。

工作队列(work queue):
工作队列可以把工作推后,交由一个内核线程去执行,它总是在进程的上下文中进行的。所以工作队列是允许睡眠的一种中断下半部实现方式。
工作队列的本质是创建一个一个普通的内核线程,我们称为工作者线程。
以下是几个工作队列抽象出来的数据结构
1:workqueue_struct表示一个工作这线程,每个cpu都会拥有一个工作者线程,
2:队列上的工作为work_struct
当我们将一个任务加入一个工作队列时候,内核会为每一个任务建立一个work_queue结构体表示该任务。同时将该结构体和相应的工作者线程结构体workqueue_struct关联起来,
将该结构体加入工作线程结构体的工作列表里。当有work被插入这个队列的时候,这个工作者线程就会唤醒,去遍历这个链表上的所有任务,执行完后,将该任务
从链表上拿走,工作者线程继续休眠。
系统在启动的时候会为每一个cpu建立一个缺省的工作队列,这个工作队列实际上创建了一个内核线程,名字为kworker/:cpu_id,例如[kworker/0:0],[kworker/1:0]等,这个内核 线程去轮询操作挂在这个工作队列中的任务。
我们也可以自己创建一个工作队列,老的内核,每创建一个工作对列就要开启一个内核线程去维护这个工作队列,这会浪费资源,新的内核:
在整个创建workqueue的流程当中我们可以看到,它并没有为新的workqueue去创建一个工作者线程,而是将wq与cpu_workqueue_struct关联起来,在这个cpu_workqueue_struct结构中还关联了gcwq,然后把workqueue放到workqueues链表上去。
当我们创建了一个任务(work_struct)以后,可以选择将这个任务挂在内核缺省的工作队列上或者我们自己创建的工作队列上。
NOTE:工作中遇到很多的情况是driver中使用了工作队列,然后上层在close该节点的时候发现clsoe失败,串口终端无响应被占用,定位发现在内核的release函数中挂掉了。
此时发现是因为没有清理driver中创建的工作队列或者线程,此时需要:

flush_workqueue(dev->work_queue);
destroy_workqueue(dev->work_queue);

但是有的时候发现上述动作做了,任然会卡在这里,此时一般有两种情况:
1.工作队列的任务中使用了while(1)并且没有sleep,导致cpu被占用,不能清除该任务
2.工作队列的任务中使用睡眠机制的函数,(例如使用了等待队列或者shedule_timeout的变体)导致任务睡眠,而flush_workqueue(dev->work_queue);并不能清掉带有睡眠属性的
工作队列。
此时对于有睡眠属性的任务,清理的时候需要去掉任务的睡眠状态,然后再去清理资源

if(priv->as_status)//判断一下等待队列的状态是否还是等待状态,如果还在等待状态则执行唤醒操作
{
		wake_up_interruptible(&priv->as_sync_wait_queue);//先唤醒睡眠
}
flush_workqueue(dev->work_queue);
destroy_workqueue(dev->work_queue);

如何在合适的场景使用这三种机制?
1.如果推后的工作需要睡眠和阻塞,就用工作队列,如果没有睡眠就用软中断和tasklet

有一点一定要清楚,虽然上述的软中断,tasklet,工作队列,是在中断的下半部被提出来的,中断的下半部一定是依赖这些来实现,
但并不表示,他们只能在中断的下半部使用,在其他的场景,仍然可以单独的使用这些

问题:在中断中使用下半部的场景?
把握一点:tasklet的创建初始化以及调度都放在中断处理程序当中
//中断处理程序,越快返回越好

static irqreturn_t wq_irq_handler(int irq, void *dev)
 { struct wq_dev mydev;
 static int count = 0;
 mydev = *(struct wq_dev*)dev; 
printk("key:%d\n",count); 
printk("ISR is working...\n"); 
if(count < 10) 
{ 
printk("------%d start-------\n",count+1);
 printk("the interrupt handler is working\n"); 
printk("the most of interrupt work will be done by following fasklet...\n");
 tasklet_init(&wq_tasklet, wq_tasklet_handler, 0);//动态创建一个tasklet结构 
tasklet_schedule(&wq_tasklet);//tasklet会被挂起,等待机会被执行 
printk("the top half has been done and bottom half will be processed...\n"); 
} c
ount++; return IRQ_HANDLED; 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章