Clock Interrupts和Scheduler的关系

Hacker News上每天总是能看到一些不错的文章,而且我感觉评论区更是藏龙卧虎,当然有时候也能看到撕逼。有空的时候,我会分享一些有意思的内容。本次就说说CPU的Clock Interrupts是怎样影响Scheduler的吧,原文在这

你觉得你平时调用的sleep(1)函数,真的能让当前进程正好延迟1秒钟吗?

CPU Clock和Interrupt的基础知识这里不再赘述,不知道的回去翻大学课本。clock interrupt的频率比CPU运行频率低很多,如果中断的频率是100Hz,那么就是操作系统每10毫秒执行一次中断。在每次中断的时候,操作系统都会做一些工作,其中之一就是调用Scheduler,决定下一个要执行的进程,执行context switch操作。Scheduler也有可能在中断之外的时间被调用,下面会提及。

关于延迟
当你执行sleep(),或者Sleep()、usleep()之类的函数,操作系统一般会立即暂停当前进程的执行,并且调用Scheduler进行调度,而不需要等到下一次中断的时候。实际的代码实现上,通常将这一延迟时间转化成一个非负整数,即延迟时间除以中断周期。然后每次触发中断的时候,都将此值减一,直至为0的时候唤醒进程。从这里就能看出来,sleep()之类函数的精确程度,取决于中断周期。

比如下面的例子:

sleep

这里假设中断的频率是100Hz,现在执行一个延迟10毫秒的函数sleep(10ms)。根据执行sleep()的时机,实际延迟时间会有很大差异:
如果在A点被执行,即中断刚刚发生后,则延迟时间几乎为10毫秒;
如果在B点被执行,即马上要发生中断的时候,则几乎不会进行任何延迟。

也就是说,如果你想要确保进程有最低程度的延迟,则指定的参数至少要是中断周期的两倍。比如中断周期为10ms时,至少要指定20ms的延迟时间,才能确保进程不会出现立刻就被唤醒的情况,此时进程的平均延迟时间为15ms(即实际时间在10-20ms之间)。

有个特例,POSIX的sleep()、usleep()和nanosleep()函数,会保证至少延迟至指定的时间。不过这依然受到中断周期的影响,导致平均增加半个中断周期的时间。如上文的例子,如果指定10ms的延迟时间,则实际平均延迟时间为15ms,指定20ms时平均延迟时间为25ms。

虽然指定最小延迟是可能的,但是在多进程/多线程环境中,指定最大延迟是不可能的。原因也比较明显——并不能确保此进程在下个中断周期内被执行。例如实时操作系统中的一个优先级不是最高的进程,或者分时系统中有很多进程在队列中等待执行,可能Scheduler调度到此进程的时候,早就已经过了设定的延时时间了。而且即使此进程是优先级最高的,并且下一次context switch的时候就可以终止sleep的执行,但是在这之前正好有一个比Scheduler优先级更高的中断发生,也会导致Scheduler推迟执行从而造成延迟增大。

利用这一特性,有时候会使用下述技巧:

sleep(0)

作用就是强制进行一次context switch,从而允许优先度较低的进程或者队列中其他进程执行一个中断周期,而本进程依然处于随时可执行的状态,只要下一次中断时没有更高优先级的进程,本进程将得以继续执行。

关于时钟同步
实际编程中经常有如下代码:

  while (true)
  {
    // do something, over and over again
    sleep (some_value);
  }

一般来说,第一遍循环可以发生在相对中断的任何时间,而以后的循环,都会基本上和中断同步。优先级越高的进程,同步率越高。
举一个具体的例子:你要给某个特定硬件编写一段程序,在接收到外来信号输入时,输出一段有规律的脉冲信号(即切换至高电平->进程内等待少量时间->切换至低电平->sleep一定时间,如此往复)。
这里同样假定中断频率为100Hz,而两次脉冲间隔10ms。如果不做时钟同步,实际的信号输出可能是下图中任何一种:

pulse

第一种波形,是当外来信号输入发生在A点。此时会立刻生成一个脉冲,然后开始延迟10ms。根据之前的探讨,进程在第一个中断处就被唤醒,只间隔了很短的时间就生成了下一个脉冲。
第二和第三种波形,都是外来信号输入发生在B点,不同之处是如果本进程的优先级比其他进程都高,则进程在第一个中断处依然继续执行,顺利切换至低电平,并且延迟至下一次中断时生成第二个脉冲,这种情况下,最初两个脉冲的间隔会稍大;另一种情况是在第一个中断处被context switch至其他更高级的进程,导致本进程没来得及进行电平切换,直到下一个中断处才有机会获得执行(如果此时它是最高优先级进程),结束进程内等待并切换成低电平,这样就生成了一个超长的高电平。

如果你使用下述代码,事先同步一下,则获得想要波形的可能性更大些:

  sleep (10ms) // This synchs to the clock
               // interrupt for each pulse
  {
    Set signal high
    waste clock cycles for the width of the pulse
      (busy wait)
    Set signal low
    sleep (10ms)
  }

当然,想要得到上图第四种那样规律的波形,只可能是高优先级的进程或者在SPNT(single process, non-threaded)系统中。

本文为悠然居(https://wordpress.youran.me/)的原创文章,转载请注明出处!

One thought on “Clock Interrupts和Scheduler的关系”

  1. 用sleep来控制多线程像六脉神剑时灵时不灵,一直没深究原因。现在明白了,thanks

    Firefox 42.0 Firefox 42.0 Windows 7 Windows 7

Leave a Reply

Your email address will not be published. Required fields are marked *