现在有一些我一直想知道的事情:sleep()是如何实现的?
如果只是使用操作系统中的API,那么API是如何制作的?
这一切都归结为在CPU上使用特殊的机器代码吗?那个CPU是否需要一个特殊的协处理器或其他小玩意,没有它你就不能睡觉()?
睡眠()的最着名的化身在C语言中(更准确地说,在C编译器附带的库中,例如GNU的libc),尽管现在几乎每种语言都有它的等价物,但是在某些语言中实现了睡眠(认为Bash)不是我们在这个问题上看到的......
编辑:在阅读了一些答案之后,我看到该进程被置于等待队列中.从那里,我可以猜出两种选择
设置一个计时器,以便内核在适当的时间唤醒进程,或者
每当内核被允许一个时间片时,它会轮询时钟以检查是否是时候唤醒一个进程.
答案只提到备选1.因此,我问:这个计时器如何表现?如果这是一个让内核唤醒进程的简单中断,那么内核如何要求定时器"在140毫秒内唤醒我,以便我可以将进程置于运行状态"?
问题的"更新"显示了对现代操作系统如何工作的一些误解.
内核不是"允许"的时间片.内核是为用户进程提供时间片的东西."计时器"未设置为唤醒休眠进程 - 它被设置为停止当前正在运行的进程.
本质上,内核试图通过停止CPU上的进程太长时间来公平地分配CPU时间.对于简化图片,假设不允许任何进程使用CPU超过2毫秒.因此,内核会将计时器设置为2毫秒,并让进程运行.当计时器触发中断时,内核获得控制权.它保存了运行进程的当前状态(寄存器,指令指针等),并且不返回控件.相反,从等待给予CPU的进程列表中选择另一个进程,并且被中断的进程进入队列的后面.
休眠过程根本不在等待CPU的队列中.相反,它存储在休眠队列中.每当内核获得定时器中断时,都会检查休眠队列,并将时间已到的进程转移到"等待CPU"队列.
当然,这是一种严格的简化.它需要非常复杂的算法来确保安全性,公平性,平衡性,优先级,防止饥饿,快速完成所有操作以及用于内核数据的最小内存量.
有一个称为睡眠队列的内核数据结构.这是一个优先级队列.每当将进程添加到休眠队列时,计算最近即将被唤醒的进程的到期时间,并设置计时器.此时,已过期的作业将从队列中取出,并且该过程将继续执行.
(有趣的琐事:在旧的unix实现中,有一个队列用于调用fork()的进程,但是没有创建子进程.它当然被称为fork队列.)
HTH!
也许操作系统的主要工作是隐藏应用程序编写器中真实硬件的复杂性.因此,任何关于操作系统如何工作的描述都会带来变得非常复杂,非常快的风险.因此,我不打算处理所有"假设"和"是的",而是"真正的操作系统需要处理的问题.我只是在高概念层面描述一个过程是什么,什么是调度程序,定时器队列如何工作.希望这是有帮助的.
什么是过程:
想想一个过程 - 让我们先谈谈流程,然后再谈谈线程 - 作为"操作系统安排的事情".进程有一个ID - 想一个整数 - 您可以将该整数视为包含该进程所有上下文的表的索引.
上下文是硬件信息 - 寄存器,内存管理单元内容,其他硬件状态 - 当加载到机器中时,将允许进程"去".上下文还有其他组件 - 打开文件列表,信号处理程序状态,最重要的是,这里是进程正在等待的事情.
进程花费大量时间睡觉(也就是等待)
一个过程花费大量时间等待.例如,读取或写入磁盘的进程将花费大量时间等待数据到达或被确认在磁盘上.操作系统人员使用术语"等待"和"休眠"(和"阻塞")在某种程度上可以互换 - 所有这些都意味着该过程正在等待某些事情发生,然后才能继续它的快乐方式.操作系统API sleep()碰巧使用底层操作系统机制进行休眠进程,这简直令人困惑.
进程可以等待其他事情:例如,网络数据包到达,窗口选择事件或计时器到期.
流程和调度
等待的进程被认为是不可运行的.它们不会进入操作系统的运行队列.但是当事件发生时进程正在等待时,它会导致操作系统将进程从非可运行状态移动到可运行状态.同时,操作系统将进程放在运行队列上,这实际上不是一个队列 - 它更像是一堆所有进程,如果操作系统决定这样做,就可以运行.
调度:
操作系统定期决定应该运行哪些进程.操作系统决定这样做的算法被称为调度算法,这有点不足为奇.调度算法的范围从死简单("每个人运行10毫秒,然后队列中的下一个人运行")到更复杂(考虑到进程优先级,执行频率,运行时限,进程间依赖性,链式锁和各种其他复杂的主题).
计时器队列 计算机里面有一个计时器.有许多方法可以实现,但经典的方式称为周期性计时器.周期性定时器定期滴答 - 在今天的大多数操作系统中,我相信这个速率是每秒100次--100 Hz - 每10毫秒.我将在后面的具体速率中使用该值,但是知道大多数值得盐的操作系统可以配置不同的刻度 - 并且许多不使用此机制并且可以提供更好的计时器精度.但我离题了.
每个勾选都会导致操作系统中断.
当操作系统处理此计时器中断时,它会将系统时间的概念再增加10毫秒.然后,它查看计时器队列并确定该队列上需要处理的事件.
计时器队列实际上是 "需要处理的事物"的队列,我们将其称为事件.此队列按到期时间排序,首先是最快的事件.
"事件"可以是"唤醒进程X"或"在那里进行磁盘I/O,因为它可能已经卡住",或"在那边的那个光纤通道链路上发送一个keepalive数据包".无论操作系统需要做什么.
当您以这种方式订购队列时,可以轻松管理出队.操作系统只是查看队列的头部,并在每次滴答时将事件的"到期时间"减少10毫秒.当到期时间变为零时,OS将该事件出列,并执行所要求的任何操作.
在休眠过程的情况下,它只是使过程再次运行.
简单,对吧?
至少有两个不同的级别来回答这个问题.(以及很多其他与之混淆的事情,我不会碰它们)
一个应用程序级别,这就是C库的功能.这是一个简单的OS调用,它只是告诉操作系统在时间过去之前不要给这个过程提供CPU时间.操作系统有一个暂停的应用程序队列,以及一些关于他们等待的信息(通常是时间,或某些数据出现在某处).
内核级别.当操作系统现在没有任何操作时,它会执行'hlt'指令.这条指令什么都不做,但它永远不会完成.当然,硬件中断正常得到服务.简而言之,操作系统的主循环看起来像这样(从很远的地方开始):
allow_interrupts (); while (true) { hlt; check_todo_queues (); }
中断处理程序简单地将东西添加到todo队列中.实时时钟被编程为周期性地(以固定速率)产生中断,或者在下一个过程想要被唤醒时的某个固定时间产生中断.
多任务操作系统有一个称为调度程序的组件,该组件负责为线程提供CPU时间,调用睡眠告诉操作系统不要给这个线程一段时间的CPU时间.
有关完整的详细信息,请参见http://en.wikipedia.org/wiki/Process_states.
我对Linux一无所知,但我可以告诉你在Windows上会发生什么.
Sleep()使进程的时间片立即结束,以将控制权返回给OS.然后,操作系统会设置一个计时器内核对象,该对象在经过一段时间后会发出信号.在内核对象发出信号之前,操作系统将不再提供该进程.即使这样,如果其他进程具有更高或相同的优先级,它可能仍然需要等待一段时间才能继续进程.
OS使用特殊CPU机器代码进行过程切换.用户模式代码无法访问这些函数,因此可以通过对OS的API调用严格访问它们.
基本上,是的,有一个"特殊的小发明" - 它不仅仅是睡眠()更重要.
传统上,在x86上,这是Intel 8253或8254"可编程间隔定时器".在早期的PC中,这是主板上的一个单独的芯片,可以由CPU编程以在预设的时间间隔后断言(通过"可编程中断控制器",另一个分立芯片).功能仍然存在,虽然它现在是更大块主板电路的一小部分.
今天的操作系统仍然对PIT进行编程以定期唤醒它(在Linux的最新版本中,默认情况下每毫秒一次),这就是内核能够实现先发制人的多任务处理的方式.