再思linux内核在中断路径内不能睡眠/调度的原因(2010)【转】

转自:http://blog.csdn.net/maray/article/details/5770889

Linux内核中断路径中不能睡眠,为什么?

这里就行了很深入的讨论,值得一看:http://bbs2.chinaunix.net/viewthread.php?tid=1618430

但是,他们的讨论最后没有得出一个明确的结论。其中,cskyrain在8楼 的思考触及到了一个要点,但是没有深入展开:

[c-sharp] view plain copy

  1. 1楼 发表于 2009-11-24 20:36  | 只看该作者
  2. 一直认为中断处理函数不能休眠的是天经地义的,可从没认真思考过问什么不能休眠,阻塞。最近看了一下ulk中对这个的解释,感觉还是有点不太明白,
  3. “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.”
  4. 上面把中断处理程序不能休眠归结为中断处理程序可以嵌套,而恢复嵌套的中断处理程序的相关数据放在内核态堆栈中,这个栈和当前进程相关联,这里有一点不明白,既然有栈存储数据,而且进程切换出去后,这个栈也不会被销毁,等进程在切回来时,不同样可以是嵌套的中断处理程序返回吗?(这里先不考了中断处理时间的太长,影响对中断处理的服务问题,只说明中断是否可休眠)。同时我google一下,看到有人对中断不能休眠的以一种解释:
  5. 中断处理程序用到的所有数据有保存在当前进程的内核堆栈中(一般情况下),如果此时发生了
  6. 进程切换,中断处理程序将被阻塞, 当在一次发生进程切换时,不一定马上就换回来。比如当
  7. 发生一个键盘中断时,键盘处理程序正在进行,此时又恰好发生了进程抢占,切换到了另一个进程
  8. 中,假如那个进程不会引起内核的稳定,那么中断处理程序将一直阻塞,内禾根本就没法响应那个中断,
  9. 直到在一次发生进程切换。假设最后又切换回执行中断处理程序的那个进程中时,如果它的内核堆栈
  10. 受到了无意的破坏怎么办呢?那中断处理程序可能无法在继续运行下去了,此次中断服务失败。而且
  11. 现在的处理程序都允许中断嵌套,一个中断被阻塞,其它的都将被阻塞。所以面对错综复杂的内核逻辑,
  12. 最好的办法就是在中断处理程序中禁止发生进程切换,这样既提高了中断处理程序的响应速度,也增加了
  13. 内核的稳定性与安全性。
  14. 我感觉他的理由有两个:一个事效率,可能影响处理速度,另一就是:如果它的内核堆栈
  15. 受到了无意的破坏怎么办?
  16. 对于第一种理由先不讨论,
  17. 可第二种,理由我感觉很牵强,如果栈那么容易破坏,哪我也可以说描述进程的数据结构什么的也可能被破坏,哪岂不是进程调度都是不安全的了,
  18. 这样按他的说法,就只有第一个效率的原因了。
  19. 回到ulk的解释,不知道是不是我理解的有问题,The price to pay for allowing nested kernel control paths is that an interrupt handler must never block
  20. 感觉他把中端不能休眠的原因都归结为可支持中断处理程序的嵌套上了。那是不是可以这样理解,如果不支持中断嵌套,中断处理程序就可以休眠了,呵呵呵,貌似这样也不对吧,
  21. 问题:中断处理程序不能休眠的原因究竟是什么呢?

7楼:

[c-sharp] view plain copy

  1. kouu 发表于 2009-11-25 11:52
  2. 回复 #6 cskyrain 的帖子
  3. 中断不能block,应该特指异步中断吧。
  4. 看了LZ这两天的帖子,我觉得还是类似4楼的说法比较靠谱:异步中断是独立的上下文,与当前进程无关,所以不能因为中断上下文的block而将无辜的进程给block了。可能就是基于这一初衷吧~ 那些已经将中断处理程序线程化了的实时linux应该是允许中断block的。
  5. 而同步的中断(比如系统调用、缺页异常)是代表当前进程的,本来就是可以block的。

8楼:

[c-sharp] view plain copy

  1. 回复 #7 kouu 的帖子
  2. 呵呵,又翻了翻书,思考了一下,由于时间问题,没心情再从头读ulk,只是跳着查了一下,难免会漏下很多东西,不过还是说说我的理解:
  3. 这里中断只代表异步中断,异常代表同步中断,这样系统调用是异常处理,不是中断处理。
  4. 这里异常处理是可以休眠block的,因为异常处理所需的数据是存储在异常栈中,而每个进程都有一个异常栈,所以异常处理和进程是相关联的,这样异常处理可以block,被调度出去。
  5. 而对于中断,分为两种情况,一种是中断使用单独的中断栈而不使用进程的内核栈的情况,这样,由于所有中断共享一个中断栈,这个中断栈不和特定进程关联,所以,这种中断时不能block的,block后他是不能再被调度。
  6. 第二种情况是,中断不使用单独的中断栈,而是使用当前进程的内核栈,这种情况我认为是和异常处理时一样的,这种中断时可以block的,之所以不准许中断 block不是技术上切换不回来,而是逻辑上为了提高处理的效率强制其不能block。
  7. 以上是我现阶段的理解,感觉,前俩个的解释应该没什么问题,但最后对不是用中断栈的中断的解释可能有错误的认识,不知kouu对这种解释有何看法?

总体上,大家得到的初步结论是:内核在中断路径内不能睡眠,不是技术上做不到,而是没有理由这么做,或者说在中断路径上睡眠不合理。一方面,外部事件导致当前进程时间片被剥夺,不合理;一方面,中断服务程序应该尽快处理完中断,保证IO吞吐率。

-------------------------------分割线----------------------------

通过阅读ULK中文版第三版164页的内容,让我对这个问题有了一个较为清晰的认识:

内核在编译的时候设置了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一个,每个占用一个单独的页框。

关于切换到中断栈的方法,请看下面的代码:

[cpp] view plain copy

  1. /*
  2. * do_IRQ handles all normal device IRQ‘s (the special
  3. * SMP cross-CPU interrupts have their own specific
  4. * handlers).
  5. */
  6. unsigned int do_IRQ(struct pt_regs *regs)
  7. {
  8. struct pt_regs *old_regs;
  9. /* high bit used in ret_from_ code */
  10. int overflow;
  11. unsigned vector = ~regs->orig_ax;
  12. struct irq_desc *desc;
  13. unsigned irq;
  14. old_regs = set_irq_regs(regs);
  15. irq_enter();
  16. irq = __get_cpu_var(vector_irq)[vector];
  17. overflow = check_stack_overflow();
  18. desc = irq_to_desc(irq);
  19. if (unlikely(!desc)) {
  20. printk(KERN_EMERG "%s: cannot handle IRQ %d vector %#x cpu %d/n",
  21. __func__, irq, vector, smp_processor_id());
  22. BUG();
  23. }
  24. if (!execute_on_irq_stack(overflow, desc, irq)) {
  25. if (unlikely(overflow))
  26. print_stack_overflow();
  27. desc->handle_irq(irq, desc);
  28. }
  29. irq_exit();
  30. set_irq_regs(old_regs);
  31. return 1;
  32. }

[cpp] view plain copy

  1. static inline int
  2. execute_on_irq_stack(int overflow, struct irq_desc *desc, int irq)
  3. {
  4. union irq_ctx *curctx, *irqctx;
  5. u32 *isp, arg1, arg2;
  6. curctx = (union irq_ctx *) current_thread_info();
  7. irqctx = hardirq_ctx[smp_processor_id()];
  8. /*
  9. * this is where we switch to the IRQ stack. However, if we are
  10. * already using the IRQ stack (because we interrupted a hardirq
  11. * handler) we can‘t do that and just have to keep using the
  12. * current stack (which is the irq stack already after all)
  13. */
  14. if (unlikely(curctx == irqctx))
  15. return 0;
  16. /* build the stack frame on the IRQ stack */
  17. isp = (u32 *) ((char*)irqctx + sizeof(*irqctx));
  18. irqctx->tinfo.task = curctx->tinfo.task;
  19. irqctx->tinfo.previous_esp = current_stack_pointer;
  20. /*
  21. * Copy the softirq bits in preempt_count so that the
  22. * softirq checks work in the hardirq context.
  23. */
  24. irqctx->tinfo.preempt_count =
  25. (irqctx->tinfo.preempt_count & ~SOFTIRQ_MASK) |
  26. (curctx->tinfo.preempt_count & SOFTIRQ_MASK);
  27. if (unlikely(overflow))
  28. call_on_stack(print_stack_overflow, isp);
  29. asm volatile("xchgl %%ebx,%%esp /n"
  30. "call  *%%edi      /n"
  31. "movl  %%ebx,%%esp /n"
  32. : "=a" (arg1), "=d" (arg2), "=b" (isp)
  33. :  "0" (irq),   "1" (desc),  "2" (isp),
  34. "D" (desc->handle_irq)
  35. : "memory", "cc", "ecx");
  36. return 1;
  37. }

最后,思考一下标题中的问题:linux内核在中断路径内不能睡眠/调度的原因

Linux是以进程为调度单位的,调度器只看到进程内核栈,而看不到中断栈。在独立中断栈的模式下,如果linux内核在中断路径内发生了调度(从技术上讲,睡眠和调度是一个意思),那么linux将无法找到“回家的路”,未执行完的中断处理代码将再也无法获得执行机会。

时间: 2024-10-10 07:11:04

再思linux内核在中断路径内不能睡眠/调度的原因(2010)【转】的相关文章

Linux内核实现中断和中断处理(二)

上回说了Linux内核实现中断会把中断分为两部分进行处理,上回讲了上部分,这回讲下部分的设计思路 下半部的实现机制 软中断 tasklet:是通过软中断实现的,但和软中断有所不同 工作队列 讲上面几个实现机制之前先讲一个古老的方法,现在版本的内核虽然已经不再食用了,但是思想还在继续使用 最早的Linux只提供了“bottom half”这种机制实现下半部分,被称为BH,实现简单粗暴,设置一个全局变量(32位整数),表示一个32个节点的链表队列,哪位设置为1证明哪个bottom half就可以执行

再谈Linux内核中的RCU机制

转自:http://blog.chinaunix.net/uid-23769728-id-3080134.html RCU的设计思想比较明确,通过新老指针替换的方式来实现免锁方式的共享保护.但是具体到代码的层面,理解起来多少还是会有些困难.在<深入Linux设备驱动程序内核机制>第4章中,已经非常明确地叙述了RCU背后所遵循的规则,这些规则是从一个比较高的视角来看,因为我觉得过多的代码分析反而容易让读者在细节上迷失方向.最近拿到书后,我又重头仔细看了RCU部分的文字,觉得还应该补充一点点内容,

深入理解Linux内核-中断和异常

Linux内核代码查看 http://androidxref.com/ 中断:被定义位一个事件,它能改变处理器执行指令的顺序.它对应硬件(CPU.其他硬件设备)电路产生的电信号. 同步中断:指令执行时CPU控制单元产生:称为同步,是因为只有在一条指令终止执行后CPU才回发出中断.也被称为异常 异步中断:其他硬件设备按照CPU时钟信号随机产生的.也被简称中断 中断的约束:1.中断必须尽快处理完成:中断一般被分两部分执行:关键而且紧急的部分,内核立即执行:其余部分内核稍后执行: 2.中断的处理必须能

Linux内核(二)中断基本概念

转载请注明出处:jiq?钦's technical Blog 首先解释一些容易混淆的概念: 中断上下文:正在执行ISR或者下半部.没有进程背景(只是暂停CPU去内存中其他地方执行一段快速代码),所以不能被切换(比如睡眠,被其他进程抢占等都不行). 进程上下文:非中断上下文(比如执行系统调用或者内核线程的时候).此时内核代表线程在执行,所以允许睡眠,被抢占等(被切换). 中断栈:每个处理器有一个专门的中断栈.以前中断处理程序都是使用中断进程的内核栈(只有一个),但是内核栈越来越不够用,所以中断处理

linux内核是中断下半部

首先阐述下为什么内核要将中断分成上下半部 因为中断本身打断了正常的程序执行,中断中不能进行任务调度,所以中断需要快返回,但是某些操作必须在中断中执行. 如果内核需要执行一个硬件相关.时间敏感.不能被中断的操作,那么这些操作就应该放到上半部中,其他能够推迟的操作应该放到下半部中去,这样完成了中断中必须完成的操作,又能很好的进行调度. 看看内核对于下半部的支持 首先说以下如何添加自己的软中断程序 首先添加自己的软中断类型,值越低优先级越高 用open_softirq增加相对应的中断处理函数 用rai

Linux 内核中断内幕【转】

转自:http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/ 本文对中断系统进行了全面的分析与探讨,主要包括中断控制器.中断分类.中断亲和力.中断线程化与 SMP 中的中断迁徙等.首先对中断工作原理进行了简要分析,接着详细探讨了中断亲和力的实现原理,最后对中断线程化与非线程化中断之间的实现机理进行了对比分析. 3 评论: 苏 春艳, 在读研究生 杨 小华 ([email protected]), 在读研究生 2007 年 5

Linux 内核中断内幕

转自:http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/index.html#resources Linux 内核中断内幕 本文对中断系统进行了全面的分析与探讨,主要包括中断控制器.中断分类.中断亲和力.中断线程化与 SMP 中的中断迁徙等.首先对中断工作原理进行了简要分析,接着详细探讨了中断亲和力的实现原理,最后对中断线程化与非线程化中断之间的实现机理进行了对比分析. 3 评论 苏 春艳, 在读研究生 杨 小华 ([e

Linux内核的idle进程分析

1. idle是什么 简单的说idle是一个进程,其pid号为 0.其前身是系统创建的第一个进程.也是唯一一个没有通过fork()产生的进程. 在smp系统中,每一个处理器单元有独立的一个执行队列,而每一个执行队列上又有一个idle进程,即有多少处理器单元.就有多少idle进程. 系统的空暇时间,事实上就是指idle进程的"执行时间".既然是idle是进程.那我们来看看idle是怎样被创建,又详细做了哪些事情? 2. idle的创建 我们知道系统是从BIOS加电自检,载入MBR中的引导

Linux内核抢占机制 - 简介

本文首发于 http://oliveryang.net,转载时请包含原文或者作者网站链接. 本文主要围绕 Linux 内核调度器 Preemption 的相关实现进行讨论.其中涉及的一般操作系统和 x86 处理器和硬件概念,可能也适用于其它操作系统. 1. 背景知识 要深入理解 Preemption 必须对操作系统的 Context Switch 做一个全面的梳理.最终可以了解 Preemption 和 Context Switch 概念上的区别与联系. 1.1 Context Switch C