浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程【转】

本文转载自:http://www.cnblogs.com/qingchen1984/p/7007631.htmlhtml

本篇文章主要介绍了"浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程",主要涉及到浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程方面的内容,对于浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程感兴趣的同窗能够参考一下。
 

简介: 本文试图完整地描述 Linux 系统中 C 语言编程中的时间问题。主要内容包括应用程序中的时间编程方法;时钟硬件简介;Glibc 时间函数的实现以及 Linux 内核对时间的支持和实现原理。这是第 1 部分,探讨应用开发中的时间编程问题。linux

 

引子程序员

咱们都生活在时间中,但却没法去思考它。什么是时间呢?彷佛这是一个永远也不能被回答的问题。然而做为一个程序员,在工做中,总有那么几回我必须思考什么是时间。好比,须要知道一段代码运行了多久;要在 log 文件中记录事件发生时的时间戳;再好比须要一个定时器以便可以按期作某些计算机操做。我发现,在计算机世界中,时间在不一样场合也每每有不一样的含义,让试图思考它的人感到迷茫。但值得庆幸的是,Linux 中的时间终究是能够理解的。所以我打算讨论一下有关时间的话题,尝试着深刻理解 Linux 系统中 C 语言编程中的时间问题。主要内容以下:算法

  • 第 1 部分是应用程序中的时间问题。有三个方面:程序计时须要;获取当前时间;定时器。
  • 第 2 部分包括时间硬件简介和 GlibC 实现时间函数的原理。
  • 第 3 和第 4 部分是 Linux 内核对时间的支持和实现原理。

如今开始第 1 部分,探讨应用开发中的时间编程问题。在这一部分中,全部的例子代码都在 GlibC 2.14,内核 2.6.33 的 Linux 系统下编译并验证执行过。读者若是使用低版本的 GlibC 和 Linux 内核有可能没法正确执行。编程


获取当前时间bash

时间的获取数据结构

在程序当中, 咱们常常要输出系统当前的时间,好比日志文件中的每个事件都要记录其产生时间。在 C 语言中获取当前时间的方法有如下几种,它们所得到的时间精度从秒级到纳秒,各有所不一样。ide


表 1. C 时间函数函数

function 定义 含义 返回值 精度
time() time 函数得到从 1970 年 1 月 1 日 0 点到当前的秒数,存储在time_t结构之中。 time_t
gettimeofday() gettimeofday 函数返回从 1970 年 1 月 1 日 0 点以来,到如今的时间。用 timeval 数据结构表示。 struct timeval
{
time_t tv_sec;
long int tv_usec;
};
微秒
clock_gettime() clock_gettime 函数返回从 1970 年 1 月 1 日 0 点以来,到如今的时间。用 timespec 数据结构表示。
支持不普遍。属于实时扩展。
struct timespec
{
time_t tv_sec;
long int tv_nsec;
};
纳秒
ftime() 函数返回从 1970 年 1 月 1 日 0 点以来,到如今的时间。用timeb数据结构表示。
已通过时, 被 time() 替代。尽可能不使用。
struct timeb {
time_t time;
unsigned short 
millitm;
short timezone;
short dstflag;
};
毫秒

 

GUN/Linux 提供了三个标准的 API 用来获取当前时间,time()/gettimeofday()/clock_gettime(),它们的区别仅在于获取的时间精度不一样,您能够根据须要选取合适的调用。ftime() 是老的一些系统中的时间调用,不少 Linux 版本虽然支持它,但仅仅是为了向前兼容性,新开发的软件不建议使用 ftime() 来得到当前时间。post

时间显示和转换

目前咱们获得的时间是一个数字,不管精度如何,它表明的仅是一个差值。好比精度为秒的 time() 函数,返回一个 time_t 类型的整数。假设当前时间为 2011 年 12 月 7 日下午 20 点 29 分 51 秒,那么 time_t 的值为:1323318591。即距离 1970 年 1 月 1 日零点,咱们已通过去了 1323318591 秒。(这里的 1970 年 1 月 1 日零点是格林威治时间,而不是北京时间。)咱们下面讨论的时间若是不特别说明都是格林威治时间,也叫 GMT 时间,或者 UTC 时间。

字符串“1323318591 秒”对于多数人都没有太大的意义,咱们更愿意看到“2011 年 12 月 7 日”这样的显示。所以当咱们获得秒,毫秒,甚至纳秒表示的当前时间以后,每每须要将这些数字转换为人们所熟悉的时间表示方法。

因为国家,习惯和时区的不一样,时间的表示方法并无一个统一的格式。为了知足各类时间显示的需求,标准 C 库提供了许多时间格式转换的函数。这些函数的数量众多,容易让人迷惑,记住它们的用法十分不易。在这里我借用 Michael Kerrisk 在《Linux Programming Interface》一书中的插图,来对这些标准 C 函数进行一个整体的概览。


图 1. 各类时间显示格式转换函数关系图
 

从上图能够看到,time()/gettimeofday() 从内核获得当前时间以后,该当前时间值能够被两大类函数转换为更加容易阅读的显示格式:

  • 固定格式转换
  • 用户指定格式转换函数。

固定格式转换

用 ctime() 函数转换出来的时间格式是系统固定的,调用者没法改动,所以被称为固定格式转换。若是您对日期格式没有特殊的要求,那么用它基本上就能够了,简单,不用记忆不少的参数。

用户指定格式转换

典型的 ctime() 格式以下:

Wed Dec 7 20:45:43 PST 2011

有些人以为这个格式太长,相似 Wed,星期三这样的信息不少状况下都没有啥用途。人们可能更喜欢其余格式:好比2011-12-07 20:45。在这种状况下,就须要进行时间显示格式转换。作法为:先把从内核获得的时间值转换为 struct tm 类型的值,而后调用 strftime() 等函数来输出自定义的时间格式字符串。

下面我列举一些实例,以便读者更清晰地理解众多的时间转换函数的用法。

各标准 C 时间转换函数的解释和举例

 

char *ctime(const time_t *clock); 

 

使用函数 ctime 将秒数转化为字符串. 这个函数的返回类型是固定的:一个可能值为”Thu Dec 7 14:58:59 2000”。这个字符串的长度和显示格式是固定的。


清单 1,time 的使用

#include <time.h>
int main ()
{
 time_t time_raw_format;
 time ( &time_raw_format ); //获取当前时间
 printf (" time is [%d]\n", time_raw_format);
 //用 ctime 将时间转换为字符串输出
 printf ( "The current local time: %s", ctime(&time_raw_format));
 return 0;
}

 

自定义格式转换

为了更灵活的显示,须要把类型 time_t 转换为 tm 数据结构。tm 数据结构将时间分别保存到表明年,月,日,时,分,秒等不一样的变量中。再也不是一个使人费解的 64 位整数了。这种数据结构是各类自定义格式转换函数所须要的输入形式。


清单 2,数据结构 tm

struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year since 1900 */
int tm_wday; /* Day of the week (Sunday = 0)*/
int tm_yday; /* Day in the year (0-365; 1 Jan = 0)*/
int tm_isdst; /* Daylight saving time flag
 > 0: DST is in effect;
 = 0: DST is not effect;
 < 0: DST information not available */
};

 

可使用 gmtime() 和 localtime() 把 time_t 转换为 tm 数据格式,其中 gmtime() 把时间转换为格林威治时间;localtime 则转换为当地时间。


清单 3,时间转换函数定义

#include <time.h>
struct tm *gmtime(const time_t *timep);
struct tm *localtime(const time_t *timep);

 

使用 tm 来表示时间,您就能够调用 asctime() 和 strftime() 将时间转换为字符串了。asctime() 的输出格式固定,和 ctime() 相同。strftime() 则相似咱们最熟悉的 printf() 函数,您能够经过输入参数自定义时间的输出格式。

 

size_t strftime(char *outstr, size_t maxsize, const char *format,
 const struct tm *timeptr);



清单 4,时间显示转换

int main ()
{
	time_t time_raw_format;
	struct tm * time_struct;
	char buf [100];
	time ( &time_raw_format );
	time_struct = localtime ( &time_raw_format );
	strftime (buf,100,"It is now: %I:%M%p.",time_struct);
	puts (buf);
	return 0;
}

 

该例子程序的输出结果以下:

 

It is now: 02:45PM.

 

从以上的例子能够看到,利用从 time() 获得的时间值,能够调用各类转换函数将其转换成更方便人们阅读的形式。

此外从前面的总结中咱们也了解到,还有两个 C 函数能够得到当前时间,gettimeofday() 以及 clock_gettime(),它们分别返回 struct timeval 或者 timespec 表明的高精度的时间值。在目前的 GLibC 中,尚未直接把 struct timeval/timespec 转换为 struct tm 的函数。通常的作法是将 timeval 中的 tv_sec 转换为 tm,使用上面所述的方法转换为字符串,最后在显示的时候追加上 tv_usec,好比下面的例子代码:


清单 5,更多时间显示转换

struct timeval tv;
time_t nowtime; 
struct tm *nowtm; 
char tmbuf[64], buf[64]; 
gettimeofday(&tv, NULL); //获取当前时间到 tv
nowtime = tv.tv_sec; //nowtime 存储了秒级的时间值
nowtm = localtime(&nowtime); //转换为 tm 数据结构
//用 strftime 函数将 tv 转换为字符串,但 strftime 函数只能达到秒级精度
strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm);
//将毫秒值追加到 strftime 转换的字符串末尾 
snprintf(buf, sizeof buf, "%s.%06d", tmbuf, tv.tv_usec);

 


时间的测量

有时候咱们要计算某段程序执行的时间,好比须要对算法进行时间分析。基本的实现思路为在被测试代码的开始和结束的地方获取当时时间,相减后获得相对值,即所须要的统计时间。为了实现高精度的时间测量,必须使用高精度的时间获取方式,通常有两种方法:

  • 系统调用 gettimeofday
  • 汇编指令 RDTSC。

gettimeofday

可使用 gettimeofday() 函数进行时间测量,其精度在 us 级别,能够用来作通常的时间分析。

gettimeofday() 将时间保存在结构 tv 之中。gettimeofday() 的第二个参数表明时区,在 Linux 中已经废弃不用,只能用 NULL 传入。一个典型的例子程序以下:


清单 6,gettimeofday 例子程序

void function() 
{ 
 unsigned int i,j; 
 double y; 
 for(i=0;i<1000;i++) 
 for(j=0;j<1000;j++) 
 y=sin((double)i); //耗时操做
} 

main() 
{ 
 struct timeval tpstart,tpend; 
 float timeuse; 

 gettimeofday(&tpstart,NULL); //记录开始时间戳
 function(); 
 gettimeofday(&tpend,NULL); //记录结束时间戳
 timeuse = 1000000*(tpend.tv_sec-tpstart.tv_sec)+ 
tpend.tv_usec-tpstart.tv_usec; //计算差值
 timeuse /= 1000000; 
 printf("Used Time:%f\n",timeuse); 
 exit(0); 
} 

 

这个程序输出函数的执行时间,咱们可使用这个来进行系统性能的测试,或者是函数算法的效率分析。在我我的机器上的输出结果是:Used Time:0.556070

RDTSC

gettimeofday() 是一个系统调用,在某些场合下频繁调用它是不合适的。好比性能要求很高的代码段内。由于 gettimeofday() 须要用户态/内核态切换,开销较大。Intel X86 处理器提供了 TSC 硬件,而且能够用非特权指令 rdtsc 来读取该硬件的时间值,这就避免了过分的内核用户态切换。

如何使用 RDTSC

参考下面的例子代码,采用 GCC 的汇编扩展,定义 rdtsc 的函数,它返回当前时间戳。

 

#define rdtsc(low,high) __asm__ \
 __volatile__("rdtsc" : "=a" (low), "=d" (high))

 

在 C 代码中使用 rdtsc 十分简单。好比:


清单 7,RDTSC 例子程序

unsigned long long get_cycles()
{
	unsigned low, high;
	unsigned long long val;
	rdtsc(low,high);
	val = high;
val = (val << 32) | low; //将 low 和 high 合成一个 64 位值
	return val;
}

double get_cpu_mhz(void)
{
	FILE* f;
	char buf[256];
	double mhz = 0.0;

f = fopen("/proc/cpuinfo","r"); //打开 proc/cpuinfo 文件
	if (!f)
		return 0.0;
	while(fgets(buf, sizeof(buf), f)) {
		double m;
		int rc;
rc = sscanf(buf, "cpu MHz : %lf", &m); //读取 cpu MHz
		if (mhz == 0.0) {
			mhz = m;
			break;
		}
	}
	fclose(f);
return mhz; //返回 HZ 值
}

int main()
{
	double mhz;
	mhz = get_cpu_mhz();
	cycles_t c1, c2;

	for(;;)
	{
		c1 = get_cycles(); 
		sleep(1);
		c2 = get_cycles();
 //c2 和 c1 的差值应该为 1000000us,即 1 秒
		printf("1 sec = %g usec\n", (c2 - c1) / mhz); 
	}
}

 

函数 get_cycles 将返回 64 位整数,表明当前时间,单位是 CPU 的 cycle 数。函数 get_cpu_mhz 得到当前 CPU 的工做频率。用两个 CPU cycle 的差值除以 CPU 频率,就是微妙。

但 RDTSC 只能在 IA 系列处理器上使用。并且因为处理器的乱序执行,RDTSC 有些状况下并不许确,在 SMP 下使用 RDTSC 也有必定的问题。但这些问题只有在须要极高时间精度的状况下才会出现,对于通常的时间测量要求,采用 RDTSC 是一个能够考虑的选择。


计时器的使用

有时咱们须要定时完成一些任务。简单的方法是使用 while 循环加 sleep。好比每隔 1 分钟检查连接状况的 heartbeat 任务等。


清单 8,sleep 加循环

while(condtion)
{
 //do something
 sleep(interval);
}

 

这能够知足不少程序的定时须要,但假如您不但愿程序“偷懒”,即上例中 sleep 的时候您仍是但愿程序作些有用的工做,那么使用定时器是一般的选择。Linux 系统上最经常使用的定时器是 setitmer 计时器。

setitimer

Linux 为每个进程提供了 3 个 setitimer 间隔计时器:

  • ITIMER_REAL:减小实际时间,到期的时候发出 SIGALRM 信号。
  • ITIMER_VIRTUAL:减小有效时间 (进程执行的时间),产生 SIGVTALRM 信号。
  • ITIMER_PROF:减小进程的有效时间和系统时间 (为进程调度用的时间)。这个常常和上面一个使用用来计算系统内核时间和用户时间。产生 SIGPROF 信号。

所谓 REAL 时间,即咱们人类天然感觉的时间,英文计算机文档中也常用 wall-clock 这个术语。说白了就是咱们一般所说的时间,好比如今是下午 5 点 10 分,那么一分钟的 REAL 时间以后就是下午 5 点 11 分。

VIRTUAL 时间是进程执行的时间,Linux 是一个多用户多任务系统,在过去的 1 分钟内,指定进程实际在 CPU 上的执行时间每每并无 1 分钟,由于其余进程会被 Linux 调度执行,在那些时间内,虽然天然时间在流逝,但指定进程并无真正的运行。VIRTUAL 时间就是指定进程真正的有效执行时间。好比 5 点 10 分开始的 1 分钟内,进程 P1 被 Linux 调度并占用 CPU 的执行时间为 30 秒,那么 VIRTUAL 时间对于进程 P1 来说就是 30 秒。此时天然时间已经到了 5 点 11 分,但从进程 P1 的眼中看来,时间只过了 30 秒。

PROF 时间比较独特,对进程 P1 来讲从 5 点 10 分开始的 1 分钟内,虽然本身的执行时间为 30 秒,但实际上还有 10 秒钟内核是在执行 P1 发起的系统调用,那么这 10 秒钟也被加入到 PROF 时间。这种时间定义主要用于全面衡量进程的性能,由于在统计程序性能的时候,10 秒的系统调用时间也应该算到 P1 的头上。这也许就是 PROF 这个名字的来历吧。

使用 setitimer Timer 须要了解下面这些接口 API:

 

int getitimer(int which,struct itimerval *value); 
int setitimer(int which,struct itimerval *newval, 
struct itimerval *oldval); 

 

itimerval 的定义以下:

 

struct itimerval { 
struct timeval it_interval; 
struct timeval it_value; 
} 

 

getitimer 函数获得间隔计时器的时间值,保存在 value 中。

setitimer 函数设置间隔计时器的时间值为 newval. 并将旧值保存在 oldval 中;which 表示使用三个计时器中的哪个。

itimerval 结构中的 it_value 是第一次调用后触发定时器的时间,当这个值递减为 0 时,系统会向进程发出相应的信号。此后将以 it_internval 为周期定时触发定时器。

给出一个具体的例子:


清单 9,setitmer 例子

void print_info(int signo) 
{ 
 printf(“timer fired\n”); //简单的打印,表示 timer 到期
} 

void init_sigaction(void) 
{ 
 struct sigaction act; 
 act.sa_handler= print_info; 
 act.sa_flags=0; 
 sigemptyset(&act.sa_mask); 
 sigaction(SIGPROF,&act,NULL); //设置信号 SIGPROF 的处理函数为 print_info
} 

void init_time() 
{ 
 struct itimerval value; 
 value.it_value.tv_sec=2; 
 value.it_value.tv_usec=0; 
 value.it_interval=value.it_value; 
 setitimer(ITIMER_PROF,&value,NULL); //初始化 timer,到期发送 SIGPROF 信号
} 

int main() 
{ 
 len=strlen(prompt); 
 init_sigaction(); 
 init_time(); 
 while(1); 
 exit(0); 
} 

 

这个程序使用 PROF 时间,每通过两秒 PROF 时间以后就会打印一下 timer fired 字符串。

须要指出:setitimer 计时器的精度为 ms,即 1000 分之 1 秒,足以知足绝大多数应用程序的须要。但多媒体等应用可能须要更高精度的定时,那么就须要考虑使用下一类定时器:POSIX Timer。

POSIX Timer

间隔定时器 setitimer 有一些重要的缺点,POSIX Timer 对 setitimer 进行了加强,克服了 setitimer 的诸多问题:

首先,一个进程同一时刻只能有一个 timer。假如应用须要同时维护多个 Interval 不一样的计时器,必须本身写代码来维护。这很是不方便。使用 POSIX Timer,一个进程能够建立任意多个 Timer。

setitmer 计时器时间到达时,只能使用信号方式通知使用 timer 的进程,而 POSIX timer 能够有多种通知方式,好比信号,或者启动线程。

使用 setitimer 时,通知信号的类别不能改变:SIGALARM,SIGPROF 等,而这些都是传统信号,而不是实时信号,所以有 timer overrun 的问题;而 POSIX Timer 则可使用实时信号。

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


表 2. POSIX Timer 函数

函数名 功能描述
timer_create 建立一个新的 Timer;而且指定定时器到时通知机制
timer_delete 删除一个 Timer
timer_gettime Get the time remaining on a POSIX.1b interval timer
timer_settime 开始或者中止某个定时器。
timer_getoverrun 获取丢失的定时通知个数。

 

使用 Posix Timer 的基本流程很简单,首先建立一个 Timer。建立的时候能够指定该 Timer 的一些特性,好比 clock ID。

clock ID 即 Timer 的种类,能够为下表中的任意一种:


表 3. POSIX Timer clock ID

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 命令显示的时间,该时间能够从新设置。好比当前时间为上午 10 点 10 分,Timer 打算在 10 分钟后到时。假如 5 分钟后,我用 date 命令修改当前时间为 10 点 10 分,那么 Timer 还会再等十分钟到期,所以实际上 Timer 等待了 15 分钟。假如您但愿不管任何人如何修改系统时间,Timer 都严格按照 10 分钟的周期进行触发,那么就可使用 CLOCK_MONOTONIC。

CLOCK_PROCESS_CPUTIME_ID 的含义与 setitimer 的 ITIMER_VIRTUAL 相似。计时器只记录当前进程所实际花费的时间;好比仍是上面的例子,假设系统很是繁忙,当前进程只能得到 50%的 CPU 时间,为了让进程真正地运行 10 分钟,应该到 10 点 30 分才容许 Timer 到期。

CLOCK_THREAD_CPUTIME_ID 以线程为计时实体,当前进程中的某个线程真正地运行了必定时间才触发 Timer。

设置到期通知方式

timer_create 的第二个参数 struct sigevent 用来设置定时器到时时的通知方式。该数据结构以下:


清单 10,结构 sigevent

 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 表示通知方式,有以下几种:


表 3. POSIX Timer 到期通知方式

通知方式 描述
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 的时候使用到,应用程序不多会须要用到这种模式。

启动定时器

建立 Timer 以后,即可以调用 timer_settime() 函数指定定时器的时间间隔,并启动该定时器了。

 

 int timer_settime(timer_t timerid, int flags,
 const struct itimerspec *new_value,
 struct itimerspec * old_value);

 

第一次看到 timer_settime 的参数列表或许会使人以为费解。先来看看 new_value 和 old_value,它们都是 struct itimerspec 数据结构。

 

struct itimerspec
{
 struct timespec it_interval; //定时器周期值
 struct timespec it_value; //定时器到期值
};

 

启动和中止 Timer 均可以经过设置 new_value 来实现:

new_value->it_interval 为定时器的周期值,好比 1 秒,表示定时器每隔 1 秒到期;

new_value->it_value 若是大于 0,表示启动定时器,Timer 将在 it_value 这么长的时间过去后到期,此后每隔 it_interval 便到期一次。若是 it_value 为 0,表示中止该 Timer。

有些时候,应用程序会先启动用一个时间间隔启动定时器,随后又修改该定时器的时间间隔,这均可以经过修改 new_value 来实现;假如应用程序在修改了时间间隔以后但愿了解以前的时间间隔设置,则传入一个非 NULL 的 old_value 指针,这样在 timer_settime() 调用返回时,old_value 就保存了上一次 Timer 的时间间隔设置。多数状况下咱们并不须要这样,即可以简单地将 old_value 设置为 NULL,忽略它。

下面给出一个使用 posix timer 的例子程序。最传统的例子就是建立通知方式为 SIGEV_SIGNAL 的 Timer。这样当定时器到期时,将产生信号通知,主程序须要定义本身的信号处理函数,来处理信号到期事件。这种例子比比皆是,我打算在这里写一个采用通知方式为 SIGEV_THREAD 的例子。该例子程序从 main 函数开始主线程,在开始的时候打印出主线程的进程 ID 和线程 ID。


清单 11,打印 TID

 pid_t tid = (pid_t) syscall (SYS_gettid);
 printf("start program in PID:[%d]TID:[%d]\n",getpid(),tid);

 

得到 ThreadID 的系统调用还没有被 GLibC 标准化,所以这里直接调用 syscall。

而后,主线程初始化建立 Timer 所须要的数据结构:


清单 12,设置通知方式

 se.sigev_notify = SIGEV_THREAD;
 se.sigev_value.sival_ptr = &timer_id;
 se.sigev_notify_function = timer_thread;
 se.sigev_notify_attributes = NULL;
 status = timer_create(CLOCK_REALTIME, &se, &timer_id);

 

这里将通知方式设为 SIGEV_THREAD,timer_thread 为线程入口函数。

而后主线程设置定时器间隔,并启动 Timer:


清单 13,启动 Timer

 ts.it_value.tv_sec = 5;
 ts.it_value.tv_nsec = 0;
 ts.it_interval.tv_sec = 5;
 ts.it_interval.tv_nsec = 0;
 status = timer_settime(timer_id, 0, &ts, 0);

 

此后主线程进入一个循环,在循环中等待线程条件变量:


清单 14,主程序中的循环

 while (counter < 5) {
 status = pthread_cond_wait (&cond, &mutex);
}

 

条件变量 cond 将在 timer_thread() 处理函数中触发,这样每 5 秒钟,定时器将调用 timer_thread() 处理函数,并唤醒主线程等待的条件变量一次。5 次以后测试程序退出。

如今咱们看看 timer_thread() 函数:


清单 15,timer_thread 函数

void timer_thread (void *arg)
{
 status = pthread_mutex_lock (&mutex);
 if (++counter >= 5) {
 status = pthread_cond_signal (&cond);
 }
 status = pthread_mutex_unlock (&mutex);
 pid_t tid = (pid_t) syscall (SYS_gettid);
 printf ("Timer %d in PID:[%d]TID:[%d]\n", counter,getpid(),tid);
}

 

在整个程序中咱们都没有使用信号,定时器到期时,将启动新的线程运行 timer_thread。所以在该函数中,咱们还打印了当前的线程号以即可以看出它们确实在不一样线程中运行。

这里是运行该程序的一个输出:

 

-bash-3.2$ gcc threadtimer.c -lrt -lpthread -o test
-bash-3.2$ ./test
start program in PID:[21483]TID:[21483]
Timer 1 in PID:[21483]TID:[21498]
Timer 2 in PID:[21483]TID:[21510]
Timer 3 in PID:[21483]TID:[21534]

 

能够看到每次 Timer 都运行在不一样的线程中。


小结

至此,但愿我已经讲述了 Linux 系统提供的大多数关于时间的编程方法。使用这些方法咱们能够:

  • 得到当前时间,并转换为合适的显示方式;
  • 衡量程序运行通过的时间;
  • 使用定时器完成周期性的任务;

另外不知道您是否和我同样,对于 Linux 系统如何实现这些机制十分好奇。计算机毕竟是一个机器,底层硬件提供了怎样的功能,操做系统和 C 库如何协同工做才能够提供这些一丝不苟的,优美的方法呢?我将在后续的部分试图探讨这个话题。

 

参考资料

学习

 
分类:  linux
相关文章
相关标签/搜索