版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xi_xix_i/article/details/134057998
目录
一、hwclock命令的参数及用法
该命令的可选参数以及对应的功能很多博客已经说的很清楚了,这篇博客写的比较详细,并且展示了使用示例。
因为项目需求,需要搞清楚hwclock的具体实现过程,所以重点梳理一下该命令及对应功能是如何实现的。因为是用的busybox来构建根文件系统,所以去busybox根文件下去仔细研究研究hwclock这个命令是如何实现具体功能的,先看看实现这个命令的文件应该是在哪:
使用grep -nR "hwclcok"
搜索一下,看看哪个文件中包含这个命令,感觉这个util-linux/hwclock.c
最像,毕竟名字就叫hwclock.c
了。其实这个util-linux
就是一个工具包,里面有装有很多命令及其实现。
打开这个文件,开头的注释写到:
/* vi: set sw=4 ts=4: */
/*
* Mini hwclock implementation for busybox
*
* Copyright (C) 2002 Robert Griebl <griebl@gmx.de>
*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
//config:config HWCLOCK
//config: bool "hwclock (5.8 kb)"
//config: default y
//config: select PLATFORM_LINUX
//config: help
//config: The hwclock utility is used to read and set the hardware clock
//config: on a system. This is primarily used to set the current time on
//config: shutdown in the hardware clock, so the hardware will keep the
//config: correct time when Linux is _not_ r
那就是这个文件没错了。找到这个文件了,可以来分析一下hwclock
是怎么实现各个功能的,以及我应该如何编写我的rtc驱动才能适配这个命令。该文件所有代码放在末尾附录。
二、 busybox/util-linux/hwclock.c:处理hwclock命令的文件
分析一下该文件的主要函数,即可梳理该文件是如何实现功能的。
1. hwclock_main()
:主函数,负责解析命令并执行对应操作
该函数代码如:”
int hwclock_main(int argc UNUSED_PARAM, char **argv)
{
const char *rtcname = NULL;
unsigned opt;
int utc;
#if ENABLE_LONG_OPTS
static const char hwclock_longopts[] ALIGN1 =
"localtime\0" No_argument "l" /* short opt is non-standard */
"utc\0" No_argument "u"
"show\0" No_argument "r"
"hctosys\0" No_argument "s"
"systohc\0" No_argument "w"
"systz\0" No_argument "t" /* short opt is non-standard */
"rtc\0" Required_argument "f"
;
#endif
/* Initialize "timezone" (libc global variable) */
tzset();
/* 下面这个函数解析命令,具体的解析过程暂时不深究,估计跟uboot或者linux内核里的一些解析过程差不多,有空再继续深究 */
opt = getopt32long(argv,
"^lurswtf:" "\0" "r--wst:w--rst:s--wrt:t--rsw:l--u:u--l",
hwclock_longopts,
&rtcname
);
/* If -u or -l wasn't given check if we are using utc
* 接下来根据解析的命令执行对应的操作(UTC是世界协调时间时)
*/
if (opt & (HWCLOCK_OPT_UTC | HWCLOCK_OPT_LOCALTIME))
utc = (opt & HWCLOCK_OPT_UTC);
else
utc = rtc_adjtime_is_utc();
if (opt & HWCLOCK_OPT_HCTOSYS)
to_sys_clock(&rtcname, utc); /* 执行to_sys_clock(硬件时钟写入系统时钟)*/
else if (opt & HWCLOCK_OPT_SYSTOHC)
from_sys_clock(&rtcname, utc); /* 将系统时钟写入硬件时钟 */
else if (opt & HWCLOCK_OPT_SYSTZ)
set_system_clock_timezone(utc);
else
/* default HWCLOCK_OPT_SHOW */
show_clock(&rtcname, utc); /* 没什么命令的话就显示硬件时间 */
return 0;
}
主函数,命令行输入hwclock
后执行,char **argv
存储命令行输入的参数。该函数主要执行两个任务:
- 通过
getopt32long()
函数来解析命令行输入的参数。 - 根据参数来执行对应的函数,实现对应的功能。
重点在于根据不同参数而调用的那些实现函数中,以-s参数为例(将硬件时钟同步到系统时钟),分析一下功能是如何实现的,该参数及对应的执行函数:
if (opt & HWCLOCK_OPT_HCTOSYS)
to_sys_clock(&rtcname, utc); /* 执行to_sys_clock(硬件时钟写入系统时钟)*/
接着来看一下to_sys_clcok()
函数:
static void to_sys_clock(const char **pp_rtcname, int utc)
{
struct timeval tv;
struct timezone tz;
tz.tz_minuteswest = timezone/60;
/* ^^^ used to also subtract 60*daylight, but it's wrong:
* daylight!=0 means "this timezone has some DST
* during the year", not "DST is in effect now".
*/
tz.tz_dsttime = 0;
tv.tv_sec = read_rtc(pp_rtcname, NULL, utc); /* 与硬件rtc有关系的函数 */
tv.tv_usec = 0;
if (settimeofday(&tv, &tz)) /* 设置系统时间 */
bb_perror_msg_and_die("settimeofday");
}
其中tv
为将要写入到系统的时间,tz
为当地时区信息。struct timeval
这个结构体有两个成员变量,一个是秒,一个是微秒。在这个函数中,微秒设置为0,而秒则是通过调用read_rtc()
函数来获取的,所以说,这个read_rtc()
函数一定很重要。获取到秒之后,通过settimeofday
来设置系统时间。这篇博客对这个函数进行了简单的讲解。
2. 核心函数之一:read_rtc()
,用来获取硬件时钟,hwclock所有指令的操作都离不开这个函数
下面来看一下这个read_rtc()
函数,代码如下:
static time_t read_rtc(const char **pp_rtcname, struct timeval *sys_tv, int utc)
{
struct tm tm_time;
int fd;
fd = rtc_xopen(pp_rtcname, O_RDONLY); /* 这个rtc_xopen就是关键啊*/
rtc_read_tm(&tm_time, fd); /* 这就是驱动的接口了,驱动要实现这个read的接口 */
#if SHOW_HWCLOCK_DIFF
{
int before = tm_time.tm_sec;
while (1) {
rtc_read_tm(&tm_time, fd);
gettimeofday(sys_tv, NULL);
if (before != (int)tm_time.tm_sec)
break;
}
}
#endif
if (ENABLE_FEATURE_CLEAN_UP)
close(fd);
return rtc_tm2time(&tm_time, utc);
}
其中SHOW_HWCLOCK_DIFF这个宏在源代码中是没有定义的,所以真正执行的就是两个函数:rtc_xopen()
和rtc_read_tm()
3. rtc_xopen()
与rtc_read_tm()
:
这俩函数在busybox/libbb/rtc.c
中,代码如下:
/* Never fails */
int FAST_FUNC rtc_xopen(const char **default_rtc, int flags)
{
int rtc;
const char *name = /* 最终会尝试打开这三个文件,如果都没打开则会报错 */
"/dev/rtc""\0"
"/dev/rtc0""\0"
"/dev/misc/rtc""\0";
if (!*default_rtc)
goto try_name;
name = ""; /*else: we have rtc name, don't try other names */
for (;;) {
rtc = open_loop_on_busy(*default_rtc, flags); /* 不断尝试访问该设备(防止设备被占用) */
if (rtc >= 0)
return rtc;
if (!name[0])
return xopen(*default_rtc, flags);
try_name:
*default_rtc = name;
name += strlen(name) + 1;
}
}
void FAST_FUNC rtc_read_tm(struct tm *ptm, int fd)
{
memset(ptm, 0, sizeof(*ptm));
xioctl(fd, RTC_RD_TIME, ptm);
ptm->tm_isdst = -1; /* "not known" */
}
rtc_xopen()
函数实现的功能,就是打开我们的rtc设备,这里就跟我们的驱动有关联了,该函数会调用open_loop_on_busy()
函数(该函数实现的功能就是在rtc设备被占用时,不断使用open
,就是用户空间使用的open()
尝试打开rtc字符设备,直到到达尝试的最大时间),来尝试打开const char *name
定义的三个字符设备(按定义的顺序来):"/dev/rtc" ,"/dev/rtc0", "/dev/misc/rtc"
,获取设备操作符。如果都没打开,则会报错无法找到rtc设备。到这里就明白了,就是open()
了上述三个设备中的一个,然后就可以使用read,write,ioctl
等来访问设备。
rtc_read_tm()
函数,则是调用了xioctl()
函数(是一个宏,最终还是调用的ioctl()
函数,只不过加了一些出错时的报错信息),并且要读取的参数类型为struct tm
结构体。
所以到这里已经知道该如何编写驱动程序,来适配这个命令了:
- 编写驱动程序,并且注册设备时必须将设备名注册为三个文件名(
"/dev/rtc" ,"/dev/rtc0", "/dev/misc/rtc"
)之一 - 在驱动程序的
file_ops
字符设备操作集中实现.unlocked_ioctl
对应的函数,该函数需要根据传入的参数执行对应的功能。以读硬件rtc时间来说,需要在驱动程序中定义相同的宏RTC_RD_TIME
,而执行的对应的函数应该能够读取硬件rtc中的时间寄存器,并将其转化为struct tm
结构体中各成员变量对应的值。
上述编写驱动程序的做法是当你不使用linux下的RTC框架时的做法,如果使用RTC框架,则更加简单,但是会有局限性,在我的另外一篇文章中提到了这个问题。
题外话
hwclock和date都可以用来显示时间,但是hwclock是获取硬件时钟的时间,而date获取的是系统时间,如果长时间没同步,或者系统时间是联网得来的,两者显示的时间是会有差异。