目录
定时器和时钟(第五章)
一、知识点总结
- 本章讨论了定时器和定时器服务;介绍了硬件定时器的原理和基于 Intel x86 的 PC 中的硬件定时器;讲解了 CPU操作和中断处理;描述了 Linux 中与定时器相关的系统调用、库函数和定时器服务命令;探讨了进程间隔定时器、定时器生厅成的信号,并通过示例演示了进程间隔定时器。编程项目的目的是要在一个多任务处理系统中实现定时器、定时器中断和间隔定时器。多任务处理系统作为一个 Linux 进程运行,该系线2统是 Linux 进程内并发任务的-个虚拟 CPU。Linux 进程的实时模式间隔定时器被设计为定期期生成 SIGALRM 信号,充当虚拟 CPU 的定时器中断,虚拟 CPU 使用 SIGALRM 信号捕捉器作为定时器的中断处理程序该项目可让读进程通过定时器队列实现任务间隔定时器,还可可让读进程使用 Linux 信号掩码来实现临界区,以防止各项任务和中断处理程序之间出现竞态态条件。摘要
- 本章讨论了定时器和定时器服务;介绍了硬件定时器的原理和基于 Intel x86 的 PC 中的硬件定时器;讲解了 CPU操作和中断处理;描述了 Linux 中与定时器相关的系统调用、库函数和定时器服务命令;探讨了进程间隔定时器、定时器生厅成的信号,并通过示例演示了进程间隔定时器。编程项目的目的是要在一个多任务处理系统中实现定时器、定时器中断和间隔定时器。多任务处理系统作为一个 Linux 进程运行,该系线2统是 Linux 进程内并发任务的-个虚拟 CPU。Linux 进程的实时模式间隔定时器被设计为定期期生成 SIGALRM 信号,充当虚拟 CPU 的定时器中断,虚拟 CPU 使用 SIGALRM 信号捕捉器作为定时器的中断处理程序该项目可让读进程通过定时器队列实现任务间隔定时器,还可可让读进程使用 Linux 信号掩码来实现临界区,以防止各项任务和中断处理程序之间出现竞态态条件。
二、知识点总结
1、硬件定时器
硬件定时器:一般硬件定时器集成在CPU的内部,有的可以使用外置的硬件定时器芯片;
特点:
可以人为通过编程来设置硬件定时器的工作频率;
硬件定时器一旦设定好了工作频率,只要上电,那么硬件定时器就会周期性的给CPU输出一个中断信号,称这个中断信号为时钟中断;
linux内核已经实现好了时钟中断对应的服务程序,这个服务程序也称之为时钟中断服务函数;
既然硬件定时器周期性的给CPU产生时钟中断,那么对应的中断服务程序就会被内核周期性的调用;
时钟中断服务函数做如下内容:
1.更新系统的运行时间,更新jiffies_64(jiffies)
2.更新实际时间
3.检查进程的时间片是否用完,决定是否需要重新调度新进程
4.检查是否有超时的软件定时器,如果有处理这个超时的软件定时器
2、Linux下的时钟函数
1)常用结构体
struct tm {
2 int tm_sec; /* seconds */
3 int tm_min; /* minutes */
4 int tm_hour; /* hours */
5 int tm_mday; /* day of the month */
6 int tm_mon; /* month */
7 int tm_year; /* year */
8 int tm_wday; /* day of the week */
9 int tm_yday; /* day in the year */
10 int tm_isdst; /* daylight saving time */
11 };
12
13 //int tm_sec 代表目前秒数,正常范围为0-59,但允许至61秒
14 //int tm_min 代表目前分数,范围0-59
15 //int tm_hour 从午夜算起的时数,范围为0-23
16 //int tm_mday 目前月份的日数,范围01-31
17 //int tm_mon 代表目前月份,从一月算起,范围从0-11
18 //int tm_year 从1900 年算起至今的年数
19 //int tm_wday 一星期的日数,从星期一算起,范围为0-6
20 //int tm_yday 从今年1月1日算起至今的天数,范围为0-365
21 //int tm_isdst 日光节约时间的旗标
2) time() 函数获取当前时间
1 SYNOPSIS
2 #include <time.h>
3
4 time_t time(time_t *t);
5
6 DESCRIPTION
7 time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).
8 //此函数会返回从公元1970年1月1日的UTC时间从0时0分0秒算起到现在所经过的秒数。如果t 并非空指针的话,此函数也会将返回值存到t指针所指的内存。
9 RETURN VALUE
10 On success, the value of time in seconds since the Epoch is returned. On error, ((time_t) -1) is returned, and errno is
11 set appropriately.
12 ERRORS
13 EFAULT t points outside your accessible address space.
14 //成功返回秒数,错误则返回(time_t) -1),错误原因存于errno中
3)asctime() asctime_r() 将时间和日期以字符串格式返回
1 #include <time.h>
2
3 struct tm *gmtime(const time_t *timep);
4 struct tm *gmtime_r(const time_t *timep, struct tm *result);
5
6 char *asctime(const struct tm *tm);
7 char *asctime_r(const struct tm *tm, char *buf);
8
9
10 /**gmtime是把日期和时间转换为格林威治(GMT)时间的函数。将参数time 所指的time_t 结构中的信息转换成真实世界所使用的时间日期表示方法,然后将结果由结构tm返回**/
11
12 /**asctime 将时间以换为字符串字符串格式返回 **/
4)mktime() 将时间结构体struct tm的值转化为经过的秒数
1 #include <time.h>
2
3 time_t mktime(struct tm *tm);
4
5 /**将时间结构体struct tm的值转化为经过的秒数**/
3、间隔定时器
- 所谓“间隔定时器(Interval Timer,简称itimer)就是指定时器采用“间隔”值(interval)来作为计时方式,当定时器启动后,间隔值interval将不断减小。当interval值减到0时,我们就说该间隔定时器到期。与上一节所说的内核动态定时器相比,二者最大的区别在于定时器的计时方式不同。内核定时器是通过它的到期时刻expires值来计时的,当全局变量jiffies值大于或等于内核动态定时器的expires值时,我们说内核内核定时器到期。而间隔定时器则实际上是通过一个不断减小的计数器来计时的。虽然这两种定时器并不相同,但却也是相互联系的。假如我们每个时钟节拍都使间隔定时器的间隔计数器减1,那么在这种情形下间隔定时器实际上就是内核动态定时器(下面我们会看到进程的真实间隔定时器就是这样通过内核定时器来实现的)。
间隔定时器主要被应用在用户进程上。每个Linux进程都有三个相互关联的间隔定时器。其各自的间隔计数器都定义在进程的task_struct结构中,如下所示(include/linux/sched.h):
struct task_struct{
……
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_incr;
struct timer_list real_timer;
……
}
(1)真实间隔定时器(ITIMER_REAL):这种间隔定时器在启动后,不管进程是否运行,每个时钟滴答都将其间隔计数器减1。当减到0值时,内核向进程发送SIGALRM信号。结构类型task_struct中的成员it_real_incr则表示真实间隔定时器的间隔计数器的初始值,而成员it_real_value则表示真实间隔定时器的间隔计数器的当前值。由于这种间隔定时器本质上与上一节的内核定时器时一样的,因此Linux实际上是通过real_timer这个内嵌在task_struct结构中的内核动态定时器来实现真实间隔定时器ITIMER_REAL的。
(2)虚拟间隔定时器ITIMER_VIRT:也称为进程的用户态间隔定时器。结构类型task_struct中成员it_virt_incr和it_virt_value分别表示虚拟间隔定时器的间隔计数器的初始值和当前值,二者均以时钟滴答次数位计数单位。当虚拟间隔定时器启动后,只有当进程在用户态下运行时,一次时钟滴答才能使间隔计数器当前值it_virt_value减1。当减到0值时,内核向进程发送SIGVTALRM信号(虚拟闹钟信号),并将it_virt_value重置为初值it_virt_incr。
(3)PROF间隔定时器ITIMER_PROF:进程的task_struct结构中的it_prof_value和it_prof_incr成员分别表示PROF间隔定时器的间隔计数器的当前值和初始值(均以时钟滴答为单位)。当一个进程的PROF间隔定时器启动后,则只要该进程处于运行中,而不管是在用户态或核心态下执行,每个时钟滴答都使间隔计数器it_prof_value值减1。当减到0值时,内核向进程发送SIGPROF信号,并将it_prof_value重置为初值it_prof_incr。
数据结构itimerval
虽然,在内核中间隔定时器的间隔计数器是以时钟滴答次数为单位,但是让用户以时钟滴答为单位来指定间隔定时器的间隔计数器的初值显然是不太方便的,因为用户习惯的时间单位是秒、毫秒或微秒等。所以Linux定义了数据结构itimerval来让用户以秒或微秒为单位指定间隔定时器的时间间隔值。其定义如下(include/linux/time.h):
struct itimerval {
struct timeval it_interval; /* timer interval */
struct timeval it_value; /* current value */
};
其中,it_interval成员表示间隔计数器的初始值,而it_value成员表示间隔计数器的当前值。这两个成员都是timeval结构类型的变量,因此其精度可以达到微秒级。
三、实践内容
计时器实现:
每个进程中同一种定时器只能使用一次。
该系统调用在POSIX.1-2001中定义了,但在POSIX.1-2008中已被废弃。所以建议使用POSIX定时器API(timer_gettime, timer_settime)代替。
函数alarm本质上设置的是低精确、非重载的ITIMER_REAL类定时器,它只能精确到秒,并且每次设置只能产生一次定时。函数setitimer 设置的定时器则不同,它们不但可以计时到微妙(理论上),还能自动循环定时。在一个Unix进程中,不能同时使用alarm和
四、问题与解决
1、time系统调用为什么返回时间值?
答:
time_t time(time_t *t);
time存在参数,为内存地址,当不为0时,系统调用将当前秒数写入指向内存。既然已经可以获取时间值了,为什么还要返回时间值呢?而不像其他系统调用,成功返回0,出错返回负值。
其主要原因应该是减少系统调用的时间,写入内存的时间相对于返回的时间更长,并且time系统调用经常被调用。
2、那么gettimeofday是怎么做到百万分之一秒精度的呢?
答:
其实是这样:内核中除了时钟中断这个每隔一段时间的滴答所带来的计时外,驱动时钟中断的定时器(比如HPET高精度定时器)也有相应的计数器的,这个计数器就可以提供比时钟中断更精确的计时,在每个时钟中断的时候,都会把这个计数器的值保存下来。所以gettimeofday返回的就是xtime中的数值,加上这个计数器的确定(当然是当前值减去保存下来的值)出来的更精确的计时。
这个计数器会从 HPET高精度计时器上的计数器,APIC电源管理计时器上的计数器,以及cpu里的时间戳计数器(time stamp counter),PIT计数器等里面选一个用,且会以这样的顺序选第一个可用的。如果机器上有 HPET高精度计时器,那么当然gettimeofday返回微秒级的时间是没啥问题的了。