RTCnode
一直以來想寫一篇關於RTC的總結,但是人太懶,在讀完John Z. Sonmez大伽的《軟技能代碼以外的生存技能》後,終於下定決心,完成這項早已計劃中的任務。首先聲明,本文是以PC爲例來闡述RTC工做的基本原理。linux
RTC(Real Time Clock),實時時鐘,是存在於PC(x86)及類PC架構的電路中,其主要的做用是記錄設備關機時的時間及在設備開機時提供時間基準,也就是說在設備機器關電的時候,記錄下當時的時間,在設備啓動時爲設備內部的時間提供基準值,從而使得設備內部的時間值不是從初值開始,而是從RTC記錄並運行的時間開始。從這個意義上RTC有被成爲牆上時間(walltimer)。服務器
聰明的你,先來看下RTC電路數據結構
其中RTCX1,RTCX2, RTCRST#, SRTCRST#鏈接到PCH上, 因此上RTC的input和output都是受PCH的控制的,也就是說RTC提供的時間是給PCH的。架構
RTCX1爲晶振的input,RTCX2爲晶振的output,也就是晶振反饋值。ide
RTCRST#的主要做用是用來清楚CMOS的,固然它也能夠用來檢測電池的電壓是否低於2V。函數
SRTCRST#用來當電池更換時清除Intel ME相關的寄存器。工具
Vbatt時由安裝的一顆3V左右(通常爲2.8V~3.3V)的鈕釦電池(通常都爲CR2302,提供最大10mA的電流)提供,這顆電池的做用時在機器移除AC電源後, RTC電路仍然能夠正常工做。當這顆電池的電壓不足的時候,就會出現RTC時間不許確。測試
Xtal是由一顆頻率爲32 .768KHz的石英晶振提供的, 此晶振的功能是提供給計時電路用的基準clock。flex
肖特基二極管D1的做用:當AC電源鏈接時,也就是VCCRTC(3.3V)有電時,D1不導通,電池不提供電流給RTC電路;當AC電源移除後,D1導通,電池工做。
提起RTC,不得不提起PC系統的另一個時間,系統時間。在Linux和Windows中,這個時間都是內核在維護的,也就是說在Linux等操做系統中,有兩套時間在運行。
系統時間的精度能夠作到微妙級,而相對應的RTC的時間精度只能作到秒級。通常狀況下系統時間的精度爲24小時最大漂移1.2秒,RTC的時間的漂移就要大的多。這就形成了系統時間的精確的要遠遠高於RTC。因此當系統運行了一段時間後,會出現系統時間和RTC不一致的狀況,這是就須要咱們按期將兩個時間進行同步。
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 |
若是你經過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項配置可能會帶來機器一些異常動做。
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有關的數據結構
此文件中的函數的主要操做就是經過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後再設置。
此文件主要是一些操做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,
};
全部的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()來建立字符型驅動的主次設備號。
此文件只有一個函數,主要用在linux啓動的時候初始化,就是實現了將系統時間從rtc時間獲取初始值的功能。
static int __init rtc_hctosys(void)。
封裝了全部對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)
提供時間格式的變換函數,方便根據具體須要去調節時間的格式。
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)
提供能夠操做/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設備。
此文件就是提供/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)
這個文件提供了/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是一款用來讀取和修改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電路是否可以正常計時,也就是說當主機啓動的時候可以獲得正確的wall time。
目前主要的測試方案
由於系統時間的精確度在微妙級,而RTC的精確度在秒級,因此說RTC時間的精度是遠遠小於系統時間(OS時間)。
隨着數據中心的規模愈來愈大,實際上不少服務器的客戶再也不關注RTC時間。一旦服務器放上機架後,幾年都不會關機或重啓一次,因此wall time幾乎沒有什麼用處,即便重啓,也由於許多數據中心有時間服務器,他們能夠經過NTP(Network Time Protocol)去同步其的時間,也就是不依賴於RTC的牆時間。但這也不是說RTC就沒有什麼卵用,在許多小型的系統中,一些微處理系統中,仍然有它的生命力。