1. 内核中时间的基本类型:
在Linux内核中,常见的时间类型有以下两种:系统时间(system time)和实时时间(real time),其实,方便理解,可以将二者分别认为是相对时间和绝对时间,同时它们分别对应于内核中的两个全局变量值:jiffies和xtime。
xtime: xtime值是从cmos电路中取得的时间,一般是从某个历史时刻(1970年1月1日0时0分)开始到现在的时间,其实也就是我们操作系统上面所显示的时间,它的精度是微秒。
jiffies:jiffies是记录从电脑开机到现在总共的时钟中断次数(拍数),它的值取决于系统的频率,单位是HZ,其倒数即表示一秒钟中断所产生的次数,在Linux 2.5内核版本之后将HZ从100提高到1000MHZ,它的精度也就是10毫秒。
根据对上面两个全局变量值的介绍,大提升应该了解到Linux系统中系统时间与实时时间之间的区别,前者表示的是从电脑开机到现在的时间,可以通过全局变量jiffies值换算而来;而实时时间则是指我们日常生活中的日期时间,它跟UTC有着密切关系,这些将在后面章节做介绍。
2. Linux time API中常见的时间结构:
(1)time_t:它是一个长整型数据,用来表示从1970年之后到现在的秒数。一般通过time函数获取。
(2)timeval结构:通过gettimeofday函数获取。
struct timeval
{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
(3)timezone结构:通过gettimeofday函数获取。
struct timezone
{
int tz_minuteswest; /* 和Greewich时间差了多少分钟*/
int tz_dsttime; /*DST types*/
};
【引申】常见的DST类型如下:
#define DST_NONE 0 /* not on dst */
#define DST_USA 1 /* USA style dst */
#define DST_AUST 2 /* Australian style dst */
#define DST_WET 3 /* Western European dst */
#define DST_MET 4 /* Middle European dst */
#define DST_EET 5 /* Eastern European dst */
#define DST_CAN 6 /* Canada */
(4)timespec结构:通过clock_gettime函数获取。
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
(5)tm结构:通常由gmtime, localtime, mktime等函数返回。
struct tm {
/*
* the number of seconds after the minute,normally in the range
* 0 to 59, but can be up to 60 to allow forleap seconds
*/
int tm_sec;
/* the number of minutes after the hour, in the range 0 to 59*/
int tm_min;
/* the number of hours past midnight, in the range 0 to 23 */
int tm_hour;
/* the day of the month, in the range 1 to 31*/
int tm_mday;
/* the number of months since January, in the range 0 to 11 */
int tm_mon;
/* thenumber of years since 1900 */
long tm_year;
/* the number of days since Sunday, in the range 0 to 6 */
int tm_wday;
/* the number of days since January 1, in the range 0 to 365 */
int tm_yday;
};
3. 常见的时间系统函数:
(1) time: #include <time.h>
time_t time(time_t *t)
若函数的参数为NULL,则返回从1970年1月1日0时0分0秒到现在(系统时间)所经过的秒;若参数非空,则将返回的值存在由指针t所指代的内存中。
(2) gettimeofday: #include <sys/time.h>
int gettimeofday(structtimeval *tv ,struct timezone *tz )
此函数可以获取两方面的时间信息,一个是可以获取到从1970年1月1日0时0分0秒到现在(系统时间)所经过的微秒,精度相比time函数精度有所提升;另外还可以获取到系统的时区信息。
【说明】
◆ gettimeofday函数成功返回0;否则返回-1,错误存储在errno中。
◆ tz_minuteswest值的确定问题:它表示的是与GTM之间相差的分钟数,其值应该为GMT(GMT +0)减去本地
时区对应的时间所得到的值,以EDT(GMT -4)为例,其值为240分钟。
◆ 在实际开发中,gettimeofday中的tz参数实际很少使用,因为各种原因,一直未能实现(所获取出来的值恒为
0),因此,通常将此处直接写成NULL。
◆ 对于gettimeofday函数的效率以及内部实现(系统调用实现),可参考
http://blog.csdn.net/russell_tao/article/details/7185588中的阐述。
◆ 与gettimeofday函数相对应的是settimeofday,它可以设置实时时间RTC。但之前必须要具有root权限。
(3) gmtime,localtime and mktime:
struct tm*gmtime(const time_t *timep)
struct tm *localtime(const time_t *timep)
time_tmktime(struct tm*tm)
以上三个函数实现了time_t与tm结构的互换。前两者将time_t结构转换成tm结构,mktime则正好相反。
【说明】gmtime与localtime之间的区别:
二者均可以将time_t结构的时间值转化成真实世界所使用的日期时间表示方法(tm结构),但是,前者返回的时间值未作时区的转换,即返回的是UTC时间;而localtime函数则返回的经过了时区转换的时间值,所获取到的值才是本地的真实时间。例如,在Linux系统中运行date命令,它显示的是经过时区转换之后的时间值(通过localtime获取),而若运行“date-u”则能显示未经过时区转换的UTC时间(通过gmtime获取)。
(3) strftime: #include <time.h>
size_tstrftime (char *s,sizetsize, const char *format,const struct tm *brokentime)
此函数的功能是将由brokentime指针所指的时间按照format指针所指的格式输出到由s指针所指向的存储空间中,其中size是指存储空间的最大值。若返回0,则表明出现错误,所写进存储空间的结果是未定义的,若为真,则返回的是写进存储空间的字符数。
(4) clock_gettime: #include <time.h>
intclock_gettime(clockid_tclk_id,struct timespec *tp);
此函数的功能是用来获取不同类型计时时钟的时间,其类型由clockid_t指定,常见的有:
CLOCK_REALTIME(与实时时间对应)
CLOCK_MONOTONIC(与系统时间对应)
【说明】
◆ clock_gettime函数能将所获得的时间值精确到纳秒级别;
◆ 函数运行成功则返回0,否则返回-1,并将错误存在errno中;
◆ 除上面的两个时钟类型之外,还有以下两种类型:
CLOCK_PROCESS_CPUTIME_ID和CLOCK_THREAD_CPUTIME_ID
但是这两种时钟类型一般出现在多处理机系统(SMP)中,值得注意的是,在老版本的Linux系统中可能会出现CPU时间之间不一致的现象,这是因为不同的CPU之间没有保证时间一致性措施,导致CPU时间之间出现偏移量,在2.6.18版本之后解决了这方面的问题,使得系统启动后不同的CPU之间具有相同的时间基准点。
◆CLOCK_REALTIME时间可以通过settime或者settimeofday函数进行修改,或者通过NTP周期性 地纠正,此时需要用到adjtimex函数;CLOCK_MONOTONIC时间则不能通过settime或者settimeofday函数进行修改,但是同样可以通过NTP进行调整,此时同样需要用到adjtimex函数。
◆对比两种时钟类型,若要在实际开发中要统计某个事件的时间,则最好是使用CLOCK_MONOTONIC,因为CLOCK_REALTIME被影响的因素太多,如手动修改,时区变化等等。
4. DST以及相关的系统函数:
(1)UTC、GMT与DST
目前世界上常见的计时方式主要有:太阳时(MT)和原子时。GMT(格林尼治时间)的正午是指当太阳横穿格林尼治子午线时的时间,由于地球的自转呈现不规则性,并且正在缓慢减速,因此格林威治时间目前已经不再作为标准时间使用,取而代之的是协调时间时(UTC),它是由原子钟提供,它是基于标准的GMT提供的准确时间,若在不需要精确到秒的前提下,通常也将GMT与UTC视作等同。
DST(daylight saving time)也称为夏令时,它是以节约能源为目的而人为规定的一种制度,它规定某段时间作为夏令时间,并在标准时间的基础上提前多长时间(通常是一个小时),同时DST还规定了规定生效的起始时间和末尾时间,详细规则会在tzset函数中介绍,值得注意的是目前只是部分国家实施了夏令时制度。
标准时间是相对于UTC/GMT时间而言的,它在UTC/GMT之上增加了时区信息,比如中国标准时间是GMT+8,即在UTC时间上增加8个小时。
(2) 系统时间、标准时间以及UTC时间之间的关系:
这节主要探讨在具体项目实现过程中,如何处理系统时间、标准时间以及UTC时间之间的关系,其中系统时间可以通过前面的系统函数获取到,它可能正处于夏令时间区域,下面这个图可以清晰地阐述三者之间的关系:
我们以localtime函数获取到本地系统时间为例,演示如何将其转换成UTC时间,前面已经说过,localtime所获取到的时间已经包含了时区信息,但是之前我们必须要确认目前的这个时间是否处于夏令时区域之内,若是,则还需要经过A阶段(去掉DST偏移量,通常是一个小时),若不是,只需要经历第二个阶段B,即去时区,最后转化成UTC,当然这两个阶段并没有严格的先后顺序。反过来,在具体实现中,还经常出现将UTC时间转化成本地时间的情况,比如NTP就是基于这样的原理,它从NTP server端获取统一的UTC时间,然后需要经过C(加时区)和D(加DST,如果存在或正好处于夏令时区域范围之内的话)两个阶段将其转化成本地系统时间。
下面主要阐述第一种情况(本地系统时间——>UTC)是如何具体实现的。当然前提是我们要知道目前所在的时区,这是一切的根本。在此之前,值得说明的是,一般来讲,时区是一个固定的信息,难以想象一个国家或地区去改变时区所带来的后果,但是DST因为是人为规定的,因此可能存在着修改的情况,基于这个事实,在具体实现中,时区信息可以存储在本地,而DST信息既可以静态存储在本地,也可以通过相关的server动态获取到。我们以静态存储的方式为例来讲解具体是如何实现去时区,去DST。
下面这个结构体存储了跟时区相关的位移量(offset)以及是否存在DST等信息,根据所在的时区信息,很容易找到系统时间与UTC时间之间的时区偏移,另外根据rule是否为-1来确定此时区是否实施了夏令时,若为-1,表明这个时区地已经实现了夏令时,则还需要经过去DST阶段,否则只需要经过去时区就可以得到UTC时间。
struct zone zones[N_ZONES] = {
/* offset rules */
{ -43200, -1 }, /* (GMT-12:00)International Date Line West */
{ -39600, -1 }, /* (GMT-11:00) Midway Island,Samoa */
{ -36000, -1 }, /* (GMT-10:00) Hawaii */
{ -32400, 0 }, /* (GMT-09:00) Alaska */
{ -28800, 0 }, /* (GMT-08:00) Pacific Time, Tijuana */
{ -25200, -1 }, /* (GMT-07:00) Arizona, Mazatlan*/
{ -25200, 13 }, /* (GMT-07:00) Chihuahua, La Paz*/
{ -25200, 0 }, /* (GMT-07:00) Mountain Time */
{ -21600, 0 }, /* (GMT-06:00) Central America */
{ -21600, 0 }, /* (GMT-06:00) Central Time */
{ -21600, 13 }, /* (GMT-06:00) Guadalajara, MexicoCity, Monterrey*/
{ -21600, -1 }, /* (GMT-06:00) Saskatchewan */
{ -18000, -1 }, /* (GMT-05:00) Bogota, Lima, Quito */
{ -18000, 0 }, /* (GMT-05:00) Eastern Time */
{ -18000, -1 }, /* (GMT-05:00) Indiana */
{ -14400, 0 }, /* (GMT-04:00) Atlantic Time */
{-14400, -1 }, /* (GMT-04:00) Caracas, La Paz */
{ -14400, 2 }, /* (GMT-04:00) Santiago */
{ -12600, 0 }, /* (GMT-03:30) Newfoundland */
{ -10800, 14 }, /* (GMT-03:00) Brasilia */
{ -10800, -1 }, /* (GMT-03:00) Buenos Aires, Georgetown*/
{ -10800, -1 }, /* (GMT-03:00) Greenland */
{ -7200, -1 }, /* (GMT-02:00) Mid-Atlantic */
{ -3600, 1 }, /* (GMT-01:00) Azores */
{ -3600, -1 }, /* (GMT-01:00) Cape Verde Is. */
{ 0, -1 }, /* (GMT) Casablanca, Monrovia */
{ 0, 1 }, /* (GMT) Greenwich MeanTime: Dublin, Edinburgh,Lisbon, London*/
{ 3600, 1 }, /* (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
{ 3600, 1 }, /* (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague */
{ 3600, 1 }, /* (GMT+01:00) Brussels, Copenhagen, Madrid, Paris*/
{ 3600, 1 }, /* (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb*/
{ 3600, -1 }, /* (GMT+01:00) West Central Africa*/
{ 7200, 1 }, /* (GMT+02:00) Athens, Istanbul, Minsk */
{ 7200, 1 }, /* (GMT+02:00) Bucharest */
{ 7200, 4 }, /* (GMT+02:00) Cairo */
{ 7200, -1 }, /* (GMT+02:00) Harare, Pretoria */
{ 7200, 1 }, /* (GMT+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius */
{ 7200, 5 }, /* (GMT+02:00) Jerusalem */
{ 10800, 6 }, /* (GMT+03:00) Baghdad */
{ 10800, -1 }, /* (GMT+03:00) Kuwait,Riyadh */
{ 10800, 7 }, /* (GMT+03:00) Moscow, St. Petersburg, Volgograd */
{ 10800, -1 }, /* (GMT+03:00) Nairobi*/
{ 12600, 8 }, /* (GMT+03:30) Tehran */
{ 14400, -1 }, /* (GMT+04:00) Abu Dhabi, Muscat */
{ 14400, 9 }, /* (GMT+04:00) Baku, Tbilisi, Yerevan */
{ 16200, -1 }, /* (GMT+04:30) Kabul*/
{ 18000, 7 }, /* (GMT+05:00)Ekaterinburg */
{ 18000, -1 }, /* (GMT+05:00) Islamabad, Karachi, Tashkent*/
{ 19800, -1 }, /* (GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi */
{ 20700, -1 }, /* (GMT+05:45) Kathmandu*/
{ 21600, 12 }, /* (GMT+06:00) Almaty, Novosibirsk */
{ 21600, -1 }, /* (GMT+06:00) Astana, Dhaka*/
{ 21600, -1 }, /* (GMT+06:00) Sri Jayawardenepura */
{ 23400, -1 }, /* (GMT+06:30) Rangoon */
{ 25200, -1 }, /* (GMT+07:00) Bangkok, Hanoi, Jakarta*/
{ 25200, 7 }, /* (GMT+07:00) Krasnoyarsk */
{ 28800, -1 }, /* (GMT+08:00) Beijing,Chongquing, Hong Kong, Urumqi*/
{ 28800, -1 }, /* (GMT+08:00) Irkutsk,Ulaan Bataar */
{ 28800, -1 }, /* (GMT+08:00) Kuala Lumpur, Singapore*/
{ 28800, -1 }, /* (GMT+08:00) Perth*/
{ 28800, -1 }, /* (GMT+08:00) Taipei*/
{ 32400, -1 }, /* (GMT+09:00) Osaka, Sapporo, Tokyo*/
{ 32400, -1 }, /* (GMT+09:00) Seoul*/
{ 32400, 7 }, /* (GMT+09:00) Yakutsk */
{ 34200, 3 }, /* (GMT+09:30) Adelaide */
{ 34200, -1 }, /* (GMT+09:30) Darwin*/
{ 36000, -1 }, /* (GMT+10:00) Brisbane*/
{ 36000, 3 }, /* (GMT+10:00) Canberra, Melbourne, Sydney*/
{ 36000, -1 }, /* (GMT+10:00) Guam, Port Moresby */
{ 36000, 10 }, /* (GMT+10:00) Hobart*/
{ 36000, 7 }, /* (GMT+10:00) Vladivostok */
{ 39600, -1 }, /* (GMT+11:00) Magadan */
{ 39600, 7 }, /* (GMT+11:00)Solomon Is., New Caledonia*/
{ 43200, 11 }, /* (GMT+12:00) Auckland, Wellington */
{ 43200, -1 }, /* (GMT+12:00) Fiji,Kamchatka, Marshall Is. */
{ 43200, -1 }, /* (GMT+12:00) NZ */
};
那么又如何去掉DST,即找到系统时间与标准时间之间的DST偏移量呢?在此之前需要了解到DST的规则问题,如规则格式、规则数据等等。
DST规则规定了实施夏令时的起始时间以及结束时间,如澳大利亚的是:从4月的第一个星期天的凌晨3点到10月的第一个星期天的凌晨2点,全世界DST可参考www.worldtimezone.com/daylight.html。下面主要阐述如何判断目前的时间是否包含有夏令时。
rpytime(rule1, year) < (gm_time + zone->z_gmtoff))< rpytime(rule2, year)
上面的式子中gm_time是本地系统时间(注意是通过localtime获取,没有加入时区,单位为秒),z_gmtoff是指制定时区的偏移量,这样式子中间代表就是标准时间;式子中rule1,rule2分别对应于DST规则中的两个界点,并利用rpytime函数计算出从1970年以来的时间总长(以秒为单位),若上面的式子成立,表明存在DST,那是因为DST使得在标准时间之上提前了1小时。