Linux的時間與時鐘中斷處理

 本文主要介紹在Linux下的時間實現以及系統如何進行時鐘中斷處理。linux

一.        Linux 的硬件時間
PC機中的時間有三種硬件時鐘實現,這三種都是基於晶振產生的方波信號輸入。這三種時鐘爲:(1)實時時鐘RTC ( Real Time Clock) (2)可編程間隔器PIT(Programmable Interval Timer )(3)時間戳計數器TSC(Time Stamp Clock)
1.      實時時鐘 RTC
   用於長時間存放系統時間的設備,即時關機後也可依靠主板CMOS電池繼續保持系統的計時,原理圖以下:

Note: Linux與RTC的關係是,當Linux啓動時從RTC讀取時間和日期的基準值,而後在Kernel運行期間便拋開RTC,以軟件的形式維護系統的時間日期,並在適當時機由Kernel將時間寫回RTC Register.
1.1 RTC Register
   (1). 時鐘與日曆Register
        共10個,地址:0x00-0x09,分別用於保存時間日曆的具體信息,詳情以下:

        00          Current Second for RTC
        01          Alarm Second
        02          Current Minute
        03          Alarm Minute
        04          Current Hour
        05          Alarm Hour
        06          Current Day of Week(1=Sunday)
        07          Current Date of Month
        08          Current Month
        09          Current Year
 (2).狀態和控制Register
    共四個,地址:0x0a-0x0d,控制RTC芯片的工做方式,並表示當前狀態。
l          狀態RegisterA , 0x0A 格式以下:
         bit[7]——UIP標誌(Update in Progress),爲1表示RTC正在更新日曆寄存器組中的值,此時日曆寄存器組是不可訪問的(此時訪問它們將獲得一個無心義的漸變值)。
bit 6:4]——這三位是用來定義RTC的操做頻率。各類可能的值以下: 

DV2 DV1 DV0        
   0   0        4.194304 MHZ
0    0   1        1.048576 MHZ
0    1   0        32.769   KHZ
1    1   0/1      
任何 
PC機一般設置成「010」。
bit 3:0]——速率選擇位(Rate Selection bits),用於週期性或方波信號輸出。 
RS3 RS2 RS1 RS0  週期性中斷   方波   週期性中斷   方波
 
  0   0      None       None       None      None
0   0   0   1   30.517μs  32.768 KHZ 3.90625ms 256 HZ
0   0   1   0   61.035μs  16.384 KHZ
0   0   1   1   122.070μs  8.192KHZ
0   1   0   0    244.141μs 4.096KHZ
0    0   1    488.281μs 2.048KHZ
0   1   1   0    976.562μs 1.024KHZ
0   1   1   1    1.953125ms    512HZ
1   0   0   0    3.90625ms     256HZ
1   0   0      7.8125ms      128HZ
1   0   1   0    15.625ms      64HZ
1    1   1    31.25ms       32HZ
1   1   0   0    62.5ms        16HZ
1   1   0   1    125ms          8HZ
 1   1   0    250ms          4HZ
1   1   1      500ms          2HZ
PC
機BIOS對其默認的設置值是「0110」
l          狀態Register B , 0x0B 格式以下:
bit[7]——SET標誌。爲1表示RTC的全部更新過程都將終止,用戶程序隨後立刻對日曆寄存器組中的值進行初始化設置。爲0表示將容許更新過程繼續。
bit[6]——PIE標誌,週期性中斷enable標誌。
bit[5]——AIE標誌,告警中斷enable標誌。
bit[4]——UIE標誌,更新結束中斷enable標誌。
bit[3]——SQWE標誌,方波信號enable標誌。
bit[2]——DM標誌,用來控制日曆寄存器組的數據模式,0=BCD,1=BINARY。BIOS老是將它設置爲0。
bit[1]——24/12標誌,用來控制hour寄存器,0表示12小時制,1表示24小時制。PC機BIOS老是將它設置爲1。
bit[0]——DSE標誌。BIOS老是將它設置爲0。
l          狀態Register C,0x0C 格式以下: 
bit[7]——IRQF標誌,中斷請求標誌,當該位爲1時,說明寄存器B中斷請求 發生。
 
bit[6]——PF標誌,週期性中斷標誌,爲1表示發生週期性中斷請求。
 
bit[5]——AF標誌,告警中斷標誌,爲1表示發生告警中斷請求。
 
bit[4]——UF標誌,更新結束中斷標誌,爲1表示發生更新結束中斷請求。
l          狀態Register D,0x0D 格式以下: 
 bit[7]——VRT標誌(Valid RAM and Time),爲1表示OK,爲0表示RTC 已經掉電。
 
 bit[6:0]——老是爲0,未定義。
2. 可編程間隔定時器 PIT
        每一個PC機中都有一個PIT,以經過IRQ0產生週期性的時鐘中斷信號,做爲系統定時器 system timer。當前使用最廣泛的是Intel 8254 PIT芯片,它的I/O端口地址是0x40~0x43。 
Intel 8254 PIT有3個計時通道,每一個通道都有其不一樣的用途:
 
(1) 通道0用來負責更新系統時鐘。每當一個時鐘滴答過去時,它就會經過IRQ0向       系統 產生一次時鐘中斷。
 
(2) 通道1一般用於控制DMAC對RAM的刷新。
 
(3) 通道2被鏈接到PC機的揚聲器,以產生方波信號。
 
    每 個通道都有一個向下減少的計數器,8254 PIT的輸入時鐘信號的頻率是1.193181MHZ,也即一秒鐘輸入1193181個clock-cycle。每輸入一個clock-cycle其時間 通道的計數器就向下減1,一直減到0值。所以對於通道0而言,當他的計數器減到0時,PIT就向系統產生一次時鐘中斷,表示一個時鐘滴答已通過去了。計數 器爲16bit,所以所能表示的最大值是65536,一秒內發生的滴答數是:1193181/65536=18.206482.
        PIT的I/O端口
     0x40   通道0 計數器 Read/Write
     0X41   通道1計數器
 Read/Write
     0X42  通道2計數器
  Read/Write
     0X43   控制字        Write Only
 Note: 因PIT I/O端口是8位,而PIT相應計數器是16位,所以必須對PIT計數器進行兩次讀寫。
   8254 PIT的控制寄存器(0X43)的格式以下
            bit[7:6] — 通道選擇位:00 ,通道0;01,通道1;10,通道2;11,read-back command,僅8254。
            bit[5:4] – Read/Write/Latch鎖定位,00,鎖定當前計數器以便讀取計數值;01,只讀高字節;10,只讀低字節;11,先高後低。
            bit[3:1] – 設定各通道的工做模式。
          000   mode0       當通道處於count out 時產生中斷信號,可用於系統定時
          001   mode1       Hardware retriggerable one-shot
          010   mode2       Rate Generator。產生實時時鐘中斷,通道0一般工做在這個模式下
          011 mode3        方波信號發生器
          100 mode4        Software triggered strobe
          101 mode5        Hardware triggered strobe
3.  時間戳計數器 TSC
         Pentium開始,全部的Intel 80x86 CPU就都包含一個64位的時間戳記數器(TSC)的寄存器。該寄存器其實是一個不斷增長的計數器,它在CPU的每一個時鐘信號到來時加1(也即每個clock-cycle輸入CPU時,該計數器的值就加1)。 
    彙編指令rdtsc能夠用於讀取TSC的值。利用CPU的TSC,操做系統一般能夠獲得更爲精準的時間度量。假如clock-cycle的頻率是400MHZ,那麼TSC就將每2.5納秒增長一次。
二.        Linux 時鐘中斷處理程序
1.       幾個概念
1)時鐘週期(clock cycle)的頻率:8253/8254 PIT的本質就是對由晶體振盪器產生的時鐘週期進行計數,晶體振盪器在1秒時間內產生的時鐘脈衝個數就是時鐘週期的頻率。Linux用宏 CLOCK_TICK_RATE來表示8254 PIT的輸入時鐘脈衝的頻率(在PC機中這個值一般是1193180HZ),該宏定義在include/asm-i386/timex.h頭文件中
#define CLOCK_TICK_RATE 1193180        kernel=2.4 &2.6

2)時鐘滴答(clock tick):當PIT通道0的計數器減到0值時,它就在IRQ0上產生一次時鐘中斷,也即一次時鐘滴答。PIT通道0的計數器的初始值決定了要過多少時鐘週期才產生一次時鐘中斷,所以也就決定了一次時鐘滴答的時間間隔長度。
3)時鐘滴答的頻率(HZ):1秒時間內PIT所產生的時鐘滴答次數。 這個值也由PIT通道0的計數器初值決定的.Linux內核用宏HZ來表示時鐘滴答的頻率,並且在不一樣的平臺上HZ有不一樣的定義值。對於ALPHA和 IA62平臺HZ的值是1024,對於SPARC、MIPS、ARM和i386等平臺HZ的值都是100。該宏在i386平臺上的定義以下 (include/asm-i386/param.h):
#define HZ 100    kernel=2.4
#define HZ   CONFIG_HZ       kernel=2.6

4)宏LATCH定義要寫到PIT通道0的計數器中的值,它表示PIT將隔多少個時鐘週期產生一次時鐘中斷。公式計算: 
LATCH=(1秒以內的時鐘週期個數)÷(1秒以內的時鐘中斷次數)=(CLOCK_TICK_RATE)÷(HZ)

定義在 <include/linux/timex.h>
#define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ)
(5)全局變量jiffies:用於記錄系統自啓動以來產生的滴答總數 。啓動時,kernel將該變量初始爲0,每次時鐘中斷處理程序timer_interrupt()將該變量加1。由於一秒鐘內增長的時鐘中斷次數等於Hz,因此jiffies一秒內增長的值也是Hz。由此可得系統運行時間是jiffies/Hz 秒。
jiffies 定義於<linux/jiffies.h>中:
extern unsigned long volatile jiffies;
Note:
在kernel 2.4,jiffies是32位無符號數;kernel 2.6,jiffies是64位無符號數。
6)全局變量xtime: 結構類型變量,用於表示當前時間距UNIX基準時間1970-01-01 00:00:00的相對秒數值。當系統啓動時,Kernel經過讀取RTC Register中的數據來初始化系統時間(wall_time),該時間存放在xtime中。
void __init time_init (void) {
              ... ...
              xtime.tv_sec = get_cmos_time ();
              xtime.tv_usec = 0;
... ... }
Note:實時時鐘RTC的最主要做用即是在系統啓動時用來初始化xtime變量。
2 .Linux 的時鐘中斷處理程序
       Linux下時鐘中斷處理由time_interrupt() 函數實現,主要完成如下任務:
l          得到xtime_lock鎖,以便對訪問的jiffies_64 (kernel2.6)和 xtime進行保護
l          須要時應答或從新設置系統時鐘。
l          週期性的使用系統時間(wall_time)更新實時時鐘RTC
l          調用體系結構無關的時鐘例程:do_timer()。
do_timer()主要完成如下任務
l          更新jiffies;
l          更新系統時間(wall_time),該時間存放在xtime變量中
l          執行已經到期的動態定時器
l          計算平均負載值
void do_timer(unsigned long ticks)
{
 jiffies_64 += ticks;
 update_process_times(user_mode(regs));
 update_times (ticks);
}
static inline void update_times(unsigned long ticks)
{
 update_wall_time ();
 calc_load (ticks);
}
                time_interrupt ()
            
                 static void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) { 
                   int count;
                   write_lock (&xtime_lock); //得到xtime_lock鎖
 
                     if(use_cyclone)
                         mark_timeoffset_cyclone();
                     else if (use_tsc) {
                         rdtscl(last_tsc_low); //TSC register到last_tsc_low
                     spin_lock (&i8253_lock); //對自旋鎖i8253_lock加鎖,對8254PIT訪問
                     outb_p (0x00, 0x43);    
 
                     count = inb_p(0x40);   
                     count |= inb(0x40) << 8;
                     if (count > LATCH) {
                         printk (KERN_WARNING "i8253 count too high! resetting../n");
                         outb_p (0x34, 0x43);
                         outb_p (LATCH & 0xff, 0x40);
                         outb(LATCH >> 8, 0x40);
                         count = LATCH - 1;
                     }
                   spin_unlock (&i8253_lock);
 
                     if (count = = LATCH) {
                             count- -;
                     }
 
                     count = ((LATCH-1) - count) * TICK_SIZE;
                     delay_at_last_interrupt = (count + LATCH/2) / LATCH;
                     } //end use_tsc
                     do_timer_interrupt (irq, NULL, regs);
                     write_unlock(&xtime_lock);
}//end time_interrupt
 
do_timer_interrupt():
    static inline void do_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
……
do_timer(regs);
if((time_status & STA_UNSYNC)= =0&&xtime.tv_sec> last_rtc_update + 660 && xtime.tv_usec >= 500000 - ((unsigned) tick) / 2 && xtime.tv_usec <= 500000 + ((unsigned) tick) / 2) {
      if (set_rtc_mmss(xtime.tv_sec) == 0)
          last_rtc_update = xtime.tv_sec;
      else
          last_rtc_update = xtime.tv_sec - 600;
……
 }
do_timer_interrupt()主要完成: 調用do_timer()和判斷是否須要更新CMOS時鐘。更新CMOS時鐘的條件以下:三個須同時成立
    1.系統全局時間狀態變量time_status中沒有設置STA_UNSYNC標誌,即Linux沒有設置外部同步時鐘(如NTP)
    2.自從上次CMOS時鐘更新已通過去11分鐘。全局變量last_rtc_update保存上次更新CMOS時鐘的時間.
    3.因爲RTC存在Update Cycle,所以應在一秒鐘間隔的中間500ms左右調用set_rtc_mmss()函數,將當前時間xtime.tv_sec寫回RTC中。
Note. Linux kernel 中定義了一個相似jiffies的變量wall_jiffies,用於記錄kernel上一次更新xtime時,jiffies的值。
 
Summary : Linux kernel在啓動時,經過讀取RTC裏的時間日期初始化xtime,此後由kernel經過初始PIT來提供軟時鐘。
                       時鐘中斷處理過程可概括爲:系統時鐘system timer在IRQ0上產生中斷;kernel調用time_interrupt();time_interrupt()判斷系統是否使用TSC,若使用 則讀取TSC register;而後讀取PIT 通道0的計數值;調用do_time_interrupt(),實現系統時間更新.
相關文章
相關標籤/搜索