首页 > 代码库 > Linux编程---时间相关
Linux编程---时间相关
时间相关的东西很难分类.所以我就写一篇好了.
一.系统时间
1.time函数
time_t time(time_t *tloc);
double difftime(time_t time1,time_t timeo);
time返回系统自1970年1月1日以来,经历了多少秒的时间.同时可以通过返回值和参数来得到.
difftime则返回两个参数的时间之差.并且结果是作为双精度浮点数返回.
2.gettimeofday函数
int gettimeofday(struct timeval *restrict tp,void *restrict tzp);
int settimeofday(const struct timeval * tp,const void *tzp);
int adjtime(const struct timeval *delta,struct timeval *olddelta);
这里都是指的从1970年1月1日到现在的时间.
并且三个函数都用到了struct timeval结构体.我就说一下.
struct timeval{
time_t tv_sec; 这个表示秒
suseconds_t tv_usec 这个表示微秒
}
头两个函数的第二个参数一定要写NULL.不然在Linux下会报错.
最后一个函数比较有趣,它是用来把系统时钟调快或者调慢.
简单来说,如果参数delta是正,那么加快系统时钟的步伐.为负则减慢时钟的步伐.通常以1%的速率来调整.第二个参数返回仍需调整的时间.
这个函数估计只有对时间要求比较高的服务器程序上才有用吧.
二.时间格式转换
struct tm *gmtime(const time_t * time);
struct tm *localtime(const time_t * time);
time_t mktime(struct tm *brokentime);
第一个函数由time_t类型的参数转换为struct tim结构体的参数.
第二个函数则会自动修正时区和进行与夏令时有关的调整
第三个函数则是反向转换.并且会设置brokentime中的成员为适当的值.
实际上这几个函数中还用到了tzset来获取时区信息.
struct tm结构中成员类型都是int类型.以tm_开头.有以下成员
sec 秒
min 分钟
hour 小时
mday 月中日期
mon 月份0~11
year 自1900年以来的年数
wday 星期几
yday 以1月1日以来的天数0~365
isdst 标志夏令时是否起作用.正表示起作用,0表示不起作用,负数表示无效(应该是会让函数错误返回吧)
格式化日期与时间
char *asctime(const struct tm *tmptr);
char *ctime(const time_t *timeval);
两个函数都返回如同data命令锁输出的时间字符串.
ctime相当于是asctime(localtime(timeval))这种调用.
为了对日期和时间字符串格式进行更为灵活和精确的控制,UNIX系统还提供了另一对函数
size_t strftime(char *s,size_t maxsize,const char *format,struct tm *timeptr);
char *strptime(const char *buf,const char *format,struct tm *timeptr);
第一个函数就类似于sscanf一样.通过%和特定符号来输出相关格式信息.具体的就不多说了,
第二个函数则是第一个函数的逆过程.把串解析到timeptr中.
三.CPU时间与墙钟时间
CPU时间就是占用CPU的时间.可以进一步分为用户时间(执行程序指令)和系统时间(运行过程中系统服务所用时间).
墙钟时间就是现实中进程运行了的时间.每次运行进程都可以产生不一样的结果.
clock_t clock();
这个函数返回CPU时间,包含用户时间和系统调用时间.这个clock表示系统内部使用的时间单位(1/CLOCKS_PER_SEC 秒).
clock_t times(struct tms *buffer);
这个函数可以报告更详细的CPU时间信息.其结果返回到buffer中.返回值返回墙钟时间.
tms结构体成员类型全部为clock_t.有以下成员
tms_utime 用户时间
tms_stime 系统时间
tms_cutime 子进程时间.只包含用户时间.只包含wait或waitpid等待了的子进程.
tms_cstime 子进程时间.只包含系统时间.只包含wait或waitpid等待了的子进程.
注意,这个结构体成员类型虽然是clock.但是要用_SC_CLK_TCK来除.因为clock函数是C库函数,而times()则是系统调用.两者的基础时间标准不一样.
四.睡眠与定时
1.sleep函数
unsigned int sleep(unsigned int seconds);
时间参数是以墙钟时间为参数的.并且当捕获信号时,会使其终止阻塞.
2.设置定时器
每个进程都有三个独立无关的时间定时器:墙钟定时器,虚拟定时器和剖面定时器.
ITIMER_REAL: 这个就是墙钟定时器,到时发送SIGALRM信号
ITIMER_VIRTUAL: 虚拟定时器,用户态才计时.它到期会发送一个SIGVTALRM信号给进程
ITIMER_PROF: 进程的CPU时间剖面定时器.在用户态和核态时均走动.到时会发送一个SIGPROF信号给调用进程.
unsigned int alarm (unsigned int seconds);
这个函数设置墙钟定时器.以秒为单位,到时则生成SIGALRM信号.对这个信号,进程的默认动作为终止.
如果函数多次调用,那么会覆盖原先的计时时间.并且返回原来还剩下的时间.因此可以用0作为参数.这样就会清除之前的alarm设置,而不发送信号.
int setittimer(int which,const struct itimerval *value,struct itimerval *ovalue);
int getitimer(int which,struct itimerval *value);
which参数就是上面的三个宏定义.value则是定时器值的设定.
itimerval结构成员也比较简单.类型都是struct timerval.成员如下
it_interval 定时间隔时间
it_value 定时开始时间
这个间隔时间如果为0,表示定时器只执行一次.否则,表示该定时器每相隔it_interval时间就发送一个信号.
开始时间表示调用后,过了it_value时间后才开始循环定时器.
如果函数参数value为NULL的话,同样也是表示清除定时器.
注意,虽说是精确到了微秒的级别,但是基础时间片仍然是按照系统频率来的.所以每次时间返回应该是系统时间片的整数倍.并且由于系统并发进程不同,每次这种信号也会稍微晚一些才发送到进程.
五.实时时钟与定时
对于一些时间要求比较高的应用会要求时间尽可能的精确,所以就用到了实时时钟.
1.实时时钟
Linux系统定义有如下几种时钟:
CLOCK_REALTIME: 系统范围的时钟,它给出自Epoch以来的时间,是唯一一个所有系统都必须支持的时钟.因此这种类型是可以移植的.
CLOCK_MONOTONIC: 也是系统范围的时钟,自从过去某一点开始以来的时间(秒和纳秒).这个时钟在系统启动之后便不能改变.
CLOCK_PROCESS_CPUTIME_ID: 进城范围的时钟,它给出进程特定的CPU时间
CLOCK_THREAD_CPUTIME_ID: 这是线程范围的时钟,它给出线程特定的CPU时间
实时时钟多用timespec结构体
struct timespec{
time_t tv_sec; 秒数
long tv_nsec; 纳秒数
};
设置时钟分辨率和时间值函数如下
int clock_getres(clockid_t clock_id,struct timespec *res);
int clock_gettime(clockid_t clock_id,struct timespec *tp);
int clock_settime(clockid_t clock_id,const struct timespec *tp);
clockid_t类型等价于int.参数中的clock_id表示类型.用上面的4个宏填入.
getres用于设置参数res为clock_id指定的时钟分辨率.
getime用于读取实时时钟,它返回clock_id指定时钟的当前值到tp所指结构(书上没讲的太清楚,我就按自己意思理解吧).
settime只有root用户才能调用.并且根据clock_id来设置对应的时钟.用其tp中时间的倒数来确定其频率.在Linux中,时钟粒度的最小值至少是一个滴答,即_SC_CLK_TCK.
2.实时睡眠
int nanosleep(const struct timespec *rep,struct timespec * rem);
这个函数比sleep粒度更细.req为指定睡眠时间.如果是时间到了返回值为0,被信号中断则返回-1,并且设置errno为EINTR,并且当rem不为NULL的时候,存储剩余睡眠时间到rem所指结构.也就是说如果还想睡就用rem为参数继续调用nanosleep.
3.实时定时器
实时定时器的分辨率为纳秒级.并且是动态创建的,至多可达32个.实时定时器既可以设置相对时间,也可以设置绝对时间,并且除开SIGALRM信号外,还可以指定其他实时信号作为定时器的到期信号.
创建和删除定时器
int timer_create(clockid_t clockid,struct sigevent *restrict evp,timer_t *restrict timerid);
int timer_delete(timer_t timerid);
对于创建函数而言,clockid参数可以指定定时器使用的时钟.也就是上面的那四个宏定义.第三个参数返回其定时器ID.并且保持ID直到被删除.第二个参数就复杂了.如果是NULL,那么使用默认信号交付.这个结构体主要有下面几个成员:
int sigev_notify 通知类型
int sigev_signo 信号类型
union signval sigev_value 信号参数值
void (*)(union sigval) sigev_notify_function 通知函数
(pthread_attr_t *) sigev_notify_attributes 通知属性
sigev_notify可以取值为
SIGEV_NONE 不生成异步信号
SIGEV_SIGNAL 生成排队的信号,并随信号携带一个应用定义的值,因为是排队的,所以一定是实时信号,或者说可靠信号.
SIGEV_THREAD 执行一个通知函数,这个是线程里面的高级用法,在线程那篇我会补充上的.
delete函数就很简单来.把create传出来的ID赋给其参数即可.并且如果当前计时器还在计时,删除之后不必担心还会再来一个信号.
对于进程调用exit或exec之后也会自动删除定时器.
create之所以没有时间相关的参数,其实是为了精确延时.如果都设置在一个函数里面,那么很可能会有较大的延迟时间.所以创建函数和设置函数是分开的.下面我就说设置函数
int timer_gettime(timer_t timerid,struct itimerspec * value);
int timer_settime(timer_t timerid,int flags,const struct itimerspec *restrict value,struct itimerspec *restrcit ovalue);
gettime可以存储timerid指定定时器中的剩余时间到value所指结构中.
settime则用value设置timerid指定的定时器.value的结构体中有两个struct timespec结构体,成员如下:
it_value; 定时时间
it_interval; 间隔时间
具体的timespec中的内容已经写过了,我就不多写了.
这里可以说的是,系统分辨率仍然存在,如果你设置的时间是分辨率的两个连续整数倍之间一个值,那么会取时间较大的一个值.
还有就是setime的flags参数了.目前只有一个参数宏可以选择TIMER_ABSTIME.这个表示绝对时间.而之前我们设置的都是相对时间.那么绝对时间和相对时间怎么定义的呢?
相对时间是根据你使用CPU时间的大小来分配的,绝对时间是墙钟时间.所以相对时间延时比较大,绝对时间延时小.这是我个人的理解,书上写的不太清楚,百度资料也少,如有错误还请指正.
定时器超期计数
为了避免循环定时发送多个信号排队占用大量资源,又允许应用检测循环次数,所以设置了定时器超期计数.
int timer_getoverrun(timer_t timerid);
这个函数就可以获得定时器有多少个循环没有被处理.如果信号总是被处理了,那么函数的返回值总是0.
标准规定最大超期数为DELAYTIMER_MAX-1次.一般就是MAXINT的值.