时间、定时器与休眠
前言
1、时间:
日历时间:UTC,格林威治时间,从1970.1.1.00:00开始,以秒数度量,存储于time_t类型
时区:不同国家使用不同时区和夏时制,时区定义于系统本地时区文件/etc/localtime
地区:显示格式差异,地区信息维护于/usr/hare/local
软件时钟(jiffies):各系统调用的精度受限于此,大小定义于常量HZ(100=HZ=10ms)
时间转换函数:日历时间、分解时间、时间打印格式之间转换
进程时间:用户CPU时间和内核CPU时间
2、定时器与休眠
间隔定时器:setitimer、alarm
POSIX间隔定时器:timer_create、timer_settime、timer_delete、信号/线程定时通知
休眠:sleep、nanosleep
一、时间数据结构与转化函数
1、time_t、struct timeval、struct tm
time_t long型,表示从1970年1月1日到现在经过的秒数。
struct timeval{
time_t tv_sec;
suseconds_t tv_usec; /*微妙级*/
};
struct tm {
int tm_sec; /* 秒 – 取值区间为[0,59] */
int tm_min; /* 分 - 取值区间为[0,59] */
int tm_hour; /* 时 - 取值区间为[0,23] */
int tm_mday; /* 一个月中的日期 - 取值区间为[1,31] */
int tm_mon; /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
int tm_year; /* 年份,其值等于实际年份减去1900 */
int tm_wday; /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,
以此类推 */
int tm_yday; /* 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表
1月1日,1代表1月2日*/
};
2、时间转换函数
①、time、gettimeofday
time_t time(time_t *timep);
返回日历时间
timep:也是日历时间 ,可为NULL
eg: t = time(NULL);
int gettimeofday(struct timeval *tv,struct timezone *tz);
success : 0 error :-1
得到的时间存放于tv指向的对象,包含有秒级和微秒级
②、日历时间和分解时间相互转化
日历----> 分解
struct tm *gmtime(const time_t *timep);/* 把日历时间转化分解时间的UTC*/
struct tm *localtime(const time_t *timep); /*转化为分解时间考虑时区和夏令时*/
返回指向 tm的指针,两种返回的是指向同一静态分配地址,注意需要copy到本来变量
分解---->日历
time_t mktime(struct tm *timeptr);
返回历史时间
3、时间打印与输入
①、打印固定格式字符串
a、日历时间为源的打印
char *ctime(const time_t *timep);
返回长达26字节的字符串,内含标准格式的日期和时间
eg: Wed Jun 8 14:22:34 2011
b、分解时间为源的打印
char *asctime(const struct tm *timeptr);
与ctime类似 无法控制字符串格式
②、用户格式的本地化字符串
a、分解时间---->格式化打印输出
size_t strftime(char *outstr, size_t max, const char *format,
const struct tm *tm);
success:返回实际字符串大小 error: 0
outstr:返回按照format参数定义的字符串
max:返回最大字符串大小,如果超过返回错误
tm:指向分解时间的指针
format:自定义格式 如 %F %T 2021-07-29 09:45:14
转换控制符 | 说明 |
---|---|
%a | 星期几的简写形式 |
%A | 月份的简写形式 |
%B | 月份的全称 |
%c | 日期和时间 |
%d | 月份中的日期,0-31 |
%H | 小时,00-23 |
%I | 12进制小时钟点,01-12 |
%j | 年份中的日期,001-366 |
%m | 年份中的月份,01-12 |
%M | 分,00-59 |
%p | 上午或下午 |
%S | 秒,00-60 |
%u | 星期几,1-7 |
%w | 星期几,0-6 |
%x | 当地格式的日期 |
%X | 当地格式的时间 |
%y | 年份中的最后两位数,00-99 |
%Y | 年 |
%Z | 地理时区名称 |
b、打印格式时间---->分解时间
char *strptime(const char *intstr, const char *format, struct tm *tm);
success:返回指向instr中未经处理的字符
format:将要把instr转换成分解时间的格式
tm:转变成的分解时间
4、更新系统时钟
settimeofday是gettimeofday的逆向
int settimeofday(const struct timeval *tv, const struct timezone *tz);
success:0 error:-1
tv:需要更新的时间 tz:NULL
adjtime(推荐)是调整时间的快慢来更新时钟,settimeofday是突变的可能会引起其他程序影响
int adjtime(const struct timeval *delta, struct timeval *olddelta);
success:0 error:-1
delta:需要更新的时间 tz:NULL
二、定时器与休眠
1、间隔定时器setitime()与alarm()
settimer工作机制是,先对it_value倒计时,当it_value为零时触发信号。然后重置为it_interval。继续对it_value倒计时。一直这样循环下去。
假如it_value为0是不会触发信号的,所以要能触发信号,it_value得大于0;假设it_interval为零,仅仅会延时。不会定时(也就是说仅仅会触发一次信号)。
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
which:
ITIMER_REAL:以系统真实的时间来计算,它送出SIGALRM信号。
ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF:以该进程在用户态下和内核态下所费的时间来计算。它送出SIGPROF信号。
new_value:要设定定时的时间,包含第一次定时和之后的间隔
old_value:存放上一次newvalue 通常为NULL
struct itimerval {
struct timeval it_interval; /* next value */
struct timeval it_value; /* current value */
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
unsigned int alarm(unsigned int seconds);
alarm()函数的主要功能是设置信号传送闹钟,即用来设置信号SIGALRM在经过参数seconds秒
数后发送给目前的进程。如果未设置信号SIGALARM的处理函数,那么alarm()默认处理终止进
程
2、POSIX间隔式定时器
相对于setitime,posix可设置多个定时器、可定时通知一个线程和信号,时间精度也加大,可知晓是否溢出(timer overrun)
流程为:
timer_create创建定时器、
timer_settime启动或停止定时器、
timer_delete删除定时器
①、创建定时器timer_create()
#include <signal.h>
#include <time.h>
int timer_create(clockid_t clockid, struct sigevent *sevp,
timer_t *timerid);
@clockid: 可选系统系统的宏,比如 CLOCK_REALTIME
CLOCK_REALTIME, 可设定系统级实时时钟
CLOCK_MONOTONIC, 不可设定的恒定时钟
CLOCK_PROCESS_CPUTIME_ID, 每进程CPU时间的时钟
CLOCK_THREAD_CPUTIME_ID, 每线程CPU时间的时钟
@sevp 环境值,结构体struct sigevent变量的地址
@timerid 定时器标识符,结构体timer_t变量的地址
link with -lrt.
返回值:
0 - 成功;-1 - 失败,errno被设置。
struct sigevent 结构体
union sigval
{
int sival_int; //integer value
void *sival_ptr; //pointer value
}
struct sigevent
{
int sigev_notify; //设置定时器到期后的行为
int sigev_signo; //设置产生信号的信号码
union sigval sigev_value; //设置产生信号的值
void (*sigev_notify_function)(union sigval);//定时器到期,从该地址启动一个线程
void*sigev_notify_attributes; //创建线程的属性
pid_t sigev_notify_thread_id;
}
sigev_notify:
SIGEV_NONE:定时器到期后什么都不做,只提供通过timer_gettime和timer_getoverrun
查询超时信息。
SIGEV_SIGNAL:定时器到期后,内核会将sigev_signo所指定的信号,传送给进程,在信号
处理程序中,si_value会被设定为sigev_value的值。
SIGEV_THREAD:定时器到期后,内核会以sigev_notification_attributes为线程属性
创建一个线程,线程的入口地址为sigev_notify_function,sigev_value
作为一个参数。
②、启动/停止或重置定时器timer_settime()
int timer_settime(timer_t timerid, int flags,const struct itimerspec *new_value,
struct itimerspec *old_value)
timerid:定时器标识,创建时的标识
flags: 0:将new_value.it_value的值为相对时间 TIMER_ABSTIME:为绝对时间
new_value:itimerspec 设定要定时间隔时间和延时的时间
old_value: 定时器的前一设置 通常设为NULL
0 - 成功; -1 - 失败,errno被设置。
itimerspec 结构体:
定时器工作时,先将it_value的时间值减到0,发送一个信号,再将it_interval的值赋给it_value,重新开始定时,如此反复。如果it_value值被设置为0,则定时器停止定时;如果it_interval等于0,那么表示该定时器不是一个时间间隔定时器,一旦it_value到期后定时器就回到未启动状态。
struct itimerspec
{
struct timespec it_interval; // 时间间隔
struct timespec it_value; // 首次到期时间
};
struct timespec
{
time_t tv_sec //Seconds.
long tv_nsec //Nanoseconds.
};
③、删除定时器timer_delete()
int timer_delete (timer_t timerid);
通过timder id删除指定的 timer
0 - 成功; -1 - 失败,errno被设置。
④、获取定时器当前值timer_gettime()
int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
获得定时器的到期时间和间隔
⑤、获得定时器溢出次数timer_getoverrun()
int timer_getoverrun(timer_t timerid);
获得定时器超限的次数
eg: 返回溢出值为2,则定时器发生了3次 0为无溢出
3、练习:通过信号发出通知
第一次触发时间为1秒之后间隔1秒发一次信号,共发5次退出
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#define CLOCKID CLOCK_REALTIME
int flag = 0;
void sig_handler(int signo)
{
static int cnt = 0;
printf("timer_signal function! %d count:%d\n", signo,cnt);
if(cnt > 5)
{
flag = 1;
}
cnt++;
}
int main()
{
timer_t timerid;
struct sigevent evp;
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = sig_handler;
act.sa_flags = 0;
// XXX int sigaddset(sigset_t *set, int signum); //将signum指定的信号加入set信号集
// XXX int sigemptyset(sigset_t *set); //初始化信号集
sigemptyset(&act.sa_mask); //初始化信号集
if (sigaction(SIGUSR1, &act, NULL) == -1)
{
perror("fail to sigaction");
exit(-1);
}
memset(&evp, 0, sizeof(struct sigevent));
evp.sigev_signo = SIGUSR1;
evp.sigev_notify = SIGEV_SIGNAL;
if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)
{
perror("fail to timer_create");
exit(-1);
}
struct itimerspec it;
it.it_interval.tv_sec = 2;
it.it_interval.tv_nsec = 0;
it.it_value.tv_sec = 1;
it.it_value.tv_nsec = 0;
if (timer_settime(timerid, 0, &it, 0) == -1)
{
perror("fail to timer_settime");
exit(-1);
}
while(!flag)
pause();
return 0;
}
4、练习、通过线程来通知
第一次触发时间为5秒之后间隔1秒运行一次处理函数,处理函数里打印tm时间
一下代码进行了封装。create_timer、init_timer、set_timer_tm、delete_timer
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/ioctl.h>
#include <termios.h>
#include <math.h>
#include <time.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
typedef void (*TIMER_FUNC)();
timer_t time_id = (timer_t)-1;
int flag = 0;
int create_timer(timer_t* ptimer_id, TIMER_FUNC timer_func,
int it_interval_tv_sec, int it_interval_tv_nsec,
int it_value_tv_sec, int it_value_tv_nsec)
{
if (NULL == ptimer_id)
return -1;
//设置sigevent
struct sigevent sigev;
memset(&sigev, 0, sizeof(struct sigevent));
sigev.sigev_notify = SIGEV_THREAD;
sigev.sigev_notify_function = timer_func;
// XXX int timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid);
// clockid--值:CLOCK_REALTIME,CLOCK_MONOTONIC,CLOCK_PROCESS_CPUTIME_ID,
// CLOCK_THREAD_CPUTIME_ID
// evp--存放环境值的地址,结构成员说明了定时器到期的通知方式和处理方式等
// timerid--定时器标识符
//创建实时时钟Timer
if (timer_create(CLOCK_REALTIME, &sigev, ptimer_id) < 0)
{
return -1;
}
struct itimerspec timer_spec;
timer_spec.it_interval.tv_sec = it_interval_tv_sec;
timer_spec.it_interval.tv_nsec = it_interval_tv_nsec;
timer_spec.it_value.tv_sec = it_value_tv_sec;
timer_spec.it_value.tv_nsec = it_value_tv_nsec;
// XXX int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value,struct itimerspec *old_value);
// timerid--定时器标识
// flags--0表示相对时间CLOCK_REALTIME,1表示绝对时间 TIMER_ABSTIME
// new_value--定时器的新初始值和间隔,如下面的it
// old_value--取值通常为0,即第四个参数常为NULL,若不为NULL
//,则返回定时器的前一个值
//第一次间隔it.it_value这么长,以后每次都是it.it_interval这么长,就是说it.
//it_value变0的时候会装载it.it_interval的值
if (timer_settime(*ptimer_id, CLOCK_REALTIME, &timer_spec, NULL) < 0)
{
printf("timer_settime err");
return 0;
}
return 0;
}
int init_timer(timer_t* ptimer_id, TIMER_FUNC timer_func)
{
if (NULL == ptimer_id)
return -1;
//设置sigevent
struct sigevent sigev;
memset(&sigev, 0, sizeof(struct sigevent));
sigev.sigev_notify = SIGEV_THREAD;
sigev.sigev_notify_function = timer_func;
if (timer_create(CLOCK_REALTIME, &sigev, ptimer_id) < 0)
{
return -1;
}
return 0;
}
int delete_timer(timer_t* ptimer_id)
{
if (NULL == ptimer_id)
return -1;
if ((long int)(*ptimer_id) >= 0)
{
if (timer_delete(*ptimer_id))
{
printf("timer_delete failed!");
return -1;
}
*ptimer_id = (timer_t)-1;
}
return 0;
}
int set_timer_tm(timer_t* ptimer_id,
int it_interval_tv_sec, int it_interval_tv_nsec,
int it_value_tv_sec, int it_value_tv_nsec)
{
if (NULL == ptimer_id)
return -1;
struct itimerspec timer_spec;
timer_spec.it_interval.tv_sec = it_interval_tv_sec;
timer_spec.it_interval.tv_nsec = it_interval_tv_nsec;
timer_spec.it_value.tv_sec = it_value_tv_sec;
timer_spec.it_value.tv_nsec = it_value_tv_nsec;
if (timer_settime(*ptimer_id, 0, &timer_spec, NULL) < 0)
{
printf("timer_settime fail\r\n");
return -1;
}
return 0;
}
void timer_handle()
{
static int timer_cnt = 0;
struct timeval tv;
struct timezone tz;
#if 0
struct tm { /* a broken-down time */
int tm_sec; /* seconds after the minute: [0 - 60] */
int tm_min; /* minutes after the hour: [0 - 59] */
int tm_hour; /* hours after midnight: [0 - 23] */
int tm_mday; /* day of the month: [1 - 31] */
int tm_mon; /* months since January: [0 - 11] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday: [0 - 6] */
int tm_yday; /* days since January 1: [0 - 365] */
int tm_isdst; /* daylight saving time flag: <0, 0, >0 */
// 以下两个字段在有些版本中是存在的,使用时需要查看对应的头文件确认
long int tm_gmtoff; /* Seconds east of UTC. */
const char *tm_zone; /* Timezone abbreviation. */
};
#endif
struct tm *local_tm;
//长整型long int 用来保存从1970年1月1日0时0分0秒到现在时刻的秒数
time_t t,t1,t2;//time_t实际上就是长整型long int
time (&t);
printf("timer_handle=%d,curTime=%s\r\n",timer_cnt,ctime(&t));
#if 1
//把目前的时间用tv 结构体返回,当地时区的信息则放到tz所指的结构中
gettimeofday (&tv,&tz);
printf("gettimeofday=%d,%d,%d,%d\r\n",
(int)tv.tv_sec, (int)tv.tv_usec, (int)tz.tz_minuteswest, (int)tz.tz_dsttime);
printf("gettimeofday,curTime=%s\r\n",ctime(&(tv.tv_sec)));
//date -s "14:39:00 2016-07-05" 设置时间 date查看时间
local_tm = localtime(&t);
printf("local_tm isdst=%d,yday=%d,wday=%d,\
year=%d,mon=%d,day=%d,\
hour=%d,min=%d,sec=%d\r\n",
local_tm->tm_isdst, local_tm->tm_yday, local_tm->tm_wday,
local_tm->tm_year, local_tm->tm_mon, local_tm->tm_mday,
local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec);
time (&t1);
sleep(2);
time (&t2);
printf("difftime=%f,%ld\r\n",difftime(t1,t2), (long)(t1-t2));
#endif
timer_cnt++;
if(timer_cnt>10)
{
delete_timer(&time_id);
flag = 1;
}
return;
}
int main(int argc, char *argv[])
{
create_timer(&time_id, timer_handle, 5, 0, 1, 0);
// sleep(2);
// set_timer_tm(&time_id, 1, 0, 60, 0);
while(flag==0){sleep(5);}
return 0;
}
5、暂停运行(休眠)一段固定时间 sleep、nanosleep
①、低分辨率休眠sleep、usleep()
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
unsigned int usleep(unsigned int useconds);
②、高分辨率休眠nanosleep
int nanosleep(const struct timespec *req,struct timespec *rem);
struct timespec
{
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
扩展:利用文件描述符进行通知的定时器、fork时能被继承
https://blog.csdn.net/zhizhengguan/article/details/117368337
参考:https://www.jianshu.com/p/aa96876ebabc
https://blog.csdn.net/sinat_36184075/article/details/80489402