linux时间编程(二) - 定时器编程应用

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 alarmunsigned 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_valueit_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时,通知信号的类别不能改变:SIGALARMSIGPROF等,而这些都是传统信号,而不是实时信号,因此有 timer overrun的问题;而 POSIX Timer则可以使用实时信号。

       3.setimer 的精度是 msPOSIX Timer是针对有实时要求的应用所设计的,接口支持ns级别的时钟精度。

         POSIX Timer 函数;

函数名

功能描述

timer_create 

创建一个新的 Timer;并且指定定时器到时通知机制

timer_delete

删除一个 Timer

timer_gettime

Get the time remaining on a POSIX.1b interval timer

timer_settime

开始或者停止某个定时器。

timer_getoverrun

获取丢失的定时通知个数。

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,setitimerposix timer,定时器是编程中常出现的工具,合理使用它们是编程者需要学习的东西。

       时间编程就这样告一段落了,下一章记录是linux进程线程同步。



你可能感兴趣的:(定时器,glibc,linus,时间编程)