调度程序在哪里运行?

时间:2017-08-14 01:09:52

标签: scheduler cpu-architecture

刚刚完成一本关于comp的书。架构,我发现自己没有完全澄清调度程序的运行位置。

我希望澄清的是调度程序正在运行的位置 - 是否已将其自己的核心分配给运行该程序而不是其他内容,或者是"调度程序"实际上只是一个更模糊的算法,它在每个正在执行的线程中实现 - 例如。在抢占线程时,运行swithToFrom()命令?

根据windows x / linux x / mac os x,我不需要具体细节。

3 个答案:

答案 0 :(得分:3)

没有调度程序没有在其自己的核心中运行。实际上,在多核CPU很常见之前,多线程是常见的 long

查看调度程序代码如何与线程代码交互的最佳方法是从一个简单,合作的单核示例开始。

假设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模式下。这是迈向特权分离的一步。

如您所见,日程安排并非一天。你可以继续:

  • 添加内存映射器
  • 添加流程
  • 添加多个核心
  • 添加超线程
  • 添加虚拟化

快乐阅读!

答案 1 :(得分:1)

每个核心分别运行内核,并通过读/写共享内存与其他核心协作。内核维护的一个共享数据结构是准备运行的任务列表,只是等待运行时间片。

内核的进程/线程调度程序在核心上运行,需要确定下一步该做什么。它是distributed algorithm,没有单一的决策线程。

通过确定应该在哪个其他 CPU上运行哪个任务,调度不起作用。它的工作原理是根据哪些任务准备好运行来确定 CPU应该现在的内容。只要线程耗尽其时间片,或者进行阻塞的系统调用,就会发生这种情况。在Linux中,even the kernel itself is pre-emptible,因此即使在系统调用过程中也可以运行高优先级的任务,这需要花费大量的CPU时间来处理。 (例如,检查open("/a/b/c/d/e/f/g/h/file", ...)中所有父目录的权限,如果它们在VFS缓存中很热,那么它不会阻塞,只会占用大量的CPU时间)。我认为这是通过将目录行走循环(由open()调用的函数“手动”调用schedule()以查看当前线程是否应该被抢占来完成的。

调度程序的决策并不总是像在队列前面执行任务那样简单:一个好的调度程序会尽量避免将一个线程从一个核心弹回到另一个核心(因为它的数据在它最后运行的核心,如果那是最近的)。因此,为了避免缓存抖动,调度程序算法可能会选择不在当前核心上运行就绪任务,如果它只是在不同的核心上运行,而是将其留给其他核心以便以后使用。这样一个简短的中断处理程序或阻塞系统调用就不会导致CPU迁移。

这在NUMA系统中尤其重要,即使缓存填充后,在“错误”核心上运行也会长期放慢。

答案 2 :(得分:-1)

三种类型的常规调度程序:

作业调度程序也称为长期调度程序。

短期调度程序也称为CPU调度程序。

中期调度程序,主要用于交换作业,因此可以进行非阻塞调用。这通常是因为没有太多的I / O工作或很少。

在操作系统手册中,它显示了这些调度程序来往的状态的自动机。作业调度程序将作业队列中的内容放入就绪队列,CPU调度程序将就绪队列从运行状态转移到运行状态。该算法就像任何其他软件一样,它必须在cpu / core上运行,它很可能是某个地方内核的一部分。

调度程序可以被抢占是没有意义的。运行时,队列中的作业可以被抢占,I / O等。没有内核不必安排自己分配任务,它只是在不调度自己的情况下获得cpu时间。是的,很可能数据可能是ram,不确定是否值得存储在cpu缓存中。