sleep()背后的算法是什么?

时间:2008-10-06 19:47:03

标签: c algorithm sleep

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

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

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

最着名的sleep()的化身在C语言中(更准确地说,在C编译器附带的库中,例如GNU的libc),虽然现在几乎每种语言都有它的等价物,但是睡眠的实现有些语言(想想Bash)不是我们在这个问题上看到的......

编辑:在阅读了一些答案后,我看到该进程被置于等待队列中。从那里,我可以猜出两种选择,

  1. 设置一个计时器,以便内核在适当的时间唤醒进程,或
  2. 每当内核被允许一个时间片时,它会轮询时钟以检查是否是时候唤醒进程。
  3. 答案只提到备选1.因此,我问:这个计时器是如何表现的?如果这是一个让内核唤醒进程的简单中断,那么内核如何要求定时器“在140毫秒内唤醒我以便让进程处于运行状态”?

8 个答案:

答案 0 :(得分:37)

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

内核不是“允许”的时间片。内核是为用户进程提供时间片的东西。 “计时器”未设置为唤醒休眠进程 - 它被设置为停止当前正在运行的进程。

本质上,内核尝试通过停止CPU上的进程太长时间来公平地分配CPU时间。对于简化图片,假设不允许任何进程使用CPU超过2毫秒。因此,内核会将计时器设置为2毫秒,并让进程运行。当计时器触发中断时,内核获得控制权。它保存了运行进程的当前状态(寄存器,指令指针等),并且不返回控件。相反,从等待获得CPU的进程列表中选择另一个进程,并且被中断的进程将进入队列的后面。

在等待CPU的事物队列中,休眠过程只是。相反,它存储在休眠队列中。每当内核获得定时器中断时,都会检查休眠队列,并将时间已到的进程转移到“等待CPU”队列。

这当然是一种粗略的简化。它需要非常复杂的算法来确保安全性,公平性,平衡性,优先级,防止饥饿,快速完成并且内核数据使用最少的内存。

答案 1 :(得分:35)

有一个称为睡眠队列的内核数据结构。这是一个优先级队列。每当将进程添加到休眠队列时,计算最近即将被唤醒的进程的到期时间,并设置计时器。此时,已过期的作业将从队列中取出,并且该过程将继续执行。

(有趣的琐事:在较旧的unix实现中,有一个队列用于调用fork()的进程,但是没有创建子进程。它当然被称为 fork队列 。)

HTH!

答案 2 :(得分:15)

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

什么是流程:

想一个过程 - 让我们先谈谈进程,然后再转到线程 - 作为“操作系统安排的事情”。进程有一个ID - 想一个整数 - 您可以将该整数视为包含该进程所有上下文的表的索引。

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

进程花费大量时间睡觉(等等)

一个过程花费大量时间等待。例如,读取或写入磁盘的进程将花费大量时间等待数据到达或被确认在磁盘上。操作系统人员使用术语“等待”和“休眠”(和“阻塞”)在某种程度上可互换使用 - 所有这些都意味着该过程正在等待某些事情发生,然后才能继续它的快乐方式。操作系统API sleep()碰巧使用底层操作系统机制进行休眠过程,这简直令人困惑。

进程可以等待其他事情:网络数据包到达,窗口选择事件或计时器到期,例如。

流程和计划

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

计划:

操作系统定期决定应该运行哪些进程。操作系统决定这样做的算法被称为调度算法,这有点不足为奇。调度算法的范围从死简单(“每个人运行10毫秒,然后队列中的下一个人运行”)到更复杂(考虑到进程优先级,执行频率,运行时限,进程间依赖关系,链式锁和各种其他复杂的主题。)

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

每次勾选都会导致操作系统中断。

当OS处理此定时器中断时,它会将系统时间的概念再增加10 ms。然后,它查看计时器队列并确定该队列上需要处理的事件。

计时器队列 是一个“需要处理的东西”的队列,我们​​称之为事件。此队列按到期时间排序,首先是最快的事件。

“事件”可能类似于“唤醒进程X”或“在那里进行磁盘I / O,因为它可能已卡住”,或“在该光纤通道链路上发送keepalive数据包”那里”。无论操作系统需要做什么。

当你以这种方式订购队列时,很容易管理出队。操作系统只是查看队列的头部,并在每次滴答时将事件的“到期时间”减少10毫秒。当到期时间变为零时,操作系统将该事件出列,并执行所要求的任何操作。

在休眠过程中,它只会使过程再次运行。

简单,是吗?

答案 3 :(得分:10)

至少有两个不同的级别来回答这个问题。 (以及很多其他与之相混淆的事情,我不会碰它们)

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

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

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

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

答案 4 :(得分:9)

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

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

答案 5 :(得分:8)

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

Sleep()使进程的时间片立即结束,以将控制权返回给OS。然后,操作系统会设置一个计时器内核对象,该对象在经过一段时间后会发出信号。在内核对象发出信号之前,操作系统将不再提供该进程。即使这样,如果其他进程具有更高或相同的优先级,它可能仍会等待一段时间才能继续进行。

操作系统使用特殊CPU机器代码进行过程切换。用户模式代码无法访问这些函数,因此可以通过对OS的API调用严格访问它们。

答案 6 :(得分:5)

基本上,是的,有一个“特殊的小发明” - 而且它不仅仅是睡眠()的重要性。

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

今天的操作系统仍然会对PIT进行编程以定期唤醒它(在Linux的最新版本中,默认情况下每毫秒一次),这就是内核能够实现先发制人的多任务处理。

答案 7 :(得分:1)

glibc 2.21 Linux

转发至nanosleep系统电话。

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

如何找到它:第一个反射是:

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魔术格式解析:

sysdeps/unix/make-syscalls.sh

然后从构建目录:

grep -r __nanosleep

引导我们:/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看起来像关键部分,所以我们发现:

# 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-cppgcc选项。

#define个参数如:

#define SYSCALL_NAME nanosleep

然后在以下处使用它们:

#include <syscall-template.S>

好的,这就是我现在要进行的宏扩展游戏。

我认为这会生成posix/nanosleep.o文件,必须与所有内容链接在一起。

Linux 4.2 x86_64 nanosleep系统调用

使用调度程序:它不是忙碌的睡眠。

搜索ctags:

sys_nanosleep

引导我们kernel/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时间等级

关于它的一些文章: