睡眠()后面的algorithm是什么?

现在我总是想知道:sleep()是如何实现的?

如果是从操作系统使用API​​,那么API是如何制作的?

这一切都归结为在CPU上使用特殊的机器码吗? 那个CPU是否需要一个特殊的协处理器或其他小玩意儿,没有它你不能睡觉()?

睡眠()的最着名的化身是C语言(在C语言编译器附带的库(比如GNU的libc)中更加准确),尽pipe今天几乎所有的语言都有它的等价物,但是在某些语言中实现睡眠认为Bash)不是我们在这个问题上看的…

编辑:在阅读了一些答案之后,我看到这个过程被放置在一个等待队列中。 从那里,我可以猜到两种select

  1. 一个定时器被设置,以便内核在适当的时候唤醒进程,或者
  2. 每当内核被允许一个时间片时,它就轮询时钟以检查是否该唤醒进程。

答案只提到替代scheme1.因此,我问:这个计时器如何performance? 如果这是一个让内核唤醒进程的简单中断,那么内核怎么会要求定时器“在140毫秒内唤醒我,这样我才能使进程处于运行状态”?

问题的“更新”显示了对现代操作系统如何工作的一些误解。

内核不是“允许”的时间片。 内核是给用户进程提供时间片的东西。 “定时器”没有设置为唤醒睡眠过程 – 它被设置为停止当前运行的过程。

本质上,内核试图通过停止CPU上的进程太长而公平地分配CPU时间。 对于简单的图片,假设没有进程被允许使用超过2毫秒的CPU。 所以,内核会把计时器设置为2毫秒,让进程运行。 当定时器触发中断时,内核得到控制。 它保存运行进程的当前状态(寄存器,指令指针等),并不返回控制。 而是从等待给予CPU的进程列表中选取另一个进程,并将被中断的进程移到队列的后面。

睡眠过程根本就不在队列中等待CPU的东西。 相反,它存储在hibernate队列中。 每当内核得到定时器中断时,就检查睡眠队列,并将已经到达时间的进程转移到“等待CPU”队列。

这当然是一个很简单的过程。 它需要非常复杂的algorithm来确保安全性,公平性,平衡性,优先级,防止饥饿,尽快完成这些操作,并且使用最less量的内核来处理内核数据。

有一个叫做睡眠队列的内核数据结构。 这是一个优先队列。 无论何时将进程添加到睡眠队列中,计算最早被唤醒的进程的到期时间,并设置计时器。 那时,已到期的作业被从队列中取出,过程继续执行。

(有趣的琐事:在较早的unix实现中,有一个队列用于调用fork()的进程,但是没有为其创buildsubprocess,当然这叫做fork队列

HTH!

也许操作系统的主要工作是隐藏应用程序编写者的一个真实硬件的复杂性。 因此,对操作系统如何工作的任何描述都会带来真正复杂,非常快的风险。 因此,我不打算处理一个真正的操作系统需要处理的所有“假设”和“是的”,我只是想在高概念层面上描述一个过程是什么,调度程序,定时器队列是如何工作的,希望这会有帮助。

什么是一个过程:

想想一个过程 – 让我们谈谈过程,稍后再讨论 – 就像“操作系统安排的事情”一样。 一个进程有一个ID–想一个整数 – 你可以把这个整数看作一个包含该进程所有上下文的表的索引。

上下文是硬件信息 – 寄存器,内存pipe理单元内容,其他硬件状态 – 当加载到机器中时,将允许进程“走”。 还有其他的上下文组件 – 打开的文件列表,信号处理程序的状态,最重要的是这里是进程正在等待的事情

进程花了很多时间睡觉(又名等待)

一个过程大部分时间都在等待。 例如,读取或写入磁盘的进程将花费大量时间等待数据到达或被确认出现在磁盘上。 操作系统的人们可以互换地使用“等待”和“睡眠”(和“阻止”),这意味着该过程正在等待事情发生,才能继续其快乐的方式。 这只是令人困惑的是,OS API sleep()碰巧使用底层OS机制来处理进程。

例如,进程可以等待其他事情:要到达的networking数据包,窗口select事件或定时器。

进程和调度

正在等待的进程被认为是不可运行的 。 他们不会进入操作系统的运行队列。 但是,当进程正在等待的事件发生时,会导致操作系统将进程从不可运行状态转移到可运行状态。 与此同时,操作系统将进程放在运行队列上,这实际上并不是一个队列 – 更多的是所有进程,如果操作系统决定这样做,它可以运行。

调度:

操作系统会定期确定应该运行哪些进程。 调度algorithm称为操作系统决定这样做的algorithm,这一点也不令人惊讶。 调度algorithm的范围从简单的(“每个人都跑10 ms,然后队列中的下一个人跑得更快)”,要复杂得多(考虑进程优先级,执行频率,运行时间限制,进程间依赖关系,链锁和各种其他复杂的主题)。

计时器队列计算机中有一个计时器。 有很多方法可以实现,但经典的方式称为周期性定时器 。 一个周期性的计时器以一个固定的时间间隔滴答 – 在今天的大多数操作系统中,我相信这个速率是每秒100次 – 每100毫秒 – 每10毫秒。 我将在后面以具体的速率使用该值,但是要知道,大部分值得使用的操作系统都可以configuration不同的刻度 – 许多操作系统不使用这种机制,并且可以提供更好的定时器精度。 但是我离题了。

每个刻度都会导致操作系统中断。

当OS处理这个定时器中断时,它将系统时间的思想增加10ms。 然后,它查看计时器队列,并决定该队列上的哪些事件需要处理。

定时器队列确实一个“需要处理的事情”的队列,我们​​将其称为事件。 该队列按到期时间sorting,最早的事件是sorting的。

一个“事件”可以是类似于“唤醒进程X”,或者“在那里启动磁盘I / O,因为它可能已经卡住了”,或者“在那边的光纤通道链路上发送一个保活包”。 无论操作系统需要做什么。

当你用这种方式排队时,pipe理排队很容易。 操作系统只是查看队列的头部,并将事件的“到期时间”每个滴答减less10毫秒。 当到期时间为零时,操作系统将该事件取出,并执行任何所需的操作。

在进程hibernate的情况下,它只是使进程再次运行。

简单,嗯?

一个多任务操作系统有一个称为调度程序的组件,这个组件负责给CPU线程提供CPU时间,调用sleep让操作系统在一段时间内不给这个线程CPU时间。

有关完整的详细信息,请参阅http://en.wikipedia.org/wiki/Process_states

至less有两个不同的层次来回答这个问题。 (还有很多其他的东西都和它混淆了,我不会去碰它们)

  1. 一个应用程序级别,这是C库所做的。 这是一个简单的操作系统调用,它只是告诉操作系统不要给这个过程CPU时间,直到时间过去。 操作系统有一个被暂停的应用程序的队列,以及有关他们在等待什么的信息(通常是时间或某些数据出现在某处)。

  2. 内核级别。 当操作系统现在没有任何事情要做,它会执行一个'hlt'指令。 这条指令什么都不做,但是它永远不会完成。 当然,硬件中断服务正常。 简而言之,操作系统的主循环看起来像这样(来自非常非常远的地方):

     allow_interrupts();
     while(true){
       HLT;
       check_todo_queues();
     }
    

    中断处理程序simpy将东西添加到todo队列中。 实时时钟被编程为周期性地(以固定的速率)产生中断,或者在下一个过程想要被唤醒时的某个固定时间产生中断。

我对Linux一无所知,但我可以告诉你在Windows上会发生什么。

Sleep()会导致进程的时间片立即结束,将控制权交还给操作系统。 然后,操作系统设置一个计时器内核对象,在经过一段时间后得到信号。 然后,操作系统将不再给予该进程更多的时间,直到内核对象得到信号。 即使如此,如果其他进程的优先级更高或相同,在继续进程之前可能还会等待一段时间。

OS使用特殊的CPU机器代码来进行过程切换。 这些函数不能被用户模式代码访问,所以它们严格通过API调用访问OS。

基本上,是的,有一个“特别的小发明” – 这对于睡眠不仅仅是重要的。

通常,在x86上,这是一个Intel 8253或8254“可编程间隔定时器”。 在早期的个人电脑中,这是主板上的一个单独的芯片,可以由CPU编程,在预设的时间间隔之后(通过“可编程中断控制器”,另一个分立芯片)发出中断。 function仍然存在,虽然它现在是一个更大的主板电路块的一小部分。

现在的操作系统仍然会对PIT进行编程,以便定期将其唤醒(在最新版本的Linux中,默认情况下每毫秒一次),这就是内核如何实现先占式多任务。

glibc 2.21 Linux

转发到nanosleep系统调用。

glibc是大多数Linux桌面发行版的C stdlib的默认实现。

如何find它:第一个reflection是:

 git ls-files | grep sleep 

这包含:

 sysdeps/unix/sysv/linux/sleep.c 

我们知道:

 sysdeps/unix/sysv/linux/ 

包含Linux细节。

在该文件的顶部,我们看到:

 /* We are going to use the `nanosleep' syscall of the kernel. But the kernel does not implement the stupid SysV SIGCHLD vs. SIG_IGN behaviour for this syscall. Therefore we have to emulate it here. */ unsigned int __sleep (unsigned int seconds) 

所以如果你信任评论,我们基本上就完成了。

在底部:

  weak_alias (__sleep, sleep) 

基本上说__sleep == sleep 。 该函数使用nanosleep

 result = __nanosleep (&ts, &ts); 

greppingg之后:

 git grep nanosleep | grep -v abilist 

我们得到一个有趣的事件的小列表,我认为__nanosleep定义在:

 sysdeps/unix/sysv/linux/syscalls.list 

在线上:

 nanosleep - nanosleep Ci:pp __nanosleep nanosleep 

这是一些超级DRY魔法格式parsing:

 sysdeps/unix/make-syscalls.sh 

然后从build目录中:

 grep -r __nanosleep 

/sysd-syscalls我们: /sysd-syscalls这是make-syscalls.sh生成并包含的内容:

 #### CALL=nanosleep NUMBER=35 ARGS=i:pp SOURCE=- ifeq (,$(filter nanosleep,$(unix-syscalls))) unix-syscalls += nanosleep $(foreach p,$(sysd-rules-targets),$(foreach o,$(object-suffixes),$(objpfx)$(patsubst %,$p,nanosleep)$o)): \ $(..)sysdeps/unix/make-syscalls.sh $(make-target-directory) (echo '#define SYSCALL_NAME nanosleep'; \ echo '#define SYSCALL_NARGS 2'; \ echo '#define SYSCALL_SYMBOL __nanosleep'; \ echo '#define SYSCALL_CANCELLABLE 1'; \ echo '#include <syscall-template.S>'; \ echo 'weak_alias (__nanosleep, nanosleep)'; \ echo 'libc_hidden_weak (nanosleep)'; \ ) | $(compile-syscall) $(foreach p,$(patsubst %nanosleep,%,$(basename $(@F))),$($(p)CPPFLAGS)) endif 

它看起来像一个Makefile的一部分。 git grep sysd-syscalls显示它包含在:

 sysdeps/unix/Makefile:23:-include $(common-objpfx)sysd-syscalls 

compile-syscall看起来像是关键部分,所以我们find:

 # This is the end of the pipeline for compiling the syscall stubs. # The stdin is assembler with cpp using sysdep.h macros. compile-syscall = $(COMPILE.S) -o $@ -x assembler-with-cpp - \ $(compile-mkdep-flags) 

请注意, -x assembler-with-cpp是一个gcc选项。

这个#define的参数是这样的:

 #define SYSCALL_NAME nanosleep 

然后在以下位置使用它们:

 #include <syscall-template.S> 

好吧,就我现在要进行的macros观扩展游戏而言。

我认为这会产生posix/nanosleep.o文件,它必须与所有东西链接在一起。

Linux 4.2 x86_64 nanosleep系统调用

使用调度程序:这不是一个繁忙的睡眠。

searchctags:

 sys_nanosleep 

将我们kernel/time/hrtimer.ckernel/time/hrtimer.c

 SYSCALL_DEFINE2(nanosleep, struct timespec __user *, rqtp, 

hrtimer代表高分辨率定时器。 从那里主线看起来像:

  • hrtimer_nanosleep
  • do_nanosleep
    • set_current_state(TASK_INTERRUPTIBLE); 这是可以中断的睡眠
    • freezable_schedule(); 调用schedule()并允许其他进程运行
  • hrtimer_start_expires
  • hrtimer_start_range_ns
  • TODO:达到arch/x86计时级别

关于它的一些文章: