时间、定时器与休眠


前言

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型,表示从197011日到现在经过的秒数。

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
%I12进制小时钟点,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,则定时器发生了30为无溢出

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值