代码之家  ›  专栏  ›  技术社区  ›  Methos

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

  •  47
  • Methos  · 技术社区  · 15 年前

    我在读罗伯特·洛夫的以下文章

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

    那说

    “…让我们讨论工作队列在流程上下文中运行的事实。这与其他下半部分机制不同,后者都在中断上下文中运行。在中断上下文中运行的代码无法休眠或阻塞,因为中断上下文没有用于重新计划的后备进程。因此,由于中断处理程序不与进程关联,因此调度程序没有任何可供睡眠的内容,更重要的是,调度程序也没有任何可唤醒的内容……”

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

    11 回复  |  直到 7 年前
        1
  •  28
  •   Henridv    12 年前

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

    当然,你可以设计一个你可以在中断时睡眠的系统,但是除了使系统难以理解和复杂(许多情况下你必须考虑),这对任何事情都没有帮助。所以从设计的角度来看,声明中断处理程序为不能休眠是非常清晰和容易实现的。


    来自罗伯特·洛夫(一个内核黑客): http://permalink.gmane.org/gmane.linux.kernel.kernelnewbies/1791

    您不能在中断处理程序中睡眠,因为中断没有 一个支持进程上下文,因此没有什么可重新安排的。 进入。换句话说,中断处理程序不与任务关联, 所以没有什么可以“安睡”,更重要的是,“没有什么可以 “醒醒”。它们必须原子地运行。

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

    页面错误处理程序可以休眠的原因是它只被调用 通过在进程上下文中运行的代码。因为内核是自己的 内存不可分页,只有用户空间内存访问才能导致 页面错误。因此,只有几个特定的地方(如 复制到,从到,从那些 所有位置都必须由可以睡眠的代码(即,进程上下文, 没有锁,等等)。

        2
  •  41
  •   Keith Smith    15 年前

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

    问题是,中断上下文不是一个进程,因此不能进入睡眠状态。

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

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

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

    1. 进程1 进入内核模式。
    2. 进程1 获得 洛卡 .
    3. 中断发生。
    4. ISR开始执行时使用 进程1 堆栈。
    5. ISR试图获取 洛卡 .
    6. ISR呼叫休眠等待 洛卡 被释放。

    这时,你就陷入了僵局。 进程1 在ISR完成其堆栈之前无法恢复执行。但是ISR被阻止等待 进程1 释放 洛卡 .

        3
  •  6
  •   Tony Lee    7 年前

    因为线程交换基础结构在那一点上是不可用的。当服务一个中断时,只有高优先级的东西才能执行-参见 Intel Software Developer's Manual on interrupt, task and processor priority . 如果您允许执行另一个线程(在您的问题中,这意味着它很容易执行),那么您就不能让它做任何事情-如果它导致了页面错误,那么您就必须在内核中使用中断服务时不可用的服务(请参见下面的原因)。

    通常,中断例程中的唯一目标是让设备停止中断,并在较低的中断级别(在UNIX中,这通常是非中断级别,但对于Windows,它是分派、APC或被动级别)对某些内容进行排队,以便在可以访问内核/OS的更多功能的地方进行繁重的提升。见- Implementing a handler .

    这是O/S必须如何工作的特性,而不是Linux固有的特性。中断例程可以在任何点执行,因此您中断的内容的状态是不一致的。如果您中断了线程调度代码,那么它的状态是不一致的,因此您不能确定是否可以“休眠”和切换线程。即使您保护线程切换代码不被中断,线程切换也是O/S的一个非常高级的特性,并且如果您保护了它所依赖的一切,那么一个中断就变成了一个建议,而不是其名称所隐含的命令。

        4
  •  3
  •   Andres Jaan Tack    15 年前

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

    调度发生在计时器中断时。基本规则是一次只能打开一个中断,因此如果您在“从设备x获取数据”中断中进入睡眠状态,则计时器中断无法运行以将其排定。

    中断也会发生多次并重叠。如果您将“获取数据”中断置于睡眠状态,然后获取更多数据,会发生什么?它足够混乱(和脆弱)以至于“抓住一切”的规则是:不要在中断中睡觉。你会做错事的。

        5
  •  2
  •   Ryan Fox    15 年前

    即使你能让ISR休眠,你也不想这样做。您希望您的ISR尽可能快,以减少丢失后续中断的风险。

        6
  •  0
  •   Sandip    15 年前

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

        7
  •  0
  •   John Saunders    15 年前

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

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

        8
  •  0
  •   Shijun Zhou    14 年前

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

        9
  •  0
  •   Infinite    14 年前

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

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

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

    然而,真正令人恼火的是,被阻塞的进程在阻塞时间内什么也做不到;对于这种惩罚,它没有做任何错误,这是不公平的。没有人能确定阻塞时间,因此无辜的过程必须等待不清楚的原因和无限的时间。

        10
  •  0
  •   Oliver Yang    12 年前

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

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

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

        11
  •  0
  •   samchen2009    11 年前

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

    但在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 }
    

    用户模式下的sp和svc模式是“相同的”(这里“相同”并不意味着它们是相等的,相反,用户模式的sp指向用户空间堆栈,而svc模式的sp r13_svc指向内核堆栈,用户进程的任务结构在上一个任务切换时更新,当发生系统调用时,当sp(sp_svc)时,进程再次进入内核空间。仍然没有改变,这两个SP是相互关联的,在这个意义上,它们是“相同的”),所以在svc模式下,内核代码可以得到有效的“current”。但是在其他特权模式下,比如中断模式,sp是“不同的”,指向在cpu_init()中定义的专用地址。在这些模式下计算的“电流”将与中断的进程无关,访问它将导致意外的行为。这就是为什么总是说系统调用可以休眠,但中断处理程序不能,系统调用在进程上下文上工作,但中断不能。