Linux0.11中的就可中断兰芝睡眠面膜为什么是错的?

3576人阅读
嵌入式/Linux/C语言(136)
原帖地址:
这里引用个人认为比较OK的解析:
呵呵,我最喜欢这种讨论了。先来献丑了,说说我的看法。
先把中断处理流程给出来
1.进入中断处理程序---&2.保存关键上下文----&3.开中断(sti指令)---&4.进入中断处理程序的handler---&5.关中断(cli指令)----&6.写EOI寄存器(表示中断处理完成)----&7.开中断。
对应于上图的1、2、3步骤,在这几个步骤中,所有中断是被屏蔽的,如果在这个时候睡眠了,操作系统不会收到任何中断(包括时钟中断),系统就基本处于瘫痪状态(例如调度器依赖的时钟节拍没有等等……)
对应上图的4(当然,准确的说应该是4步骤的后面一点,先把话说保险点,免得思一克又开始较真&)。这个时候不能睡眠的关键是因为上下文。
大家知道操作系统以进程调度为单位,进程的运行在进程的上下文中,以进程描述符作为管理的数据结构。进程可以睡眠的原因是操作系统可以切换不同进程的上下文,进行调度操作,这些操作都以进程描述符为支持。
中断运行在中断上下文,没有一个所谓的中断描述符来描述它,它不是操作系统调度的单位。一旦在中断上下文中睡眠,首先无法切换上下文(因为没有中断描述符,当前上下文的状态得不到保存),其次,没有人来唤醒它,因为它不是操作系统的调度单位。
此外,中断的发生是非常非常频繁的,在一个中断睡眠期间,其它中断发生并睡眠了,那很容易就造成中断栈溢出导致系统崩溃。
如果上述条件满足了(也就是有中断描述符,并成为调度器的调度单位,栈也不溢出了,理论上是可以做到中断睡眠的),中断是可以睡眠的,但会引起很多问题.例如,你在时钟中断中睡眠了,那操作系统的时钟就乱了,调度器也了失去依据;例如,你在一个IPI(处理器间中断)中,其它CPU都在死循环等你答复,你确睡眠了,那其它处理器也不工作了;例如,你在一个DMA中断中睡眠了,上面的进程还在同步的等待I/O的完成,性能就大大降低了……还可以举出很多例子。所以,中断是一种紧急事务,需要操作系统立即处理,不是不能做到睡眠,是它没有理由睡眠。
好了,罗嗦了一大堆,大家见仁见智,不要骂人就好。
======================================================
5. 中断处理时可否睡眠问题
Linux 设计中,中断处理时不能睡眠,这个内核中有很多保护措施,一旦检测到内核会异常。
当一个进程A因为中断被打断时,中断处理程序会使用 A 的内核栈来保存上下文,因为是“抢”的 A 的CPU,而且用了 A 的内核栈,因此中断应该尽可能快的结束。如果 do_IRQ 时又被时钟中断打断,则继续在 A 的内核栈上保存中断上下文,如果发生调度,则 schedule 进 switch_to,又会在
A 的 task_struct-&thread_struct 里保存此时时种中断的上下文。
假如其是在睡眠时被时钟中断打断,并 schedule 的话,假如选中了进程 A,并 switch_to 过去,时钟中断返回后则又是位于原中断睡眠时的状态,抛开其扰乱了与其无关的进程A的运行不说,这里的问题就是:该如何唤醒之呢??
另外,和该中断共享中断号的中断也会受到影响。
======================================================
再一篇,也分析的很到位:
Linux是以进程为调度单位的,调度器只看到进程内核栈,而看不到中断栈。在独立中断栈的模式下,如果linux内核在中断路径内发生了调度(从技术上讲,睡眠和调度是一个意思),那么linux将无法找到“回家的路”,未执行完的中断处理代码将再也无法获得执行机会。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1965538次
积分:22047
积分:22047
排名:第227名
原创:257篇
转载:226篇
评论:524条
(2)(3)(1)(2)(4)(4)(2)(1)(1)(1)(4)(1)(3)(7)(13)(1)(2)(2)(1)(3)(1)(4)(4)(3)(1)(15)(6)(6)(5)(7)(6)(2)(3)(2)(7)(3)(2)(3)(1)(1)(1)(2)(4)(2)(1)(1)(2)(6)(5)(3)(4)(8)(17)(4)(8)(17)(17)(5)(7)(5)(17)(2)(7)(7)(8)(15)(23)(21)(33)(30)(21)(10)(5)(15)(10)linux中断--中断下半部机制的使用&中断编程
中断程序一般会包含在某个设备的驱动程序中,因此,中断处理程序本质上还是一个内核模块。在上篇文章中也看到了一个简单的中断处理流程和内核模板的写法非常相似。但是那个中断是最简单的中断,没有用到中断处理的下半部的处理机制,在别的文章中也讲述了下半部的处理机制,这里简单的使用介绍下!
上文中我们通过一个简单的例子分析了一个中断程序的基本结构。可以看到,中断处理程序在处理中断时起到了关键作用,也是一个中断程序必不可少的部分。不过,现如今的中断处理流程都会分为两部分:上半部分(top half)和下半部分(bottom half)。为什么要将一个中断分为如此两部分?下面的几个经典原因可以很好的诠释这个问题。
1.中断可以随时的打断处理机对其他程序的执行,如果被打断的代码对系统很重要,那么此时中断处理程序的执行时间应该是越短越好。
2.通过上文我们知道,中断处理程序正在执行时,会屏蔽同条中断线上的中断请求;而更严重的是,如果设置了IRQF_DISABLED,那么该中断服务程序执行是会屏蔽所有其他的中断请求。那么此时应该让中断处理程序执行的越快越好。
上面的几个例子都要求中断服务程序的执行时间越短越好。一般的,中断处理程序会在上半部分执行。而事实上,几乎所有的情况,上半部分就只执行中断处理程序。因此,我们可以这样认为:一个完整的中断处理流程是由中断处理程序和下半部分共同完成的。
这样划分是有一定原因的,因为我们必须有一个快速、异步而且简单的处理程序专门来负责对硬件的中断请求做出快速响应,与此同时也要完成那些对时间要求很严格的操作。而那些对时间要求相对宽松,其他的剩余工作则会在稍候的任意时间执行,也就是在所谓的下半部分去执行。
总之,这样划分一个中断处理过程主要是希望减少中断处理程序的工作量(当然了,理想情况是将全部工作都抛给下半段。但是中断处理程序至少应该完成对中断请求的相应。),因为在它运行期间至少会使得同级的中断请求被屏蔽,这些都直接关系到整个系统的响应能力和性能。而在下半段执行期间,则会允许响应所有的中断。
和上半段只能通过中断处理程序实现不同的是,下半部可以通过多种机制来完成:小任务(tasklet),工作队列,软中断。在本博客后续的文章当中你会看到,不管是那种机制,它们均为下半部提供了一种执行机制,比上半部灵活多了。至于何时执行,则由内核负责。
以上是上下部分划分的基本概述,通过tasklet和工作队列机制,你可以更深刻的理解下部分的执行。
tasklet的实现
tasklet(小任务)机制是中断处理下半部分最常用的一种方法,其使用也是非常简单的。正如在前文中你所知道的那样,一个使用tasklet的中断程序首先会通过执行中断处理程序来快速完成上半部分的工作,接着通过调用tasklet使得下半部分的工作得以完成。可以看到,下半部分被上半部分所调用,至于下半部分何时执行则属于内核的工作。对应到我们此刻所说的tasklet就是,在中断处理程序中,除了完成对中断的响应等工作,还要调用tasklet,如下图示。
tasklet由tasklet_struct结构体来表示,每一个这样的结构体就表示一个tasklet。在&linux/interrupt.h&中可以看到如下的定义:
tasklet_struct
structtasklet_struct *
unsigned long
void(*func)(unsignedlong);
unsigned long
在这个结构体中,第一个成员代表链表中的下一个tasklet。第二个变量代表此刻tasklet的状态,一般为TASKLET_STATE_SCHED,表示此tasklet已被调度且正准备运行;此变量还可取TASKLET_STATE_RUN,表示正在运行,但只用在多处理器的情况下。count成员是一个引用计数器,只有当其值为0时候,tasklet才会被激活;否则被禁止,不能被执行。而接下来的func变量很明显是一个函数指针,它指向tasklet处理函数,这个处理函数的唯一参数为data。
使用tasklet
在使用tasklet前,必须首先创建一个tasklet_struct类型的变量。通常有两种方法:静态创建和动态创建。这样官方的说法仍然使我们不能理解这两种创建到底是怎么一回事。不够透过源码来分析倒是可以搞明白。
在&linux/interrupt.h&中的两个宏:
464#define DECLARE_TASKLET(name, func, data) \
465struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
467#define DECLARE_TASKLET_DISABLED(name, func, data) \
468struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
就是我们进行静态创建tasklet的两种方法。通过第一个宏创建的tasklet处于激活状态,再通过调度函数被挂起尽而被内核执行;而通过第二个宏创建的tasklet处于禁止状态。从两个宏的定义可以看到,所谓的静态创建就是直接定义个一个名为name的tasklet_struct类型的变量,并将宏中各个参数相应的赋值给这个name变量的各个成员。注意,两个宏在功能上差异就在于对name变量count成员的赋值上,具体原因在第一部分已经说明。也许你对ATOMIC_INIT这样的初始化方式感到疑惑,那么看完定义后,你就会一目了然:
15#define ATOMIC_INIT(i) { (i) }
190typedef struct{
192} atomic_t;
与静态创建相对的是动态创建,通过给tasklet_init函数传递一个事先定义的指针,来动态创建一个tasklet。这个函数源码如下。
470void tasklet_init(structtasklet_struct *t,
471 void(*func)(unsignedlong), unsignedlongdata)
473 t-&next = NULL;
474 t-&state = 0;
475 atomic_set(&t-&count, 0);
476 t-&func =
477 t-&data =
相信你在阅读上面的代码是基本上没有什么难以理解的地方,不过这里还是要特别说明一下atomic_set函数:
35static inlinevoidatomic_set(atomic_t *v,inti)
37 v-&counter =
首先tasklet_init当中,将&t-&count传递给了此函数。也就是说将atomic_t类型的成员count的地址传递给了atomic_set函数。而我们在此函数中却要为count变量中的成员counter赋值。如果说我们当前要使用i,那么应该是如下的引用方法:t-》count.i。明白了吗?
ok,通过上述两种方法就可以创建一个tasklet了。同时,你应该注意到不管是上述那种创建方式都有func参数。透过上述分析的源码,我们可以看到func参数是一个函数指针,它指向的是这样的一个函数:
void tasklet_handler(unsignedlongdata);
如同上半部分的中断处理程序一样,这个函数需要我们自己来实现。
创建好之后,我们还要通过如下的方法对tasklet进行调度:
tasklet_schedule(&my_tasklet)
通过此函数的调用,我们的tasklet就会被挂起,等待机会被执行
在此只分析上下两部分的调用关系,完整代码在这里查看。
static struct tasklet_
static void mytasklet_handler(unsigned longdata)
printk(&This is tasklet handler..\n&);
static irqreturn_t myirq_handler(int irq,void* dev)
staticintcount=0;
if(count&10)
printk(&-----------%d start--------------------------\n&,count+1);
printk(&The interrupt handeler is working..\n&);
printk(&The most of interrupt work will be done by following tasklet..\n&);
tasklet_init(&mytasklet,mytasklet_handler,0);
tasklet_schedule(&mytasklet);
printk(&The top half has been done and bottom half will be processed..\n&);
returnIRQ_HANDLED;
从代码中可以看到,在上半部中通过调用tasklet,使得对时间要求宽松的那部分中断程序推后执行。
为什么还需要工作队列?
工作队列(work queue)是另外一种将中断的部分工作推后的一种方式,它可以实现一些tasklet不能实现的工作,比如工作队列机制可以睡眠。这种差异的本质原因是,在工作队列机制中,将推后的工作交给一个称之为工作者线程(worker thread)的内核线程去完成(单核下一般会交给默认的线程events/0)。因此,在该机制中,当内核在执行中断的剩余工作时就处在进程上下文(process context)中。也就是说由工作队列所执行的中断代码会表现出进程的一些特性,最典型的就是可以重新调度甚至睡眠。
对于tasklet机制(中断处理程序也是如此),内核在执行时处于中断上下文(interrupt context)中。而中断上下文与进程毫无瓜葛,所以在中断上下文中就不能睡眠。
因此,选择tasklet还是工作队列来完成下半部分应该不难选择。当推后的那部分中断程序需要睡眠时,工作队列毫无疑问是你的最佳选择;否则,还是用tasklet吧。
中断上下文
在了解中断上下文时,先来回顾另一个熟悉概念:进程上下文(这个中文翻译真的不是很好理解,用&环境&比它好很多)。一般的进程运行在用户态,如果这个进程进行了系统调用,那么此时用户空间中的程序就进入了内核空间,并且称内核代表该进程运行于内核空间中。由于用户空间和内核空间具有不同的地址映射,并且用户空间的进程要传递很多变量、参数给内核,内核也要保存用户进程的一些寄存器、变量等,以便系统调用结束后回到用户空间继续执行。这样就产生了进程上下文。
所谓的进程上下文,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容。当内核需要切换到另一个进程时(上下文切换),它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态继续执行。上述所说的工作队列所要做的工作都交给工作者线程来处理,因此它可以表现出进程的一些特性,比如说可以睡眠等。
对于中断而言,是硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理,中断上下文就可以理解为硬件传递过来的这些参数和内核需要保存的一些环境,主要是被中断的进程的环境。因此处于中断上下文的tasklet不会有睡眠这样的特性。
工作队列的使用
内核中通过下述结构体来表示一个具体的工作:
struct work_struct
unsigned long
structlist_
void(*func)(void*);
structtimer_
而这些工作(结构体)链接成的链表就是所谓的工作队列。工作者线程会在被唤醒时执行链表上的所有工作,当一个工作被执行完毕后,相应的work_struct结构体也会被删除。当这个工作链表上没有工作时,工作线程就会休眠。
通过如下宏可以创建一个要推后的完成的工作:
DECLARE_WORK(name,void(*func)(void*),void*data);
也可以通过下述宏动态创建一个工作:
INIT_WORK(structwork_struct *work,void(*func)(void*),void*data);
与tasklet类似,每个工作都有具体的工作队列处理函数,原型如下:
void work_handler(void*data)
将工作队列机制对应到具体的中断程序中,即那些被推后的工作将会在func所指向的那个工作队列处理函数中被执行。
实现了工作队列处理函数后,就需要schedule_work函数对这个工作进行调度,就像这样:
schedule_work(&work);
这样work会马上就被调度,一旦工作线程被唤醒,这个工作就会被执行(因为其所在工作队列会被执行)。
(PS;在前面很多篇文章中从理论的角度分析了中断机制的处理流程,分为上下两部完成中断处理,其实就是为了满足各个条件才分两步的。用实际的例子讲述怎么使用下半部 /kernel-book/ch03/3.3.3.htm)
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467142',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'2221人阅读
 这个问题实际上是一个老生常谈的问题,答案也很简单,Linux在上下文中是不能睡眠的,原因在于Linux的软中断实现上下文有可能是中断上下文,如果在中断上下文中睡眠,那么会导致Linux无法调度,直接的反应是系统Kernel Panic,并且提示dequeue_task出错。所以,在软中断上下文中,我们不能使用信号量等可能导致睡眠的函数,这一点在编写IO回调函数时需要特别注意。在最近的一个项目中,我们在dm-io的callback函数中去持有semaphore访问竞争资源,导致了系统的kernel
panic。其原因就在于dm-io的回调函数在scsi soft irq中执行,scsi soft irq是一个软中断,其会在硬中断发生之后被执行,执行上下文为中断上下文。
  中断上下文中无法睡眠的原因大家一定很清楚,原因在于中断上下文不是一个进程上下文,其没有一个专门用来描述CPU寄存器等信息的数据结构,所以无法被调度器调度。如果将中断上下文也设计成进程上下文,那么调度器就可以对其进行调度,如果在开中断的情况下,其自然就可以睡眠了。但是,如果这样设计,那么中断处理的效率将会降低。中断(硬中断、软中断)处理都是些耗时不是很长,对实时性要求很高,执行频度较高的应用,所以,如果采用一个专门的后台daemon对其处理,显然并不合适。
  Linux对中断进行了有效的管理,一个中断发生之后,都会通过相应的中断向量表获取该中断的处理函数。在Linux中都会调用do_IRQ这个函数,在这个函数中都会执行__do_IRQ(),__do_IRQ函数调用该中断的具体执行函数。在执行过程中,该函数通过中断号找到具体的中断描述结构irq_desc,该结构对某一具体硬件中断进行了描述。在irq_desc结构中存在一条链表irqaction,这条链表中的某一项成员都是一个中断处理方法。这条链表很有意思,其实现了中断共享,例如传统的PCI总线就是采用共享中断的方法,该链表中的一个节点就对应了一个PCI设备的中断处理方法。在PCI设备驱动加载时,都需要注册本设备的中断处理函数,通常会调用request_irq这个函数,通过这个函数会构造一个具体的irq
action,然后挂接到某个具体irq_desc的action链表下,实现中断处理方法的注册。在__do_IRQ函数中会通过handle_IRQ_event()函数遍历所有的action节点,完成中断处理过程。到目前为止,中断处理函数do_IRQ完成的都是上半部的工作,也就是设备注册的中断服务程序。在中断上半部中,通常都是关中断的,基本都是完成很简单的操作,否则将会导致中断的丢失。耗时时间相对较长,对实时性要求不是最高的应用都会被延迟处理,都会在中断下半部中执行。所以,在中断上半部中都会触发软中断事件,然后执行完毕,退出服务。
  __do_IRQ完成之后,返回到do_IRQ函数,在该函数中调用了一个非常重要的函数irq_exit(),在该函数中调用invoke_softirq(),invoke_softirq调用do_softirq()函数,执行软中断的操作。此时,程序的执行环境还是中断上下文,但是与中断上半部不同的是,软中断执行过程中是开中断的,能够被硬中断而中断。所以,如果用户的程序在软中断中睡眠,操作系统该如何调度呢?只有kernel panic了。另外,软中断除了上述执行点之外,还有其他的执行点,在内核中还有一个软中断的daemon处理软中断事务,驱动程序也可以自己触发一个软中断事件,并且在软中断的daemon上下文中执行。但是硬中断触发的事件都不会在这个daemon的上下文中执行,除非修改Linux中的do__IRQ代码。
  上述对软中断的执行做了简要分析,我对Linux中的硬中断管理机制做了一些代码分析,这一块代码量不是很大,可移植性非常的好~~建议大家阅读,对我上述的分析和理解存在什么不同意见,欢迎大家讨论
为什么在中断上下文中不能休眠?
这个问题有很多人问过,我看了下linux得内核代码,原因如下当然我不能保证一定对,如果有牛人理解得更好,欢迎指正)
1.中断处理的时候,不应该发生进程切换,因为在中断context中,唯一能打断当前中断handler的只有更高优先级的中断,它不会被进程打断(这点对于softirq,tasklet也一样,因此这些bottom&half也不能休眠),如果在中断context中休眠,则没有办法唤醒它,因为所有的wake_up_xxx都是针对某个进程而言的,而在中断context中,没有进程的概念,没有一个task_struct(这点对于softirq和tasklet一样),因此真的休眠了,比如调用了会导致block的例程,内核几乎肯定会死.
2.schedule()在切换进程时,保存当前的进程上下文(CPU寄存器的值、进程的状态以及堆栈中的内容),以便以后恢复此进程运行。中断发生后,内核会先保存当前被中断的进程上下文(在调用中断处理程序后恢复);
但在中断处理程序里,CPU寄存器的值肯定已经变化了吧(最重要的程序计数器PC、堆栈SP等),如果此时因为睡眠或阻塞操作调用了schedule(),则保存的进程上下文就不是当前的进程context了.所以不可以在中断处理程序中调用schedule()。
3.2.4内核中schedule()函数本身在进来的时候判断是否处于中断上下文:
if(unlikely(in_interrupt()))
因此,强行调用schedule()的结果就是内核BUG,但我看2.6.18的内核schedule()的实现却没有这句,改掉了.
4.中断handler会使用被中断的进程内核堆栈,但不会对它有任何影响,因为handler使用完后会完全清除它使用的那部分堆栈,恢复被中断前的原貌.
5.处于中断context时候,内核是不可抢占的,因此,如果休眠,则内核一定挂起.
---------------------------------------------------------------------------
在windows&NT内核中,这个也一样,和linux内核不一样的一个地方是:NT内核发明了irql概念,内核是可抢占的,但在内核中只能是高irql的抢占低IRQL,所有的中断处理程序运行在DIRQL上,而进程调度运行在dispatch&level上,这个比DIRQL低,此外相当于linux中的softirq机制的dpc机制实现bottom&half的处理,但dpc也是运行在dispatch&level上的,因此它和中断handler一样不能被进程调度抢占,这样如果在这些例程中休眠或者调用可能block的函数,则内核一定hang,windows&NT在这种情况下会调用KeBugCheckEx()挂起机器,BSOD了.
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:6660次
排名:千里之外
转载:10篇新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
稍有积蓄, 积分 229, 距离下一级还需 271 积分
论坛徽章:0
本帖最后由 frank529 于
09:26 编辑
这个问题有很多人问,但感觉一直没有一个合理的解释。参见帖子《关于LINUX在中断(硬软)中不能睡眠的真正原因》:
先来看这个问题的起源(ULK中文第三版147页):
允许内核控制路径嵌套执行必须付出代价,那就是中断处理程序必须永不阻塞,换句话说,中断处理程序运行期间不能发生进程切换。事实上,嵌套的内核控制路径恢复执行时需要的所有数据都存放在内核态堆栈中,这个栈毫无疑义的属于当前进程。
The price to pay for allowing nested kernel control paths is that an interrupt handler must never block, that is, no process switch can take place until an interrupt handler is running. In fact, all the data needed to resume a nested kernel control path is stored in the Kernel Mode stack, which is tightly bound to the current process.
根据这段描述,我们可以得到以下几点信息:
1、中断是可以嵌套的,即文中所说的“内核控制路径嵌套”,意思就是一个中断服务程序可以被高优先级的中断打断。
2、中断处理程序不能阻塞的原因是由于中断嵌套。
3、中断上下文是保存在当前进程的内核栈中。
参考帖子《关于LINUX在中断(硬软)中不能睡眠的真正原因》的回复,大家认为中断服务程序不能嵌套的原因大致有以下几点:
1、中断上下文不像进程上下文,没法保存,也就是中断服务程序睡眠了就再也切不回来了
2、中断是一种紧急事务,必须立即处理,比如时钟中断,一睡眠的话系统时间都乱了
我认为以上观点都没道理,理由如下:
1、参考ULK给出的信息2,中断不能睡眠是由于中断可以嵌套,以上理由跟中断嵌套没有关系
2、参考ULK给出的信息3,中断上下文是保存着当前进程的内核栈中,而且每个进程都有各自的内核栈,互不影响。既然保存了上下文,中断服务程序切走了当然还能切回来,而且切到其他进程也不影响其他进程响应新的中断。
3、第二个理由只是说明紧急中断不应该睡眠,而不是中断不能睡眠的理由。我在中断服务程序里先把紧急的事干了,后面的事不紧急了,凭啥不让我睡眠?就算切换到其他进程,它还是能再次响应中断。
因此,我觉得问题应该这么问:为什么中断嵌套的代价是中断服务程序不能阻塞?
希望高手能给个一针见血的回答。
&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp
稍有积蓄, 积分 229, 距离下一级还需 271 积分
论坛徽章:0
本帖最后由 frank529 于
16:28 编辑
ULK中文第三版147页后面又写道:
“Page Fault(缺页)”异常发生在内核态...当处理这样一个异常时,内核可以挂起当前进程,并用另一个进程代替它,直到请求的页可以使用为止。只要被挂起的进程又获得处理器,处理缺页异常的内核控制路径就恢复执行。
与异常形成对照的是,尽管处理中断的内核控制路径代表当前进程运行,但由于I/O设备产生的中断并不引用当前进程专有的数据结构。事实上,当一个给定的中断发生时,要预测哪个进程将会运行是不可能的。
这里说明了:
缺页异常处理程序是可以挂起的,缺页异常也是中断。所以前面说的“中断服务程序不能阻塞”里的“中断”指的是其他I/O设备产生的中断,是相对于异常而言的。
后面那段红色的字似乎想要解释原因,但说得不清不楚,还是不明白。
小富即安, 积分 2812, 距离下一级还需 2188 积分
论坛徽章:0
frank529 发表于
这个问题有很多人问,但感觉一直没有一个合理的解释。参见帖子《关于LINUX在中断(硬软)中不能睡眠的真正原 ...
首先要明白一点:
& && &是Linux对中断处理,在设计上就是“不能够进程切换”,也就是说对于硬件系统,不一定就不能实现“中断中发起进程切换”。
& && &硬件的角度跟能力上来说(抛开软件实现),中断是可以嵌套的!只不过Linux设计的时候处理成不准嵌套!
富足长乐, 积分 5501, 距离下一级还需 2499 积分
论坛徽章:2
两个不太靠谱、互补的理由:
& & - 中断处理程序有负担,要求速入速出;
& & - 扰乱了调度体系。线程化中断可以解决这个问题,但基于上一条理由,仍不推荐睡眠。
稍有积蓄, 积分 229, 距离下一级还需 271 积分
论坛徽章:0
本帖最后由 frank529 于
10:31 编辑
tempname2 发表于
两个不太靠谱、互补的理由:
& & - 中断处理程序有负担,要求速入速出;
& & - 扰乱了调度体系。线程化 ...
中断处理程序有什么负担?就是中断上下文吧,无法就是消耗一点内核栈空间而已,所以我觉得第一条理由不成立。如果中断可以睡眠,那就没必要分成上下半了,都在中断服务程序处理就行了,紧急先处理了,然后有条件的睡眠,条件满足了再切回来执行非紧急的部分。
你第二条说的很有道理,当前中断可能跟当前进程没有任何关系,在中断处理程序里睡眠了,就阻塞了当前进程,影响了当前进程的调度。
所谓“中断不能睡眠”应该这么描述:由于中断服务程序使用了当前进程的内核栈,而硬件IO中断可能在任何时间发生,这个中断可能与当前进程没有任何关系,如果在中断服务程序中睡眠,会导致当前进程毫无理由的被阻塞,扰乱了进程的调度。
ULK说的中断服务程序不能阻塞实在是太笼统了。系统调用也是中断,缺页异常也是中断,这些中断服务程序都是可以睡眠的,因为它们的发生是与当前进程密切相关的,而硬件IO的中断不一定与当前进程有关,所以不能因为硬件IO的中断服务程序而无故阻塞当前进程。
至于Linux为了简化设计而人为的禁止中断服务程序睡眠那又是另外一回事了,这里不作讨论。

我要回帖

更多关于 兰芝睡眠面膜 的文章

 

随机推荐