《信息安全系统设计与实现》学习笔记8

一、学习笔记

1.硬件定时器

      使用一个倒计时值对计数器进行编程,每个时钟信号减1。当计数减为0时,计数器向CPU生成一个定时器中断,将计数值重新加载到计数器中,并重复倒计时。计数器周期称为定时器刻度,是系统的基本计时单元。

2.个人计算机定时器

基于Intel x86的个人计算机有数个定时器:

  • 实时时钟(RTC)
  • 可编程间隔定时器(PIT)
  • 多核CPU中的本地定时器
  • 高分辨率定时器

3.CPU操作

每个CPU都有:

  • 一个程序计数器(PC),也称为指令指针(IP)
  • 一个标志或状态寄存器(SR)
  • 一个堆栈指针(SP)
  • 几个通用寄存器

      当PC指向内存中要执行的下一条指令时,SR包含CPU的当前状态,SP指向当前堆栈栈顶。堆栈是CPU用于特殊操作(如push、pop调用和返回等)的一个内存区域。

      由于无效地址、非法指令、越权等原因,可能会出现一个错误状态,称为异常或陷阱。当CPU遇到异常时,它会根据内存中预先安装的指针来执行软件中的异常处理程序。在每条指令执行结束时,CPU会检查挂起的中断。中断是I/O设备或协处理器发送 给CPU的外部信号,请求CPU服务。如果有挂起的中断请求,但是CPU未处于接受中断 的状态,即它的状态寄存器已经屏蔽了中断,CPU会忽略中断请求,继续执行下一条指令。 否则,它将直接执行中断处理。在中断处理结束时,它将恢复指令的正常执行。

      中断处理和异常处理都在操作系统内核中进行。在大多数情况下,用户级程序无法访问它们。

4.中断处理

      外部设备(如定时器)的中断被馈送到中断控制器的预定义输入行,按优先级对中断输入排序,并将具有最高优先级的中断作为中断请求(IRQ)路由到CPU。在每条指令执行结束时,如果CPU未处于接受中断的状态,即在CPU的状态寄存器中屏蔽了中断.它将忽略中断请求,使其处于挂起状态,并继续执行下一条指令。如果CPU处于接受中断状态,即中断未被屏蔽,那么CPU将会转移它正常的执行顺序来进行中断处理。对于每个中断,可以编程中断控制器以生成一个唯一编号,叫作中断向量,标识中断源。在获取中断向量号后,CPU用它作为内存中中断向量表中的条目索引,条目包含一个指向中断处理程序入口地址的指针来实际处理中断。当中断处理结束时,CPU恢复指令的正常执行。

5.时钟服务函数

(1)gettimeofday-settimeofday


      gettimeofday()函数用于返回当前时间(当前秒的秒和微秒)。settimeofday()函数用于设置当前时间。在Unix/Linux中,时间表示自1970年1月1日00:00:00起经过的秒数。它可以通过库函数ctime(&time)转换为日历形式。

(2)time系统调用


      以秒为单位返回当前时间。如果参数t不是NULL,还会将时间存储在t指向的内存中。 time系统调用具有一定的局限性,只提供以秒为单位的分辨率,而不是以微秒为单位。

(3)times系统调用


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

(4)time和data命令

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

6.间隔定时器

      Linux为每个进程提供了三种不同类型的间隔计时器,可用作进程计时的虚拟时钟。间隔定时器由setitimer()系统调用创建。getitimer()系统调用返回间隔定时器的状态。

      各间隔定时器在参数which指定的不同时间域中工作。当间隔定时器定时到期时,会向进程发送一个信号,并将定时器重置为指定的间隔值(如果是非零数)。一个信号就是发送给某个进程进行处理的一个数字(1到31)。有3类间隔定时器,分别是:

  • ITIMER_REAL:实时减少,在到期时生成一个SIGALRM(14)信号。
  • IT1MER_V1RTUAL;仅当进程在用户模式下执行时减少,在到期时生成一个 SIGVTALRM(26)信号。
  • ITIMER_PROF:当进程正在用户模式和系统(内核)模式下执行时减少。这类间隔 定时器与IT1MER_VIRTUAL结合使用,通常用于分析应用程序在用户模式和内核模式下花费的时间。它在到期时生成一个SIGPROF(27)信号。

7.定时器中断

      整个基本系统在一个虚拟CPU上运行,它是一个Linux进程。定时器向Linux进程发 出的信号可看作是对基本系统虚拟CPU的中断。

8.临界区

      在基本代码系统中,只有一种执行实体,即任务,一次只执行一个任务。某任务在收 到切换命令、进入休眠或退出之前,会一直执行下去。此外.任务切换只会发生在操作结束时,而不会发生在任何操作过程中。因此,任务之间没有竞争,因此在基本代码系统中没有临界区.

二、问题及解决办法

1.我想知道Linux有哪些定时器?它们的不同与用法,于是查了资料

sleep,usleep和nanosleep
      sleep()和nanosleep()都是使进程睡眠一段时间后被唤醒,但是二者的实现完全不同。Linux中并没有提供系统调用sleep(),sleep()是在库函数中实现的,它是通过调用alarm()来设定报警时间,调用sigsuspend()将进程挂起在信号SIGALARM上,sleep()只能精确到秒级上。nanosleep()则是Linux中的系统调用,它是使用定时器来实现的,该调用使调用进程睡眠,并往定时器队列上加入一个timer_list型定时器,time_list结构里包括唤醒时间以及唤醒后执行的函数,通过nanosleep()加入的定时器的执行函数仅仅完成唤醒当前进程的功能。系统通过一定的机制定时检查这些队列(比如通过系统调用陷入核心后,从核心返回用户态前,要检查当前进程的时间片是否已经耗尽,如果是则调用schedule()函数重新调度,该函数中就会检查定时器队列,另外慢中断返回前也会做此检查),如果定时时间已超过,则执行定时器指定的函数唤醒调用进程。当然,由于系统时间片可能丢失,所以nanosleep()精度也不是很高。

信号量SIGALRM + alarm()
      alarm方式虽然很好,但这种方式的精度能达到1秒,是无法低于1秒的精度。其中利用了*nix系统的信号量机制,首先注册信号量SIGALRM处理函数,调用alarm(),设置定时长度

RTC机制
      RTC机制利用系统硬件提供的Real Time Clock机制,通过读取RTC硬件/dev/rtc,通过ioctl()设置RTC频率,这种方式比较方便,利用了系统硬件提供的RTC,精度可调,而且非常高.该种方式要求系统有RTC设备

select()
      能精确到1us,目前精确定时的最流行方案。通过使用select(),来设置定时器;原理利用select()方法的第5个参数,第一个参数设置为0,三个文件描述符集都设置为NULL,第5个参数为时间结构体

2.运行书上代码

(1)setitimer

#include <signal.h>
#include <stdio.h>
#include <sys/time.h>

int count = 0;
struct itimerval t;

void timer_handler(int sig)
{
    printf("timer_handler: signal=%d count=%d
", sig, ++count);
    if (count>=8)
    {
        printf("cancel timer
");
        t.it_value.tv_sec = 0;
        t.it_value.tv_usec = 0;
        setitimer(ITIMER_VIRTUAL, &t, NULL);
    }
}

int main()
{
    struct itimerval timer;
     signal(SIGVTALRM, timer_handler);
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = 100000;
    timer.it_interval.tv_sec = 1;
    timer.it_interval.tv_usec = 0;
    setitimer(ITIMER_VIRTUAL,&timer, NULL);
    printf("looping:enter Control-C to Terminate
");
    while(1);
}

运行结果

(2)gettimeofday

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

struct timeval t;

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

运行结果出错了

上网查了原因 https://blog.csdn.net/weixin_40877924/article/details/108762118
也可直接在源代码上加头文件time.h
然后我换了一个代码

#include <sys/time.h>
#include <stdio.h>
#include <unistd.h>

void foo()
{
        int i = 0;
        for (; i <= 10000; i++);
}

int main(int argc, char *argv[])
{
        struct timeval start;
        struct timeval end;
        float timeuse;
        gettimeofday(&start, NULL);
        printf("start time is %d:%d
", start.tv_sec, start.tv_usec);
        foo();
        gettimeofday(&end, NULL);
        printf("end time is %d:%d
", end.tv_sec, end.tv_usec);
        timeuse = 1000000 * (end.tv_sec - start.tv_sec) + ( end.tv_usec - start.tv_usec );
        printf("timeuse1 is %f 
", timeuse);
        timeuse /= 1000000;
        printf("timeuse2 is %f 
", timeuse);
        printf("use time is %f
", timeuse);
        return 0;
}

运动结果

三、学习感悟

      在自学的过程中,经常会遇到很多问题,甚至是运行和书上一模一样的代码,都会出现问题,这时就要及时查资料,寻找解决办法。但是运行代码不是出结果了就结束了,还要尽量看懂它,这样才会有所收获。

原文地址:https://www.cnblogs.com/ffffatal/p/15510559.html