linux rtc应用程序,ARM Linux RTC 时间的读取与设置

问题描述

这里的平台是 i.MX6 Yocto Linux,没有使用 NTP 对时服务,而是使用我们自己对时机制。RTC 芯片是 PCF8563T,不使用 SoC 上的 RTC。Linux 系统使用 date 命令设置时间,使用 hwclock 命令同步 RTC 时间。一段时间后发现 Linux 系统时间与本地时间相差 8 个小时。

分析问题

直觉告诉我们这是时区造成的,要么是系统的时区配置不对,要么是 RTC 时间的读写不对。要解决这个问题,我需要重新理清时区配置,以及 RTC 时间与 Linux 系统时间的关系。

时区配置

时区配置主要是两个文件:/etc/localtime 和 /etc/timezone,其中 /etc/localtime 是软链接,看来是已经配置成 CST(中国标准时间)的了。

# ls -l /etc/localtime

lrwxrwxrwx 1 root root 33 Jan 1 1970 /etc/localtime -> /usr/share/zoneinfo/Asia/Shanghai

# cat /etc/timezone

Asia/Shanghai

我们这里并没有 /etc/sysconfig/clock 文件,即便加上去了,似乎也不起作用。

# cat /etc/sysconfig/clock

ZONE="Asia/Shanghai"

UTC=false

ARC=false

时间方案

设定了时区,还要确定 Linux 系统的时间方案。Linux 支持 UTC 时间,也就是本初子午线上的时间,它和以前的格林尼治时间(GMT)的区别似乎在于 UTC 是由多个原子钟平均出来的。

协调世界时,又称世界统一时间、世界标准时间、国际协调时间。由于英文(CUT,Coordinated Universal Time)和法文(TUC)的缩写不同,作为妥协,后来都统一简称 UTC。

在文件 /etc/default/rcS 中,设定了系统是否使用 UTC,UTC=yes 则使用 UTC 时间,UTC=no 则使用本地时间。

# Assume that the BIOS clock is set to UTC time (recommended)

UTC=yes

读写 RTC 硬件时间,使用 hwclock 命令,其用法如下:

Usage: hwclock [-r|--show] [-s|--hctosys] [-w|--systohc] [-t|--systz] [-l|--localtime] [-u|--utc] [-f|--rtc FILE]

Options:

[-r|--show] Show hardware clock time

[-s|--hctosys] Set system time from hardware clock(RTC时间 --> 系统时间)

[-w|--systohc] Set hardware clock to system time (系统时间 --> RTC时间)

[-u|--utc] Hardware clock is in UTC

[-l|--localtime] Hardware clock is in local time

[-f|--rtc FILE] FILE Use specified device (e.g. /dev/rtc2)

隐约感觉到问题就在于 --utc 和 --localtime 参数!

继续往下看!

保存RTC时间戳

ARM Linux 有个时间戳文件 /etc/timestamp,一开始的值为:

# cat /etc/timestamp

201611171515

我们找一下,看看它是怎么产生的:

# grep -rn "/etc/timestamp" /*

/etc/init.d/bootmisc.sh:62:if test -e /etc/timestamp

/etc/init.d/bootmisc.sh:65: read TIMESTAMP < /etc/timestamp

/etc/init.d/save-rtc.sh:13:date -u +%4Y%2m%2d%2H%2M > /etc/timestamp

我们把目光定位在 /etc/init.d/save-rtc.sh,它的内容如下:

# cat /etc/init.d/save-rtc.sh

#!/bin/sh

### BEGIN INIT INFO

# Provides: save-rtc

# Required-Start:

# Required-Stop: $local_fs hwclock

# Default-Start: S

# Default-Stop: 0 6

# Short-Description: Store system clock into file

# Description:

### END INIT INFO

# Update the timestamp

date -u +%4Y%2m%2d%2H%2M > /etc/timestamp

那到底是它怎么被调用的呢?我们找到了端倪,实际上 /etc/rc0.d/S25save-rtc.sh 和 /etc/rc6.d/S25save-rtc.sh 都是 /etc/init.d/save-rtc.sh 的软链接。

再看看内核启动后的第一个用户进程 init,它会调用脚本 /etc/inittab,其中:

# Boot-time system configuration/initialization script.

# This is run first except when booting in emergency (-b) mode.

si::sysinit:/etc/init.d/rcS

可以看到,inittab 中启动的第一个脚本是 /etc/init.d/rcS,其中包含如下内容片段:

. /etc/default/rcS

exec /etc/init.d/rc S

也就是说,该脚本会先设置 /etc/default/rcS 中的环境变量,其中包括一个 UTC 变量:

# Assume that the BIOS clock is set to UTC time (recommended)

UTC=yes

然后执行 /etc/init.d/rc S,rc 中会循环调用 rcS.d 中的脚本,其中一段内容如下:

# Now run the START scripts for this runlevel.

for i in /etc/rc$runlevel.d/S*

do

[ ! -f $i ] && continue

if [ $previous != N ] && [ $previous != S ]

then

#

# Find start script in previous runlevel and

# stop script in this runlevel.

#

suffix=${i#/etc/rc$runlevel.d/S[0-9][0-9]}

stop=/etc/rc$runlevel.d/K[0-9][0-9]$suffix

previous_start=/etc/rc$previous.d/S[0-9][0-9]$suffix

#

# If there is a start script in the previous level

# and _no_ stop script in this level, we don't

# have to re-start the service.

#

[ -f $previous_start ] && [ ! -f $stop ] && continue

fi

case "$runlevel" in

0|6)

startup $i stop

;;

*)

startup $i start

;;

esac

done

当 runlevel 为 0 或 6 时,也就是关机模式(halt、poweroff)或重启模式(reboot)会分别调用 rc0.d 和 rc6.d 中的 S* 脚本。

而这里面就有 S25save-rtc.sh 脚本,它执行了如下操作,更新了时间戳文件:

date -u +%4Y%2m%2d%2H%2M > /etc/timestamp

总结一下:执行保存 RTC时间戳的是 /etc/init.d/save-rtc.sh 脚本,当我们的 ARM Linux 关机和重启时,会将 RTC 时间以“年月日时分”的格式保存到 /etc/timestamp 文件,而且是以 UTC 时间保存的。

使用RTC时间戳

在前面我们搜索出来了,操作 RTC 时间戳的脚本除了 /etc/init.d/save-rtc.sh 之外,还有 /etc/init.d/bootmisc.sh,bootmisc.sh 脚本的部分片段如下:

. /etc/default/rcS

#

# This is as good a place as any for a sanity check

#

# Set the system clock from hardware clock

# If the timestamp is more recent than the current time,

# use the timestamp instead.

test -x /etc/init.d/hwclock.sh && /etc/init.d/hwclock.sh start

if test -e /etc/timestamp

then

SYSTEMDATE=`date -u +%4Y%2m%2d%2H%2M`

read TIMESTAMP < /etc/timestamp

if [ ${TIMESTAMP} -gt $SYSTEMDATE ]; then

date -u ${TIMESTAMP#????}${TIMESTAMP%????????}

test -x /etc/init.d/hwclock.sh && /etc/init.d/hwclock.sh stop

fi

fi

: exit 0

先设置环境变量 UTC=yes,然后执行 /etc/init.d/hwclock.sh start,我们看看 hwclock.sh 脚本干了些什么:

[ ! -x /sbin/hwclock ] && exit 0

[ -f /etc/default/rcS ] && . /etc/default/rcS

[ "$UTC" = "yes" ] && tz="--utc" || tz="--localtime"

case "$1" in

start)

if [ "$HWCLOCKACCESS" != no ]

then

if [ -z "$TZ" ]

then

hwclock $tz --hctosys

else

TZ="$TZ" hwclock $tz --hctosys

fi

fi

;;

stop|restart|reload|force-reload)

if [ "$HWCLOCKACCESS" != no ]

then

hwclock $tz --systohc

fi

exit 0

;;

# 省略

hwclock.sh 脚本先判断 UTC 变量是否为 yes,如果是则设置参数 tz 为 --utc,否则为 --localtime。

当后面紧接着的是 start 命令时,执行 hwclock $tz --hctosys 将 RTC 硬件时间同步到 Linux 系统。

回到 bootmisc.sh 脚本,将 RTC 时间读取到 Linux 系统后,接下来判断 data -u 系统时间是否领先于 /etc/timestamp 保存的时间,如果没有则说明 Linux 系统时间落后了,有可能是因为 RTC 时间出错了。这时候就会认为上次关机或重启时保存的 RTC 时间戳更合理,从而将其更新到系统时间,然后执行 hwclock $tz --systohc 将它同步到 RTC 硬件。(此时的 RTC 时间依然不正确,但认为更接近实际的时间)

bootmisc.sh 的调用实际上是通过 /etc/rcS.d/S55bootmisc.sh,可以从 inittab 入手分析。

# ls -l /etc/rcS.d/S55bootmisc.sh

lrwxrwxrwx 1 root root 21 Jan 1 1970 /etc/rcS.d/S55bootmisc.sh -> ../init.d/bootmisc.sh

时间为什么会改变

虽然分析了一通 RTC 时间和 Linux 系统时间,但依然没有解决时间被修改的幽灵现象。但显然,直觉告诉我,很可能是因为某个地方读写 RTC 时间没有正确使用 --utc 和 --localtime 参数导致的。

经检查,我们加入的用户代码没有相关操作,那么可能是这个 Yocto Linux 里隐藏了。这时候想起 cron 定时任务,检查发现真的在这里埋了雷!

# cat /var/spool/cron/root

30 * * * * /usr/bin/ntpdate-sync silent

0 0-23/12 * * * /sbin/hwclock --hctosys

系统时间应该就是在这里被修改的,因为此处将 RTC 时间同步到系统时间并没有加 --utc 参数,hwclock 默认使用的是 --localtime。

直接把它删掉吧,毕竟我们有自己的对时机制。

时间为什么不正确

有的开发人员故意将 /etc/timestamp 设置成一个很大的时间,并且一些嵌入式设备没有提供正常关机或重启的功能,而是直接断电,因此没法更新 RTC 时间戳。

另一个原因是 UTC 的设置问题。例如在这里,系统设置了 UTC=yes,那么在使用 hwclock --hctosys 或者 hwclock --systohc 命令时,应该统一加上 --utc 参数。意思是将 RTC 硬件时间看作 UTC 时间,从而使 hwclock 在读写时间的时候协调好时区问题。这样的话,即便是 hwclock --show 也应该加上 -u|--utc 参数,也就是把 RTC 硬件时间当成 UTC 时间来看,所以打印处理的时间会加上 8 个小时,形成我们的本地时间。

总结一下:遇到这个问题,我首先通读了 PCF8563T 的芯片手册,发现根本没有时区的概念。RTC 的意义在于系统断电的情况下继续保持时间,它能处理好年月日和星期的关系,也能处理闰年的问题,但是没有时区的概念。时区是在系统层面处理的,通常来说,一个嵌入式 Linux 设备有俩个时间,一个是 RTC 实时时钟的时间,一个是系统时间。hwclock 等应用程序遵守 RTC 字符驱动程序接口,并通过 /dev/rtc 进行操作。所以,时区对 RTC 设备来说是透明的,因为被 hwclock 过滤掉了。经过这么一分析,时间和时区的问题就很简单了,只需要把操作统一起来就成!要么都加 --utc,要么都加 --localtime(缺省)。既然系统设置了 UTC=yes,那我们都遵循它的规定就好了,加上 --utc 吧。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值