linux定时测量
linux内核完成两种定时测量:
1.(时钟)保存当前的时间和日期--时钟电路同时用于跟踪当前时间和产生精确的时间度量。
2.(定时器)维持定时器--定时器电路由内核编程,所以它们以固定的、预先定义的频率发出中断。
本文转自:http://blog.csdn.net/jiebaoabcabc/article/details/37883167
一、linux定时测量分类:
1、实时时钟(RTC)
RTC实时时钟(Real-Time Clock),说穿了所谓RTC仅仅是一个可以连续计数的计数器,这个计数器可以预先配置为固定频率单位的累进脉冲。
RTC模块在有供电的情况下可以连续不断的计数,为其他电路持续提供实时的时钟信息。
而Linux内核对RTC的唯一用途就是把RTC用作“离线”或“后台”的时间与日期维护器。当Linux内核启动时,它从RTC中读取时间与日期的基准值。然后再运行期间内核就完全抛开RTC,从而以软件的形式维护系统的当前时间与日期,并在需要时将时间回写到RTC芯片中。所以,RTC时钟只是个为后面我们介绍的那些时钟起一个初始化的作用,仅此而已!
2、时间戳计数器(TSC)
用来记录系统节拍产生次数,系统开始工作后,掌管系统的时间。
3、可编程间隔定时器(PIT)
系统对PIT进行编程,在设定的节拍下记到规定次数后产生定时器中断。
二、linux定时器计数变量:
1.xtime变量
xtime内核变量存放当前时间和日期,它在系统初始化时被RTC附上初值,然后在系统运行过程中不断进行累加,保持与当前时间保持同步。
它是由timespec结构来描述的,可以提供纳秒级别的时间刻度。
struct timespec { long ts_sec; long ts_nsec; }; xtime由/kernel/time/timekeeping.c维护。 /** * do_gettimeofday - Returns the time of day in a timeval * @tv: pointer to the timeval to be set * * NOTE: Users should be converted to using getnstimeofday() */ void do_gettimeofday(struct timeval *tv) { struct timespec now; getnstimeofday(&now); tv->tv_sec = now.tv_sec; tv->tv_usec = now.tv_nsec/1000; } /** * getnstimeofday - Returns the time of day in a timespec * @ts: pointer to the timespec to be set * * Returns the time of day in a timespec. */ void getnstimeofday(struct timespec *ts) { struct timekeeper *tk = &timekeeper; unsigned long seq; s64 nsecs = 0; WARN_ON(timekeeping_suspended); do { seq = read_seqbegin(&tk->lock); ts->tv_sec = tk->xtime_sec; nsecs = timekeeping_get_ns(tk); } while (read_seqretry(&tk->lock, seq)); ts->tv_nsec = 0; timespec_add_ns(ts, nsecs); }
系统调用gettimeofday通过getnstimeofday获取时间,getnstimeofday 又通过xtime获取时间。
例: #include <stdio.h> #include <sys/time.h> // timeval gettimeofday int main(void) { struct timeval tv; gettimeofday(&tv, NULL); printf("%ld %ld\n", tv.tv_sec, tv.tv_usec); return 0; }
2.jiffies变量
jiffies内核变量用来记录自系统启动以来产生的节拍次数。
jiffies是一个有可能溢出的32位变量,jiffies_64这个64位版本的jiffies变量基本没有溢出的可能,但是开销会比32位变量大。
/include/linux/jiffies.h对此进行如下声明:
/*
*The 64-bit value is not atomic - you MUST NOT read it
*without sampling the sequence number in xtime_lock.
*get_jiffies_64() will do this for you as appropriate.
*/
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_datajiffies;
三、linux定时器的使用:
1.alarm闹钟
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
函数功能:创建一个闹钟,闹钟时间到会产生一个SIGALARM信号。
返回值:成功:如果调用此alarm()前,进程已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
输入参数:闹钟值,秒为单位
例: #include<unistd.h> // alarm pause #include<stdio.h> #include<stdlib.h> int main(void){ int ret; alarm(50); ret=alarm(5); // Returns the last time the rest of the alarm clock value printf("%d\n",ret); pause(); return 0; }
2.sleep定时器
#include <unistd.h>
unsigned int sleep (unsigned int__seconds); //秒级定时休眠
int usleep (__useconds_t __useconds); //毫秒级定时休眠
使用最为广泛的定时器,方便简单快捷。但是需要注意sleep可以被信号唤醒,如果不去检查返回值,会导致定时延时的偏差。
例: #include <stdio.h> #include <unistd.h> // sleep #include <signal.h> #define FOREVER 0 #define NOINT 1 #define NORMAL 2 void signal_handler_alarm(int val) { fprintf(stdout,"Caught signal SIGALRM %d %d...\n", val, SIGALRM); } int Sleep(int mode, int sec); int main() { int ret = 0; signal(SIGALRM, signal_handler_alarm); alarm(3); ret = sleep(10); printf("ret1 = %d\n", ret); ret = sleep(5); printf("ret2 = %d\n", ret); alarm(3); ret = Sleep(NOINT, 10); printf("ret3 = %d\n", ret); Sleep(FOREVER, 10); return 0; } int Sleep(int mode, int sec) { int ret = sec; switch(mode) { case FOREVER: while(1) { sleep(10); } break; case NOINT: while(ret > 0) { ret = sleep(ret); } break; case NORMAL: ret = sleep(sec); break; default: ret = -1; } return ret; }
sleep是阻塞一段时间的一个方法,usleep是它的微秒级版本,usleep的useconds参数必须小于1000000。如果useconds值是0,则调用没有实际效果。
它们的功能都可以由select代替:
例: #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <time.h> //获取当前时间戳 unsigned int getTimeStamp() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec*1000 + (tv.tv_usec)/1000; } //select做的延时函数 int Sleep(int sec, int msec) { struct timeval tv; tv.tv_sec = sec; tv.tv_usec = msec * 1000; return select(0, NULL, NULL, NULL, &tv); } int main(int argc, char **argv) { unsigned int time_val = 0; for ( ; ; ) { time_val = getTimeStamp(); Sleep(0, 500); //usleep(500*1000); printf("[test]: time = %d\n", (int)(getTimeStamp() - time_val)); } return 0; }
3.settimer
#include <sys/time.h>
intsetitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
返回值:setitimer()调用成功返回0,否则返回-1。
输入参数:
1.which为定时器类型,setitimer支持3种类型的定时器:
ITIMER_REAL:以系统真实的时间来计算,它送出SIGALRM信号。
ITIMER_VIRTUAL: -以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF:以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号。
2.struct itimerval {
struct timevalit_interval;
struct timevalit_value;
};
struct timeval {
long tv_sec;
long tv_usec;
};
it_interval指定间隔时间,it_value指定初始定时时间。如果只指定it_value,就是实现一次定时;如果同时指定 it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。
输出参数:
1. ovalue用来保存先前的值,常设为NULL。
如果是以setitimer提供的定时器来休眠,只需阻塞等待定时器信号就可以了。
例:#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <math.h> #include <sys/time.h> void sigroutine(int signo){ switch (signo){ case SIGALRM: printf("Catch a signal -- SIGALRM \n"); break; case SIGVTALRM: printf("Catch a signal -- SIGVTALRM \n"); break; } return; } int main() { struct itimerval value, ovalue, value2; printf("process id is %d\n", getpid()); signal(SIGALRM, sigroutine); signal(SIGVTALRM, sigroutine); value.it_value.tv_sec = 1; value.it_value.tv_usec = 0; value.it_interval.tv_sec = 1; value.it_interval.tv_usec = 0; // • ITIMER_REAL:减少实际时间,到期的时候发出 SIGALRM 信号。 setitimer(ITIMER_REAL, &value, &ovalue); //(1) value2.it_value.tv_sec = 1; value2.it_value.tv_usec = 0; value2.it_interval.tv_sec = 1; value2.it_interval.tv_usec = 0; // • ITIMER_VIRTUAL:减少有效时间 (进程执行的时间),产生 SIGVTALRM 信号。 setitimer(ITIMER_VIRTUAL, &value2, &ovalue); //(2) // • ITIMER_PROF:减少进程的有效时间和系统时间 (为进程调度用的时间)。这个经常和上面一个使用用来计算系统内核时间和用户时间。产生 SIGPROF 信号。 for(;;) { sleep(10); } }
setitimer 计时器的精度为 ms,即 1000分之 1 秒,足以满足绝大多数应用程序的需要。但多媒体等应用可能需要更高精度的定时,那么就需要考虑使用下一类定时器:POSIX Timer。
4.posix timer
间隔定时器 setitimer有一些重要的缺点,POSIX Timer对 setitimer进行了增强,克服了 setitimer的诸多问题:
1.首先,一个进程同一时刻只能有一个 timer。假如应用需要同时维护多个 Interval不同的计时器,必须自己写代码来维护。这非常不方便。使用 POSIX Timer,一个进程可以创建任意多个 Timer。
2.setitmer 计时器时间到达时,只能使用信号方式通知使用 timer的进程,而 POSIX timer可以有多种通知方式,比如信号,或者启动线程。
使用 setitimer时,通知信号的类别不能改变:SIGALARM,SIGPROF等,而这些都是传统信号,而不是实时信号,因此有 timer overrun的问题;而 POSIX Timer则可以使用实时信号。
3.setimer 的精度是 ms,POSIX Timer是针对有实时要求的应用所设计的,接口支持ns级别的时钟精度。
POSIX Timer 函数;
函数名 |
功能描述 |
|
|
|
删除一个 Timer |
|
Get the time remaining on a POSIX.1b interval timer |
|
开始或者停止某个定时器。 |
|
获取丢失的定时通知个数。 |
1.创建定时器函数:
#include<time.h>
#include<signal.h>
int timer_create (clockid_t clock_id, structsigevent *evp, timer_t * timerid)
函数描述:创建一个新的
Timer
;并且指定定时器到时通知机制
返回值:调用成功返回0,否则返回-1。
输入参数:1.clock_id --代表Timer的种类,可以为下表中的任意一种:
Clock ID |
描述 |
CLOCK_REALTIME |
Settable system-wide real-time clock; |
CLOCK_MONOTONIC |
Nonsettable monotonic clock |
CLOCK_PROCESS_CPUTIME_ID |
Per-process CPU-time clock |
CLOCK_THREAD_CPUTIME_ID |
Per-thread CPU-time clock |
CLOCK_REALTIME:真实时间,这个时间是系统保存的时间,可以由date命令查看,定时器如果以此为标准定时的话,当系统保存的时间被修改,那么timer就会依照新的时间轴进行定时,所以此时定时器定时的时间会与当初设想的时间不一样。
CLOCK_MONOTONIC:单调时间,这个时间是以系统运行时间来测算的,所以不用担心时间轴改变而引起定时时间不准的问题,它会严格在定时时间到达时触发。
CLOCK_PROCESS_CPUTIME_ID:以调用定时器的进程实际花费的时间为标准测算,定时时间依赖进程占用CPU的时间。
CLOCK_THREAD_CPUTIME_ID:同理,以调用定时器的线程实际花费的时间为标准测算,定时时间依赖线程占用CPU的时间。
2. evp 用来设置定时器触发通知方式:
struct sigevent {
int sigev_notify; /* Notification method */
int sigev_signo; /* Notification signal */
union sigval sigev_value; /* Data passed with
notification */
void (*sigev_notify_function) (union sigval);
/* Function used for thread
notification (SIGEV_THREAD) */
void *sigev_notify_attributes;
/* Attributes for notification thread
(SIGEV_THREAD) */
pid_t sigev_notify_thread_id;
/* ID of thread to signal (SIGEV_THREAD_ID) */
};
其中 sigev_notify表示定时器到期通知方式,有如下几种:
通知方式 |
描述 |
SIGEV_NONE |
定时器到期时不产生通知。 |
SIGEV_SIGNAL |
定时器到期时将给进程投递一个信号,sigev_signo可以用来指定使用什么信号。 |
SIGEV_THREAD |
定时器到期时将启动新的线程进行需要的处理 |
SIGEV_THREAD_ID(仅针对 Linux) |
定时器到期时将向指定线程发送信号。 |
如果采用 SIGEV_NONE方式,使用者必须调用timer_gettime
函数主动读取定时器已经走过的时间。类似轮询。
如果采用 SIGEV_SIGNAL方式,使用者可以选择使用什么信号,用 sigev_signo表示信号值,比如 SIG_ALARM。
如果使用 SIGEV_THREAD方式,则需要设置 sigev_notify_function,当 Timer到期时,将使用该函数作为入口启动一个线程来处理信号;sigev_value保存了传入 sigev_notify_function的参数。sigev_notify_attributes如果非空,则应该是一个指向 pthread_attr_t的指针,用来设置线程的属性(比如 stack大小,detach 状态等)。
SIGEV_THREAD_ID 通常和 SIGEV_SIGNAL联合使用,这样当 Timer到期时,系统会向由sigev_notify_thread_id指定的线程发送信号,否则可能进程中的任意线程都可能收到该信号。这个选项是 Linux对 POSIX 标准的扩展,目前主要是 GLibc 在实现 SIGEV_THREAD 的时候使用到,应用程序很少会需要用到这种模式。
输出参数:timerid
The timer_create() function returns, in the location referenced by timerid, a timer ID oftype timer_t usedto identify the timer in timer requests. This timer ID will be unique withinthe calling process until the timer is deleted.
用以返回定时器ID,也是用来描述申请定时器的时间点。
2.删除定时器函数:
#include <time.h>
int timer_delete (timer_t timerid)
函数描述:根据timerid删除一个 Timer
返回值:调用成功返回0,否则返回-1。
输入参数:timerid
来源自创建定时器时,创建函数提供的定时器id号,应用程序需要维护这个ID直至此定时器被销毁。
3.获取定时器信息:
#include <time.h>
int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
函数描述:timer_gettime() returns the time until next expiration, and the interval,for the timer specified by timerid, in the bufferpointed to by curr_value. The timeremaining until the next timer expiration is returned in curr_value->it_value; this is always a relative value, regardless ofwhether the TIMER_ABSTIME flag was used when arming the timer.If the value returned in curr_value->it_value is zero, then the timer is currentlydisarmed. The timer interval is returned in curr_value->it_interval. If the valuereturned in curr_value->it_intervalis zero, then thisis a "one-shot" timer.
返回值:调用成功返回0,否则返回-1。
输入参数:timerid 定时器ID号
输出参数:curr_value 当前定时器相关变量数据存放结构
curr_value->it_value保存的是当前时间点到下一次触发定时器的时间间隔。
curr_value->it_interval保存的是定时器定时间隔,如果显示为0说明此定时器是单次定时。
4.设置、启动、停止定时器:
#include <time.h>
int timer_settime(timer_t timerid, int flags,
const struct itimerspec *new_value,
struct itimerspec * old_value);
函数描述:timer_settime() arms or disarms the timer identified by timerid. The new_value argument is pointer to an itimerspec structurethat specifies the new initial value and the new interval for the timer.
配置定时器的函数。配置完成后自动启动定时器。
返回值:调用成功返回0,否则返回-1。
输入参数:1.timerid 定时器ID
2.flags 选择0(默认相对时间)或者TIMER_ABSTIME(绝对时间)
如果flag没有设定为TIMER_ABSTIME,则定时器从调用开始在it_value内超时;即value->it_value代表计时器第一次超时的时间。
如果设定为TIMER_ABSTIME,该函数表现为时间直到下一次超时被设定为it_value指定的绝对时间和与timerid相联的时钟值的差值。如果已经到了it_value指定的值,那么超时后的处理就会立即执行。
3. new_value 定时器设置定时时间和重装时间
输出参数:old_value 输出定时器以前的定时配置
5.获取丢失的定时通知函数
#include <time.h>
int timer_getoverrun (timer_t timerid)
函数描述:获取丢失的定时通知个数
返回值:返回溢出个数,可以是0,说明没有溢出。如果返回-1说明出错。
输入参数:timerid 定时器ID
例: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <time.h> #include <math.h> #include <sys/time.h> void handle(union sigval v) { time_t t; char p[32]; time(&t); strftime(p, sizeof(p), "%T", localtime(&t)); printf("%s thread %lu, val = %d, signal captured\n", p, pthread_self(), v.sival_int); return; } int main() { struct sigevent evp; struct itimerspec ts; timer_t timer; int ret; memset (&evp, 0, sizeof (evp)); evp.sigev_value.sival_ptr = &timer; evp.sigev_notify = SIGEV_THREAD; evp.sigev_notify_function = handle; evp.sigev_value.sival_int = 3; //作为handle()的参数 ret = timer_create(CLOCK_REALTIME, &evp, &timer); if( ret) perror("timer_create"); /*it_value用于指定当前的定时器到期时间。当定时器到期,it_value的值会被更新成it_interval 的值。如果it_interval的值为0,则定时器不是一个时间间隔定时器,一旦it_value到期就会回到未启动状态。*/ ts.it_interval.tv_sec = 1; ts.it_interval.tv_nsec = 0; ts.it_value.tv_sec = 3; ts.it_value.tv_nsec = 0; ret = timer_settime(timer, TIMER_ABSTIME, &ts, NULL); if( ret ) perror("timer_settime"); while(1); //int timer_gettime(timer_t timerid,struct itimerspec *value); //int timer_getoverrun(timer_t timerid); //timer_delete (timer_t timerid); }
注意:这5个函数调用都需要加上链接命令-lrt,否则会出现下列错误:
/tmp/cc1tvkPH.o: In function `main':
posix_timer.c:(.text+0xde): undefinedreference to `timer_create'
posix_timer.c:(.text+0x139): undefinedreference to `timer_settime'
collect2: ld 返回 1
四、小结
至此,分析完了linux几种常用的定时器编程和使用,包括alarm,sleep,select,setitimer和posix timer,定时器是编程中常出现的工具,合理使用它们是编程者需要学习的东西。
时间编程就这样告一段落了,下一章记录是linux进程线程同步。