不,调度程序没有在它自己的核心中运行。事实上,在多核 CPU 普及之前,多线程就很普遍了。
了解调度程序代码如何与线程代码交互的最佳方式是从一个简单、协作的单核示例开始。
假设thread A 正在运行,而thread B 正在等待一个事件。 thread A 发布该事件,这导致 thread B 变为可运行。事件逻辑必须调用调度程序,并且出于本示例的目的,我们假设它决定切换到thread B。此时调用堆栈将如下所示:
thread_A_main()
post_event(...)
scheduler(...)
switch_threads(threadA, threadB)
switch_threads会将CPU状态保存在栈上,保存thread A的栈指针,并用thread B的栈指针的值加载CPU栈指针。然后它将从堆栈中加载剩余的 CPU 状态,其中 堆栈 现在是堆栈 B。此时,调用堆栈已变为
thread_B_main()
wait_on_event(...)
scheduler(...)
switch_threads(threadB, threadC)
换句话说,线程 B 现在已经唤醒,处于它之前将控制权交给线程 C 时的状态。当switch_threads() 返回时,它会将控制权返回给thread B。
这种对堆栈指针的操作通常需要一些手工编码的汇编程序。
添加中断
Thread B 正在运行并且发生定时器中断。调用堆栈现在是
thread_B_main()
foo() //something thread B was up to
interrupt_shell
timer_isr()
interrupt_shell 是一个特殊函数。它不被称为。它由硬件抢先调用。 foo() 没有调用interrupt_shell,所以当interrupt_shell 将控制权交还给foo() 时,它必须准确地恢复CPU 状态。这与普通函数不同,后者根据调用约定返回离开 CPU 状态。由于interrupt_shell 遵循与调用约定不同的规则,因此也必须用汇编程序编写。
interrupt_shell 的主要工作是识别中断源并调用适当的中断服务例程 (ISR),在本例中为timer_isr(),然后将控制权返回给正在运行的线程。
添加抢占式线程切换
假设timer_isr() 决定是时间片的时候了。线程 D 将获得一些 CPU 时间
thread_B_main()
foo() //something thread B was up to
interrupt_shell
timer_isr()
scheduler()
现在,scheduler() 无法调用 switch_threads(),因为我们处于中断上下文中。但是,它可以很快被调用,通常就像interrupt_shell 所做的最后一件事一样。这使得thread B 堆栈保存在此状态
thread_B_main()
foo() //something thread B was up to
interrupt_shell
switch_threads(threadB, threadD)
添加延迟服务例程
某些操作系统不允许您执行复杂的逻辑,例如在 ISR 中进行调度。一种解决方案是使用延迟服务例程 (DSR),它以高于线程但低于中断的优先级运行。使用这些是为了虽然scheduler() 仍需要保护以免被 DSR 抢占,但可以毫无问题地执行 ISR。这减少了内核必须屏蔽(关闭)中断以保持其逻辑一致的位置数量。
我曾经将一些软件从具有 DSR 的操作系统移植到没有 DSR 的操作系统。对此的简单解决方案是创建一个比所有其他线程运行的优先级更高的“DSR 线程”。 “DSR 线程”只是替换了其他操作系统使用的 DSR 调度程序。
添加陷阱
您可能已经在我目前给出的示例中观察到,我们正在从线程和中断上下文中调用调度程序。有两条进路和两条出路。它看起来有点奇怪,但它确实有效。然而,向前看,我们可能希望将线程代码与内核代码隔离开来,我们使用陷阱来做到这一点。这是使用陷阱重做的事件发布
thread_A_main()
post_event(...)
user_space_scheduler(...)
trap()
interrupt_shell
kernel_space_scheduler(...)
switch_threads(threadA, threadB)
陷阱导致中断或类似中断的事件。在 ARM CPU 上,它们被称为“软件中断”,这是一个很好的描述。
现在所有对switch_threads() 的调用都在中断上下文中开始和结束,这通常发生在特殊的 CPU 模式下。这是迈向特权分离的一步。
如您所见,日程安排不是一天完成的。你可以继续:
- 添加内存映射器
- 添加进程
- 添加多个核心
- 添加超线程
- 添加虚拟化
阅读愉快!