Linux时间编程
一、Linux时间类型
在Linux系统当中,时间分为两种类型:格林威治时间和日历时间。
Coordinated Universal Time(UTC)是世界标准的时间,即常说的格林威治标准时间(Greenwich Mean Time,GMT);UTC与GMT两者几乎是同一概念,都是指格林威治时间,只不过UTC的称呼更为正式一点。两者的区别是UTC是天文学上的概念,而GMT是基于一个原子钟。
Calendar Time是用“一个标准时间点(如1970年1月1日0点)到此时经过的秒数”来表示的时间,即日历时间;它与格林威治时间不同。
GMT是中央市区,北京在东8区,相差8个小时,所以北京时间=GMT时间+8小时。
++获取系统时间函数有:time()、gettimeofday();++
++设置系统时间函数有:stime()、settimeofday();++
二、Linux时间格式
2.1 time_t 时间类型
time_t类型定义在time.h中:
#ifndef __TIME_T
#define __TIME_T
typedef long time_t
#endif
可见,time_t实际是一个长整型。其值表示为从UTC(coordinated universal time)时间1970年1月1日00时00分00秒(也称为Linux系统的Epoch时间)到当前时刻的秒数。由于time_t类型长度的限制,它所表示的时间不能晚于2038年1月19日03时14分07秒(UTC)。为了能够表示更久远的时间,可用64位或更长的整型数来保存日历时间。
time_t类型的时间可以通过time()函数获取。
2.2 struct tm时间类型
struct tm结构在time.h中定义为:
#include <time.h>
struct tm {
int tm_sec; /* seconds */
int tm_min; /* minutes */
int tm_hour; /* hours */
int tm_mday; /* day of the month */
int tm_mon; /* month */
int tm_year; /* year */
int tm_wday; /* day of the week */
int tm_yday; /* day in the year */
int tm_isdst; /* daylight saving time */
};
ANSI C标准称使用tm结构的这种时间为分解时间(broken-down time)。其成员介绍如下:
- tm_sec:秒,取值区间为[0,59];
- tm_min:分,取值区间为[0,59];
- tm_hour:时,取值区间为[0,23];
- tm_mday:日期,取值区间为[1,31];
- tm_mon:月份,取值区间为[0,11];
- tm_year:年份,其值为1900年至今的年数;
- tm_wday:星期,取值区间为[0,6],0代表星期天,1代表星期一,以次类推;
- tm_yday:从年的1月1日开始的天数,取值区间为[0,365],0代表1月1日;
- tm_isdst:夏令时标识符,使用夏令时,tm_isdst为正;不使用夏令时,tm_isdst为0;不了解情况时,tm_isdst为负。
使用gmtime()和localtime()可将time_t时间类型转换为struct tm结构体;
使用mktime()可将struct tm结构体转换为time_t时间类型;
使用asctime()将struct tm转换为字符串形式。
2.3 struct timeval时间类型
struct timeval结构体在sys/time.h中定义如下:
#include <sys/time.h>
struct timeval {
time_t tv_sec; /* seconds:秒 */
suseconds_t tv_usec; /* microseconds:微妙 */
};
tv_sec是time_t时间类型,其值也表示为从UTC(coordinated universal time)时间1970年1月1日00时00分00秒(也称为Linux系统的Epoch时间)到当期时刻的秒数。
设置时间函数settimeofday()与获取时间函数gettimeofday()均使用该事件类型作为参数传递。
2.4 struct timespec时间类型
struct timespec结构体在time.h中定义为:
typedef long time_t;
struct timespec {
time_t tv_sec; /* seconds:秒 */
long tv_nsec; /* microseconds:纳妙 */
};
它是POSIX.4标准定义的时间结构,精确度到纳秒,一般由clock_gettime(clockid_t, struct timespec *)获取特定时钟的时间。常用如下4种时钟:
- CLOCK_REALTIME 统当前时间,从1970年1.1日算起
- CLOCK_MONOTONIC 系统的启动时间,不能被设置
- CLOCK_PROCESS_CPUTIME_ID 本进程运行时间
- CLOCK_THREAD_CPUTIME_ID 本线程运行时间
三、Linux时间编程接口
接下来介绍的时间编程函数接口均属于Linux系统调用函数。
3.1 time()函数
【函数原型】:
#include <time.h>
time_t time(time_t *tloc);
【函数说明】:
该函数用于获取日历时间,即从1970年1月1日0点到现在所经历的秒数。参数tloc通常设置为NULL,若tloc非空,time()函数也会将返回值存到tloc指针指向的内存中。
【返回值】:
该函数执行成功返回秒数,失败则返回((time_t)-1)值,错误原因存于errno中。
【使用例程】:
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t seconds = 0;
seconds = time((time_t *)NULL);
printf("seconds = %d\n",seconds);
return 0;
}
执行结果为:
seconds = 1434620150
通常用户得到日历时间的秒数没有实际的意义,但可以为时间转化做一些辅助性质的工作。为了更好的利用时间,用户需要将这些秒数转换为更易接受的时间表示方式,这些表示时间的方式有格林威治时间、本地时间等。
3.2 stime()函数
【函数原型】:
#define _SVID_SOURCE /* glibc2 needs this */
#include <time.h>
int stime(time_t *t);
【函数说明】:
该函数用于设置系统时间,参数t指向time_t时间格式变量,表示从UTC(coordinated universal time)时间1970年1月1日00时00分00秒(也称为Linux系统的Epoch时间)到当前时刻的秒数。
函数执行成功返回0,失败返回-1,并将错误代码放在errno中。
3.3 gmtime()函数
【函数原型】:
#include <time.h>
struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
【函数说明】:
该函数将time()函数获取的日历时间转化为tm结构体表示的格林威治标准时间,并保存在struct tm结构体中。参数是time()函数获取的日历时间。
gmtime_r()是gmtime()的线程安全版本,在libc5.2.5及以后版本中可用。
3.4 localtime()函数
【函数原型】:
#include <time.h>
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);
【函数说明】:
该函数将time()函数获取的日历时间转化为tm结构体表示的本地时区时间,并保存至struct tm结构体中。参数是time()函数获取的日历时间。
localtime_r()是localtime()的线程安全版本,在libc5.2.5及以后版本中可用。
【使用实例】:
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t seconds = 0;
struct tm *utc_time = NULL;
struct tm *local_time = NULL;
seconds = time((time_t *)NULL);
printf("seconds = %d\n",seconds);
utc_time = gmtime(&seconds);
printf("UTC hour is : %d\n",utc_time->tm_hour);
local_time = localtime(&seconds);
printf("Local hour is : %d\n",local_time->tm_hour);
return 0;
}
执行结果为:
seconds = 1434621313
UTC hour is : 9 //格林威治时间是9点
Local hour is : 17 //Linux系统的当前时间是17点
在命令行中执行data命令,可以看到:
Thu Jun 18 17:59:30 CST 2015
说明date命令打印的是Linux系统的本地时间。
3.5 ctime()函数
【函数原型】:
#include <time.h>
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);
【函数说明】:
该函数将timep指向的日历时间(time_t时间类型)转化为本地时间的字符串形式,并返回该字符串指针,参数是time()函数返回的日历时间。
ctime_r()是ctime()的线程安全版本,在libc5.2.5及以后版本中可用。
【函数使用步骤】:
使用time()获取日历时间—>使用ctime()将日历时间直接转换为本地时间字符串。
3.6 asctime()函数
【函数原型】:
#include <time.h>
time_t asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);
【函数说明】:
该函数将struct tm结构体时间转化为字符串形式,并返回该字符串指针,参数是gmtime()函数或localtime()函数返回的struct tm结构体时间。
asctime_r()是asctime()的线程安全版本,在libc5.2.5及以后版本中可用。
【函数使用步骤】:
- 方法一:使用time()获取日历时间—>使用gmtime()将日历时间转化为格林威治时间—>使用asctime()将struct tm格式的时间转化为字符串;
- 方法二:使用time()获取日历时间—>使用localtime()将日历时间转化为本地时间—>使用asctime()将struct tm格式的时间转化为字符串;
【使用例程】:
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t t;
char *ptr;
struct tm *utc;
struct tm *local;
t = time((time_t *)NULL);
printf("seconds = %d\n",t);
ptr = ctime(&t);
printf("Local time is : %s\n",ptr);
utc = gmtime(&t);
ptr = asctime(utc);
printf("UTC time is :%s\n",ptr);
local = localtime(&t);
ptr = asctime(local);
printf("Local time is :%s\n",ptr);
return 0;
}
执行结果为:
seconds = 1434622915
Local time is : Thu Jun 18 18:21:55 2015
UTC time is :Thu Jun 18 10:21:55 2015
Local time is :Thu Jun 18 18:21:55 2015
在命令行终端中执行date命令,结果如下:
Thu Jun 18 18:24:32 CST 2015
3.7 mktime()函数
【函数原型】:
#include <time.h>
time_t mktime(struct tm *p_tm);
【函数说明】:
该函数将p_tm指向的tm结构体时间类型转换成从1970年1月1日00时00分00秒至今的GMT时间经过的秒数。
【函数实例】:
#include <time.h>
#include <stdio.h>
int main(void)
{
time_t timep;
struct tm *p_tm;
timep = time((time_t *)NULL);
printf("time():%d\n",timep);
p_tm = local(&timep);
timep = mktime(p_tm);
printf("time()->localtime()->mktime():%d\n",timep);
return 0;
}
3.8 difftime()函数
【函数原型】:
#include <time.h>
double difftime(time_t timep1, time_t timep2);
【函数说明】:
difftime()函数比较参数timep1和timep2时间是否相同,并返回之间相差的秒数,返回类型为double。
【使用实例】:
#include <time.h>
#include <stdio.h>
int main(void)
{
time_t timep1,timep2;
timep1 = time(NULL);
sleep(2);
timep2 = time(NULL);
printf("The difference is %f seconds\n", difftime(timep1, timep2));
return 0;
}
3.9 gettimeofday()函数
【函数原型】:
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
【函数说明】:
该函数用于获取从UTC(coordinated universal time)时间1970年1月1日00时00分00秒(也称为Linux系统的Epoch时间)到当前时刻的时间差,并将此时间存入tv指向的结构体中,当地时区信息则放到tz指向的结构体中;该函数常用于计算耗时。函数执行成功返回0,否则返回-1,错误代码存于errno。
【参数说明】:
参数tv用于存放返回从UTC(coordinated universal time)时间1970年1月1日00时00分00秒(也称为Linux系统的Epoch时间)到当前时刻的时差,时间差以秒或微妙为单位;
参数tz常设置为NULL,因为libc或glibc中并未支持tz,因此在Linux中未使用;struct timezone结构体定义如下:
#include <sys/time.h>
struct timezone {
int tz_minuteswest; /* miniutes west of Greenwich:和GMT时间差了多少分钟 */
int tz_dsttime; /* type of DST correction:日光节约时间的状态 */
};
tz_dsttime所代表的状态如下:
DST_NONE /* not on dst */
DST_USA /* USA style dst */
DST_AUST /* Australian style dst */
DST_WET /* Western European dst */
DST_MET /* Middle European dst */
DST_EET /* Eastern European dst */
DST_CAN /* Canada */
DST_GB /* Great Britain and Eire */
DST_RUM /* Rumania */
DST_TUR /* Turkey */
DST_AUSTALT /* Australian style with shift in 1986 */
【常用用法】:
在做某件事之前调用gettimeofday(),在做完该事情之后调用gettimeofday(),两个函数的tv参数返回值的差就是该事件所消耗的时间。
【使用实例】:
#include <stdio.h>
#include <sys/time.h>
void function(void)
{
unsigned int i,j,k;
for(i = 0;i < 500;i++)
for(j = 0;j < 500;j++)
k++;
}
int main(void)
{
time_t t;
float timeuse;
struct timeval tpstart,tpend;
t = time((time_t *)NULL);
printf("seconds is %d\n",t);
gettimeofday(&tpstart,NULL);
printf("seconds is %d,usec is %d\n",tpstart.tv_sec,tpstart.tv_usec);
function();
gettimeofday(&tpend,NULL);
timeuse = 1000*1000*(tpend.tv_sec - tpstart.tv_sec) + tpend.tv_usec - tpstart.tv_usec;
timeuse /= 1000*1000;
printf("User time:%f\n",timeuse);
return 0;
}
执行结果为:
User time:0.000727
3.10 settimeofday()函数
【函数原型】:
#include <sys/time.h>
int settimeofday(const struct timeval *tv , const struct timezone *tz);
【函数说明】:
该函数把当前时间设成由tv指向的结构体数据;当tz指针不为空时,则设成tz指向的结构体数据。函数执行成功返回0,否则返回-1,错误代码存于errno。只有root权限才能使用此函数修改时间。
3.11 strftime()函数
【函数原型】:
#include <time.h>
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
【函数说明】:
该函数将参数tm结构的时间结构,参照参数format所指定的字符串格式做转换,转换后的字符串内容将复制到参数s所指的字符串数组中,该字符串的最大长度为参数max所控制。下面是参数format的格式指令:
- %a:当地星期日期的名称缩写,如Sun;
- %A:当地星期日期的名称全称,如Sunday;
- %b:当地月份的英文缩写;
- %h:当地月份的英文缩写;
- %B:当地月份的名称全称;
- %m:月份,表示法为01~12;
- %C:以year/100表示年份;
- %d:月里的天数,表示法为01~31;
- %e:月里的天数,表示法为1~31;
- %c:当地适当的日期与时间表示法表示完整时间;
- %H:以24小时制表示小时数,表示法为00~23;
- %k:以24小时制表示小时数,表示法为0~23;
- %l:以12小时制表示小时数,表示法为01~12;
- %M:分钟数,表示法为00~59;
- %S:秒数,00~59;
- %s:从1970年1月1日0时算起至今的UTC时间所经过的秒数;
- %j:一年中的天数(001~366);
- %u:一星期中的星期日期,范围1~7,星期一从1开始;
- %U:一年中的星期数(00~53),一月第一个星期日开始为01;
- %w:一星期中的星期日期,范围0~6,星期日从0开始;
- %W:一年中的星期数(00~53),一月第一个星期一开始为01;
- %p:显示对应的AM或PM;
- %P:显示对应的am或pm;
- %R:相当于使用“%H:%M”格式;
- %r:相当于使用“%I:%M:%S %P”格式;
- %D:相当于“%m%d%y”格式,如06/19/15;
- %T:24小时时间表示,相当于“%H:%M:%S”格式;
- %x:当地适当的日期表示年月日;
- %X:当地适当的时间表示时分秒;
- %y:一世纪中的年份表示;
- %Y:完整的公元年份表示;
- %Z:使用的时区名称;
- %n:同\n;
- %t:同\t;
- %%:%符号;
返回值复制到参数s所指的字符串数组的总字符数,不包括字符串结束符。如果返回0,表示未复制字符串到参数s内,但不表示一定有错误发生。环境变量TZ和TC_TIME会影响此函数结果。
【使用实例】:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char outstr[200];
time_t t;
struct tm *tmp;
t = time(NULL);
tmp = localtime(&t);
if (tmp == NULL) {
perror("localtime");
exit(EXIT_FAILURE);
}
if (strftime(outstr, sizeof(outstr), argv[1], tmp) == 0) {
fprintf(stderr, "strftime returned 0");
exit(EXIT_FAILURE);
}
printf("Result string is \"%s\"\n", outstr);
exit(EXIT_SUCCESS);
} /* main */
3.12 strptime()函数
【函数原型】:
#define _XOPEN_SOURCE /* glibc2 needs this */
#include <time.h>
char *strptime(const char *s, const char *format, struct tm *tm);
【函数说明】:
该函数与scanf类似,同strftime()函数相反,将一个字符串格式时间解释称为struct tm格式时间。
【使用实例】:
#include <stdio.h>
#include <time.h>
int main() {
struct tm tm;
char buf[255];
strptime("2001-11-12 18:31:01", "%Y-%m-%d %H:%M:%S", &tm);
strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &tm);
puts(buf);
return 0;
}
3.13 clock()函数
【函数原型】:
#include <time.h>
clock_t clock(void);
【函数说明】:
该函数用来返回进程所占用CPU的大约时间。
3.14 ftime()函数
【函数原型】:
#include <sys/timeb.h>
int ftime(struct timeb *tp);
【函数说明】:
该函数将日前时间日期由参数tp所指向的结构体输出,该函数无论成功还是失败都返回0。struct timep结构体定义如下:
struct timeb{
time_t time; /* 从1970年1月1日0点至今的秒数 */
unsigned short millitm; /* 为千分之一秒 */
short timezone; /* 为目前时区和Greenwich相差的时间,单位为妙 */
short dstflag; /* 为日光节约时间的修正状态,若非0为启用日光节约时间的修正 */
};
ftime()函数在4.2的BSD中支持。
3.15 tzset()函数
【函数原型】:
#include <time.h>
void tzset(void);
extern char *tzname[2];
extern long timezone;
extern int daylight;
【函数说明】:
该函数将环境变量TZ设给全局变量tzname,也就是从环境变量取得目前当地的时区,时间转换函数会自动调用此函数。若TZ为设置,tzname会依照/etc/localtime找出最接近当地的时区。若TZ为NULL,或是无法判认,则使用UTC时区。此函数总是成功,并初始化tzname。
四、Linux休眠编程接口
有时候为了降低CPU的占用率及程序的运行速度,需要让程序休眠一段时间,在Linux应用程序中,可以使用:sleep()和usleep()函数实现。对应Linux内核中,休眠的函数有:udelay()、ndelay()、mdelay()。delay函数是忙等待,占用CPU时间,而sleep函数使调用进程休眠,并不占用CPU时间。
4.1 sleep()函数
【函数原型】:
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
【函数说明】:
该函数使调用sleep()的程序休眠,直到seconds秒之后才被唤醒。该函数返回0(seconds时间到了),或返回seconds还剩余的秒数。
4.2 usleep()函数
【函数原型】:
/* BSD version */
#include <unistd.h>
void usleep(unsigned long usec);
/* SUSv2 version */
#define _XOPEN_SOURCE 500
#include <unistd.h>
int usleep(useconds_t usec);
【函数说明】:
该函数使调用usleep()的程序休眠,直到usec微妙之后才被唤醒。该函数睡眠的实际时间会比usec略有延长,因为任何系统活动和时间处理调用的微处理粒度。
该函数执行成功返回0,失败返回-1。该函数可以被信号唤醒,同时返回EINTR。
五、Linux定时闹钟编程接口
Linux应用程序为我们的每一个进程提供了一个定时闹钟alarm,当定时器指定的时间到时,系统会向调用进程发送SIGALARM信号,如果忽略或者不捕获此信号,则其默认动作是终止调用该alarm函数的进程;当然也可以通过signal()函数向系统注册一个自己的定时闹钟处理函数。关于信号处理方面的知识,可参照信号相关章节的内容。
5.1 alarm()函数
【函数原型】:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
【函数说明】:
alarm()函数向系统设定一个闹钟,并在闹钟时间到时内核向该进程发送SIGALRM信号。
【注意事项】:
- 一个进程只能有一个alarm闹钟;
- 闹钟时间到了后,若不再次调用alarm(),将不会有新的闹钟产生;
- 任何以seconds非0的调用,都将重新更新闹钟定时时间,并返回上一个闹钟剩余时间;若为第一次设置闹钟,则返回0;以seconds为0的调用,表示取消以前的闹钟,并将剩余时间返回。
- 在Linux系统中提到,sleep()有可能是使用alarm()来实现的,因此,在一个进程中同时使用alarm()和sleep()并不明智。
【使用实例】:
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int ret;
sleep(3);
ret = alarm(3);
printf("%d\n",ret);
pause();
printf("This will not been print.\n");
return 0;
}
执行结果为:
0 //第一次为该进程设置闹钟
Alarm clock
注意最后一个printf并未打印出来,因为程序没有注册SIGALARM信号处理函数,系统默认处理动作是结束alarm()调用进程。
5.2 setitimer()函数
Linux系统为每个进程提供了三个独立的计时器,每个计时器在不同的时域中递减。当任何一个定时器到点了,都会向进程发送一个信号,并且重启计时器。
【函数原型】:
#include <sys/time.h>
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
【函数说明】:
getitimer()函数获取ITIMER_REAL、ITIMER_VIRTUAL或ITIMER_PROF三个定时器中的一个的时间信息,并保存到value指向的对象中。
setitimer()函数设置ITIMER_REAL、ITIMER_VIRTUAL或ITIMER_PROF三个定时器中的一个的定时时间为value指向的对象,若ovalue非空,则将老的时间信息保存到该对象中。
setitimer()比alarm()功能更加强大,支持3种类型的定时器(但setitimer()并不是标准C库的函数):
- ITIMER_REAL:以系统真实的时间来计算,它会送出SIGALRM信号;
- ITIMER_PROF:以该进程在用户态下和内核态下所费时间来计算,它送出SIGPROF信号;
- ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号;
该函数执行成功返回0,失败返回-1,并将错误号存放在errno中。
【参数说明】:
witch用于指定定时器类型,其取值为上面三种之一;value是struct itimerval的一个实例;ovalue参数不做处理。struct itimerval结构体定义如下:
struct itimerval {
struct timeval it_interval; /* next value */
struct timeval it_value; /* current value */
};
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
it_value变量用于设置计时器的计时时间,为0时表示禁止;it_interval变量用于设置当计时器到时,重置的时间,从而实现循环定时。也就是说,计时器从it_value开始递减,当递减到0时,向进程发送一个信号,并重置定时器为it_interval,如此循环,从而可以实现循环闹钟的功能。
【使用实例】:
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h>
void sig_func(int signo)
{
printf("Catch a signal.\n");
static int realCnt = 0;
static int virtualCnt = 0;
switch(signo)
{
case SIGALRM:
printf("The %d times:SIGALRM\n",realCnt++);
break;
case SIGVTALRM:
printf("The %d times:SIGVTALRM\n",virtualCnt++);
break;
default:
break;
}
}
int main(void)
{
struct itimerval tv,tv1,otv;
signal(SIGALRM,sig_func);
signal(SIGVTALRM,sig_func);
//how long to run the first time
tv.it_value.tv_sec = 3;
tv.it_value.tv_usec = 0;
//after the first time,how long to run next time
tv.it_interval.tv_sec = 2;
tv.it_interval.tv_usec = 0;
if(setitimer(ITIMER_REAL,&tv,&otv) != 0)
{
printf("setitimer err %d\n",errno);
return -1;
}
tv1.it_value.tv_sec = 1;
tv1.it_value.tv_usec = 0;
tv1.it_interval.tv_sec = 1;
tv1.it_interval.tv_usec = 0;
if(setitimer(ITIMER_VIRTUAL,&tv1,&otv) != 0)
{
printf("setitimer err %d\n",errno);
return -1;
}
while(1)
{
sleep(30);
printf("otv:%d,%d,%d,%d\n",otv.it_value.tv_sec,otv.it_value.tv_usec,
otv.it_interval.tv_sec,otv.it_interval.tv_usec);
}
return 0;
}