2023年6月30日发(作者:)
Linux系统时间与RTC时间【转】
Linux的RTC驱动相对还是⽐较简单的,可以将它作为⼀个普通的字符型设备,或者⼀个misc设备,也可以是⼀个平台设备,这都没有关系,主要还是对rtc_ops这个⽂件操作结构体中的成员填充,这⾥主要涉及到两个⽅⾯⽐较重要:
1. 在Linux中有硬件时钟与系统时钟等两种时钟。硬件时钟是指主机板上的时钟设备,也就是通常可在BIOS画⾯设定的时钟。系统时钟则是指kernel中的时钟。当Linux启动时,系统时钟会去读取硬件时钟的设定,之后系统时钟即独⽴运作。所有Linu 系统时钟的设定就是我们常⽤的date命令,⽽我们写的RTC驱动就是为硬件时钟服务的,它有属于⾃⼰的命令hwclock,因此使⽤date命令是不可能调⽤到我们的驱动的(在这点上开始把我郁闷到了,写完驱动之后,傻傻的⽤date指令来测试,当然
hwclock –r 显⽰硬件时钟与⽇期hwclock –s 将系统时钟调整为与⽬前的硬件时钟⼀致。hwclock –w 将硬件时钟调整为与⽬前的系统时钟⼀致。
2. 第⼆点就是内核空间和⽤户空间的交互,在系统启动结束,我们实际是处在⽤户态,因此我们使⽤指令输⼊的内容也是在⽤户态,⽽我们的驱动是在内核态的,内核态和⽤户态是互相不可见的,因此我们需要特殊的函数来实现这两种形态的交互,copy_from_user(从⽤户态到内核态)copy_to_user (从内核态到⽤户态)当然这两个函数需要我们在内核驱动中实现。RTC最基本的两个命令就是设置时间,读取时间。
设置时间——设置时间会调⽤系统默认的RTC_SET_TIME,很显然就是处在⽤户态的⽤户将⾃⼰所要设置的时间信息传递给内核态,case RTC_SET_TIME: { struct rtc_time rtc_tm; if (copy_from_user(&rtc_tm, (struct rtc_time*)arg, sizeof(struct rtc_time))) return -EFAULT; sep4020_rtc_settime(&rtc_tm);//把⽤户态得到的信息传递给设置时间这个函数 return 0; }读取时间——设置时间会调⽤系统默认的RTC_RD_TIME,很显然就是需要通过内核态的驱动将芯⽚时钟取出,并传递给⽤户态case RTC_RD_TIME: /* Read the time/date from RTC */ { sep4020_rtc_gettime(&septime);//通过驱动的读函数读取芯⽚时钟 copy_to_user((void *)arg, &septime, sizeof septime);//传递给⽤户态 }
--------------------------------------------------------------------------------------------------------------------
⾸先搞清楚RTC在kernel内的作⽤:linux系统有两个时钟:⼀个是由主板电池驱动的“Real Time Clock”也叫做RTC或者叫CMOS时钟,硬件时钟。当操作系统关机的时候,⽤这个来记录时间,但是对于运⾏的系统是不⽤这个时间的。另⼀个时间是 “System clock”也叫内核时钟或者软件时钟,是由软件根据时间中断来进⾏计数的,内核时钟在系统关机的情况下是不存在的,所以,当操作系统启动的时候,内核时钟是要读取RTC时间来进⾏时间同步。并且在系统关机的时候将系统时间写回RTC中进⾏同步。如前所述,Linux内核与RTC进⾏互操作的时机只有两个:1) 内核在启动时从RTC中读取启动时的时间与⽇期;2) 内核在需要时将时间与⽇期回写到RTC中。系统启动时,内核通过读取RTC来初始化内核时钟,⼜叫墙上时间,该时间放在xtime变量中。The current time of day (the wall time) is defined in kernel/timer.c:struct timespec xtime;The timespec data structure is defined in as:struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */};问题1:系统启动时在哪读取RTC的值并设置内核时钟进⾏时间同步的呢?最有可能读取RTC设置内核时钟的位置应该在arch/arm/kernel/time.c⾥的time_init函数内.time.c为系统的时钟驱动部分.time_init函数会在系统初始化时,由init/main.c⾥的start_kernel函数内调⽤.X86架构就是在这⾥读RTC值并初始化系统时钟xtime的.
ARM架构的time_init代码如下:/* arch/arm/kernel/time.c */void __init time_init(void){if (system_timer->offset == NULL) system_timer->offset = dummy_gettimeoffset;system_timer->init();#ifdef CONFIG_NO_IDLE_HZif (system_timer->dyn_tick) system_timer->dyn_tick->lock = SPIN_LOCK_UNLOCKED;#endif}上 ⾯system_timer->init()实际执⾏的是时钟驱动体系架构相关(具体平台)部分定义的init函数,若是s3c2410平台,则执 ⾏的为arch/arm/mach-s3c2410/time.c⾥定义的s3c2410_timer_init函数.不过 s3c2410_timer_init()也没有读RTC的代码.整个时钟驱动既然在系统时钟驱动初始化的过程中没有读RTC值并设置内核时钟,那会在哪设置呢?我搜了⼀下,发现内核好象只有在arch/cris/kernel/time.c⾥有RTC相关代码,如下:
/* arch/cris/kernel/time.c *//* grab the time from the RTC chip *///读RTC的函数unsigned long get_cmos_time(void){unsigned int year, mon, day, hour, min, sec;sec = CMOS_READ(RTC_SECONDS);min = CMOS_READ(RTC_MINUTES);hour = CMOS_READ(RTC_HOURS);day = CMOS_READ(RTC_DAY_OF_MONTH);mon = CMOS_READ(RTC_MONTH);…………return mktime(year, mon, day, hour, min, sec);}这个函数会在update_xtime_from_cmos内被调⽤:void update_xtime_from_cmos(void){if(have_rtc) { _sec = get_cmos_time(); _nsec = 0;}}另外还有设置rtc的函数int set_rtc_mmss(unsigned long nowtime); /* write time into RTC chip */不过我加了printk测试了⼀下,好象arch/cris/kernel/time.c这个⽂件和这两个函数只是适⽤与X86?ARM平台启动时并不⾛这边.因此执⾏不到这些函数。那ARM平台启动时,系统是在哪读RTC的值并对内核时钟(WallTime)进⾏初始化的呢?已解决:嵌⼊式Linux内核(ARM)是在系统启动时执⾏/etc/init.d/脚本,这个脚本会调⽤hwclock⼩程序读取RTC的值并设置系统时钟。(换句话说,这要取决于你制作的⽂件系统⾥是否有这样的脚本)/* /etc/init.d/ */DAEMON1=/sbin/hwclockstart() { local RET ERROR= [ ! -f /etc/adjtime ] && echo "0.0 0 0.0" > /etc/adjtime log_status_msg "Setting the System Clock using the Hardware Clock " -n # Copies Hardware Clock time to System Clock using the correct # timezone for hardware clocks in local time, and sets kernel # timezone. DO NOT REMOVE. [ "$HWCLOCKACCESS" != no ] && $DAEMON1 --hctosys $GMT $BADYEAR # # Now that /usr/share/zoneinfo should be available, # announce the local time. # log_status_msg "System Clock set. Local time: `date`" log_status_msg "" return 0}hwclock最先读取的设备⽂件是 /dev/rtc ,busybox⾥⾯的hwclock是这样实现的:static int xopen_rtc(int flags){int rtc;if (!rtcname) { rtc = open("/dev/rtc", flags); if (rtc >= 0) return rtc; rtc = open("/dev/rtc0", flags); if (rtc >= 0) return rtc; rtcname = "/dev/misc/rtc";}return xopen(rtcname, flags);}2. 内核如何更新RTC时钟?通过set_rtc函数指针指向的函数,set_rtc在arch/arm/kernel/time.c内/* arch/arm/kernel/time.c *//** hook for setting the RTC's idea of the current time.*/int (*set_rtc)(void);但是set_rtc函数指针在哪初始化的呢?set_rtc应该是和RTC驱动相关的函数.搜索kernel源码后发现,好象内核其他地⽅并没有对其初始化。待解决!set_rtc在do_set_rtc内调⽤static inline void do_set_rtc(void){……if (set_rtc()) /* * rtc update failed. Try again in 60s */ next_rtc_update = _sec + 60;else next_rtc_update = _sec + 660; /* update every ~11 minutes by default*/}do_set_rtc在timer_tick⾥调⽤/** Kernel system timer support.
*/void timer_tick(struct pt_regs *regs){profile_tick(CPU_PROFILING, regs);do_leds();do_set_rtc();do_timer(1);……}timer_tick为Kernel提供的体系架构⽆关的时钟中断处理函数,通常会在体系架构相关的时钟中断处理函数内调⽤它。如s3c2410是这样的:在arch/arm/mach-s3c2410/time.c中* IRQ handler for the timer*/static irqreturn_ts3c2410_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs){write_seqlock(&xtime_lock);timer_tick(regs);write_sequnlock(&xtime_lock);return IRQ_HANDLED;}
*nix 下 timer机制 标准实现,⼀般是⽤ sigalarm + setitimer() 来实现的,但这样就与 select/epoll 等逻辑有所冲突,我希望所有 event 的通知逻辑都从 select/epoll 中触发。(FreeBSD 中 kqueue 默认就有 FILTER_TIMER,多好)ps. /dev/rtc 只能被 open() ⼀次,因此上⾯希望与 epoll 合并的想法基本不可能了~下⾯是通过 /dev/rtc (real-time clock) 硬件时钟实现的 timer机制。:-)其中 ioctl(fd, RTC_IRQP_SET, 4) 的第三个参数只能是 2, 4, 8, 16, 32 之⼀,表⽰ xx Hz。-------------------------------------------------#include
--------------------------------------------------------------------------------------------------------------------User mode test code:#include
fd = open ("/dev/rtc", O_RDONLY); if (fd == -1) { perror("/dev/rtc"); exit(1); } // Alarm example,10 mintues later alarm
/* Read the RTC time/date */ retval = ioctl(fd, RTC_RD_TIME, &rtc_tm); if (retval == -1) { perror("ioctl"); exit(1); } fprintf(stderr, "Current RTC date/time is %d-%d-%d,%02d:%02d:%02d.n",
rtc__mday, rtc__mon + 1, rtc__year + 1900, rtc__hour, rtc__min, rtc__sec);
// Setting alarm time rtc__min += 10; if (rtc__sec >= 60) { rtc__sec %= 60; rtc__min++; } if (rtc__min == 60) { rtc__min = 0; rtc__hour++; } if (rtc__hour == 24) rtc__hour = 0;
// setting retval = ioctl(fd, RTC_ALM_SET, &rtc_tm); if (retval == -1) { perror("ioctl"); exit(1); } /* Read the current alarm settings */ retval = ioctl(fd, RTC_ALM_READ, &rtc_tm); if (retval == -1) { perror("ioctl"); exit(1); } fprintf(stderr, "Alarm time now set to %02d:%02d:%02d.n", rtc__hour, rtc__min, rtc__sec); /* Enable alarm interrupts after setting*/ retval = ioctl(fd, RTC_AIE_ON, 0); if (retval == -1) { perror("ioctl"); exit(1); } /* This blocks until the alarm ring causes an interrupt */ retval = read(fd, &data, sizeof(unsigned long)); if (retval == -1) { perror("read"); exit(1); } irqcount++; fprintf(stderr, " okay. Alarm rang.n");}------------------------------------------------------------------------------------------------------------------------------------------------S3C2410 RTC(Real Time Clock)简介实时时钟(RTC)单元可以在系统电源关半闭的情况下依靠备⽤电池⼯作。RTC可以通过使⽤STRB/LDDRB这两个ARM指令向CPU传递8位数据(BCD码)。数据包括秒、分、⼩时、⽇期、天、⽉、和年。RTC单元依靠⼀个外部的32.768kHZ的⽯晶,特性BCD码:秒、分、时、⽇期、天、⽉和年润年产⽣器报警功能:报警中断,或者从power-off状态唤醒。移除了2000年的问题独⽴的电源引⾓:RTCVDD为RTOS内核时间Tick time⽀持毫秒Tick time中断。Round reset 功能。RTC在power-off模式或者正常操作模式时可以在⼀指定的时间产⽣⼀个报警信号。在正常操作模式下,报警中断(ALMINT)被激活,在power-off模式下,电源管理唤醒信号(PMWKUP)和ALMINT⼀起被激活。RTC报警寄存器(RTCALM)决定报RTC TICK TIME被⽤于中断请求。TICNT寄存器有⼀个中断使能位和中断的计数值。当计数值到达0时TICK TIME中断。所以中断的周期如下:周期= (n+1 ) /128 秒n:Tick time计数值(1~127)这个RTC time tick可以被⽤于实时操作系统(RTOS)内核 time tick。如果time tick通过RTC time tick产⽣,那么RTOS的时间相关的功能就需要总是与实时时间同步。ROUND RESET 功能Rund reset功能可以通过RTC round reset寄存器(RTCRST)来执⾏。 The round boundary (30, 40, or 50 sec.) of the second carry generation can be selected, and the second value is rounded to zero in the round reset. For example, when the cuNOTEAll RTC registers have to be accessed for each byte unit using the STRB and LDRB instructions or char type pointer.---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------在.../drivers/rtc/Makefile中与我们有关的项有obj-$(CONFIG_RTC_LIB) += -$(CONFIG_RTC_HCTOSYS) += -$(CONFIG_RTC_CLASS) += -core-y := class.o interface.o
rtc-core-$(CONFIG_RTC_INTF_DEV) += -core-$(CONFIG_RTC_INTF_PROC) += -core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o
obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o其中 rtc-lib.c :提供了⼀些时间格式相互转化的函数。 hctosys.c:在启动时初始化系统时间。 RTC核⼼⽂件: class.c interface.c rtc-dev.c:字符设备的注册和⽤户层⽂件操作函数接⼝。 rtc-proc.c rtc-sysfs.c rtc-s3c.o:S3C2410 RTC的芯⽚平台4> 在根⽂件系统的 做的动作, 把 pc linux上的 /etc/localtime 复制到 板⼦的 /etc/下⾯即可5> mknod /dev/rtc c 254 0下⾯的动作只需做⼀次 ,⼀旦写⼊RTC chip后, chip就⾃⼰计时了,除⾮电池没电了。板⼦第⼀次启动后,假如设置系统时间为2007年10⽉2⽇,13:49分,可以这样设置1> date 1> hwclock –w如果没有出错, 就已经把2007年10⽉2⽇,13:49分 写⼊RTC chip了,测试:反复执⾏hwclock ,看看是否时间在变化。3> 重启板⼦, 测试, 执⾏hwclock ,看看时间是否在流逝 。
发布者:admin,转转请注明出处:http://www.yc00.com/news/1688117800a84536.html
评论列表(0条)