1.1 Linux下RTC时间的读写分析
1.1.1 系统时间与RTC时间
Linux系统下包含两个时间:系统时间和RTC时间。
系统时间:是由主芯片的定时器进行维护的时间,一般情况下都会选择芯片上最高精度的定时器作为系统时间的定时基准,以避免在系统运行较长时间后出现大的时间偏移。特点是掉电后不保存。
RTC时间:是指系统中包含的RTC芯片内部所维护的时间。RTC芯片都有电池+系统电源的双重供电机制,在系统正常工作时由系统供电,在系统掉电后由电池进行供电。因此系统电源掉电后RTC时间仍然能够正常运行。
每次Linux系统启动后在启动过程中会检测和挂载RTC驱动,在挂载后会自动从RTC芯片中读取时间并设置到系统时间中去。此后如果没有显式的通过命令去控制RTC的读写操作,系统将不会再从RTC中去获取或者同步设置时间。
linux命令中的date和time等命令都是用来设置系统时间的。
1.1.2 获取系统的时间
date |
示例:
[root@XiaoLong /]# date Sat Apr 30 06:04:29 UTC 2016 [root@xiaolong 2016-4-28]# date 2016年 04月 30日 星期六 14:00:39 CST |
1.1.3 查看命令使用帮助信息
[root@XiaoLong /]# man date //PClinux系统 |
1.1.4 使用date查看与设置系统时间
- 命令格式:
date [参数]... [+格式] |
- 命令功能:
date 可以用来显示或设定系统的日期与时间。 |
- 命令参数:
使用示例: date '+%A' 必要参数: %H 小时(以00-23来表示)。 %I 小时(以01-12来表示)。 %K 小时(以0-23来表示)。 %l 小时(以0-12来表示)。 %M 分钟(以00-59来表示)。 %P AM或PM。 %r 时间(含时分秒,小时以12小时AM/PM来表示)。 %s 总秒数。起算时间为1970-01-01 00:00:00 UTC。 %S 秒(以本地的惯用法来表示)。 %T 时间(含时分秒,小时以24小时制来表示)。 %X 时间(以本地的惯用法来表示)。 %Z 市区。 %a 星期的缩写。 %A 星期的完整名称。 %b 月份英文名的缩写。 %B 月份的完整英文名称。 %c 日期与时间。只输入date指令也会显示同样的结果。 %d 日期(以01-31来表示)。 %D 日期(含年月日)。 %j 该年中的第几天。 %m 月份(以01-12来表示)。 %U 该年中的周数。 %w 该周的天数,0代表周日,1代表周一,异词类推。 %x 日期(以本地的惯用法来表示)。 %y 年份(以00-99来表示)。 %Y 年份(以四位数来表示)。 %n 在显示时,插入新的一行。 %t 在显示时,插入tab。 MM 月份(必要) DD 日期(必要) hh 小时(必要) mm 分钟(必要) ss 秒(选择性) 选择参数: -d<字符串> 显示字符串所指的日期与时间。字符串前后必须加上双引号。 -s<字符串> 根据字符串来设置日期与时间。字符串前后必须加上双引号。 -u 显示GMT。 --help 在线帮助。 --version 显示版本信息 |
- 使用说明:
在显示方面,使用者可以设定欲显示的格式,格式设定为一个加号后接数个标记,其中可用的标记列表如下: % : 打印出 %:
示例:date '+%T'
%n : 下一行 %t : 跳格 %H : 小时(00..23) %I : 小时(01..12) %k : 小时(0..23) %l : 小时(1..12) %M : 分钟(00..59) %p : 显示本地 AM 或 PM %r : 直接显示时间 (12 小时制,格式为 hh:mm:ss [AP]M) %s : 从 1970 年 1 月 1 日 00:00:00 UTC 到目前为止的秒数 %S : 秒(00..61) %T : 直接显示时间 (24 小时制) %X : 相当于 %H:%M:%S %Z : 显示时区 %a : 星期几 (Sun..Sat) %A : 星期几 (Sunday..Saturday) %b : 月份 (Jan..Dec) %B : 月份 (January..December) %c : 直接显示日期与时间 %d : 日 (01..31) %D : 直接显示日期 (mm/dd/yy) %h : 同 %b %j : 一年中的第几天 (001..366) %m : 月份 (01..12) %U : 一年中的第几周 (00..53) (以 Sunday 为一周的第一天的情形) %w : 一周中的第几天 (0..6) %W : 一年中的第几周 (00..53) (以 Monday 为一周的第一天的情形) %x : 直接显示日期 (mm/dd/yy) %y : 年份的最后两位数字 (00.99) %Y : 完整年份 (0000..9999) |
- 设定时间
date -s //设置当前时间,只有root权限才能设置,其他只能查看。 date -s 20080523 //设置成20080523,这样会把具体时间设置成空00:00:00 date -s 01:01:01 //设置具体时间,不会对日期做更改 date -s ”01:01:01 2008-05-23″ //这样可以设置全部时间 date -s ”01:01:01 20080523″ //这样可以设置全部时间 date -s ”2008-05-23 01:01:01″ //这样可以设置全部时间 date -s ”20080523 01:01:01″ //这样可以设置全部时间 |
- 加减:
date +%Y%m%d //显示前天年月日 date +%Y%m%d --date="+1 day" //显示前一天的日期 date +%Y%m%d --date="-1 day" //显示后一天的日期 date +%Y%m%d --date="-1 month" //显示上一月的日期 date +%Y%m%d --date="+1 month" //显示下一月的日期 date +%Y%m%d --date="-1 year" //显示前一年的日期 date +%Y%m%d --date="+1 year" //显示下一年的日期 示例: [root@xiaolong tiny4412]# date +%Y%m%d --date="+1 year" 20170430 |
1.1.5 系统时间设置与显示
1.1.5.1 显示日期
[root@XiaoLong /]# date '+%c' Sat Apr 30 06:20:27 2016 [root@XiaoLong /]# date '+%D' 04/30/16 [root@XiaoLong /]# date '+%x' 04/30/16 [root@XiaoLong /]# date '+%T' 06:20:46 [root@XiaoLong /]# date '+%X' 06:20:51 |
1.1.5.2 设定日期时间
[root@XiaoLong /]# date --date 14:40:00 //设置时间为14点40分00秒 Sat Apr 30 14:40:00 UTC 2016 [root@XiaoLong /]# date -s 23:27:00 //设置时间为23点27分00秒 Sat Apr 30 23:27:00 UTC 2016 |
1.2 时间处理相关函数
头文件:#include <time.h>
函数集合:
char *asctime(const struct tm *); char *asctime_r(const struct tm *restrict, char *restrict); clock_t clock(void); int clock_getcpuclockid(pid_t, clockid_t *); int clock_getres(clockid_t, struct timespec *); int clock_gettime(clockid_t, struct timespec *); int clock_nanosleep(clockid_t, int, const struct timespec *, struct timespec *); int clock_settime(clockid_t, const struct timespec *); char *ctime(const time_t *); char *ctime_r(const time_t *, char *); double difftime(time_t, time_t); struct tm *getdate(const char *); struct tm *gmtime(const time_t *); struct tm *gmtime_r(const time_t *restrict, struct tm *restrict); struct tm *localtime(const time_t *); struct tm *localtime_r(const time_t *restrict, struct tm *restrict); time_t mktime(struct tm *); int nanosleep(const struct timespec *, struct timespec *); size_t strftime(char *restrict, size_t, const char *restrict, const struct tm *restrict); char *strptime(const char *restrict, const char *restrict, struct tm *restrict); time_t time(time_t *); int timer_create(clockid_t, struct sigevent *restrict,timer_t *restrict); int timer_delete(timer_t); int timer_gettime(timer_t, struct itimerspec *); int timer_getoverrun(timer_t); int timer_settime(timer_t, int, const struct itimerspec *restrict,struct itimerspec *restrict); void tzset(void); |
- 相关结构体:
struct tm { int tm_sec; 代表目前秒数,正常范围为0-59,但允许至61秒 int tm_min; 代表目前分数,范围0-59 int tm_hour; 从午夜算起的时数,范围为0-23 int tm_mday; 目前月份的日数,范围01-31 int tm_mon; 代表目前月份,从一月算起,范围从0-11 int tm_year; 从1900 年算起至今的年数 int tm_wday; 一星期的日数,从星期一算起,范围为0-6 int tm_yday; 从今年1月1日算起至今的天数,范围为0-365 int tm_isdst; 日光节约时间的旗标 }; |
1.2.1 time函数(获取秒单位时间)
函数名:time
头文件:time.h
函数原型:time_t time(time_t * timer)
功能: 获取当前的系统时间,返回的结果是一个time_t类型,其实就是一个大整数,其值表示从CUT时间1970年1月1日00:00:00到当前时刻的秒数。然后调用localtime将time_t所表示的CUT时间转换为本地时间(我们是+8区,比CUT多8个小时)并转成struct tm类型,该类型的各数据成员分别表示年月日时分秒。
补充说明:time函数的原型也可以理解为 long time(long *tloc),即返回一个long型整数。
因为在time.h这个头文件中time_t实际上就是:
#ifndef _TIME_T_DEFINED typedef long time_t; /* time value */ #define _TIME_T_DEFINED /* avoid multiple defines of time_t */ #endif |
如果参数填NULL就表示获取当前系统时间的秒数。
示例:
int main(int argc,char **argv) // char *argv[] { long data; data=time(NULL); data+=8*60*60; Get_RTC_Timer(data); //自己编写秒转时间的函数 printf("时间:%d年%d月%d日%d点%d分%d秒 周%d\n ",RTC_Timer.year,RTC_Timer.month,RTC_Timer.day,RTC_Timer.hour,RTC_Timer.minute,RTC_Timer.sec,RTC_Timer.week); return 0; } |
1.2.2 ctime函数(转为字符串格式时间)
char *ctime(const time_t *timep); char *ctime_r(const time_t *timep, char *buf); |
注:若在linux下使用本函数,需要include <time.h>头文件
功能说明:将秒单位的时间转为字符串类型的时间。
示例:
#include <stdio.h>
#include <time.h>
int main(int argc,char **argv)
{
time_t timep;
struct tm *p;
time(&timep);
p = localtime(&timep);
printf("%d-%d-%d %d:%d:%d\n", (1900 + p->tm_year), ( 1 + p->tm_mon), p->tm_mday,p->tm_hour , p->tm_min,
p->tm_sec);
struct tm result;
localtime_r(&timep,&result);
printf("%d-%d-%d %d:%d:%d\n", (1900 + result.tm_year), ( 1 +result.tm_mon), result.tm_mday,result.tm_hour , result.tm_min,
result.tm_sec);
return 0;
}
结果示例:
[wbyq@wbyq linux_c]$ ./a.out
2020-8-17 14:19:2
2020-8-17 14:19:2
1.2.3 localtime转换得到当前日期时间
struct tm *localtime(const time_t *); struct tm *localtime_r(const time_t *restrict, struct tm *restrict); |
说明:
/*该函数将有time函数获取的值timep转换真实世界所使用的时间日期表示方法,然后将结果由结构tm返回*/
/**需要注意的是localtime函数可以将时间转换本地时间,但是localtime函数不是线程安全的。
多线程应用里面,应该用localtime_r函数替代localtime函数,因为localtime_r是线程安全的**/
示例:
#include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <dirent.h> #include <libgen.h> #include <stdlib.h> #include <time.h> int main(int argc,char **argv) { time_t timep; struct tm *p; time(&timep); p = localtime(&timep); printf("%d-%d-%d %d:%d:%d\n", (1900 + p->tm_year), ( 1 + p->tm_mon), p->tm_mday,p->tm_hour , p->tm_min, p->tm_sec); struct tm result; localtime_r(&timep,&result); printf("%d-%d-%d %d:%d:%d\n", (1900 + result.tm_year), ( 1 +result.tm_mon), result.tm_mday,result.tm_hour , result.tm_min, result.tm_sec); return 0; } |
1.2.4 mktime函数(标准时间转为秒单位时间)
time_t mktime(struct tm *); |
功能:将struct tm结构体数据转为秒单位时间返回。
1.2.5 asctime函数: 时间转字符串
把timeptr指向的tm结构体中储存的时间转换为字符串,返回的字符串格式为:Www Mmm dd hh:mm:ss yyyy。其中Www为星期;Mmm为月份;dd为日;hh为时;mm为分;ss为秒;yyyy为年份。
char *asctime(const struct tm *); char *asctime_r(const struct tm *restrict, char *restrict); |
1.2.6 gmtime函数: 获取格林威治时间
struct tm *gmtime(const time_t *); struct tm *gmtime_r(const time_t *restrict, struct tm *restrict); |
gmtime是把日期和时间转换为格林威治(GMT)时间的函数。将参数time 所指的time_t 结构中的信息转换成真实世界所使用的时间日期表示方法,然后将结果由结构tm返回。
示例:
#include "stdio.h"
#include "time.h"
#include "stdlib.h"
int main(void)
{
time_t t;
struct tm *gmt, *area;
tzset(); /*tzset()*/
t = time(NULL);
area = localtime(&t);
printf("本地时间: %s", asctime(area));
gmt = gmtime(&t);
printf("国际时间: %s", asctime(gmt));
return 0;
}
运行结果:
[wbyq@wbyq linux_c]$ ./a.out
本地时间: Mon Aug 17 14:26:48 2020
国际时间: Mon Aug 17 06:26:48 2020
1.2.7 nanosleep高精度延时函数
#include <time.h> int nanosleep(const struct timespec *req, struct timespec *rem); 返回值 0 :请示的时间间隔结束。 -1:信号中断或失败,并设置errno。 这个函数功能是暂停某个进程直到你规定的时间后恢复,参数req就是你要暂停的时间,其中req->tv_sec是以秒为单位,而tv_nsec以纳秒为单位(10的-9次方秒)。由于调用nanosleep是使进程进入TASK_INTERRUPTIBLE,这就意味着有可能会没有等到你规定的时间就因为其它信号而唤醒,此时函数返回-1,且剩余的时间会被记录在rem中。 |
nanosleep()函数会导致当前的线程将暂停执行,直到rqtp参数所指定的时间间隔。或者在指定时间间隔内有信号传递到当前线程,将引起当前线程调用信号捕获函数或终止该线程。
结构体:
struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; |
示例:
#include <stdio.h> #include <string.h> #include <time.h> int main() { struct timespec req1={2,10}; struct timespec req2; nanosleep(&req1,&req2); printf("hello!\n"); return 0; } |
1.2.8 Linux下定时器信号
在linux下如果对定时要求不太精确的话,使用alarm()和signal()就行了,但是如果想要实现精度较高的定时功能的话,就要使用setitimer函数。
示例:
#include <stdio.h> #include <signal.h> /*信号处理函数*/ void sighandler(int signal_num) { alarm(1); printf("SIGNAL:%d\n",signal_num); } int main(int argc,char **argv) { /*绑定信号到特定的函数*/ signal(SIGALRM,sighandler); alarm(1); while(1) { } return 0; } |
1.2.9 精确定时
linux上定时函数 setitimer 的使用介绍:
#include <sys/time.h> int getitimer(int which, struct itimerval *curr_value); int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value); |
参数介绍:
- which为定时器类型,setitimer支持3种类型的定时器:ITIMER_REAL: 以系统真实的时间来计算,它送出SIGALRM信号。
ITIMER_VIRTUAL: -以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF: 以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号。 - setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例;第三个参数可不做处理。
- setitimer()调用成功返回0,否则返回-1。
- 形参如下
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
};
struct timeval {
long tv_sec;
long tv_usec;
};
it_interval指定间隔时间,it_value指定初始定时时间。如果只指定it_value,就是实现一次定时;如果同时指定 it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。
tv_sec提供秒级精度,tv_usec提供微秒级精度,以值大的为先,注意1s = 1000000us。
ovalue用来保存先前的值,常设为NULL。
如果是以setitimer提供的定时器来休眠,只需阻塞等待定时器信号就可以了。
struct itimerval { struct timeval it_interval; /* 初始定时时间*/ struct timeval it_value; /* 间隔时间 */ }; struct timeval { long tv_sec; /* 秒单位 */ long tv_usec; /* 微秒单位*/ }; |
- 实现一次定时
#include <stdio.h> #include <sys/time.h> #include <signal.h> void sighandler(int signalnum) { printf("%d\n",signalnum); } int main(int argc,char *argv[]) { signal(SIGALRM,sighandler); struct itimerval time; //时间结构体 time.it_interval.tv_sec=0; time.it_interval.tv_usec=0; time.it_value.tv_sec=1; //实现一次定时 time.it_value.tv_usec=1000; setitimer(ITIMER_REAL,&time,NULL); while(1) { } return 0; } |
- 实现重复定时
#include <stdio.h> #include <sys/time.h> #include <signal.h> void sighandler(int signalnum) { printf("%d\n",signalnum); } int main(int argc,char *argv[]) { signal(SIGALRM,sighandler); struct itimerval time; //时间结构体 time.it_interval.tv_sec=1; //实现重复定时 time.it_interval.tv_usec=1000; time.it_value.tv_sec=1; time.it_value.tv_usec=1000; setitimer(ITIMER_REAL,&time,NULL); while(1) { } return 0; } |
1.2.10 获取当前系统的精确时间
#include <sys/time.h> int gettimeofday(struct timeval *tv, struct timezone *tz); int settimeofday(const struct timeval *tv, const struct timezone *tz) |
作用:
该函数的第2个形参在使用的时候一般置为NULL,该参数在Linux帮助文档里解释是:时区结构的使用已过时; tz参数通常应该被指定为NULL。
需要打印代码执行到某处的时间,或者需要计算程序执行的时间差(精确到微妙级)。这时会用到gettimeofday函数,它可以返回自1970-01-01 00:00:00到现在经历的秒数。
参数说明:
struct timeval{ long int tv_sec; // 秒数 long int tv_usec; // 微秒数 } |
其中time_t和suseconds_t都是long int类型。在32位下为4个字节,能够表示的最大正整数是2147483647,而这个表示的时间最大能到2038-01-19 03:14:07,超过了之后就变为-2147483648,这就是linux2038年的问题。而64位系统下的time_t类型即long类型长度为8个字节,可以用到几千亿年,这么长的时间完全不用担心溢出的问题。
struct timezone{ int tz_minuteswest;/*格林威治时间往西方的时差*/ int tz_dsttime;/*DST 时间的修正方式*/ } timezone 参数若不使用则传入NULL即可。 |
在一段代码前后分别使用gettimeofday可以计算代码执行时间
#include "stdio.h"
#include "time.h"
#include "stdlib.h"
#include <sys/time.h>
#include <unistd.h>
int main(void)
{
struct timeval tv;
gettimeofday(&tv,NULL);
printf("1 tv_sec=%ld,tv_usec=%ld\n",tv.tv_sec,tv.tv_usec);
sleep(2); //延时2秒
gettimeofday(&tv,NULL);
printf("2 tv_sec=%ld,tv_usec=%ld\n",tv.tv_sec,tv.tv_usec);
return 0;
}
输出结果:
[wbyq@wbyq linux_c]$ gcc app.c
[wbyq@wbyq linux_c]$ ./a.out
1 tv_sec=1597647069,tv_usec=982304
2 tv_sec=1597647071,tv_usec=983015
1.2.11 strftime
strftime函数,根据区域设置格式化本地时间/日期,函数的功能:将时间格式化,或者说格式化一个时间字符串。
strptime()函数, 按照特定时间格式将字符串转换(解析)为时间类型。(在linux帮助页可以查看用法示例)
strftime函数示例代码:
#include "stdio.h"
#include "time.h"
#include "stdlib.h"
int main(void)
{
time_t time_sec=time(NULL); //得到当前秒时间
struct tm restrict;
localtime_r(&time_sec,&restrict); //转为本地时间
char buff[100];
strftime(buff,100,"%Y-%m-%d %H:%M:%S",&restrict); //转为字符串格式
printf("%s\n",buff);
return 0;
}
输出结果:
[wbyq@wbyq linux_c]$ ./a.out
2020-08-17 14:40:09