一、学习笔记
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;
}
运动结果
三、学习感悟
在自学的过程中,经常会遇到很多问题,甚至是运行和书上一模一样的代码,都会出现问题,这时就要及时查资料,寻找解决办法。但是运行代码不是出结果了就结束了,还要尽量看懂它,这样才会有所收获。