80x86体系结构上,内核与时钟密切相关,与时钟相关的硬件有:实时时钟(Real Time Clock,RTC)、时间戳计数器(Time Stamp Counter,TSC)、可编程间隔定时器(Programmable Interval Timer, PIT)SMP系统上的本地APIC定时器高精度事件定时器(High Precision Event Timer,HPET)。
RTC是一个独立于CPU的专用芯片,它依靠独立于系统供电电源的小电池给RTC的振荡器进行供电,因此在关机时也能保证时间是正确的。即使关闭PC电源,也会继续运转。这些芯片一般与主板的CMOS芯片组集成在一个芯片中。
例如:Motorala 146818,实时时钟中断是从IRQ8上引入的,能发出周期性的中断,频率在2HZ~8192之间,可以对其编程实现对时间的长短计时。
tick(滴答)是HZ的倒数,也就是发生两次定时中断的时间间隔。如HZ为100时,tick为1/100=10ms(毫秒)。jiffies为Linux内核中的一个全局变量,用来记录从系统启动以来产生的节拍数,extern u64 __jiffy_data jiffies_64;extern unsigned long volatile __jiffy_data jiffies;
节拍率,即系统定时器的频率,在内核中通过HZ这个宏进行定义。在进行内核编程的时候,切记不要假设HZ不会发生变化,事实上,大多数体系结构的HZ都是可调的
HZ的理想取值:从2.5内核开始,这个取值在i386体系结构中就改为了1000(2.6.13版本后的内核,加入了250这个取值)。改变HZ的取值,对于操作系统而言,意味着改变时钟中断的频率:
墙上时间:墙上挂钟的时间,即真实时间。用户级进程一般使用该时间。
进程时间:进程消耗的时间,包括用户空间的和内核空间的,一般在对程序进行分析、统计的时候使用。
单调时间:保持稳定线性递增的时间,其重要性不在于绝对值是多少,而在于通过多次采样并计算相对时间。单调时间不会因为墙上时间的改变而受到影响。
1.1.1 基本数据类型
typedef long time_t
这个记录的是自UTC以来,流逝过的秒数。
一个问题是,是否会溢出?当然会,对于32位机,在2038年会溢出,但幸运的是,那个时候,很可能我们已经在使用64位机了
1.1.1 毫秒级与纳秒级
struct timeval{
time_t tv_sec; // seconds
susecond_t tv_usec; // microseconds
}
struct timespec{
time_t tv_sec; // seconds
long tv_nsec; // nanoseconds
}
显而易见的原因,后者更受用户的青睐。
1.1.1 标准c提供的时间
struct tm{
···
} // 定义在<time.h>中,使用我们更容易理解的方式,更生活化
1.1.2 进程时间类型
typedef long clock_t; 对于不同函数,该变量表示HZ,或者CLOCKS_PER_SEC
1.1 接口
系统提供的接口主要有两类:获取、设置时间。前者还包括进程时间的获取
另外,也能够对系统时钟进行调校
接口简单罗列如下:
time() // s ßà stime()
gettimeofday() // us ßà settimeofday()
clock_gettime() // ns ßàclock_settime()
times() // clock_t
另外,系统还提供各种数据结构转换的接口,调校时钟的接口等
1 Linux定时器
1.1 Linux提供的睡眠系统调用– 临时定时器
Linux提供了几个睡眠的系统调用
sleep() // s
usleep() // us
nanosleep() // ns
clock_nanosleep() //POSIX规范接口,更高级,提供各种类型的定时器1.1 进程唯一的实时定时器
对于进程唯一的实时定时器的使用,系统提供了两类接口:alarm / ualarm,以及 getitimer /setitimer
前者是简单的小闹钟,结合信号量使用,精确度在秒级
后者则更为高级,其提供了3种工作模式,能够较好的过滤掉诸如上下文切换带来的统计误差:
REAL:记录墙上时间的流逝(SIGALARM)
VIRTUAL:只记录进程空间的时间流逝(SIGVTALARM)
PROF:在进程执行以及内核为进程服务时,记录时间的流逝(SIGPROF)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------使用定时器的目的无非是为了周期性的执行某一任务,或者是到了一个指定时间去执行某一个任务。要达到这一目的,一般有两个常见的比较有效的方法。一个是用linux内部的三个定时器,另一个是用sleep, usleep函数让进程睡眠一段时间,使用alarm定时发出一个信号,还有那就是用gettimeofday, difftime等自己来计算时间间隔,然后时间到了就执行某一任务,但是这种方法效率低,所以不常用。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigalrm_fn(int sig)
{
/* Do something */
printf("alarm!\n");
alarm(2);
return;
}
int main(void)
{
signal(SIGALRM, sigalrm_fn);
alarm(2);
/* Do someting */
while(1) pause();
}
//********************************************************************************//
Signal
|
Description
|
SIGABRT
|
由调用abort函数产生,进程非正常退出
|
SIGALRM
|
用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
|
SIGBUS
|
某种特定的硬件异常,通常由内存访问引起
|
SIGCANCEL
|
由Solaris Thread Library内部使用,通常不会使用
|
SIGCHLD
|
进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
|
SIGCONT
|
当被stop的进程恢复运行的时候,自动发送
|
SIGEMT
|
和实现相关的硬件异常
|
SIGFPE
|
数学相关的异常,如被0除,浮点溢出,等等
|
SIGFREEZE
|
Solaris专用,Hiberate或者Suspended时候发送
|
SIGHUP
|
发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送
|
SIGILL
|
非法指令异常
|
SIGINFO
|
BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程
|
SIGINT
|
由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程
|
SIGIO
|
异步IO事件
|
SIGIOT
|
实现相关的硬件异常,一般对应SIGABRT
|
SIGKILL
|
无法处理和忽略。中止某个进程
|
SIGLWP
|
由Solaris Thread Libray内部使用
|
SIGPIPE
|
在reader中止之后写Pipe的时候发送
|
SIGPOLL
|
当某个事件发送给Pollable Device的时候发送
|
SIGPROF
|
Setitimer指定的Profiling Interval Timer所产生
|
SIGPWR
|
和系统相关。和UPS相关。
|
SIGQUIT
|
输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程
|
SIGSEGV
|
非法内存访问
|
SIGSTKFLT
|
Linux专用,数学协处理器的栈异常
|
SIGSTOP
|
中止进程。无法处理和忽略。
|
SIGSYS
|
非法系统调用
|
SIGTERM
|
请求中止进程,kill命令缺省发送
|
SIGTHAW
|
Solaris专用,从Suspend恢复时候发送
|
SIGTRAP
|
实现相关的硬件异常。一般是调试异常
|
SIGTSTP
|
Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
|
SIGTTIN
|
当Background Group的进程尝试读取Terminal的时候发送
|
SIGTTOU
|
当Background Group的进程尝试写Terminal的时候发送
|
SIGURG
|
当out-of-band data接收的时候可能发送
|
SIGUSR1
|
用户自定义signal 1
|
SIGUSR2
|
用户自定义signal 2
|
SIGVTALRM
|
setitimer函数设置的Virtual Interval Timer超时的时候
|
SIGWAITING
|
Solaris Thread Library内部实现专用
|
SIGWINCH
|
当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
|
SIGXCPU
|
当CPU时间限制超时的时候
|
SIGXFSZ
|
进程超过文件大小限制
|
SIGXRES
|
Solaris专用,进程超过资源限制的时候发送
|
所需头文件
unsigned int alarm(unsigned int seconds);
Linux内置的3个定时器
Linux为每个任务安排了3个内部定时器:
ITIMER_REAL:实时定时器,不管进程在何种模式下运行(甚至在进程被挂起时),它总在计数。定时到达,向进程发送SIGALRM信号。
ITIMER_VIRTUAL:这个不是实时定时器,当进程在用户模式(即程序执行时)计算进程执行的时间。定时到达后向该进程发送SIGVTALRM信号。
ITIMER_PROF:进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计数。定时到达产生SIGPROF信号。ITIMER_PROF记录的时间比ITIMER_VIRTUAL多了进程调度所花的时间。
定时器在初始化是,被赋予一个初始值,随时间递减,递减至0后发出信号,同时恢复初始值。在任务中,我们可以一种或者全部三种定时器,但同一时刻同一类型的定时器只能使用一个。
使用滴答数计时。用到的函数有:
#include <sys/time.h>
int getitimer(int which, struct itimerval *value);
int setitimer(int which, struct itimerval*newvalue, struct itimerval* oldvalue);
strcut timeval
{
long tv_sec; /*秒*/
long tv_usec; /*微秒*/
};
struct itimerval
{
struct timeval it_interval; /*时间间隔*/
struct timeval it_value; /*当前时间计数*/
};
it_interval用来指定每隔多长时间执行任务, it_value用来保存当前时间离执行任务还有多长时间。比如说, 你指定it_interval为2秒(微秒为0),开始的时候我们把it_value的时间也设定为2秒(微秒为0),当过了一秒, it_value就减少一个为1, 再过1秒,则it_value又减少1,变为0,这个时候发出信号(告诉用户时间到了,可以执行任务了),并且系统自动把it_value的时间重置为it_interval的值,即2秒,再重新计数。
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <signal.h>
int count = 0;
void set_timer()
{
struct itimerval itv, oldtv;
itv.it_interval.tv_sec = 5;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = 5;
itv.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &itv, &oldtv);
}
void sigalrm_handler(int sig)
{
count++;
printf("timer signal.. %d\n", count);
}
int main()
{
signal(SIGALRM, sigalrm_handler);
set_timer();
while (count < 1000)
{}
exit(0);
}