20191330雷清逸 学习笔记8

20191330 雷清逸 学习笔记8(第五章)

一、知识点归纳以及自己最有收获的内容

知识点归纳

摘要

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

最有收获的部分

  • 有关硬件定时器的相关知识
  • 有关个人计算机定时器的相关知识
  • 有关CPU操作的知识
  • 有关中断处理的知识
  • 有关时钟服务函数的知识
  • 有关间隔定时器的知识

硬件定时器

  • 定时器是由时钟源和可编程计数器组成的硬件设备。时钟源通常是一个晶体振荡器,会产生周期性电信号,以精确的频率驱动计数器。
  • 计数器周期称为定时器刻度,是系统的基本计时单元。

个人计算机定时器

  • 1.实时时钟(RTC):

    (1)RTC由一个小型备用电池供电。即使在个人计算机关机时,它也能继续运行。它用于实时提供时间和日期信息。

    (2)在所有类Unix系统中,时间变量是一个长整数,包含从1970年7月1日起经过的秒数。

  • 2.可编程间隔定时器(PIT)(Wang2015):

    (1)PIT是与CPU分离的一个硬件定时器。

    (2)可对它进行编程,以提供以毫秒为单位的定时器刻度。

  • 3.多核CPU中的本地定时器(Intel 1997;Wang 2015):

    (1)在多核CPU中,每个核都是一个独立的处理器,它有自己的本地定时器,由CPU时钟驱动。

4.高分辨率定时器:

(1)大多数电脑都有一个时间戳定时器(TSC),由系统时钟驱动。

CPU操作

  • 每个CPU都有一个程序计数器(PC),也称为指令指针(IP),以及一个标志或状态寄存器(SR)、一个堆栈指针(SP)和几个通用寄存器,当PC指向内存中要执行的下一条指令时,SR包含CPU的当前状态,如操作模式、中断掩码和条件码,SP指向当前堆栈栈顶。

  • 在以上各步骤中,由于无效地址、非法指令、越权等原因,可能会出现一个错误状态,称为异常或陷阱。

  • 中断是I/O设备或协处理器发送给CPU的外部信号,请求CPU服务。

  • 中断处理和异常处理都在操作系统内核中进行。在大多数情况下,用户级程序无权访问它们,但它们是理解操作系统(如Linux)定时器服务和信号的关键。

中断处理

  • 外部设备(如定时器)的中断被馈送到中断控制器的预定义输入行(Intel 1990;Wang 2015),按优先级对中断输入排序,并将具有最高优先级的中断作为中断请求(IRQ)路由到CPU。
  • 对于每个中断,可以编程中断控制器以生成一个唯一编号,叫做中断向量,标识中断源。在获取中断向量号后,CPU用它作为内存中中断向量表(AMD64 2011)中的条目索引,条目包含一个指向中断处理程序入口地址的指针来实际处理中断。

时钟服务函数

时钟服务可通过系统调用、库函数和用户级命令调用。

以下是Linux的一些基本时钟服务函数:

  • 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内核的系统调用;settimeofday()函数用于设置当前时间;它可以通过库函数ctime(&time)转化为日历形式。

  • time系统调用
time_t time(time_t *t)

该函数会以为单位返回当前时间

time系统调用具有一定的局限性,只提供以为单位的分辨率,而不是以微秒为单位。

  • times系统调用
clock_t times(struct tms *buf)

该函数可用于获取某进程的具体执行时间。它将进程时间存储在struct tms buf中

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

间隔定时器

Linux为每个进程提供了三种不同类型的间隔计时器。

间隔计时器由setitimer()系统调用创建。getitimer()系统调用返回间隔定时器的状态。

  • 分类:

    (1)ITIMER_REAL:实时减少,在到期时生成一个SIGALRM(14)信号。

    (2)ITIMER_VIRTUAL:仅当进程在用户模式下执行时减少,在到期时生成一个SIGVTALRM(26)信号。

    (3)ITIMER_PROF:当进程正在用户模式和系统(内核)模式下执行时减少。

    函数getitimer()用当前值填充curr_value指向的结构体,即参数which(ITIMER_REAL、ITIMER_VIRTUAL或ITIMER_PROF三者之一)指定的定时器在下次到期之前剩余的时间。

REAL模式间隔定时器

  • VIRTUAL和PROF模式下的间隔计时器仅在执行进程时才有效。这类定时器的信息可保存在各进程的PROC结构体中。
  • 在大多数操作系统内核中,使用的数据结构都是定时器队列。

二、问题与解决思路

问题:Linux下的定时器有sleep,usleep,nanosleep等等,它们有什么区别呢?

答:经过对相关资料的查阅,发现它们的区别如下:

sleep()和nanosleep()都是使进程睡眠一段时间后被唤醒,但是二者的实现完全不同。
Linux中并没有提供系统调用sleep(),sleep()是在库函数中实现的,它是通过调用alarm()来设定报警时间,调用sigsuspend()将进程挂起在信号SIGALARM上,sleep()只能精确到秒级上。

nanosleep()则是Linux中的系统调用,它是使用定时器来实现的,该调用使调用进程睡眠,并往定时器队列上加入一个timer_list型定时器,time_list结构里包括唤醒时间以及唤醒后执行的函数,通过nanosleep()加入的定时器的执行函数仅仅完成唤醒当前进程的功能。系统通过一定的机制定时检查这些队列(比如通过系统调用陷入核心后,从核心返回用户态前,要检查当前进程的时间片是否已经耗尽,如果是则调用schedule()函数重新调度,该函数中就会检查定时器队列,另外慢中断返回前也会做此检查),如果定时时间已超过,则执行定时器指定的函数唤醒调用进程。当然,由于系统时间片可能丢失,所以nanosleep()精度也不是很高。

alarm()也是通过定时器实现的,但是其精度只精确到秒级,另外,它设置的定时器执行函数是在指定时间向当前进程发送SIGALRM信号。

三、实践内容与截图,代码链接

实践内容为书本P162-163页的示例5.1,示例5.2及示例5.3

  • 示例5.1(gettimeofday系统调用)
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

struct timeval t;

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

实践截图如下:

  • 示例5.2(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));
}

实践截图如下:

  • 示例5.3(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);
}

实践截图如下:

posted @ 2021-11-04 19:48  20191330雷清逸  阅读(35)  评论(0编辑  收藏  举报