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

news2025/1/21 10:19:07

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注册框图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/105775.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【我不熟悉的javascript】map和weakmap的区别

map 保存键值对&#xff0c;并且能够记住键的原始插入顺序&#xff0c;任何值(对象或者基本类型)都可以作为一个键或一个值 基本方法有 get, set, has, delete, clear长度 .size迭代 keys, values, extries, forEach可以使用for ...of 迭代任何值(对象或者基本类型)都可以作为…

如何在头条做营销:2022今日头条营销价值洞察报告.pdf(附下载链接)

省时查报告-专业、及时、全面的行研报告库省时查方案-专业、及时、全面的营销策划方案库【免费下载】2022年11月份热门报告盘点《底层逻辑》高清配图华为2021数字化转型&#xff1a;从战略到执行.pdf华为项目管理金种子培训教材.pdf清华大学256页PPT元宇宙研究报告.pdf&#xf…

nn.AdaptiveAvgPool2d和nn.AvgPool2d的区别

nn.AdaptiveAvgPool2d 功能&#xff1a;该函数与二维平均池化运算类似&#xff0c;区别主要体现在自适应上&#xff0c;对于任何输入大小&#xff0c;输出大小均为指定的HW大小。 nn.AdaptiveAvgPool2d(output_size)output_size&#xff1a;指定的输出大小&#xff0c;可以是…

echarts-for-weixin只显示折线图,其他不显示解决办法

小程序使用echarts-for-weixin展示图表&#xff0c;结果只展示折线图&#xff0c;其他的统统不显示&#xff0c;百度大法一下午终于知道可能是echarts包内只有折线图。 下载好替换搞定&#xff01;

【Vue 快速入门系列】3分钟掌握Vue中插槽的使用与理解

文章目录前言一、常规实现方式二、匿名插槽三、具名插槽四、作用域插槽前言 插槽作用&#xff1a;让父组件可以向子组件指定位置插入html结构&#xff0c;也是一种组件间通信的方式&#xff0c;适用于 父组件 > 子组件 。 插槽分类&#xff1a;默认插槽、具名插槽、作用域插…

docker部署solr+zk集群

在三台服务器上搭建solr集群&#xff0c;具体架构如下所示&#xff1a; 192.168.184.127 solr1/zk1 192.168.184.128 solr2/zk2 192.168.184.129 solr3/zk3 1.安装docker 三台服务器上都执行以下命令 # setenforce 0 # systemctl stop firewall…

代码随想录Day56|583.两个字符串的删除操作 、72.编辑距离、编辑距离总结篇

文章目录583.两个字符串的删除操作72.编辑距离编辑距离总结篇583.两个字符串的删除操作 文章讲解&#xff1a;代码随想录 (programmercarl.com) 题目链接&#xff1a;583. 两个字符串的删除操作 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 给定两个单词 wor…

Flume EmbeddedAgent

flume flume 二次开发&#xff0c;对EmbeddedAgent的简易改造&#xff0c;动态控制agent&#xff0c;实现启动、关闭等功能。 模块结构如下所示&#xff1a; flume-parent github地址 1、用途 1.1、本地调试 对flume不是特别熟悉的开发者&#xff0c;都没有办法一次开发完…

消息队列mq

1. 为什么使用消息队列&#xff1f; 其实就是问问你消息队列都有哪些使用场景&#xff0c;然后你项目里具体是什么场景&#xff0c;说说你在这个场景里用消息队列是什么&#xff1f; 解耦、异步、削峰 2. 消息队列优缺点 2.1.优点 优点上面已经说了&#xff0c;就是在特殊…

并查集的原理及实现

Ⅰ. 并查集原理 在一些应用问题中&#xff0c;需要将 n 个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这…

前端基础_线型Line styles

线型Line styles 线型包括如下属性。 lineWidth value lineCap type lineJoin type miterLimit value 通过这些属性来设置线的样式。下面将结合实例来讲解一下各属性的应用及应用后的效果。 lineWidth属性 该属性设置当前绘线的粗细&#xff0c;属性值必须…

ArcGIS编辑绘制图斑又慢又难?这些高效的处理技巧你值得拥有!

GIS画图是不是画得很慢! 图斑修改是不是无从下手! 图纸矢量化是不是琐碎繁杂、工作量大! 其实,强大的ArcGIS有很多高效的图斑编辑技巧,掌握这些技巧,无论是绘制图斑、还是修改图斑,还是图纸矢量化,绝对让你事半功倍! NO.1—自动完成面 当你要绘制一个图斑的相邻图…

华为云桌面,企业云上办公为何都偏好它?

在众多云上办公产品中&#xff0c;华为云桌面基于华为云的三十年投入的技术强、资源多、创新快和更可靠的优势&#xff0c;在众多云上办公产品中脱颖而出&#xff0c;成为众多企业数字化转型道路上不二选择&#xff0c;类似于三一重工、中泰模具、小飞侠等企业都选择了华为云桌…

非递归前序、中序遍历代码推演出后序遍历代码(极其透彻)

一、前言 众所周知&#xff0c;二叉树的遍历方式有三种&#xff1a;前序遍历、中序遍历和后序遍历。 &#x1f34c; 前序遍历&#xff1a;首先访问根节点&#xff0c;然后递归遍历左子树&#xff0c;最后递归遍历右子树。 &#x1f34c; 中序遍历&#xff1a;首先递归遍历左…

pypower的简单应用1

目录 一、背景描述 二、如何打开IEEE30节点并进行潮流计算 三、如何修改已有模型参数 四、完整代码 五、注意事项 pypower与matpower非常类似&#xff0c;可以利用matpower学习pypower&#xff0c;当然也有一些不同之处。下面记录一下应用pypower解决的问题。 一、背景描述…

Java优先队列的代码实现过程详解

1.优先队列定义 普通的队列是一种先进先出的数据结构&#xff0c;元素在队列尾追加&#xff0c;而从队列头删除。在某些情况下&#xff0c;我们可能需要找出队列中的最大值或者最小值&#xff0c;例如使用一个队列保存计算机的任务&#xff0c;一般情况下计算机的任务都是有优先…

《Python程序开发》期末作业

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、题目&#xff1a; 1 .选修课信息(1).xlsx&#xff0c;信息如下&#xff1a; 2 .学生选课信息表.xls&#xff0c;信息如下 3.任务 二、将文件中的信息导入数据库 …

脱水蔬菜开启蔬菜产业发展新道路 国内市场正不断扩大

根据观研报告网发布的《中国脱水蔬菜市场发展现状研究与投资前景预测报告&#xff08;2022-2029年&#xff09;》显示&#xff0c;脱水蔬菜又称复水菜&#xff0c;是将新鲜蔬菜经过洗涤、烘干等加工制作&#xff0c;脱去蔬菜中大部分水分后而制成的一种干菜&#xff0c;食用时只…

Netty实战与源码剖析(一)——浅谈NIO编程

1 前言 很久之前就想写与Netty相关的博客了&#xff0c;但由于个人时间安排的问题一直拖到了现在&#xff0c;借助这个机会&#xff0c;重新温习Java高级编程的同时&#xff0c;也把Netty实战以及源码剖析分享给各位读者。 2 Netty是什么&#xff1f; Netty is a NIO client …

Spring—Spring IOC

文章目录Spring IOC容器1. 什么是IOC2.IOC的核心原理IOC如何充当对象容器&#xff1f;具体什么作为对象容器&#xff1f;IOC的核心原理图3. IOC容器的底层原理IOC的实现&#xff0c;依赖于以下3门技术上边提到的三种技术如何实现IOC的呢&#xff1f;4.IOC(接口)————————…