# 5. 计时器和计时器服务

本章介绍计时器和计时器服务。它解释了硬件定时器的原理和基于Intel x86的PC中的硬件定时器。它涵盖了CPU操作和中断处理。它描述了与计时器相关的系统调用、库函数和Linux计时器服务的命令。它讨论了进程间隔定时器、计时器生成信号,并通过示例演示了进程间隔定时器。编程项目是在多任务系统中实现计时器、计时器中断和间隔计时器。多任务系统作为一个Linux进程运行,充当Linux进程内并发任务的虚拟CPU。Linux进程的实时模式间隔计时器被编程周期性地生成SIGALRM信号,它作为虚拟CPU的计时器中断,虚拟CPU使用SIGALRM信号捕获器作为计时器中断处理程序。该项目是供读者使用计时器队列实现任务的间隔计时器。它还允许读者使用Linux信号掩码来实现临界区,以防止任务和中断处理程序之间的竞争条件。

## 5.1 硬件定时器

计时器是一种硬件设备,由时钟源和可编程计数器组成。时钟源通常是晶体振荡器,它以精确的频率生成周期性电信号驱动计数器。计数器被编程为倒计时值,每次时钟信号减1。当计数减少到0时,计数器向CPU生成计时器中断,重新加载计数值到计数器,然后再次进行倒计时。计数器的周期称为定时器滴答声,它是系统的基本定时单位。

## 5.2 PC计时器

基于Intel x86的PC有几个计时器(Bovet和Cesati 2005)。

1. 实时时钟(RTC):RTC由一个小备用电池供电。即使PC的电源关闭,它仍然持续运行。它用于保持实时以提供时间和日期信息。当Linux启动时,它使用RTC更新系统时间变量以跟踪当前时间。在所有类Unix系统中,时间变量是一个包含自1970年1月1日开始经过的秒数的长整数。

2. 可编程间隔计时器(PIT)(Wang 2015):PIT是与CPU分离的硬件计时器。它可以编程为以毫秒为单位的分辨率提供计时器滴答声。在所有I/O设备中,PIT以最高优先级IRQ0中断。PIT计时器中断由Linux内核的计时器中断处理程序处理,以为系统操作提供基本定时单位,例如进程调度、进程间隔计时器和大量其他计时事件。

3. 多核CPU的本地定时器(Intel 1997;Wang 2015):在多核CPU中,每个核都是独立的处理器,具有由CPU时钟驱动的本地计时器。

4. 高分辨率计时器:大多数PC都配备有时间戳计数器(TSC),它由系统时钟驱动。它的内容可以通过64位TSC寄存器读取。由于不同系统板的时钟速率可能有所不同,因此TSC不适用于实时设备,但可以提供纳秒级的计时器分辨率。一些高端PC还可以配备一个特殊的高速计时器,以提供纳秒范围的计时器分辨率。

## 5.3 CPU操作

每个CPU都有一个程序计数器(PC),也称为指令指针(IP)、标志或状态寄存器(SR)、堆栈指针(SP)和几个通用寄存器,其中PC指向下一个要在内存中执行的指令,SR包含CPU的当前状态,例如操作模式、中断掩码和条件码,SP指向当前堆栈的顶部。堆栈是CPU用于特殊操作(例如推、弹、调用和返回)的内存区域。 CPU的操作可以通过一个无限循环来建模。

```
while(power-on){
(1). 获取指令:将*PC加载为指令,将PC增加到指向内存中的下一个指令;
(2). 解码指令:解释指令的操作代码并生成操作数;
(3). 执行指令:对操作数执行操作,如果需要,将结果写入存储器;执行可能会使用堆栈、隐式更改PC等。
(4). 检查挂起的中断;可能处理中断;
}
```

在每个上述步骤中,都可能出现错误条件,称为异常或陷阱,由于无效地址、非法指令、特权违规等,当CPU遇到异常时,它会按照预先安装在内存中的指针执行软件中的异常处理程序。每个指令执行结束时,CPU会检查挂起的中断。中断是来自I/O设备或协处理器的外部信号,请求CPU服务。如果有挂起的中断请求,但CPU没有处于接受中断状态,即其状态寄存器屏蔽了中断,则CPU将忽略中断请求并继续执行下一条指令。否则,它将直接执行中断处理。在中断处理的结束处,它将恢复指令的正常执行。中断处理和异常处理由操作系统内核处理。在很大程度上,它们无法从用户级程序中访问,但它们是理解操作系统中计时器服务和信号的关键,例如Linux。

## 5.4 中断处理

来自外部设备的中断,例如定时器,被馈送到预定义的中断控制器(Intel 1990;Wang 2015)的输入线路中,该控制器对中断输入进行优先级排序,并将具有最高优先级的中断路由为中断请求(IRQ),发送给CPU。在执行每条指令结束时,如果CPU不处于接受中断的状态,即在CPU的状态寄存器中掩盖了中断,它将忽略中断请求,使其保持挂起状态,并继续执行下一条指令。如果CPU处于接受中断的状态,即中断未掩盖,CPU将改变正常的执行顺序来处理中断。对于每个中断,中断控制器可以编程生成一个唯一的数字,称为中断向量,它标识中断源。在获取中断向量号之后,CPU将其用作内存中中断向量表(AMD64 2011)中条目的索引,该表包含中断处理程序的条目地址的指针,实际处理中断。中断处理完成后,CPU恢复指令的正常执行。

## 5.5 时间服务函数

在几乎每个操作系统(OS)中,OS内核都提供各种与时间有关的服务。时间服务可以通过系统调用、库函数和用户级命令调用。在本节中,我们将涵盖Linux的一些基本时间服务函数。


### 5.5.1 Gettimeofday-Settimeofday

```c
#include <sys/time.h>

int gettimeofday(struct timeval *tv, struct timezone *tz);
int settimeofday(const struct timeval *tv, const struct timezone *tz);
```

这些是调用Linux内核的系统调用。第一个参数`tv`指向一个`timeval`结构体。

```c
struct timeval {
time_t tv_sec; /* 秒 */
suseconds_t tv_usec; /* 微秒 */
};
```

第二个参数`timezone`已经过时,应该设置为`NULL`。`gettimeofday()`函数返回当前时间,包括当前秒和微秒。`settimeofday()`函数设置当前时间。在Unix/Linux中,时间用自1970年1月1日00:00:00以来经过的秒数表示。它可以通过库函数`ctime(&time)`转换为日历形式。以下示例演示了`gettimeofday()`和`settimeofday()`。

(1)。获取系统时间的`gettimeofday()`系统调用:

```c
/********* gettimeofday.c文件 *********/
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

struct timeval t;

int main()
{
gettimeofday(&t, NULL);
printf("sec=%ld usec=%d\n", t.tv_sec, t.tv_usec);
printf((char *)ctime(&t.tv_sec));
}
```

 苏格拉底挑战

 

 

 

 

 

 

 

 

 

 

 

 

询问的问题

 

 

 

 

 

 

 

posted on 2023-11-03 21:27  灰灰爱跳舞  阅读(187)  评论(0编辑  收藏  举报