RTC
- 原創,轉載請寫明出處。
一直以來想寫一篇關於RTC的總結,可是人太懶,在讀完John Z. Sonmez大伽的《軟技能代碼之外的生存技能》后,終於下定決心,完成這項早已計划中的任務。首先聲明,本文是以PC為例來闡述RTC工作的基本原理。
RTC的基本概念
RTC(Real Time Clock),實時時鍾,是存在於PC(x86)及類PC架構的電路中,其主要的作用是記錄設備關機時的時間及在設備開機時提供時間基准,也就是說在設備機器關電的時候,記錄下當時的時間,在設備啟動時為設備內部的時間提供基准值,從而使得設備內部的時間值不是從初值開始,而是從RTC記錄並運行的時間開始。從這個意義上RTC有被成為牆上時間(walltimer)。
RTC電路
聰明的你,先來看下RTC電路

其中RTCX1,RTCX2, RTCRST#, SRTCRST#連接到PCH上, 所以上RTC的input和output都是受PCH的控制的,也就是說RTC提供的時間是給PCH的。
RTCX1為晶振的input,RTCX2為晶振的output,也就是晶振反饋值。
RTCRST#的主要作用是用來清楚CMOS的,當然它也可以用來檢測電池的電壓是否低於2V。
SRTCRST#用來當電池更換時清除Intel ME相關的寄存器。
Vbatt時由安裝的一顆3V左右(一般為2.8V~3.3V)的紐扣電池(一般都為CR2302,提供最大10mA的電流)提供,這顆電池的作用時在機器移除AC電源后, RTC電路仍然可以正常工作。當這顆電池的電壓不足的時候,就會出現RTC時間不准確。
Xtal是由一顆頻率為32 .768KHz的石英晶振提供的, 此晶振的功能是提供給計時電路用的基准clock。
肖特基二極管D1的作用:當AC電源連接時,也就是VCCRTC(3.3V)有電時,D1不導通,電池不提供電流給RTC電路;當AC電源移除后,D1導通,電池工作。
RTC工作原理
提起RTC,不得不提起PC系統的另外一個時間,系統時間。在Linux和Windows中,這個時間都是內核在維護的,也就是說在Linux等操作系統中,有兩套時間在運行。
系統時間的精度可以做到微妙級,而相對應的RTC的時間精度只能做到秒級。一般情況下系統時間的精度為24小時最大漂移1.2秒,RTC的時間的漂移就要大的多。這就造成了系統時間的精確的要遠遠高於RTC。所以當系統運行了一段時間后,會出現系統時間和RTC不一致的情況,這是就需要我們定期將兩個時間進行同步。
RTC和CMOS
RTC的時間和設置都保存在CMOS RAM中,詳細的如下表,其中前10個字節(offset 00 ~ 09h)存儲着RTC確切的時間,也就是你在BIOS setup utility中看到的時間,當然這個時間是變動的,剩下的都是RTC的配置字節。可以通過70/71h端口去操作,也可以通過ioctl命令去配置。
| Offset Hex |
Offset Dec |
Field Size |
Function |
| 00h |
0 |
1 byte |
RTC seconds. Contains the seconds value of current time |
| 01h |
1 |
1 byte |
RTC seconds alarm. Contains the seconds value for the RTC alarm |
| 02h |
2 |
1 byte |
RTC minutes. Contains the minutes value of the current time |
| 03h |
3 |
1 byte |
RTC minutes alarm. Contains the minutes value for the RTC alarm |
| 04h |
4 |
1 byte |
RTC hours. Contains the hours value of the current time |
| 05h |
5 |
1 byte |
RTC hours alarm. Contains the hours value for the RTC alarm |
| 06h |
6 |
1 byte |
RTC day of week. Contains the current day of the week |
| 07h |
7 |
1 byte |
RTC date day. Contains day value of current date |
| 08h |
8 |
1 byte |
RTC date month. Contains the month value of current date |
| 09h |
9 |
1 byte |
RTC date year. Contains the year value of current date |
| 0Ah |
10 |
1 byte |
Status Register A |
|
|
|
|
Bit 7 = Update in progress (0 = Date and time can be read, 1 = Time update in progress) |
|
|
|
|
Bits 6-4 = Time frequency divider (010 = 32.768KHz |
|
|
|
|
Bits 3-0 = Rate selection frequency (0110 = 1.024KHz square wave frequency) |
| 0Bh |
11 |
1 byte |
Status Register B |
|
|
|
|
Bit 7 = Clock update cycle (0 = Update normally, 1 = Abort update in progress) |
|
|
|
|
Bit 6 = Periodic interrupt (0 = Disable interrupt (default), 1 = Enable interrupt) |
|
|
|
|
Bit 5 = Alarm interrupt (0 = Disable interrupt (default), 1 = Enable interrupt) |
|
|
|
|
Bit 4 = Update ended interrupt (0 = Disable interrupt (default), 1 = Enable interrupt) |
|
|
|
|
Bit 3 = Status register A square wave frequency (0 = Disable square wave (default), 1 = Enable square wave) |
|
|
|
|
Bit 2 = 24 hour clock (0 = 24 hour mode (default), 1 = 12 hour mode) |
|
|
|
|
Bit 1 = Daylight savings time (0 = Disable daylight savings (default), 1 = Enable daylight savings) |
| 0Ch |
12 |
1 byte |
Status Register C - Read only flags indicating system status conditions |
|
|
|
|
Bit 7 = IRQF flag |
|
|
|
|
Bit 6 = PF flag |
|
|
|
|
Bit 5 = AF flag |
|
|
|
|
Bit 4 UF flag |
|
|
|
|
Bits 3-0 = Reserved |
| 0Dh |
13 |
1 byte |
Status Register D - Valid CMOS RAM flag on bit 7 (battery condition flag) |
|
|
|
|
Bit 7 = Valid CMOS RAM flag (0 = CMOS battery dead, 1 = CMOS battery power good) |
|
|
|
|
Bit 6-0 = Reserved |
| 0Eh |
14 |
1 byte |
Diagnostic Status |
|
|
|
|
Bit 7 = Real time clock power status (0 = CMOS has not lost power, 1 = CMOS has lost power) |
|
|
|
|
Bit 6 = CMOS checksum status (0 = Checksum is good, 1 = Checksum is bad) |
|
|
|
|
Bit 5 = POST configuration information status (0 = Configuration information is valid, 1 = Configuration information in invalid) |
|
|
|
|
Bit 4 = Memory size compare during POST (0 = POST memory equals configuration, 1 = POST memory not equal to configuration) |
|
|
|
|
Bit 3 = Fixed disk/adapter initialization (0 = Initialization good, 1 = Initialization bad) |
|
|
|
|
Bit 2 = CMOS time status indicator (0 = Time is valid, 1 = Time is invalid) |
|
|
|
|
Bit 1-0 = Reserved |
Linux下RTC相關函數
如果你通過70/71h端口去訪問RTC的數據,在linux下有兩種方式,第一種是通過函數inb級outb去操作,讀取的
#include <sys/io.h>
iopl(3);
unsigned char index, data;
outb(index, 0x70);
data = inb(0x71);
第二種方式是通過訪問/dev/port。
unsigned char index, data;
int fp = open("/dev/port",O_RDWR);
if(fp >0)
{
lseek(fp, 0x70, SEEK_SET);
write(fp, &index, 1);
lseek(fp, 0x71, SEEK_SET);
read(fp, data, 1);
}
close(fp);
以上都是以讀取為例的,你可以將每一中方式最后一行的inb改為outb, read改為write就將讀取改為設置了。
以上兩種方式是常見的方式,實際上linux提供以一系列的ioctl命令去操作RTC的讀取及配置。強烈推薦這種方式。
X86 架構的Linux下存在/dev/rtc這個字符型設備文件,有些有多個rtc設備的系統,可能會有/dev/rtc0…. /dev/rtc1等等。
首先需要打開rtc設備文件
#include <linux/rtc.h>
struct rtc_time rtc;
int fd = open("/dev/rtc", O_RDONLY);
if(fd <= 0)
{
fd = open("/dev/rtc0", O_RDONLY);
}
然后運行ioctl命令,
ret = ioctl(fd,RTC_RD_TIME, &rtc);
詳細的rtc的ioctl命令可以參考linux/rtc.h文件
/*
* Generic RTC interface.
* This version contains the part of the user interface to the Real Time Clock
* service. It is used with both the legacy mc146818 and also EFI
* Struct rtc_time and first 12 ioctl by Paul Gortmaker, 1996 - separated out
* from <linux/mc146818rtc.h> to this file for 2.4 kernels.
*
* Copyright (C) 1999 Hewlett-Packard Co.
* Copyright (C) 1999 Stephane Eranian <eranian@hpl.hp.com>
*/
#ifndef _LINUX_RTC_H_
#define _LINUX_RTC_H_
/*
* The struct used to pass data via the following ioctl. Similar to the
* struct tm in <time.h>, but it needs to be here so that the kernel
* source is self contained, allowing cross-compiles, etc. etc.
*/
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;
};
/*
* This data structure is inspired by the EFI (v0.92) wakeup
* alarm API.
*/
struct rtc_wkalrm {
unsigned char enabled; /* 0 = alarm disabled, 1 = alarm enabled */
unsigned char pending; /* 0 = alarm not pending, 1 = alarm pending */
struct rtc_time time; /* time the alarm is set to */
};
/*
* Data structure to control PLL correction some better RTC feature
* pll_value is used to get or set current value of correction,
* the rest of the struct is used to query HW capabilities.
* This is modeled after the RTC used in Q40/Q60 computers but
* should be sufficiently flexible for other devices
*
* +ve pll_value means clock will run faster by
* pll_value*pll_posmult/pll_clock
* -ve pll_value means clock will run slower by
* pll_value*pll_negmult/pll_clock
*/
struct rtc_pll_info {
int pll_ctrl; /* placeholder for fancier control */
int pll_value; /* get/set correction value */
int pll_max; /* max +ve (faster) adjustment value */
int pll_min; /* max -ve (slower) adjustment value */
int pll_posmult; /* factor for +ve correction */
int pll_negmult; /* factor for -ve correction */
long pll_clock; /* base PLL frequency */
};
/*
* ioctl calls that are permitted to the /dev/rtc interface, if
* any of the RTC drivers are enabled.
*/
* Data structure to control PLL correction some better RTC feature
* pll_value is used to get or set current value of correction,
* the rest of the struct is used to query HW capabilities.
* This is modeled after the RTC used in Q40/Q60 computers but
* should be sufficiently flexible for other devices
*
* +ve pll_value means clock will run faster by
* pll_value*pll_posmult/pll_clock
* -ve pll_value means clock will run slower by
* pll_value*pll_negmult/pll_clock
*/
struct rtc_pll_info {
int pll_ctrl; /* placeholder for fancier control */
int pll_value; /* get/set correction value */
int pll_max; /* max +ve (faster) adjustment value */
int pll_min; /* max -ve (slower) adjustment value */
int pll_posmult; /* factor for +ve correction */
int pll_negmult; /* factor for -ve correction */
long pll_clock; /* base PLL frequency */
};
/*
* ioctl calls that are permitted to the /dev/rtc interface, if
* any of the RTC drivers are enabled.
*/
#define RTC_AIE_ON _IO('p', 0x01) /* Alarm int. enable on */
#define RTC_AIE_OFF _IO('p', 0x02) /* ... off */
#define RTC_UIE_ON _IO('p', 0x03) /* Update int. enable on */
#define RTC_UIE_OFF _IO('p', 0x04) /* ... off */
#define RTC_PIE_ON _IO('p', 0x05) /* Periodic int. enable on */
#define RTC_PIE_OFF _IO('p', 0x06) /* ... off */
#define RTC_WIE_ON _IO('p', 0x0f) /* Watchdog int. enable on */
#define RTC_WIE_OFF _IO('p', 0x10) /* ... off */
#define RTC_ALM_SET _IOW('p', 0x07, struct rtc_time) /* Set alarm time */
#define RTC_ALM_READ _IOR('p', 0x08, struct rtc_time) /* Read alarm time */
#define RTC_RD_TIME _IOR('p', 0x09, struct rtc_time) /* Read RTC time */
#define RTC_SET_TIME _IOW('p', 0x0a, struct rtc_time) /* Set RTC time */
#define RTC_IRQP_READ _IOR('p', 0x0b, unsigned long) /* Read IRQ rate */
#define RTC_IRQP_SET _IOW('p', 0x0c, unsigned long) /* Set IRQ rate */
#define RTC_EPOCH_READ _IOR('p', 0x0d, unsigned long) /* Read epoch */
#define RTC_EPOCH_SET _IOW('p', 0x0e, unsigned long) /* Set epoch */
#define RTC_WKALM_SET _IOW('p', 0x0f, struct rtc_wkalrm)/* Set wakeup alarm*/
#define RTC_WKALM_RD _IOR('p', 0x10, struct rtc_wkalrm)/* Get wakeup alarm*/
#define RTC_PLL_GET _IOR('p', 0x11, struct rtc_pll_info) /* Get PLL correction */
#define RTC_PLL_SET _IOW('p', 0x12, struct rtc_pll_info) /* Set PLL correction */
/* interrupt flags */
#define RTC_IRQF 0x80 /* any of the following is active */
常用的是RTC_RD_TIME和RTC_SET_TIME, RTC_AIE_OFF, RTC_UIE_OFF,RTC_PIE_OFF盡量少用,或者說在沒有搞懂原理之前,不要使用(RTC_AIE_ON可能會導致機器被喚醒),因為此3項配置可能會帶來機器一些異常動作。
Linux RTC驅動
RTC的驅動代碼在kernel代碼的drivers/rtc目錄下。在此目錄下會看到一大堆文件,這是因為linux支持眾多rtc設備,本文是以x86架構的rtc為例的。
RTC涉及的代碼如下:
driver/rtc/class.c: 此文件向linux內核驅動模型注冊了一個類RTC, 同時為底層的RTC驅動提供了注冊/注銷RTC接口。
driver/rtc/rtc-dev.c: 將各種各樣的RTC設備抽象成一個字符設備,同時提供文件操作函數集。
driver/rtc/rtc-sysfs.c: 用戶可以通過sysfs文件系統方便快捷的操作rtc設備。
driver/rtc/rtc-proc.c: 可以通過proc文件系統獲得rtc的相關信息,比如rtc_time, rtc_data等信息。
driver/rtc/interface.c: 提供應用程序和驅動的接口函數,主要是為rtc提供相關的調用接口。
driver/rtc/rtc-lib.c: 提供了一個rtc和data以及time之間的轉換函數
driver/rtc/hctosys.c: 用於開機啟動的時候獲取rtc的值。
driver/rtc/rtc-cmos.c: x86架構rtc驅動。
include/linux/rtc.h:定義了與RTC有關的數據結構
rtc-mc146818-lib.c
此文件中的函數的主要操作就是通過70h/71h, 72h/73h端口去讀寫cmos RAM去實現的,提供了最底層的和硬件交互的函數。
unsigned int mc146818_get_time(struct rtc_time *time): 獲取rtc的時間。
int mc146818_set_time(struct rtc_time *time):設置rtc的 時間。
static inline unsigned char mc146818_is_updating(void):判斷cmos的時間是否在更新中,如果是,delay后再設置。
rtc-cmos.c
此文件主要是一些操作CMOS RAM的函數,這些函數是rtc-mc146818-lib.c文件函數的上層,實際的實現在rtc-mc146818-lib.c中。
static const struct rtc_class_ops cmos_rtc_ops = {
.read_time = cmos_read_time,
.set_time = cmos_set_time,
.read_alarm = cmos_read_alarm,
.set_alarm = cmos_set_alarm,
.proc = cmos_procfs,
.alarm_irq_enable = cmos_alarm_irq_enable,
};
class.c文件
所有的Linux驅動無非都有幾個關於設備的函數, rtc的在class.c文件中。
class.c中定義的第一個比較重要的是struct class *rtc_class, 也就是說rtc_class派生於class, 而class又有kobject成員,也就說class完全是一個抽象的東東,沒有對應的實體,有點像C++中的虛函數。Class的定義可以在include/linux/device.h文件中找到。
struct class {
const char *name;
struct module *owner;
const struct attribute_group **class_groups;
const struct attribute_group **dev_groups;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*shutdown_pre)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
class.c文件中作為這個rtc驅動最基礎的幾個函數如下:
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner)
用來rtc 設備的注冊。
void rtc_device_unregister(struct rtc_device *rtc) 移除注冊的rtc設備類
static void rtc_device_release(struct device *dev) 釋放rtc設備資源。
static int __init rtc_init(void) 創建rtc class,而這個函數有調用rtc-dev.c中的rtc_dev_init()來創建字符型驅動的主次設備號。
hctosys.c
此文件只有一個函數,主要用在linux啟動的時候初始化,就是實現了將系統時間從rtc時間獲取初始值的功能。
static int __init rtc_hctosys(void)。
interface.c
封裝了所有對rtc進行操作的函數,基於rtc.cmos,當然如果是ARM或其它非x86架構的話,就會基於其相應的硬件操作的函數。
int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
rtc-lib.c
提供時間格式的變換函數,方便根據具體需要去調節時間的格式。
void rtc_time64_to_tm(time64_t time, struct rtc_time *tm)
time64_t rtc_tm_to_time64(struct rtc_time *tm)
ktime_t rtc_tm_to_ktime(struct rtc_time tm)
struct rtc_time rtc_ktime_to_tm(ktime_t kt)
rtc-dev.c
提供可以操作/dev/rtc虛擬設備的函數接口,需要重點關注的是函數
static int rtc_dev_open(struct inode *inode, struct file *file)
static long rtc_dev_ioctl(struct file *file,unsigned int cmd, unsigned long arg)
static int rtc_dev_release(struct inode *inode, struct file *file)
這是一個標准的dev的接口,我們可以用可以用標准的open,close及ioctl函數在應用層操作rtc設備。
rtc-proc.c
此文件就是提供/proc/driver/rtc的底層文件
而提供的函數主要包括
static int rtc_proc_show(struct seq_file *seq, void *offset)(用來顯示上圖)
static int rtc_proc_open(struct inode *inode, struct file *file)
static int rtc_proc_release(struct inode *inode, struct file *file)
void rtc_proc_add_device(struct rtc_device *rtc)
void rtc_proc_del_device(struct rtc_device *rtc)
rtc-sysfs.c
這個文件提供了/sys/class/rtc接口的文件,也就是說通過這些文件可以間接讀取或者改寫rtc設備的屬性。
/sys/class/rtc/rtc0下的這些文件相對應以下函數:
static ssize_t name_show(struct device *dev, struct device_attribute *attr, char *buf)
static ssize_t date_show(struct device *dev, struct device_attribute *attr, char *buf)
static ssize_t time_show(struct device *dev, struct device_attribute *attr, char *buf)
static ssize_t max_user_freq_show(struct device *dev, struct device_attribute *attr, char *buf)
hwclock工具
hwclock是一款用來讀取和修改RTC時間的Linux下的工具。實際上它也是基於Linux RTC driver去做的操作。
hwclock其參數的中文解釋如下:
--adjust hwclock每次更改硬件時鍾時,都會記錄在/etc/adjtime文件中。使用--adjust參數,可使hwclock根據先前的記錄來估算硬件時鍾的偏差,並用來校正的硬件時鍾。
--debug 顯示hwclock執行時詳細的信息。
--directisa hwclock預設從/dev/rtc設備來存取硬件時鍾。若無法存取時,可用此參數直接以I/O指令來存取硬件時鍾。
--hctosys 將系統時鍾調整為與的硬件時鍾一致。hwclock會將硬件時間按照硬件時鍾的時區轉換為本地時區進的時間,
--set --date=<日期與時間> 設定硬件時鍾。
--show 顯示硬件時鍾的時間與日期。
--systohc 將硬件時鍾調整為與的系統時鍾一致。設置硬件時鍾時hwclock會自動將系統時間轉換為硬件時鍾所對應時區的時間。
--test 僅測試程序,而不會實際更改硬件時鍾。
--utc 將硬件時間當做UTC時間來看待。若要使用格林威治時間,請加入此參數,hwclock會執行轉換的工作。
--localtime 將硬件時鍾當做本地時間來看待,此時hwclock不會執行時間轉換工作。
--version 顯示版本信息。
常用的命令主要有將系統時間同步到RTC時間的hwclock -w及讀取當前RTC時間的hwclock.
RTC的測試
RTC的測試主要是確定RTC電路是否能夠正常計時,也就是說當主機啟動的時候能夠得到正確的wall time。
目前主要的測試方案
- 用系統時間同步RTC時間,並記錄當前時間
- 等待10秒鍾。
- 讀取RTC時間及系統時間
- 比較RTC時間和系統時間是否相同。
因為系統時間的精確度在微妙級,而RTC的精確度在秒級,所以說RTC時間的精度是遠遠小於系統時間(OS時間)。
總結
隨着數據中心的規模越來越大,實際上很多服務器的客戶不再關注RTC時間。一旦服務器放上機架后,幾年都不會關機或重啟一次,所以wall time幾乎沒有什么用處,即使重啟,也因為許多數據中心有時間服務器,他們可以通過NTP(Network Time Protocol)去同步其的時間,也就是不依賴於RTC的牆時間。但這也不是說RTC就沒有什么卵用,在許多小型的系統中,一些微處理系統中,仍然有它的生命力。
