tft每日頭條

 > 生活

 > Linux下RTC時間的讀寫分析

Linux下RTC時間的讀寫分析

生活 更新时间:2025-04-09 08:25:36
Linux下RTC時間的讀寫分析

1.1.1 系統時間與RTC時間​

Linux系統下包含兩個時間:系統時間和RTC時間。​

系統時間:是由主芯片的定時器進行維護的時間,一般情況下都會選擇芯片上最高精度的定時器作為系統時間的定時基準,以避免在系統運行較長時間後出現大的時間偏移。特點是掉電後不保存。​

RTC時間:是指系統中包含的RTC芯片内部所維護的時間。RTC芯片都有電池 系統電源的雙重供電機制,在系統正常工作時由系統供電,在系統掉電後由電池進行供電。因此系統電源掉電後RTC時間仍然能夠正常運行。​

每次linux系統啟動後在啟動過程中會檢測和挂載RTC驅動,在挂載後會自動從RTC芯片中讀取時間并設置到系統時間中去。此後如果沒有顯式的通過命令去控制RTC的讀寫操作,系統将不會再從RTC中去獲取或者同步設置時間。​

linux命令中的date和time等命令都是用來設置系統時間的,而hwCLOCK命令是用來設置和讀寫RTC時間的。​

嵌入式進階教程分門别類整理好了,看的時候十分方便,由于内容較多,這裡就截取一部分圖吧。

Linux下RTC時間的讀寫分析(Linux下RTC時間的讀寫分析)1

需要的朋友私信【内核】即可領取。

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 顯示版本信息

使用說明:​

在顯示方面,使用者可以設定欲顯示的格式,格式設定為一個加号後接數個标記,其中可用的标記列表如下: % : 打印出 %:​

示例: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: d:d:d\n"​ "rtc_date\t: d-d-d\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, "d:", alrm.time.tm_hour);​ else​ seq_printf(seq, "**:");​ if ((unsigned int)alrm.time.tm_min <= 59)​ seq_printf(seq, "d:", alrm.time.tm_min);​ else​ seq_printf(seq, "**:");​ if ((unsigned int)alrm.time.tm_sec <= 59)​ seq_printf(seq, "d\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, "d-", alrm.time.tm_year 1900);​ else​ seq_printf(seq, "****-");​ if ((unsigned int)alrm.time.tm_mon <= 11)​ seq_printf(seq, "d-", alrm.time.tm_mon 1);​ else​ seq_printf(seq, "**-");​ if (alrm.time.tm_mday && (unsigned int)alrm.time.tm_mday <= 31)​ seq_printf(seq, "d\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注冊框圖

Linux下RTC時間的讀寫分析(Linux下RTC時間的讀寫分析)2

導讀-最新發表 - 内核技術中文網 - 構建全國最權威的内核技術交流分享論壇

[驅動知識]Linux下RTC時間的讀寫分析 - 論壇 - 内核技術中文網 - 構建全國最權威的内核技術交流分享論壇

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2025 - www.tftnews.com All Rights Reserved