10 移植U-Boot
10.1 RTC的作用及时间表示
“RTC”的英文全称是Real-Time Clock,翻译过来是实时时钟芯片。实时时钟芯片是日常生活中应用最为广泛的电子器件之一,它为人们或者电子系统提供精确的实时时间。实时时钟芯片通过引脚对外提供时间读写接口,通常内部带有电池,保证在外部系统关电时,内部电路正常工作,时间正常运行。不同的时钟芯片内部机制不一样,时间数据存储格式、读写操作方式也不一样,Linux系统和驱动封装了不同时钟芯片的操作细节,为应用程序提供了统一的时间操作接口。
那么在Linux世界里,时间是怎么表示的呢?是不是与人们一样用年月日+时分秒来表示时间呢?聪明的程序员自然不会让计算机这么做,正所谓越简单越科学,直接用一个整数表示时间,这个整数代表当前与Epoch Time的时间差(以秒为单位)。Epoch Time 是指一个特定的时间:1970年1月1日0时0分0秒。假设现在距离1970年1月1日0时0分0秒走了N秒,在Linuxx系统里,时间数值就是N。
那么问题来了,为什么要从1970年1月1日0时0分0秒开始呢?那年发生了什么,以至于Unix系统以它作“纪元”。原来Unix就是在 那个时代产生的,1969年发布的雏形,最早是基于硬件60Hz的时间计数。1971年底出版的《Unix Programmer's Manual》里定义的Unix Time是以1971年1月1日00:00:00作为起始时间,每秒增长60。后来考虑到如果每秒60个数字,则1.1年后时间值就到达最大,于是改成以秒为计数单位,时间能表示到68.1年之长,就不在乎起始时间是1970还是1971年,遂改成人工记忆、计算比较方便的1970年。于是Unix的世界开启了“纪元”,Unix时间戳也就成为了一个专有名称。后Linux系统沿用了这种定义时间的方式。
当时计算机操作系统是32位,时间也是用一个32位的有符号数来表示,数据取值范围为-2147483648~2147483647,也就是说时间最大值只能取到2147483647(秒),换算成年也即2147483647÷365÷24÷60÷60=68.1年,也就是说32位能表示的最长时间是1970+68.1=2038年。精确点讲,2038年01月19日03时14分07秒,时间便会达到最大值,为0x7FFFFFFF。过了这个时间点,下一秒时间值便会变为0x80000000,算下来也就是1901年12月13日20时45分52秒,这样便会出现时间回归的现象,很多系统便会运行异常。
上边说过了,那是Unix系统“元年”时候的事情,32位的时间已经足以解决当时的问题了。现在主流CPU都是64位的,使用64位的数据表示时间也是顺其自然的事,用64位的有符号数来表示时间,可以表示到292,277,026,596年12月4日15时30分08秒,相信我们再也不用愁时间回归的问题了。
10.2 RTC的操作命令
弄清楚了时间怎么表示之后,Linux是怎么使用和维护时间的呢?我们怎么通过Linux操作时间呢?
10.2.1 系统时间和硬件时间
在Linux中有系统时钟与硬件时钟两种时钟。系统时间是由CPU主芯片的定时器进行维护的时间,一般情况下都会选择芯片上精度最高的定时器作为系统时间的定时基准,以避免在系统运行较长时间后出现大的时间偏移。特点是系统掉电后,系统时间将丢失。硬件时钟是指系统中包含的RTC芯片内部所维护的时间。RTC芯片都有电池+系统电源的双重供电机制,在系统正常工作时由系统供电,在系统掉电后由电池进行供电。因此系统电源掉电后RTC时间仍然能够正常运行,Linux系统中硬件时钟的基本目的是在Linux不运行时保持时间。
在Linux启动时,将系统时间从硬件时钟初始化,然后不再使用硬件时钟。在系统开机时,由Linux操作系统从RTC芯片读取硬件时间后,由CPU内部定时器维护时间运行。此后操作系统使用的时间都是系统时间,如果没有显式的通过命令去控制RTC的读写操作,系统将不会再从RTC中去获取或者同步设置时间。
10.2.2 系统时间操作命令
查看系统时间:
date
Sat May 1 08:11:19 EDT 2020
格式化输出:
date +"%Y-%m-%d"
2020-05-01
2秒后输出:
date -d "2 second" +"%Y-%m-%d %H:%M.%S"
2020-05-01 14:21.31
显示1234567890秒的时间:
date -d "1970-01-01 1234567890 seconds" +"%Y-%m-%d %H:%m:%S"
2009-02-13 23:02:30
普通转格式:
date -d "2009-05-01" +"%Y/%m/%d %H:%M.%S"
2020/05/01 00:00.00
输出其他日期:
date -d "+1 day" +%Y%m%d #显示后一天的日期
date -d "-1 day" +%Y%m%d #显示前一天的日期
date -d "-1 month" +%Y%m%d #显示上一月的日期
date -d "+1 month" +%Y%m%d #显示下一月的日期
date -d "-1 year" +%Y%m%d #显示前一年的日期
date -d "+1 year" +%Y%m%d #显示下一年的日期
设置系统时间:
date -s 20200501 #设置成20200501,这样会把具体时间设置成空00:00:00
date -s 01:01:01 #设置具体时间,不会对日期做更改
date -s "01:01:01 2020-05-01" #这样可以设置全部时间
date -s "01:01:01 20200501" #这样可以设置全部时间
date -s "2020-05-01 01:01:01" #这样可以设置全部时间
date -s "20200501 01:01:01" #这样可以设置全部时间
命令更多参数使用方法可访问:https://www.cnblogs.com/machangwei-8/p/10352546.html
10.2.3 硬件时间操作命令
显示硬件时间:
hwclock或 hwclock -r 或 hwclock --show
2000年04月11日 星期二 13时24分35秒 -0.109687 seconds
设置硬件时钟:
hwclock --set --date '2015-04-11 13:36:11'11
将系统时钟同步到硬件时钟:
hwclock -w
将硬件时钟同步到系统时钟:
hwclock -s
命令更多参数使用方法可访问:https://www.cnblogs.com/wj78080458/p/9806774.html
10.3 RTC的数据结构和函数
在Linux环境中,我们学会了使用命令,修改系统时间和硬件时间。在编程时我们当然可以直接使用system系统调用来操作时间,但是这样既显得不够专业,也不能满足大部分需求,因为很多情况下我们不只是要修改时间,而是要对时间进行运算处理。
RTC编程,重点是学习时间相关的结构体和相关操作函数。
10.3.1 时间相关的数据结构
在C语言涉及中经常需要定时触发事件,涉及到获取系统时间,其结构体类型有多种。Linux系统下,与时间有关的数据类型定义在头文件 /usr/include/sys/time.h 中:
只要有以下几种时间相关的数据类型:
1.time_t 类型:长整型
一般用来表示从Epoch Time(1970年1月1日午夜(00:00:00))以来的秒数,单位为秒。
#define _TIME_T
typedef long time_t;
#endif
由函数time_t time(time_t* lpt)来获取time_t 数据,函数返回自Epoch Time(1970年1月1日午夜(00:00:00))起经过的时间,以秒为单位。如果 lpt不为空,则返回值也存储在lpt指向的变量中。
示例:
time_t t = time(NULL);
2.struct timeb 结构
它有四个成员,一个是秒,另一个是毫秒。
struct timeb{
time_t time;
unsigned short millitm;
short timezone;
short dstflag;
};
time是从Epoch Time(1970年1月1日午夜(00:00:00))起累计的秒数。
millitm是一秒内的毫秒数。
dstflag不为0,说明这是夏令时时间。
timezone是UTC 时间和本地时间的相差分钟数。
由函数int ftime(struct timeb *tp) 来获取timeb,调用成功返回0,调用失败返回-1。
示例:
struct timeb tp;
ftime(&tp);
3.struct timeval 和struct timezone结构
timeval 有两个成员,一个是秒,另一个表示微秒。
struct timeval{
long tv_sec; /*秒*/
long tv_usec;/*微秒*/
};
tv_sec为Epoch Time到创建struct timeval时的秒数,tv_usec为微秒数,即秒后面的零头。比如当tv_sec为1234567890,tv_usec为1234,即当前时间距Epoch时间1234567890秒,1234微秒。
struct timezone{
int tz_minuteswest;/*和greenwich 时间差了多少分钟*/
int tz_dsttime; /*type of DST correction*/
};
tz_minuteswest表示当前系统所在时区和UTC的时间差,tz_minuteswest以分钟计算。比如北京GMT+8区,tz_minuteswest为-480。tz_dsttime的定义为日光节约时间(DST,也就是夏令时。
由函数int gettimeofday(struct timeval*tv,struct timezone *tz )来获取timeval和timezone,在gettimeofday()函数中tv或者tz都可以为空。如果为空则就不返回其对应的结构体。函数执行成功后返回0,失败后返回-1,错误代码存于errno中。
示例:
struct timeval tv;
gettimeofday(&tv, NULL);
4.struct tm 结构
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 日,以此类推 */
int tm_isdst; /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0; 不了解情况时,tm_isdst()为负。*/
};
int tm_sec 代表目前秒数,正常范围为0-59
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 日光节约时间的旗标
由函数struct tm* gmtime(const time_t*timep)解析得到tm,gmtime()将参数timep 所指的time_t 数据类型中的信息转换成真实世界所使用的时间日期表示方法,然后将结果由结构tm的指针返回。 示例:
struct tm* tm =NULL ;
time_t t = time(NULL);
tm = gmtime(&t);