Linux中的时间编程和实现原理.docx

上传人:b****7 文档编号:10131836 上传时间:2023-02-08 格式:DOCX 页数:21 大小:75.16KB
下载 相关 举报
Linux中的时间编程和实现原理.docx_第1页
第1页 / 共21页
Linux中的时间编程和实现原理.docx_第2页
第2页 / 共21页
Linux中的时间编程和实现原理.docx_第3页
第3页 / 共21页
Linux中的时间编程和实现原理.docx_第4页
第4页 / 共21页
Linux中的时间编程和实现原理.docx_第5页
第5页 / 共21页
点击查看更多>>
下载资源
资源描述

Linux中的时间编程和实现原理.docx

《Linux中的时间编程和实现原理.docx》由会员分享,可在线阅读,更多相关《Linux中的时间编程和实现原理.docx(21页珍藏版)》请在冰豆网上搜索。

Linux中的时间编程和实现原理.docx

Linux中的时间编程和实现原理

Linux中的时间编程和实现原理

本文试图完整地描述Linux系统中C语言编程中的时间问题。

主要内容包括应用程序中的时间编程方法;时钟硬件简介;Glibc时间函数的实现以及Linux内核对时间的支持和实现原理。

这一部分,探讨应用开发中的时间编程问题。

引子

我们都生活在时间中,但却无法去思考它。

什么是时间呢?

似乎这是一个永远也不能被回答的问题。

然而作为一个程序员,在工作中,总有那么几次我必须思考什么是时间。

比如,需要知道一段代码运行了多久;要在log文件中记录事件发生时的时间戳;再比如需要一个定时器以便能够定期做某些计算机操作。

我发现,在计算机世界中,时间在不同场合也往往有不同的含义,让试图思考它的人感到迷茫。

但值得庆幸的是,Linux中的时间终究是可以理解的。

因此我打算讨论一下有关时间的话题,尝试着深入理解Linux系统中C语言编程中的时间问题。

主要内容如下:

∙第1部分是应用程序中的时间问题。

有三个方面:

程序计时需要;获取当前时间;定时器。

∙第2部分包括时间硬件简介和GlibC实现时间函数的原理。

∙第3和第4部分是Linux内核对时间的支持和实现原理。

现在开始第1部分,探讨应用开发中的时间编程问题。

在这一部分中,所有的例子代码都在GlibC2.14,内核2.6.33的Linux系统下编译并验证执行过。

读者如果使用低版本的GlibC和Linux内核有可能无法正确执行。

获取当前时间

时间的获取

在程序当中,我们经常要输出系统当前的时间,比如日志文件中的每一个事件都要记录其产生时间。

在C语言中获取当前时间的方法有以下几种,它们所获得的时间精度从秒级到纳秒,各有所不同。

表1.C时间函数

function定义

含义

返回值

精度

time()

time函数获得从1970年1月1日0点到当前的秒数,存储在time_t结构之中。

time_t

gettimeofday()

gettimeofday函数返回从1970年1月1日0点以来,到现在的时间。

用timeval数据结构表示。

structtimeval

{

time_ttv_sec;

longinttv_usec;

};

微秒

clock_gettime()

clock_gettime函数返回从1970年1月1日0点以来,到现在的时间。

用timespec数据结构表示。

支持不广泛。

属于实时扩展。

structtimespec

{

time_ttv_sec;

longinttv_nsec;

};

纳秒

ftime()

函数返回从1970年1月1日0点以来,到现在的时间。

用timeb数据结构表示。

已经过时,被time()替代。

尽量不使用。

structtimeb{

time_ttime;

unsignedshort

millitm;

shorttimezone;

shortdstflag;

};

毫秒

GUN/Linux提供了三个标准的API用来获取当前时间,time()/gettimeofday()/clock_gettime(),它们的区别仅在于获取的时间精度不同,您可以根据需要选取合适的调用。

ftime()是老的一些系统中的时间调用,很多Linux版本虽然支持它,但仅仅是为了向前兼容性,新开发的软件不建议使用ftime()来获得当前时间。

时间显示和转换

目前我们得到的时间是一个数字,无论精度如何,它代表的仅是一个差值。

比如精度为秒的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库提供了许多时间格式转换的函数。

这些函数的数量众多,容易让人迷惑,记住它们的用法十分不易。

在这里我借用MichaelKerrisk在《LinuxProgrammingInterface》一书中的插图,来对这些标准C函数进行一个总体的概览。

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

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

∙固定格式转换

∙用户指定格式转换函数。

固定格式转换

用ctime()函数转换出来的时间格式是系统固定的,调用者无法改动,因此被称为固定格式转换。

如果您对日期格式没有特殊的要求,那么用它基本上就可以了,简单,不用记忆很多的参数。

用户指定格式转换

典型的ctime()格式如下:

WedDec720:

45:

43PST2011

有些人觉得这个格式太长,类似Wed,星期三这样的信息很多情况下都没有啥用途。

人们可能更喜欢其他格式:

比如2011-12-0720:

45。

在这种情况下,就需要进行时间显示格式转换。

做法为:

先把从内核得到的时间值转换为structtm类型的值,然后调用strftime()等函数来输出自定义的时间格式字符串。

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

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

char*ctime(consttime_t*clock);

使用函数ctime将秒数转化为字符串.这个函数的返回类型是固定的:

一个可能值为”ThuDec714:

58:

592000”。

这个字符串的长度和显示格式是固定的。

清单1,time的使用

#include

intmain()

{

time_ttime_raw_format;

time(&time_raw_format);//获取当前时间

printf("timeis[%d]\n",time_raw_format);

//用ctime将时间转换为字符串输出

printf("Thecurrentlocaltime:

%s",ctime(&time_raw_format));

return0;

}

自定义格式转换

为了更灵活的显示,需要把类型time_t转换为tm数据结构。

tm数据结构将时间分别保存到代表年,月,日,时,分,秒等不同的变量中。

不再是一个令人费解的64位整数了。

这种数据结构是各种自定义格式转换函数所需要的输入形式。

清单2,数据结构tm

structtm{

inttm_sec;/*Seconds(0-60)*/

inttm_min;/*Minutes(0-59)*/

inttm_hour;/*Hours(0-23)*/

inttm_mday;/*Dayofthemonth(1-31)*/

inttm_mon;/*Month(0-11)*/

inttm_year;/*Yearsince1900*/

inttm_wday;/*Dayoftheweek(Sunday=0)*/

inttm_yday;/*Dayintheyear(0-365;1Jan=0)*/

inttm_isdst;/*Daylightsavingtimeflag

>0:

DSTisineffect;

=0:

DSTisnoteffect;

<0:

DSTinformationnotavailable*/

};

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

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

#include

structtm*gmtime(consttime_t*timep);

structtm*localtime(consttime_t*timep);

使用tm来表示时间,您就可以调用asctime()和strftime()将时间转换为字符串了。

asctime()的输出格式固定,和ctime()相同。

strftime()则类似我们最熟悉的printf()函数,您可以通过输入参数自定义时间的输出格式。

size_tstrftime(char*outstr,size_tmaxsize,constchar*format,

conststructtm*timeptr);

清单4,时间显示转换

 

intmain()

{

time_ttime_raw_format;

structtm*time_struct;

charbuf[100];

time(&time_raw_format);

time_struct=localtime(&time_raw_format);

strftime(buf,100,"Itisnow:

%I:

%M%p.",time_struct);

puts(buf);

return0;

}

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

Itisnow:

02:

45PM.

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

此外从前面的总结中我们也了解到,还有两个C函数可以获得当前时间,gettimeofday()以及clock_gettime(),它们分别返回structtimeval或者timespec代表的高精度的时间值。

在目前的GLibC中,还没有直接把structtimeval/timespec转换为structtm的函数。

一般的做法是将timeval中的tv_sec转换为tm,使用上面所述的方法转换为字符串,最后在显示的时候追加上tv_usec,比如下面的例子代码:

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

structtimevaltv;

time_tnowtime;

structtm*nowtm;

chartmbuf[64],buf[64];

gettimeofday(&tv,NULL);//获取当前时间到tv

nowtime=tv.tv_sec;//nowtime存储了秒级的时间值

nowtm=localtime(&nowtime);//转换为tm数据结构

//用strftime函数将tv转换为字符串,但strftime函数只能达到秒级精度

strftime(tmbuf,sizeoftmbuf,"%Y-%m-%d%H:

%M:

%S",nowtm);

//将毫秒值追加到strftime转换的字符串末尾

snprintf(buf,sizeofbuf,"%s.%06d",tmbuf,tv.tv_usec);

时间的测量

有时候我们要计算某段程序执行的时间,比如需要对算法进行时间分析。

基本的实现思路为在被测试代码的开始和结束的地方获取当时时间,相减后得到相对值,即所需要的统计时间。

为了实现高精度的时间测量,必须使用高精度的时间获取方式,一般有两种方法:

∙系统调用gettimeofday

∙汇编指令RDTSC。

gettimeofday

可以使用gettimeofday()函数进行时间测量,其精度在us级别,可以用来做一般的时间分析。

gettimeofday()将时间保存在结构tv之中。

gettimeofday()的第二个参数代表时区,在Linux中已经废弃不用,只能用NULL传入。

一个典型的例子程序如下:

清单6,gettimeofday例子程序

voidfunction()

{

unsignedinti,j;

doubley;

for(i=0;i<1000;i++)

for(j=0;j<1000;j++)

y=sin((double)i);//耗时操作

}

main()

{

structtimevaltpstart,tpend;

floattimeuse;

gettimeofday(&tpstart,NULL);//记录开始时间戳

function();

gettimeofday(&tpend,NULL);//记录结束时间戳

timeuse=1000000*(tpend.tv_sec-tpstart.tv_sec)+

tpend.tv_usec-tpstart.tv_usec;//计算差值

timeuse/=1000000;

printf("UsedTime:

%f\n",timeuse);

exit(0);

}

这个程序输出函数的执行时间,我们可以使用这个来进行系统性能的测试,或者是函数算法的效率分析。

在我个人机器上的输出结果是:

UsedTime:

0.556070

RDTSC

gettimeofday()是一个系统调用,在某些场合下频繁调用它是不合适的。

比如性能要求很高的代码段内。

因为gettimeofday()需要用户态/内核态切换,开销较大。

IntelX86处理器提供了TSC硬件,并且可以用非特权指令rdtsc来读取该硬件的时间值,这就避免了过度的内核用户态切换。

如何使用RDTSC

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

#definerdtsc(low,high)__asm__\

__volatile__("rdtsc":

"=a"(low),"=d"(high))

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

比如:

清单7,RDTSC例子程序

unsignedlonglongget_cycles()

{

unsignedlow,high;

unsignedlonglongval;

rdtsc(low,high);

val=high;

val=(val<<32)|low;//将low和high合成一个64位值

returnval;

}

doubleget_cpu_mhz(void)

{

FILE*f;

charbuf[256];

doublemhz=0.0;

f=fopen("/proc/cpuinfo","r");//打开proc/cpuinfo文件

if(!

f)

return0.0;

while(fgets(buf,sizeof(buf),f)){

doublem;

intrc;

rc=sscanf(buf,"cpuMHz:

%lf",&m);//读取cpuMHz

if(mhz==0.0){

mhz=m;

break;

}

}

fclose(f);

returnmhz;//返回HZ值

}

intmain()

{

doublemhz;

mhz=get_cpu_mhz();

cycles_tc1,c2;

for(;;)

{

c1=get_cycles();

sleep

(1);

c2=get_cycles();

//c2和c1的差值应该为1000000us,即1秒

printf("1sec=%gusec\n",(c2-c1)/mhz);

}

}

函数get_cycles将返回64位整数,代表当前时间,单位是CPU的cycle数。

函数get_cpu_mhz获得当前CPU的工作频率。

用两个CPUcycle的差值除以CPU频率,就是微妙。

但RDTSC只能在IA系列处理器上使用。

而且由于处理器的乱序执行,RDTSC有些情况下并不准确,在SMP下使用RDTSC也有一定的问题。

但这些问题只有在需要极高时间精度的情况下才会出现,对于一般的时间测量要求,采用RDTSC是一个可以考虑的选择。

计时器的使用

有时我们需要定时完成一些任务。

简单的方法是使用while循环加sleep。

比如每隔1分钟检查链接情况的heartbeat任务等。

清单8,sleep加循环

while(condtion)

{

//dosomething

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这个名字的来历吧。

使用setitimerTimer需要了解下面这些接口API:

intgetitimer(intwhich,structitimerval*value);

intsetitimer(intwhich,structitimerval*newval,

structitimerval*oldval);

itimerval的定义如下:

structitimerval{

structtimevalit_interval;

structtimevalit_value;

}

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

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

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

此后将以it_internval为周期定时触发定时器。

给出一个具体的例子:

清单9,setitmer例子

voidprint_info(intsigno)

{

printf(“timerfired\n”);//简单的打印,表示timer到期

}

voidinit_sigaction(void)

{

structsigactionact;

act.sa_handler=print_info;

act.sa_flags=0;

sigemptyset(&act.sa_mask);

sigaction(SIGPROF,&act,NULL);//设置信号SIGPROF的处理函数为print_info

}

voidinit_time()

{

structitimervalvalue;

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信号

}

intmain()

{

len=strlen(prompt);

init_sigaction();

init_time();

while

(1);

exit(0);

}

这个程序使用PROF时间,每经过两秒PROF时间之后就会打印一下timerfired字符串。

需要指出:

setitimer计时器的精度为ms,即1000分之1秒,足以满足绝大多数应用程序的需要。

但多媒体等应用可能需要更高精度的定时,那么就需要考虑使用下一类定时器:

POSIXTimer。

POSIXTimer

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

首先,一个进程同一时刻只能有一个timer。

假如应用需要同时维护多个Interval不同的计时器,必须自己写代码来维护。

这非常不方便。

使用POSIXTimer,一个进程可以创建任意多个Timer。

setitmer计时器时

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 表格模板 > 合同协议

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1