预计阅读本页时间:-
5.5.2 时钟软件
时钟硬件所做的全部工作是根据已知的时间间隔产生中断。涉及时间的其他所有工作都必须由软件——时钟驱动程序完成。时钟驱动程序的确切任务因操作系统而异,但通常包括下面的大多数任务:
1)维护日时间。
2)防止进程超时运行。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
3)对CPU的使用情况记账。
4)处理用户进程提出的alarm系统调用。
5)为系统本身的各个部分提供监视定时器。
6)完成概要剖析、监视和统计信息收集。
时钟的第一个功能是维持正确的日时间,也称为实际时间(real time),这并不难实现,只需要如前面提到的那样在每个时钟滴答将计数器加1即可。惟一要当心的事情是日时间计数器的位数,对于一个频率为60Hz的时钟来说,32位的计数器仅仅超过2年就会溢出。很显然,系统不可能在32位中按照自1970年1月1日以来的时钟滴答数来保存实际时间。
可以采取三种方法来解决这一问题。第一种方法是使用一个64位的计数器,但这样做使维护计数器的代价很高,因为1秒内需要做很多次维护计数器的工作。第二种方法是以秒为单位维护日时间,而不是以时钟滴答为单位,该方法使用一个辅助计数器来对时钟滴答计数,直到累计完整的一秒。因为232 秒超过了136年,所以该方法可以工作到22世纪。
第三种方法是对时钟滴答计数,但是这一计数工作是相对于系统引导的时间,而不是相对于一个固定的外部时间。当读入备份时钟或者用户输入实际时间时,系统引导时间就从当前日时间开始计算,并且以任何方便的形式存放在内存中。以后,当请求日时间时,存储的日时间值加到计数器上就可以得到当前的日时间。所有这三种方法如图5-33所示。

时钟的第二个功能是防止进程超时运行。每当启动一个进程时,调度程序就将一个计数器初始化为以时钟滴答为单位的该进程时间片的取值。每次时钟中断时,时钟驱动程序将时间片计数器减1。当计数器变为0时,时钟驱动程序调用调度程序以激活另一个进程。
时钟的第三个功能是CPU记账。最精确的记账方法是,每当一个进程启动时,便启动一个不同于主系统定时器的辅助定时器。当进程终止时,读出这个定时器的值就可以知道该进程运行了多长时间。为了正确地记账,当中断发生时应该将辅助定时器保存起来,中断结束后再将其恢复。
一个不太精确但更加简单的记账方法是在一个全局变量中维护一个指针,该指针指向进程表中当前运行的进程的表项。在每一个时钟滴答,使当前进程的表项中的一个域加1。通过这一方法,每个时钟滴答由在该滴答时刻运行的进程“付费”。这一策略的一个小问题是:如果在一个进程运行过程中多次发生中断,即使该进程没有做多少工作,它仍然要为整个滴答付费。由于在中断期间恰当地对CPU进行记账的方法代价过于昂贵,因此很少使用。
在许多系统中,进程可以请求操作系统在一定的时间间隔之后向它报警。警报通常是信号、中断、消息或者类似的东西。需要这类报警的一个应用是网络,当一个数据包在一定时间间隔之内没有被确认时,该数据包必须重发。另一个应用是计算机辅助教学,如果学生在一定时间内没有响应,就告诉他答案。
如果时钟驱动程序拥有足够的时钟,它就可以为每个请求设置一个单独的时钟。如果不是这样的情况,它就必须用一个物理时钟来模拟多个虚拟时钟。一种方法是维护一张表,将所有未完成的定时器的信号时刻记入表中,还要维护一个变量给出下一个信号的时刻。每当日时间更新时,时钟驱动程序进行检查以了解最近的信号是否已经发生。如果是的话,则在表中搜索下一个要发生的信号的时刻。
如果预期有许多信号,那么通过在一个链表中把所有未完成的时钟请求按时间排序链接在一起,这样来模拟多个时钟则更为有效,如图5-34所示。链表中的每个表项指出在前一个信号之后等待多少时钟滴答引发下一个信号。在本例中,等待处理的信号对应的时钟滴答分别是4203、4207、4213、4215和4216。

在图5-34中,经过3个时钟滴答发生下一个中断。每一次滴答时,下一个信号减1,当它变为0时,就引发与链表中第一个表项相对应的信号,并将这一表项从链表中删除,然后将下一个信号设置为现在处于链表头的表项的取值,在本例中是4。
注意在时钟中断期间,时钟驱动程序要做几件事情——将实际时间增1,将时间片减1并检查它是否为0,对CPU记账,以及将报警计数器减1。然而,因为这些操作在每一秒之中要重复许多次,所以每个操作都必须仔细地安排以加快速度。
操作系统的组成部分也需要设置定时器,这些定时器被称为监视定时器(watchdog timer) [1] 。例如,为了避免磨损介质和磁头,软盘在不使用时是不旋转的。当数据需要从软盘读出时,电机必须首先启动。只有当软盘以全速旋转时,I/O才可以开始。当一个进程试图从一个空闲的软盘读取数据时,软盘驱动程序启动电机然后设置一个监视定时器以便在足够长的时间间隔之后引发一个中断(因为不存在来自软盘本身的达到速度的中断)。
时钟驱动程序用来处理监视定时器的机制和用于用户信号的机制是相同的。惟一的区别是当一个定时器时间到时,时钟驱动程序将调用一个由调用者提供的过程,而不是引发一个信号。这个过程是调用者代码的一部分。被调用的过程可以做任何需要做的工作,甚至可以引发一个中断,但是在内核之中中断通常是不方便的并且信号也不存在。这就是为什么要提供监视定时器机制。值得注意的是,只有当时钟驱动程序与被调用的过程处于相同的地址空间时,监视定时器机制才起作用。
时钟最后要做的事情是剖析(profiling)。某些操作系统提供了一种机制,通过该机制用户程序可以让系统构造它的程序计数器的一个直方图,这样它就可以了解时间花在了什么地方。当剖析是可能的事情时,在每一时钟滴答驱动程序都要检查当前进程是否正在被进行剖析,如果是,则计算对应于当前程序计数器的区间(bin) [2] 号(一段地址范围),然后将该区间的值加1。这一机制也可用来对系统本身进行剖析。