zoukankan      html  css  js  c++  java
  • 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
    ", 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
    ");
    		exit(1);
    	}
    	gettimeofday(&t,NULL);
    	printf("sec=%ld usec=%ld
    ",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
    ",start);
    	for(i=0;i<123456789;i++)
    	end = time(NULL);
    	printf("end =%ld time=%ld
    ",end,end-start);
    }
    

    实践截图如下:

  • 相关阅读:
    LeetCode 338. 比特位计数
    LeetCode 208. 实现 Trie (前缀树)
    初识restful api接口
    破解 Navicat Premium 12
    ES6 Reflect的认识
    ES6 WeakMap和WeakSet的使用场景
    sublime 注释模版插件DocBlockr的使用
    js call方法的使用
    ES6 Generator的应用场景
    ES6 Symbol的应用场景
  • 原文地址:https://www.cnblogs.com/leo-skr/p/15505637.html
Copyright © 2011-2022 走看看