chapter 5 定时器及时钟服务

发布时间 2023-11-05 20:46:24作者: 20211108俞振阳

学习笔记:定时器与时间服务

摘要

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

5.1 硬件定时器

  • 定时器是一个硬件设备,包括时钟源和可编程计数器。
  • 时钟源通常是晶体振荡器,以精确的频率生成周期性电信号来驱动计数器。
  • 计数器编程为倒计时值,每个时钟信号将其递减1。当计数减至0时,计数器会向 CPU 生成定时器中断,重新加载计数值并再次进行倒计时。
  • 计数器的周期称为定时器滴答,是系统的基本时间单位。

5.2 PC 定时器

  • 基于 Intel x86 架构的 PC 具有几种定时器:
    1. 实时时钟(RTC):由小型备用电池供电,在 PC 关机时仍持续运行,用于提供时间和日期信息。
    2. 可编程间隔定时器(PIT):是一个独立于 CPU 的硬件定时器,可编程以毫秒为分辨率提供定时器滴答。
    3. 多核 CPU 中的本地定时器:每个核心都有自己的本地定时器,由 CPU 时钟驱动。
    4. 高分辨率定时器:大多数 PC 配备时间戳计数器(TSC),提供纳秒级定时器分辨率,部分高端 PC 可装备特殊的高速定时器。

5.3 CPU 操作

  • 每个 CPU 都有程序计数器(PC)、标志或状态寄存器(SR)、堆栈指针(SP)和几个通用寄存器。
  • CPU 操作可以用一个无限循环来建模,包括指令获取、解码、执行以及处理中断和异常。
  • 异常或陷阱是由于无效地址、非法指令、特权违规等错误条件引起的情况。CPU 遇到异常时,会执行软件中预先安装的异常处理程序。
  • 中断是来自 I/O 设备或协处理器的外部信号,请求 CPU 服务。CPU 在每条指令执行结束时检查是否有未决中断请求。中断处理和异常处理由操作系统内核处理,对用户级程序大多不可见,但对于理解操作系统中的定时器服务和信号很重要,比如 Linux。

5.4 中断处理

  • 外部设备(如定时器)产生的中断被馈送到中断控制器的预定义输入线,中断控制器优先级排序并将具有最高优先级的中断路由为 CPU 的中断请求(IRQ)。
  • CPU 在执行指令结束时,如果不接受中断(即 CPU 的状态寄存器中屏蔽了中断),将忽略中断请求,保持中断处于挂起状态,并继续执行下一条指令。
  • CPU 接受中断时,将其正常执行序列转移到中断处理。每个中断都可以由中断控制器编程生成一个唯一编号,称为中断向量,用于标识中断源。
  • 中断向量作为内存中中断向量表的索引,其中包含指向实际处理中断的中断处理程序入口地址。

5.5 时间服务函数

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

5.5.1 获取和设置时间

gettimeofdaysettimeofday 系统调用

  • gettimeofday:

    • 用于获取当前时间,其参数 tv 指向 timeval 结构。
    • timeval 结构:
      struct timeval {
          time_t tv_sec;        // 秒
          suseconds_t tv_usec;  // 微秒
      };
      
    • gettimeofday() 返回当前时间,以秒和当前秒的微秒表示。
  • settimeofday:

    • 用于设置当前时间。
    • 在 Unix/Linux 中,时间是自 1970 年 1 月 1 日 00:00:00 以来经过的秒数的表示。
    • settimeofday() 设置当前时间为参数指定的时间。

示例演示 gettimeofdaysettimeofday

  • gettimeofday 系统调用示例

    #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));
    }
    
    • 程序将显示当前时间的秒、微秒以及当前日期和时间的日历形式。
  • settimeofday 系统调用示例

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/time.h>
    #include <time.h>
    struct timeval t;
    int main()
    {
        int r;
        t.tv_sec = 123456789;
        t.tv_usec= 0;
        r = settimeofday(&t, NULL);
        if (!r){
            printf(“settimeofday() failed\n”);
            exit(1);
        }
        gettimeofday(&t, NULL);
        printf("sec=%ld usec=%ld\n", t.tv_sec, t.tv_usec);
        printf(“%s”, ctime(&t.tv_sec)); // 以日历形式显示时间
    }
    
    • 程序输出将显示设定的时间和以日历形式表示的日期。在某些 Linux 系统中,设定的时间可能是临时的,在实际情况下会被系统自动纠正。

5.5.2 时间系统调用

time 系统调用

  • time_t time(time_t *t)
    • 返回当前时间的秒数。
    • 如果参数 t 不为 NULL,还会将时间存储在 t 指向的内存中。
    • 限制在于它只提供秒级的时间分辨率。

示例演示 time 系统调用

#include <stdio.h>
#include <time.h>
time_t start, end;
int main()
{
    int i;
    start = time(NULL);
    printf(“start=%ld\n”, start);
    for (i=0; i<123456789; i++); // 延迟以模拟计算
    end = time(NULL);
    printf(“end =%ld time=%ld\n”, end, end-start);
}
  • 输出将打印开始时间、结束时间以及开始到结束的秒数。

5.5.3 times 系统调用

  • clock_t times(struct tms *buf);
    • 用于获取进程的详细执行时间。
    • 它将进程时间存储在 struct tms 结构中。
    • struct tms 结构:
      struct tms{
          clock_t tms_utime;  // 用户模式时间
          clock_t tms_stime;  // 系统模式时间
          clock_t tms_cutime; // 子进程的用户时间
          clock_t tms_cstime; // 子进程的系统时间
      };
      
    • 所有报告的时间单位都是时钟滴答。

5.5.4 时间和日期命令

  • date:打印或设置系统日期和时间。
  • time:报告用户模式、系统模式和总时间的进程执行时间。
  • hwclock:查询和设置硬件时钟(RTC),也可以通过 BIOS 完成。

5.9 总结

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

问题

  1. 修改示例 5.1 程序:使用 ctime(&seconds) 库函数将秒数转换为日历形式字符串,并打印当前日期和时间。

  2. 修改示例 5.1 程序:在程序中添加一个延迟循环以模拟程序的长时间计算。使用 gettimeofday() 在循环之前和之后获取当前时间,然后打印两个时间之间的差异。能否通过这种方式测量程序的总执行时间?给出理由。

  3. 修改示例 5.1 程序

    • 安装一个用于捕获 SIGPROF(27)信号的信号处理程序。
    • 以 PROF 模式启动另一个间隔定时器,周期与 VIRTUAL 模式定时器相同。
    • 统计由 PROF 定时器生成的信号数(pcount)和由 VIRTUAL 定时器生成的信号数(vcount)。
    • 在信号处理程序中打印 pcount 和 vcount。当任一计数达到 100 时,停止定时器并打印计数。
    • 在主程序的 while(1) 循环中,添加 getpid() 和 getppid() 系统调用,使进程在系统模式下花费一些时间。
    • 回答以下问题:
      • 哪个定时器会更快地生成信号?为什么?
      • 如何确定进程的用户模式执行时间和系统模式执行时间?
      • 使用 time a.out 运行程序,它将打印 a.out 在实际模式、用户模式和系统模式下的执行时间。说明时间命令是如何实现的。
  4. 修改示例 5.1 程序:设置一个 REAL 模式间隔定时器。

  5. 累积定时器队列:设计算法,将每个 TQE(定时器队列条目)的到期时间与所有先前 TQE 的到期时间的累积总和相关联。设计算法,在定时器请求在到期前取消时删除 TQE。

苏格拉底挑战

点击查看问答 Linux 定时器和中断处理