为什么在中断上下文中执行的内核代码/线程无法休眠?

时间:2009-06-27 20:41:27

标签: linux-kernel

我正在阅读Robert Love的以下文章

http://www.linuxjournal.com/article/6916

“...让我们讨论工作队列在进程上下文中运行的事实。这与其他下半部机制形成对比,后者都在中断上下文中运行。在中断上下文中运行的代码无法休眠或阻塞因为中断上下文没有用于重新调度的后备进程。因此,由于中断处理程序与进程没有关联,因此调度程序无需进入休眠状态,更重要的是,调度程序无需唤醒。 ..“

我不明白。 AFAIK,内核中的调度程序是O(1),它是通过位图实现的。那么什么阻止了scehduler将中断上下文置于睡眠状态并采取下一个可调度进程并将其传递给控件?

11 个答案:

答案 0 :(得分:41)

  

那么是什么阻止了scehduler将中断上下文置于睡眠状态并采取下一个可调度进程并将其传递给控件?

问题是中断上下文不是进程,因此无法进入休眠状态。

当发生中断时,处理器将寄存器保存到堆栈中并跳转到中断服务程序的开始。这意味着当中断处理程序正在运行时,它正在中断发生时正在执行的进程的上下文中运行。中断正在该进程的堆栈上执行,当中断处理程序完成时,该进程将继续执行。

如果你试图在中断处理程序中睡眠或阻塞,你不仅会停止中断处理程序,还会中断它中断的进程。这可能很危险,因为中断处理程序无法知道中断进程正在做什么,或者即使该进程被挂起也是安全的。

事情可能出错的一个简单场景是中断处理程序和它中断的进程之间的死锁。

  1. Process1 进入内核模式。
  2. Process1 获取 LockA
  3. 发生中断。
  4. ISR使用 Process1 的堆栈开始执行。
  5. ISR尝试获取 LockA
  6. ISR调用sleep以等待 LockA 被释放。
  7. 此时,您遇到了僵局。在使用其堆栈完成ISR之前, Process1 无法恢复执行。但ISR被阻止等待 Process1 发布 LockA

答案 1 :(得分:29)

我认为这是一个设计理念。

当然,您可以设计一个可以在中断时睡觉的系统,但除了使系统难以理解和复杂(许多情况需要考虑)之外,这对任何事都没有帮助。因此从设计的角度来看,声明中断处理程序无法入睡非常清晰且易于实现。


来自Robert Love(内核黑客): http://permalink.gmane.org/gmane.linux.kernel.kernelnewbies/1791

您无法在中断处理程序中睡眠,因为中断没有 支持流程上下文,因此无需重新安排 成。换句话说,中断处理程序与任务无关, 所以没有什么可以“入睡”而且(更重要的是)“无所事事” 醒来“。他们必须原子地运行。

这与其他操作系统不同。在大多数操作系统中, 中断没有线程。然而,通常是下半部分。

页面错误处理程序可以睡眠的原因是它只被调用 通过在进程上下文中运行的代码。因为内核是自己的 内存不可分页,只有用户空间内存访问才能产生 页面错误。因此,只有几个特定的​​地方(如打电话给 copy_ {to,from} _user())可能导致内核中的页面错误。那些 必须由可以睡眠的代码(即,处理上下文, 没有锁,等等。

答案 2 :(得分:6)

因为此时线程切换基础结构无法使用。在处理中断时,只能执行更高优先级的内容 - 请参阅Intel Software Developer's Manual on interrupt, task and processor priority。如果你确实允许另一个线程执行(你在问题中暗示这很容易),你将无法让它做任何事情 - 如果它导致页面错误,你必须使用服务在内核中,在中断服务期间无法使用(请参阅下面的原因)。

通常,中断例程中的唯一目标是让设备停止中断并在较低的中断级别排队(在unix中这通常是非中断级别,但对于Windows,它是分派,apc或被动在你可以访问kernel / os的更多功能的情况下进行繁重的工作。见 - Implementing a handler

这是O / S必须如何工作的属性,而不是Linux中固有的东西。中断例程可以在任何时刻执行,因此您中断的状态不一致。如果您中断了线程调度代码,则其状态不一致,因此您无法确定可以“休眠”并切换线程。即使您保护线程切换代码不被中断,线程切换也是O / S的一个非常高级别的功能,如果您保护它所依赖的所有内容,中断就会比其名称隐含的命令更具建议性。 / p>

答案 3 :(得分:3)

  

那么是什么阻止了scehduler将中断上下文置于睡眠状态并采取下一个可调度进程并将其传递给控件?

调度发生在定时器中断上。基本规则是一次只能打开一个中断,所以如果你在“从设备X获取数据”中断进入休眠状态,则无法运行定时器中断来安排它。

中断也会多次发生并重叠。如果您将“获取数据”中断置于休眠状态,然后获取更多数据,会发生什么?令人困惑(并且脆弱),以至于所有规则都是:没有在中断中睡觉。你会做错的。

答案 4 :(得分:2)

即使您可以让ISR进入睡眠状态,您也不会想要这样做。您希望ISR尽可能快,以降低丢失后续中断的风险。

答案 5 :(得分:1)

禁止中断处理程序阻止是一种设计选择。当某些数据在设备上时,中断处理程序拦截当前进程,准备数据传输并启用中断;在处理程序启用当前中断之前,设备必须挂起。我们希望保持I / O忙,我们的系统响应,然后我们最好不要阻止中断处理程序。

我不认为“不稳定状态”是一个重要原因。进程,无论它们处于用户模式还是内核模式,都应该意识到它们可能被中断中断。如果中断处理程序和当前进程都会访问某些内核模式数据结构,并且存在竞争条件,那么当前进程应该禁用本地中断,而且对于多处理器体系结构,应该在关键部分使用自旋锁

我也不认为中断处理程序是否被阻止,它无法被唤醒。当我们说“阻塞”时,基本上它意味着阻塞的进程正在等待某个事件/资源,因此它将自己链接到该事件/资源的某个等待队列。每当资源被释放时,释放过程负责唤醒等待过程。

然而,真正讨厌的事情是被阻止的进程在阻塞时间内无能为力;这种惩罚没有错,这是不公平的。没有人可以肯定地预测阻塞时间,因此无辜的过程必须等待不明原因和无限时间。

答案 6 :(得分:1)

本质上,问题是在中断处理程序中是否可以获得有效的“当前”(当前进程task_structure的地址),如果是,则可以相应地修改其中的内容以使其进入“睡眠”状态,如果状态以某种方式改变,稍后可以由调度程序返回。答案可能取决于硬件。

但在ARM中,由于“当前”与中断模式下的处理无关,因此不可能。请参阅以下代码:

#linux/arch/arm/include/asm/thread_info.h 
94 static inline struct thread_info *current_thread_info(void)
95 {
96  register unsigned long sp asm ("sp");
97  return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
98 }
在USER模式和SVC模式下的sp是“相同的”(这里的“相同”并不意味着它们是相等的,相反,用户模式的sp指向用户空间堆栈,而svc模式的sp r13_svc指向内核堆栈,其中用户进程的task_structure在上一个任务切换时更新,当系统调用发生时,进程再次进入内核空间,当sp(sp_svc)仍未更改时,这两个sp相互关联,从这个意义上说,它们'因此,在SVC模式下,内核代码可以获得有效的“当前”。但在其他特权模式下,比如说中断模式,sp是'不同',指向cpu_init()中定义的专用地址。在这些模式下计算的“当前”将与中断的过程无关,访问它将导致意外行为。这就是为什么总是说系统调用可以休眠而中断处理程序不能,系统调用在进程上下文中工作但不中断。

答案 7 :(得分:0)

高级中断处理程序屏蔽所有低优先级中断的操作,包括系统定时器中断的操作。因此,中断处理程序必须避免将自身卷入可能导致其休眠的活动中。如果处理程序休眠,则系统可能会挂起,因为计时器被屏蔽并且无法调度休眠线程。 这有意义吗?

答案 8 :(得分:0)

如果更高级别的中断例程到达必须在一段时间之后发生的下一件事,那么它需要将一个请求放入定时器队列,要求运行另一个中断例程(一段时间后,优先级较低。

当该中断例程运行时,它会将优先级提升回原始中断例程的级别,并继续执行。这与睡眠效果相同。

答案 9 :(得分:0)

linux内核有两种分配中断堆栈的方法。一个是在中断进程的内核堆栈上,另一个是每个CPU的专用中断堆栈。如果每个CPU将中断上下文保存在专用中断堆栈上,那么实际上中断上下文与任何进程完全无关。 “当前”宏将产生指向当前运行进程的无效指针,因为具有某种体系结构的“当前”宏是使用堆栈指针计算的。中断上下文中的堆栈指针可能指向专用中断堆栈,而不是某个进程的内核堆栈。

答案 10 :(得分:0)

它只是Linux OS中的设计/实现选择。这种设计的优点很简单,但它可能不适合实时操作系统要求。

其他操作系统有其他设计/实现。

例如,在Solaris中,中断可能具有不同的优先级,允许在中断线程中调用大多数设备中断。中断线程允许休眠,因为每个中断线程在线程的上下文中都有单独的堆栈。 中断线程设计适用于实时线程,它应该具有比中断更高的优先级。