Linux系统编程第五章学习笔记

前言

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

1 硬件定时器

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

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寄存器读取。

3 CPU操作

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

while (开机){
(1). 取指令:加载*PC作为指令,将PC递增以指向内存中的下一条指令;
(2). 解码指令:解释指令的操作代码并生成操作数;
(3). 执行指令:对操作数执行操作,如果需要,将结果写入内存; 执行可能使用堆栈,隐式更改PC等。
(4). 检查待处理的中断; 可能会处理中断;
}
在上述每个步骤中,由于无效地址、非法指令、特权违规等,可能会发生错误条件,称为异常或陷阱。当CPU遇到异常时,它会遵循内存中预先安装的指针来执行软件中的异常处理程序。在每个指令执行结束时,CPU会检查待处理的中断。中断是来自I/O设备或协处理器的外部信号,请求CPU服务。如果存在待处理的中断请求但CPU不处于接受中断的状态,即其状态寄存器屏蔽了中断,CPU将忽略中断请求并继续执行下一条指令。否则,它将将执行转向中断处理。在中断处理结束后,它将恢复正常执行指令。中断处理和异常处理在操作系统内核中处理。在很大程度上,它们对用户级程序是不可访问的,但它们是理解操作系统中的定时器服务和信号的关键,如Linux等。

4 中断处理

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

5 时间服务函数

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

5.1 Gettimeofday-Settimeofday

#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结构。

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系统调用

示例 5.1
通过gettimeofday()获取系统时间

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

struct timeval t;

int main()
{
    gettimeofday(&t, NULL);
    printf("秒数=%ld 微秒=%d\n", t.tv_sec, t.tv_usec);
    printf((char *)ctime(&t.tv_sec));
    return 0;
}

这个程序应该显示当前时间的秒数、微秒数,以及当前日期和时间的日历形式,如下所示:

秒数=1515624303 微秒=860772
Wed Jan 10 14:45:03 2018

(2). Settimeofday系统调用

示例 5.2
通过settimeofday()设置系统时间

/********* settimeofday.c 文件 *********/
#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)); // show time in calendar form

这个程序的输出应该类似于:

sec=123456789 usec=862
Thu Nov 29 13:33:09 1973

根据打印的日期和年份(1973),似乎settimeofday() 操作已成功。然而,在某些Linux系统中,例如Ubuntu 15.10,效果可能仅是临时的。如果读者再次运行gettimeofday程序,它将显示Linux已将系统时间更改回正确的实时时间。这表明Linux内核具有使用实时时钟(和其他时间同步协议)来校正系统时间与实际时间之间的任何偏差的能力。

5.2 时间系统调用

示例 5.3

时间系统调用

time_t time(time_t *t);

返回当前时间(以秒为单位)。如果参数 t 不是 NULL,它还将时间存储在 t 指向的内存中。时间系统调用的限制是它仅以秒为分辨率,而不是微秒。此示例演示如何获取系统时间(以秒为单位)。

/************ time.c 文件 ***********/
#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.3 times 系统调用

clock_t times(struct tms *buf);

可以用来获取进程的详细执行时间。它将进程时间存储在一个 struct tms 结构中,其中包括用户模式时间、系统模式时间、子进程的用户时间和子进程的系统时间。所有报告的时间都以时钟滴答为单位,用于分析正在执行的进程,包括其子进程(如果有的话)的时间。

5.4 时间和日期命令

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

苏格拉底挑战

学习定时器与时间服务

posted @ 2023-11-04 13:54  20211120  阅读(48)  评论(0编辑  收藏  举报