momo zone

调核人的blog

关于IRQ和睡眠

今天看了cu上的讨论,关于IRQ为什么不能睡眠/进程切换,感觉获益匪浅,因为这个问题是有一定深度的 ,所以说出来的理由也是五花八门。

但有一个理由很多人认同: 因为中断没有堆栈(上下文),所以一旦睡眠/进程切换后很难被唤醒/调度。

“中断没有堆栈”在2.4 时代中断没有自己的堆栈,发生中断时,因为要保存现场需要把数据压入内核栈,中断必须“ 借用” 当前进程的堆栈,所以中断不是没有堆栈而是堆栈不确定,谁叫中断是异步呢(同步的叫异常 ,必须马上处理的) 。

“一旦睡眠/进程切换后很难被唤醒/调度” :正如上面讲的,因为借用的是当前进程的堆栈,中断虽然没有上下文,但他实际所在的是当前进程上下文 ,所以一旦中断的被睡眠,那么现场还是能够保护的:内核将中断的CS:eip和SS:esp保存在被抢占任务A的thread_info中,当任务A被重新唤醒的时候, 任务A从中断的CS:eip开始执行, 这也能正常执行下去, 中断执行完后, 从ret_from_intr中返回. 可以恢复任务A的抢占前的场景。

然后事情没有那么简单,上述任务A因为一个中断被强占了,不能继续执行,然后中断又睡眠了,那么无辜的任务A也只能等再次被唤醒/调度才有可能执行,这不符合内核实时性的要求。如果真要让中断可睡眠/调度还有做大量的小概率事件的考虑,比如睡眠之后又出现了同一IRQ号的中断,中断比较频繁堆栈溢出怎么办。另外还要从中断的设计初衷思考: 中断本来就是比较紧急的任务,如果被休眠那么延迟很久后中断程序的执行还有意义吗? 不过也不能一概而论。也许中断不能休眠/调度真的是一个设计问题,而不是技术上不能实现

下面再说一下 kernel 2.6中关于中断方面的演化:

首先中断处理例程分为上半部/下半部,上半部主要是响应中断,下半部是处理中断handler 。中断handler被放置在一个tasklet或工作队列中。因为工作队列以内核线程去执行,在进程上下文 ,所以可以安全地休眠,而tasklet工作在软中断上下文(和上半部一样?),不能休眠。

总之把中断handler交给工作队列去处理可以实现一定程度的中断休眠,尽管2.6改进的是中断延后处理方面。

kernel 2.6的另一个重大改进是设置的中断堆栈:

内核在编译的时候设置了THREAD_SIZE的值为8K的话, 那么每个进程的内核栈的大小就为8K, 此时如果发生中断时, 那么进程的寄存器等值就会保存到它的8K的内核栈中. 但是如果设置了THREAD_SIZE的大小为4K的话, 内核就会使用3种类型的内核栈, 异常栈, 硬件中断请求栈以及软中断请求栈( When using 4K stacks, interrupts get their own stack instead of using the currently active kernel stack.)

* 异常栈:每个进程一个。

* 硬中断请求栈:每个CPU一个,每个占用一个单独的页框。do_IRQ()函数内部通过调用execute_on_irq_stack负责切换到该栈中来。

* 软中断请求栈:每个CPU一个,每个占用一个单独的页框。

当使用了中断栈以后,如果一旦中断睡眠/调度 ,那么中断将永远不会被唤醒了,因为调度器根本就看不到中断栈,也无法从进程上下文看到中断。

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: