赞
踩
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xi_xix_i/article/details/134057998
该命令的可选参数以及对应的功能很多博客已经说的很清楚了,这篇博客写的比较详细,并且展示了使用示例。
因为项目需求,需要搞清楚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驱动才能适配这个命令。该文件所有代码放在末尾附录。
分析一下该文件的主要函数,即可梳理该文件是如何实现功能的。
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
来设置系统时间。这篇博客对这个函数进行了简单的讲解。
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()
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获取的是系统时间,如果长时间没同步,或者系统时间是联网得来的,两者显示的时间是会有差异。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。