野仙生活网

野仙生活网

「驱动知识」Linux下RTC时间的读写分析

民生 0

Linux下RTC时间的读写分析​

1.1.1 系统时间与RTC时间​

Linux系统下包含两个时间:系统时间和RTC时间。​

系统时间:是由主芯片的定时器进行维护的时间,一般情况下都会选择芯片上最高精度的定时器作为系统时间的定时基准,以避免在系统运行较长时间后出现大的时间偏移。特点是掉电后不保存。​

RTC时间:是指系统中包含的RTC芯片内部所维护的时间。RTC芯片都有电池+系统电源的双重供电机制,在系统正常工作时由系统供电,在系统掉电后由电池进行供电。因此系统电源掉电后RTC时间仍然能够正常运行。​

每次Linux系统启动后在启动过程中会检测和挂载RTC驱动,在挂载后会自动从RTC芯片中读取时间并设置到系统时间中去。此后如果没有显式的通过命令去控制RTC的读写操作,系统将不会再从RTC中去获取或者同步设置时间。​

linux命令中的date和time等命令都是用来设置系统时间的,而hwclock命令是用来设置和读写RTC时间的。​

1.1.2 获取系统的时间

date

示例:​

[root@XiaoLong /]# date​
Sat Apr 30 06:04:29 UTC 2022​
[root@xiaolong 2022-8-19]# date​
2022年 08月 19日 星期五 14:00:39 CST

1.1.3 查看命令使用帮助信息

查看date帮助:​
[root@XiaoLong /]# date -help //嵌入式开发板​
[root@XiaoLong /]# man date //PClinux系统

1.1.4 使用date查看与设置系统时间

命令格式:

date [参数]... [+格式]​

命令功能:

date 可以用来显示或设定系统的日期与时间。​

命令参数:

使用示例: date '+%A'​
必要参数: ​
%H 小时(以00-23来表示)。 ​
%I 小时(以01-12来表示)。 ​
%K 小时(以0-23来表示)。 ​
%l 小时(以0-12来表示)。 ​
%M 分钟(以00-59来表示)。 ​
%P AM或PM。 ​
%r 时间(含时分秒,小时以12小时AM/PM来表示)。 ​
%s 总秒数。起算时间为1970-01-01 00:00:00 UTC。 ​
%S 秒(以本地的惯用法来表示)。 ​
%T 时间(含时分秒,小时以24小时制来表示)。 ​
%X 时间(以本地的惯用法来表示)。 ​
%Z 市区。 ​
%a 星期的缩写。 ​
%A 星期的完整名称。 ​
%b 月份英文名的缩写。 ​
%B 月份的完整英文名称。 ​
%c 日期与时间。只输入date指令也会显示同样的结果。 ​
%d 日期(以01-31来表示)。 ​
%D 日期(含年月日)。 ​
%j 该年中的第几天。 ​
%m 月份(以01-12来表示)。 ​
%U 该年中的周数。 ​
%w 该周的天数,0代表周日,1代表周一,异词类推。 ​
%x 日期(以本地的惯用法来表示)。 ​
%y 年份(以00-99来表示)。 ​
%Y 年份(以四位数来表示)。 ​
%n 在显示时,插入新的一行。 ​
%t 在显示时,插入tab。 ​
MM 月份(必要) ​
DD 日期(必要) ​
hh 小时(必要) ​
mm 分钟(必要)​
ss 秒(选择性) ​
选择参数:​
-d<字符串> 显示字符串所指的日期与时间。字符串前后必须加上双引号。 ​
-s<字符串> 根据字符串来设置日期与时间。字符串前后必须加上双引号。 ​
-u 显示GMT。 ​
--help 在线帮助。 ​
--version 显示版本信息

使用说明:​

资料直通车:最新Linux内核源码资料文档+视频资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

在显示方面,使用者可以设定欲显示的格式,格式设定为一个加号后接数个标记,其中可用的标记列表如下: % : 打印出 %:​

示例:date '+%T'

%n : 下一行​%t : 跳格​%H : 小时(00..23)​%I : 小时(01..12)​%k : 小时(0..23)​%l : 小时(1..12)​%M : 分钟(00..59)​%p : 显示本地 AM 或 PM​%r : 直接显示时间 (12 小时制,格式为 hh:mm:ss [AP]M)​%s : 从 1970 年 1 月 1 日 00:00:00 UTC 到目前为止的秒数​%S : 秒(00..61)​%T : 直接显示时间 (24 小时制)​%X : 相当于 %H:%M:%S​%Z : 显示时区 %a : 星期几 (Sun..Sat)​%A : 星期几 (Sunday..Saturday)​%b : 月份 (Jan..Dec)​%B : 月份 (January..December)​%c : 直接显示日期与时间​%d : 日 (01..31)​%D : 直接显示日期 (mm/dd/yy)​%h : 同 %b​%j : 一年中的第几天 (001..366)​%m : 月份 (01..12)​%U : 一年中的第几周 (00..53) (以 Sunday 为一周的第一天的情形)​%w : 一周中的第几天 (0..6)​%W : 一年中的第几周 (00..53) (以 Monday 为一周的第一天的情形)​%x : 直接显示日期 (mm/dd/yy)​%y : 年份的最后两位数字 (00.99)​%Y : 完整年份 (0000..9999)

设定时间

date -s //设置当前时间,只有root权限才能设置,其他只能查看。​date -s 20080523 //设置成20080523,这样会把具体时间设置成空00:00:00​date -s 01:01:01 //设置具体时间,不会对日期做更改​date -s ”01:01:01 2008-05-23″ //这样可以设置全部时间​date -s ”01:01:01 20080523″ //这样可以设置全部时间​date -s ”2008-05-23 01:01:01″ //这样可以设置全部时间​date -s ”20080523 01:01:01″ //这样可以设置全部时间

加减:

date +%Y%m%d //显示前天年月日​date +%Y%m%d --date="+1 day" //显示前一天的日期​date +%Y%m%d --date="-1 day" //显示后一天的日期​date +%Y%m%d --date="-1 month" //显示上一月的日期​date +%Y%m%d --date="+1 month" //显示下一月的日期​date +%Y%m%d --date="-1 year" //显示前一年的日期​date +%Y%m%d --date="+1 year" //显示下一年的日期​
示例:​
[root@xiaolong tiny4412]# date +%Y%m%d --date="+1 year"​
20170430

1.1.5 系统时间设置与显示​

1.1.5.1 显示日期

[root@XiaoLong /]# date '+%c'​
Sat Apr 30 06:20:27 2016​
[root@XiaoLong /]# date '+%D'​
04/30/16​
[root@XiaoLong /]# date '+%x'​
04/30/16​
[root@XiaoLong /]# date '+%T'​
06:20:46​
[root@XiaoLong /]# date '+%X'​
06:20:51

1.1.5.2 设定日期时间​

[root@XiaoLong /]# date --date 14:40:00设置时间为14点40分00秒​
Sat Apr 30 14:40:00 UTC 2016​
[root@XiaoLong /]# date -s 23:27:00设置时间为23点27分00秒​
Sat Apr 30 23:27:00 UTC 2016

1.1.5.3 开发板上的时间格式设置(busybox)​

[root@XiaoLong /]# date -r app // -r选项可以打印出指定文件的最后修改时间 ​
Fri Apr 29 05:17:34 UTC 2016​
[root@XiaoLong /]# date -d 23:39:00 //打印出指定格式时间(只是打印效果没有其他效果)​
Sat Apr 30 23:39:00 UTC 2016​
[root@XiaoLong /]# date -s 12:20:30 //设置系统时间为12点20分30秒​
Sat Apr 30 12:20:30 UTC 2016​
[root@XiaoLong /]# date -s 2016.04.30-23:20:10 //设置系统时间为2016年4月30日23点20分10秒​
Sat Apr 30 23:20:10 UTC 2016

1.1.5.3 开发板上的时间格式设置(busybox)​

[root@XiaoLong /]# date -r app // -r选项可以打印出指定文件的最后修改时间 ​
Fri Apr 29 05:17:34 UTC 2016​
[root@XiaoLong /]# date -d 23:39:00 //打印出指定格式时间(只是打印效果没有其他效果)​
Sat Apr 30 23:39:00 UTC 2016​
[root@XiaoLong /]# date -s 12:20:30 //设置系统时间为12点20分30秒​
Sat Apr 30 12:20:30 UTC 2016​
[root@XiaoLong /]# date -s 2016.04.30-23:20:10 //设置系统时间为2016年4月30日23点20分10秒​
Sat Apr 30 23:20:10 UTC 2016

查看RTC的信息

[root@XiaoLong /]# ​
[root@XiaoLong /]# cat /proc/driver/rtc​
rtc_time : 00:09:27​
rtc_date : 2016-05-01​
alrm_time : 23:24:07​
alrm_date : 2016-05-01​
alarm_IRQ : no​
alrm_pending : no​
update IRQ enabled : no​
periodic IRQ enabled : no​
periodic IRQ frequency : 1​
max user IRQ frequency : 32768​
24hr : yes​
periodic_IRQ : no

1.2 RTC驱动子系统分析​

内核RTC子系统参考代码:​

\linux-3.5\drivers\rtc\目录下全是RTC驱动示例代码​

其中:rtc-s3c.c 是三星公司编写的RTC驱动

1.2.1 RTC核心文件​

  1. /drivers/rtc/class.c 这个文件向linux设备模型核心注册了一个类RTC,然后向驱动程序提供了注册/注销接口​
  2. /drivers/rtc/rtc-dev.c 这个文件定义了基本的设备文件操作函数,如:open,read等​
  3. /drivers/rtc/interface.c 顾名思义,这个文件主要提供了用户程序与RTC驱动的接口函数,用户程序一般通过ioctl与RTC驱动交互,这里定义了每个ioctl命令需要调用的函数​
  4. /drivers/rtc/rtc-sysfs.c 与sysfs有关​
  5. /drivers/rtc/rtc-proc.c 与proc文件系统有关​
  6. /include/linux/rtc.h 定义了与RTC有关的数据结构

1.2.2 基本数据结构​

这个结构是RTC驱动程序的基本数据结构,但是他不像其他核心的基本结构一样,驱动程序以他为参数调用注册函数注册到核心。这个结构是由注册函数返回给驱动程序的。

struct rtc_device​{​struct device dev;​struct module *owner;​
int id;​char name[RTC_DEVICE_NAME_SIZE];​
const struct rtc_class_ops *ops;​struct mutex ops_lock;​
struct cdev char_dev;​unsigned long flags;​
unsigned long irq_data;​
spinlock_t irq_lock;​
wait_queue_head_t irq_queue;​
struct fasync_struct *async_queue;​
struct rtc_task *irq_task;​
spinlock_t irq_task_lock;​
int irq_freq;​
int max_user_freq;​
struct timerqueue_head timerqueue;​
struct rtc_timer aie_timer;​
struct rtc_timer uie_rtctimer;​
struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */​
int pie_enabled;​
struct work_struct irqwork;​
/* Some hardware can't support UIE mode */​
int uie_unsupported;​
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL​
struct work_struct uie_task;​struct timer_list uie_timer;​/* Those fields are protected by rtc->irq_lock */​unsigned int oldsecs;​unsigned int uie_irq_active:1;​unsigned int stop_uie_polling:1;​unsigned int uie_task_active:1;​unsigned int uie_timer_active:1;​#endif​};

1.2.3 RTC实现的基本操作函数​

这个结构是RTC驱动程序要实现的基本操作函数,注意这里的操作不是文件操作。驱动程序通过初始化这样一个结构,将自己实现的函数与RTC核心联系起来。这里面的大部分函数都要驱动程序来实现。而且这些函数都是操作底层硬件的,属于最底层的函数。

struct rtc_class_ops {​int (*open)(struct device *);​void (*release)(struct device *);​int (*ioctl)(struct device *, unsigned int, unsigned long);​int (*read_time)(struct device *, struct rtc_time *);​int (*set_time)(struct device *, struct rtc_time *);​int (*read_alarm)(struct device *, struct rtc_wkalrm *);​int (*set_alarm)(struct device *, struct rtc_wkalrm *);​int (*proc)(struct device *, struct seq_file *);​int (*set_mmss)(struct device *, unsigned long secs);​int (*read_callback)(struct device *, int data);​int (*alarm_irq_enable)(struct device *, unsigned int enabled);​};

RTC子系统里驱动一般只需要实现设置时间和获取时间的函数接口即可,用户可以在应用层通过ioctl函数传入对应的命令调用驱动层的接口,实现时间获取与设置。​

常用的两个命令:​

#define RTC_RD_TIME_IOR(RTC_MAGIC, 0x09, struct rtc_time)/* Read RTC time. */​
#define RTC_SET_TIME_IOW(RTC_MAGIC, 0x0a, struct rtc_time)/* Set RTC time. */

1.2.4 时间结构​

代表了时间与日期,从RTC设备读回的时间和日期就保存在这个结构体中。

struct rtc_time {​
int tm_sec; //秒​
int tm_min; //分钟​
int tm_hour; //小时​
int tm_mday; //天​
int tm_mon; //月​
int tm_year; //年​
int tm_wday; //一周中的某一天​
int tm_yday; //一年中的某一天​
int tm_isdst; //夏令时有效​
};

1.2.5 RTC初始化与注销模块​

定义路径:\drivers\rtc\class.c

static int __init rtc_init(void)​{​创建类*/​rtc_class = class_create(THIS_MODULE, "rtc");​if (IS_ERR(rtc_class)) {​printk(KERN_ERR "%s: couldn't create class\n", __FILE__);​return PTR_ERR(rtc_class);​}​rtc_class->suspend = rtc_suspend;​rtc_class->resume = rtc_resume;​rtc_dev_init(); ​rtc_sysfs_init(rtc_class);​return 0;​}

首先调用class_create创建了一个类--rtc。类是一个设备的高层视图,他抽象出了底层的实现细节。类的作用就是向用户空间提供设备的信息,驱动程序不需要直接处理类。然后初始化类结构的相应成员,rtc_suspend,rtc_resume这两个函数也是在class.c中实现的。接下来调用rtc_dev_init(),这个函数为RTC设备动态分配设备号,保存在rtc_devt中。最后调用rtc_sysfs_init,初始化rtc_class的属性。

//注销RTC​
static void __exit rtc_exit(void)​
{​
rtc_dev_exit();​
class_destroy(rtc_class);​
ida_destroy(&rtc_ida);​
}

1.2.6 注册RTC设备

struct rtc_device *rtc_device_register(const char *name, struct device *dev,​const struct rtc_class_ops *ops,​struct module *owner)​{​struct rtc_device *rtc;​struct rtc_wkalrm alrm;​int id, err;​/* (1):处理一个idr的结构,idr在linux内核中指的就是整数ID管理机制,从本质上来说,idr是一种将整数ID号和特定指针关联在一起的机制。*/​id = ida_simple_get(&rtc_ida, 0, 0, GFP_KERNEL);​if (id < 0) {​err = id;​goto exit;​}​/*(2):分配了一个rtc_device的结构--rtc,并且初始化了相关的成员:id, rtc_class_ops等等*/​rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);​if (rtc == NULL) {​err = -ENOMEM;​goto exit_ida;​}​rtc->id = id;​rtc->ops = ops;​rtc->owner = owner;​rtc->irq_freq = 1;​rtc->max_user_freq = 64;​rtc->dev.parent = dev;​rtc->dev.class = rtc_class;​rtc->dev.release = rtc_device_release;​mutex_init(&rtc->ops_lock);​spin_lock_init(&rtc->irq_lock);​spin_lock_init(&rtc->irq_task_lock);​init_waitqueue_head(&rtc->irq_queue);​/* Init timerqueue */​timerqueue_init_head(&rtc->timerqueue);​INIT_WORK(&rtc->irqwork, rtc_timer_do_work);​/* Init aie timer */​rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, (void *)rtc);​/* Init uie timer */​rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, (void *)rtc);​/* Init pie timer */​hrtimer_init(&rtc->pie_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);​rtc->pie_timer.function = rtc_pie_update_irq;​rtc->pie_enabled = 0;​
/* Check to see if there is an ALARM already set in hw */​
err = __rtc_read_alarm(rtc, &alrm);​
if (!err && !rtc_valid_tm(&alrm.time))​
rtc_initialize_alarm(rtc, &alrm);​
strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);​
dev_set_name(&rtc->dev, "rtc%d", id);​
/*(3)首先调用rtc_dev_prepare(在rtc-dev.c中定义)。因为RTC设备本质来讲还是字符设备,所以这里初始化了字符设备相关的结构:设备号以及文件操作。然后调用device_register将设备注册到linux设备模型核心。这样在模块加载的时候,udev daemon就会自动为我们创建设备文件rtc(n)。*/​
rtc_dev_prepare(rtc);​
err = device_register(&rtc->dev);​
if (err) {​
put_device(&rtc->dev);​
goto exit_kfree;​
}​
/*(4):先后调用rtc_dev_add_device,rtc_sysfs_add_device,rtc_proc_add_device三个函数。rtc_dev_add_device注册字符设备,rtc_sysfs_add_device只是为设备添加了一个闹钟属性,rtc_proc_add_device 创建proc文件系统接口。*/​
rtc_dev_add_device(rtc);​
rtc_sysfs_add_device(rtc);​
rtc_proc_add_device(rtc);​
dev_info(dev, "rtc core: registered %s as %s\n",​
rtc->name, dev_name(&rtc->dev));​
return rtc;​exit_kfree:​
kfree(rtc);​
exit_ida:​
ida_simple_remove(&rtc_ida, id);​
exit:​
dev_err(dev, "rtc core: unable to register %s, err = %d\n",​
name, err);​
return ERR_PTR(err);​
}

1.2.7 rtc-dev.c ​

初始化了一个file_operations结构--rtc_dev_fops,并定义了这些操作函数。​

的定义路径:\drivers\rtc\rtc-dev.c​

1.2.7.1 实现基本文件操作接口

/*实现的文件操作集合接口*/​
static const struct file_operations rtc_dev_fops = {​
.owner= THIS_MODULE,​
.llseek= no_llseek,​
.read= rtc_dev_read,​
.poll= rtc_dev_poll,​
.unlocked_ioctl= rtc_dev_ioctl,​
.open= rtc_dev_open,​
.release= rtc_dev_release,​
.fasync= rtc_dev_fasync,​
};

1.2.7.2 函数的实现(以rtc_dev_read为例)​

这里的read不是应用程序用来获取时间的,而是有其他的作用,他帮助应用程序周期性地完成一些工作。如果要使用这个功能,应用程序首先保证RTC驱动程序提供这样的功能。这个功能是这样实现的:进程读取/dev/rtc(n),进程睡眠直到RTC中断将他唤醒。我们可以发现,这里的睡眠是ldd3中提到的手工睡眠。这个函数的手工休眠过程如下:首先调用DECLARE_WAITQUEUE(wait, current),声明一个等待队列入口,然后调用add_wait_queue将这个入口加入到RTC的irq等待队列里,然后进入循环。在循环里首先把进程的状态改成TASK_INTERRUPTIBLE,这样进程就不能再被调度运行。但是现在进程还在运行,没有进入睡眠状态。程序然后读取RTC里面的irq_data,​

如果不是零,那么程序跳出这个循环,进程不会睡眠。因为这个irq_data在rtc的中断处理程序会被赋值,而读过之后就会清零,所以如果数据不是零的话说明发生过一次中断。如果是零那么没有发生中断,调用schedule,进程会被调度出可运行队列,从而让出处理器,真正进入睡眠。跳出循环代表被唤醒,然后将进程状态改变为可运行,移除等待队列入口。最后将读回的数据传给用户空间。

1.2.7.2 函数的实现(以rtc_dev_read为例)​
这里的read不是应用程序用来获取时间的,而是有其他的作用,他帮助应用程序周期性的完成一些工作。如果要使用这个功能,应用程序首先保证RTC驱动程序提供这样的功能。这个功能是这样实现的:进程读取/dev/rtc(n),进程睡眠直到RTC中断将他唤醒。我们可以发现,这里的睡眠是ldd3中提到的手工睡眠。这个函数的手工休眠过程如下:首先调用DECLARE_WAITQUEUE(wait, current),声明一个等待队列入口,然后调用add_wait_queue将这个入口加入到RTC的irq等待队列里,然后进入循环。在循环里首先把进程的状态改成TASK_INTERRUPTIBLE,这样进程就不能再被调度运行。但是现在进程还在运行,没有进入睡眠状态。程序然后读取RTC里面的irq_data,​
如果不是零,那么程序跳出这个循环,进程不会睡眠。因为这个irq_data在rtc的中断处理程序会被赋值,而读过之后就会清零,所以如果数据不是零的话说明发生过一次中断。如果是零那么没有发生中断,调用schedule,进程会被调度出可运行队列,从而让出处理器,真正进入睡眠。跳出循环代表被唤醒,然后将进程状态改变为可运行,移除等待队列入口。最后将读回的数据传给用户空间。

1.2.8 interface.c 功能函数的实现​

路径:\drivers\rtc\interface.c​

interface.c里的所有函数的实现都对应于rtc-dev.c 中ioctl相应的命令。对应关系如下:​

RTC_ALM_READ rtc_read_alarm 读取闹钟时间​
RTC_ALM_SET rtc_set_alarm 设置闹钟时间​
RTC_RD_TIME rtc_read_time 读取时间与日期​
RTC_SET_TIME rtc_set_time 设置时间与日期​
RTC_PIE_ON RTC_PIE_OFF rtc_irq_set_state 开关RTC全局中断的函数​
RTC_AIE_ON RTC_AIE_OFF rtc_alarm_irq_enable 使能禁止RTC闹钟中断​
RTC_UIE_OFF RTC_UIE_ON rtc_update_irq_enable 使能禁止RTC更新中断​
RTC_IRQP_SET rtc_irq_set_freq 设置中断的频率​

以上就是所有ioctl的命令与实现的对应关系。其中如果不涉及中断的话,有两个命令需要我们特别关心一下,就是RTC_RD_TIME与RTC_SET_TIME。因为RTC最基本的功能就是提供时间与日期。这两个命令恰恰是获取时间和设置时间。​

函数用了一个信号来保证在同一时刻只有一个进程可以获取时间。锁定了这个信号量后,调用rtc->ops里面read函数,这个函数是由具体的驱动程序实现的,操作底层硬件。读回的时间是存放在rtc_time结构里面的。​

rtc_set_time 函数其实和rtc_read_time函数差不多,同样是锁定信号量,同样是调用底层驱动函数。但是这里的设置时间提供了两个调用:一个是set_time,一个是set_mmss。因为有的RTC硬件只计算秒数,不关心墙钟时间,所以如果是这样的RTC,必须实现set_mmss来设置时间。

/*​自01-01-1970就是将公历日期转换为秒。​*/​int rtc_tm_to_time(struct rtc_time *tm, unsigned long *time)​{​*time = mktime(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,​tm->tm_hour, tm->tm_min, tm->tm_sec);​return 0;​}

1.2.9 rtc-sysfs.c 部分​

这个部分主要是有关sysfs的操作。rtc-sysfs.c中定义了这样一个设备属性组,如下:

static struct device_attribute rtc_attrs[] = {​__ATTR(name, S_IRUGO, rtc_sysfs_show_name, NULL),​__ATTR(date, S_IRUGO, rtc_sysfs_show_date, NULL),​__ATTR(time, S_IRUGO, rtc_sysfs_show_time, NULL),​__ATTR(since_epoch, S_IRUGO, rtc_sysfs_show_since_epoch, NULL),​__ATTR(max_user_freq, S_IRUGO | S_IWUSR, rtc_sysfs_show_max_user_freq,​rtc_sysfs_set_max_user_freq),​__ATTR(hctosys, S_IRUGO, rtc_sysfs_show_hctosys, NULL),​{ },​};

这个属性组是在class.c的模块初始化函数中,由rtc_sysfs_init函数赋值给rtc_class->dev_attrs的,以后属于这个类的设备都会有这些属性。但是我们知道要想一个设备结构拥有一种属性,必须调用device_create_file,这样才会使这个属性出现在sysfs相关设备目录里。但是在这里的代码中只是给这个类的dev_attrs域赋值了这个属性组指针,而没有调用device_create_file。我原来以为是在rtc_device_resgister函数中,由rtc_sysfs_add_device完成这个工作,但是这个函数只是给设备添加了闹钟属性,并没有处理这个属性组。最后发现这个工作是由device_register来完成的。这里的调用关系有点复杂:​

device_register调用device_add​
device_add调用 device_add_attrs​
调用device_add_attributes​

device_add_attributes调用device_create_file来完成设备的属性设置的。​

设置完属性后,在/sys/class/rtc/rtc(n)的目录下就会出现name,date,time等文件,用户读这些文件的时候就会调用相应的函数。如读取name文件,就会调用rtc_sysfs_show_name函数,这个函数也是在rtc-sysfs.c中实现的,作用是读取并显示时间。

1.2.10 rtc-proc.c ​

这个文件提供RTC的proc文件系统接口。proc文件系统是软件创建的文件系统,内核通过他向外界导出信息,下面的每一个文件都绑定一个函数,当用户读取这个文件的时候,这个函数会向文件写入信息。rtc-proc.c中初始化了一个文件操作:

static const struct file_operations rtc_proc_fops = {​
.open= rtc_proc_open,​
.read= seq_read,​
.llseek= seq_lseek,​
.release= rtc_proc_release,​
};

驱动在向RTC核心注册自己的时候,由注册函数rtc_device_resgister调用rtc_proc_add_device来实现proc接口的初始化,这个函数如下定义:

void rtc_proc_add_device(struct rtc_device *rtc)​
{​
if (rtc->id == 0)​
proc_create_data("driver/rtc", 0, NULL, &rtc_proc_fops, rtc);​
}

他主要调用了proc_create_data。proc_create_data完成创建文件节点的作用,并将文件的操作函数与节点联系起来。调用这个函数后,在/proc/driver目录下就会有一个文件rtc,应用程序打开这个文件就会调用rtc_proc_open函数,这个函数如下定义:

static int rtc_proc_open(struct inode *inode, struct file *file)​{​int ret;​struct rtc_device *rtc = PDE(inode)->data;​if (!try_module_get(THIS_MODULE))​return -ENODEV;​
ret = single_open(file, rtc_proc_show, rtc);​if (ret)​module_put(THIS_MODULE);​
return ret;​}

我们知道一个proc的文件必须与一个操作函数组成一个proc入口项,这个文件才能正常工作。这个函数最主要作用就是调用single_open,创建一个proc文件入口项,使其操作函数是rtc_proc_show,并初始化seq_file接口。rtc_proc_show函数如下定义:

static int rtc_proc_show(struct seq_file *seq, void *offset)​{​int err;​struct rtc_device *rtc = seq->private;​const struct rtc_class_ops *ops = rtc->ops;​struct rtc_wkalrm alrm;​struct rtc_time tm;​err = rtc_read_time(rtc, &tm);​if (err == 0) {​seq_printf(seq,​"rtc_time\t: %02d:%02d:%02d\n"​"rtc_date\t: %04d-%02d-%02d\n",​tm.tm_hour, tm.tm_min, tm.tm_sec,​tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);​}​err = rtc_read_alarm(rtc, &alrm);​if (err == 0) {​seq_printf(seq, "alrm_time\t: ");​if ((unsigned int)alrm.time.tm_hour <= 24)​seq_printf(seq, "%02d:", alrm.time.tm_hour);​else​seq_printf(seq, "**:");​if ((unsigned int)alrm.time.tm_min <= 59)​seq_printf(seq, "%02d:", alrm.time.tm_min);​else​seq_printf(seq, "**:");​if ((unsigned int)alrm.time.tm_sec <= 59)​seq_printf(seq, "%02d\n", alrm.time.tm_sec);​else​seq_printf(seq, "**\n");​seq_printf(seq, "alrm_date\t: ");​if ((unsigned int)alrm.time.tm_year <= 200)​seq_printf(seq, "%04d-", alrm.time.tm_year + 1900);​else​seq_printf(seq, "****-");​if ((unsigned int)alrm.time.tm_mon <= 11)​seq_printf(seq, "%02d-", alrm.time.tm_mon + 1);​else​seq_printf(seq, "**-");​if (alrm.time.tm_mday && (unsigned int)alrm.time.tm_mday <= 31)​seq_printf(seq, "%02d\n", alrm.time.tm_mday);​else​seq_printf(seq, "**\n");​seq_printf(seq, "alarm_IRQ\t: %s\n",​alrm.enabled ? "yes" : "no");​seq_printf(seq, "alrm_pending\t: %s\n",​alrm.pending ? "yes" : "no");​seq_printf(seq, "update IRQ enabled\t: %s\n",​(rtc->uie_rtctimer.enabled) ? "yes" : "no");​seq_printf(seq, "periodic IRQ enabled\t: %s\n",​(rtc->pie_enabled) ? "yes" : "no");​seq_printf(seq, "periodic IRQ frequency\t: %d\n",​rtc->irq_freq);​seq_printf(seq, "max user IRQ frequency\t: %d\n",​rtc->max_user_freq);​}​seq_printf(seq, "24hr\t\t: yes\n");​if (ops->proc)​ops->proc(rtc->dev.parent, seq);​return 0;​}

这个函数就是最后给用户显示信息的函数了,可以看出他通过调用rtc_deivce中的操作函数,读取时间,日期和一些其他的信息显示给用户。 ​

1.3 RTC注册框图