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

我正在阅读Robert Love的文章

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

说的是

“…让我们讨论工作队列在进程上下文中运行的事实,这与其他下半部分机制相反,它们都运行在中断上下文中,在中断上下文中运行的代码无法进入hibernate或阻塞状态,因为中断因为中断处理程序与进程没有关联,所以调度程序没有任何东西可以进入睡眠状态,更重要的是,没有任何东西可以让调度程序唤醒……“

我不明白。 AFAIK,内核中的调度器是O(1),这是通过位图实现的。 那么是什么阻止了调度者把中断环境放到睡眠中,并采取下一个可调度的过程,并将其传递给控制?

我认为这是一个devise理念。

当然,你可以devise一个可以中断睡眠的系统,但是除了使系统难以理解和复杂化(许多情况下你必须考虑到这些),这并没有什么帮助。 所以从deviseangular度来说,声明中断处理程序是不能睡眠的,非常清晰,易于实现。


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

你不能睡在中断处理程序中,因为中断没有后台进程的上下文,因此没有任何事情可以重新安排回去。 换句话说,中断处理程序与任务没有关联,所以没有什么可以“睡觉”,更重要的是没有什么可以唤醒的。 他们必须自动运行。

这与其他操作系统无异。 在大多数操作系统中,中断不是线程化的。 然而,下半部分往往是。

页面error handling程序可以睡眠的原因是它只能由进程上下文中运行的代码调用。 由于内核自己的内存不可分页,因此只有用户空间内存访问会导致页面错误。 因此,只有less数特定的地方(如调用copy_ {to,from} _user())会导致内核中的页面错误。 这些地方都必须由可以睡眠的代码(即处理上下文,不locking等)来完成。

那么是什么阻止了调度者把中断环境放到睡眠中,并采取下一个可调度的过程,并将其传递给控制?

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

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

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

中断处理程序和中断处理程序之间的一个死锁可能会导致错误。

  1. Process1进入内核模式。
  2. Process1获取LockA
  3. 中断发生。
  4. ISR开始使用Process1的堆栈执行。
  5. ISR试图获得LockA
  6. ISR调用睡眠等待LockA被释放。

在这一点上,你有一个僵局。 在ISR完成堆栈之前, Process1不能恢复执行。 但ISR被阻塞,等待Process1释放LockA

因为线程交换基础设施在这一点上是不可用的。 在服务中断时,只能执行更高优先级的内容 – 请参阅APIC有关中断优先级的文档 。 如果你确实允许另一个线程执行(你暗示你的问题很容易做到),你将无法做任何事 – 如果它导致页面错误,你将不得不使用服务在内核中,在中断服务期间不可用(参见下面的原因)。

通常,在中断例程中,唯一的目标是让设备停止中断,并在较低的中断级别(在unix中,这通常是非中断级别,但对于Windows,它是dispatch,apc或被动级别)排队在您可以访问内核/操作系统的更多function的地方进行繁重的工作。 请参阅 – 实现处理程序 。

编辑:这是O / S如何工作的属性,而不是Linux中固有的东西。 一个中断程序可以在任何时候执行,所以你中断的状态是不一致的。 如果你中断了线程调度代码,它的状态是不一致的,所以你不能确定你能“hibernate”并切换线程。 即使你保护线程切换代码不被中断,线程切换也是O / S的一个非常高层次的特性,如果你保护了它所依赖的所有东西,那么中断就变成了一个build议,而不是它的名字所暗示的命令。

编辑2:删除了“关机”一词的使用,因为它暗示了读者一些行动是由操作系统来做到这一点。 增加了更多权威性参考的链接来certificate我的回应。

那么是什么阻止了调度者把中断环境放到睡眠中,并采取下一个可调度的过程,并将其传递给控制?

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

中断也会发生多次并重叠。 如果你把“获得数据”中断睡觉,然后获得更多的数据,会发生什么? 这种混乱(和脆弱)足以让所有的规则是:在中断中不睡觉。 你会做错的。

即使你可以让一个ISR睡觉,你也不想这样做。 您希望您的ISR尽可能快地降低丢失后续中断的风险。

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

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

当这个中断程序运行时,它会把优先级提高到原始中断程序的等级,并继续执行。 这与睡眠效果相同。

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

禁止中断处理程序来阻止是一个deviseselect。 当一些数据在设备上时,中断处理程序拦截当前进程,准备传输数据并启用中断; 在处理程序启用当前中断之前,设备必须挂起。 我们要保持我们的I / O繁忙和我们的系统响应,那么我们最好不要阻塞中断处理程序。

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

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

然而,真正令人讨厌的是阻塞的进程在阻塞时间内什么都不能做, 这个惩罚没有做错,这是不公平的。 没有人可以肯定地预测阻塞时间,所以无辜的过程必须等待不明确的原因和无限的时间。

这只是Linux操作系统中的一个devise/实现select。 这种devise的优点很简单,但对实时操作系统的要求可能不是很好。

其他操作系统有其他devise/实现。

例如,在Solaris中,中断可以具有不同的优先级,允许大多数设备中断在中断线程中调用。 中断线程允许进入hibernate状态,因为每个中断线程在线程的上下文中都有单独的堆栈。 中断线程devise对于实时线程来说是好的,它应该比中断具有更高的优先级。

就本质而言,问题是在中断处理程序中是否可以得到一个有效的“当前”(地址到当前进程的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 } 

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