操作系统调度程序如何重新获得CPU的控制权?

73

我最近开始学习CPU和操作系统的工作原理,但对于提供多任务处理的单CPU机器的操作有些困惑。

假设我的机器只有一个CPU,这意味着在任何给定的时间,只能运行一个进程。

现在,我可以假设操作系统用来控制访问宝贵的CPU时间的调度程序也是一个进程。

因此,在这台机器上,任何时候都只有用户进程或调度系统进程之一在运行,而不是两者同时运行。

所以问题来了:

一旦调度程序将CPU的控制权交给另一个进程,它如何重新获得CPU时间以再次运行自己以执行其调度工作?我的意思是,如果当前正在运行的任何给定进程都不放弃CPU,则调度程序本身如何再次运行并确保适当的多任务处理?

到目前为止,我一直在想,好吧,如果用户进程通过系统调用请求I/O操作,那么在系统调用中我们可以确保调度程序再次分配一些CPU时间。但我甚至不确定这是否以这种方式起作用。

另一方面,如果所讨论的用户进程天生就需要大量CPU运算,那么从这个角度来看,它可能会永远运行,从而不让其他进程甚至调度程序再次运行。

假设采用时间片轮转调度算法,我不知道当它甚至没有在运行时,调度程序如何为另一个进程切割时间以执行?

如果您能提供任何见解或参考资料,我将不胜感激。


7
我希望我可以尽力点赞这个问题。我开始学习和你一样的概念,也遇到了同样的问题。这些内容应该和CPU和操作系统设计的基础知识一样出现在我们的书籍和文章中。感谢您发布这个问题! - Anton Sergeyev
3个回答

58

操作系统设置一个硬件定时器 (可编程间隔计时器或PIT),每隔N毫秒生成一次中断。该中断被传递到内核,并打断用户代码的执行。

它的工作原理与其他硬件中断类似。例如,当磁盘完成IO操作时,它会强制切换到内核。


9
这是正确的答案。但例如,在旧系统中为 Windows 98 编程时,程序员必须显式地调用 yield() 命令。否则,没有其他进程能够重新掌控 CPU,最终会导致系统崩溃。 - MatijaSh
如果你首先提到了至关重要的IO中断,我会再加一点。 - Martin James
5
Windows 98肯定不需要那个。 - jalf
为什么Win98最终会崩溃? - piggyback
6
如Jalf所提到的,32位Windows(Windows NT 4.0,Windows 95)及其后继版本实现了抢占式多任务处理。而你所说的内容仅适用于Windows 3.x及更早版本(这些版本采用的是协作式多任务处理)。 - raiks
@MatijaSh 为什么会导致系统崩溃?我同意其他一切都会无响应。拥有CPU的程序应该仍然运行吧? - undefined

13

谷歌“中断”。中断是多线程、抢占式内核(如Linux/Windows)的核心。如果没有中断,操作系统将永远不会做任何事情。

在调查/学习时,请尝试忽略任何解释中在第一段中提到的“定时器中断”、“循环轮询”和“时间片”或“量子” - 如果不是错误的话,它们会带来极大的误导。

在操作系统术语中,中断有两种类型:

  • 硬件中断-由外围设备发出的实际硬件信号引起的。这些可能发生在(几乎)任何时间,并将执行从正在运行的任何线程切换到驱动程序中的代码。

  • 软件中断-由当前运行的线程从操作系统调用引起的中断。

任何一种中断都可能请求调度程序使处于等待状态的线程准备/运行,或者导致正在等待/运行的线程被抢占。

最重要的中断是来自外围设备驱动程序的硬件中断-使正在等待来自磁盘、NIC卡、鼠标、键盘、USB等硬件的数据的线程准备就绪。使用抢占式内核的最主要原因以及所有锁定、同步、信号等问题,是这样的系统具有非常好的IO性能,因为硬件外围设备可以快速地使正在等待该硬件数据的线程准备/运行,而不需要任何来自不合作线程或等待周期性计时器重新安排的延迟。

导致周期性调度运行的硬件定时器中断很重要,因为许多系统调用都有超时机制,以防止例如外围响应时间过长。

在多核系统上,操作系统有一个跨处理器驱动程序,可以在其他核心上引发硬件中断,从而允许操作系统将线程中断/调度/分派到多个核心上。

在严重超载的盒子或运行CPU密集型应用程序(少数)的盒子上,操作系统可以使用周期性定时器中断和由此产生的调度来循环运行一组就绪线程,这组线程大于可用核心数量,并允许每个线程共享可用的CPU资源。在大多数系统中,这种情况很少发生,重要性不大。

每当我看到“量子”、“放弃其时间片的其余部分”、“轮流使用”等类似术语时,我都感到非常不适...


那就是我一直在寻找的正确答案 :) - pfyuit
没有中断,操作系统将永远不会做任何事情。我不认为这是真的。而这正是“放弃剩余的时间片”有用的地方。Sleep() Windows API 调用给内核(操作系统)一个机会去做一些事情。而这正是在合作式多任务操作系统中所谓的合作的含义。 - undefined
在操作系统的术语中,sleep()调用就是一个中断。 - undefined
此外,如果没有实际的硬件定时器中断,那么如何使一个休眠的线程再次准备/运行呢? - undefined
没有硬件中断的情况下,其他人需要调用Sleep()(合作性地)以便内核能够再次执行其任务。你可以将其想象成一个单一的调用堆栈:内核 -> 程序A -> sleep() -> 内核 -> 程序B -> sleep() -> 内核 -> 程序A -> sleep() -> 等等。但当然这样是行不通的,因为调用堆栈会无限增长。所以我们需要进行堆栈交换。 - undefined

5
为了补充@usr的回答,引用自《理解Linux内核》schedule( )函数 schedule( )实现了调度程序。它的目标是在运行队列列表中找到一个进程,然后将CPU分配给它。它由几个内核例程直接或间接地调用。[...] 惰性调用 通过将当前[进程]的need_resched字段设置为1,也可以以惰性方式调用调度程序。由于在恢复用户模式进程的执行之前(参见第4章“从中断和异常返回”中的部分),始终会检查此字段的值,因此在不久的将来一定会调用schedule( )。

1
+1 非常感谢这个优秀的参考资料。我在你引用的书中找到了 usr 在其他答案中提供的解释:“当然,单个处理器一次只能运行一个进程。[...] 时间共享依赖于计时器中断,因此对进程透明。不需要在程序中插入任何额外的代码来确保 CPU 时间共享。” - Edwin Dalorzo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接