1 基本概念
定時機制連同一些更可見的內核活動(如檢查超時)來驅使進程切換。
兩種主要的定時測量:
- 保存當前的時間和日期,以便能通過time(), ftime()和gettimeofday()系統調用把它們返回給用戶程序。
- 維持定時器,這種機制能夠告訴內核或用戶程序某一時間間隔已經過去了。
定時測量是由基於固定頻率振盪器和計數器的幾個硬件電路完成的。
2 時鐘和定時器電路
時鐘電路用於跟蹤當前時間和產生精確的時間度量。
定時器電路由內核編程,所以它們以udingde,預先定義的頻率發出中斷。
時鐘電路的分類
- 用於跟蹤當前時間
- 實時時鐘RTC
- 時間戳計數器TSC
- 產生週期性的時鐘中斷,用於計時
- 可編程間隔定時器PIT
2.1 實時時鐘RTC——IRQ8上產生中斷
當PC被切斷電源,RTC還繼續工作。
內核通過0x70和0x71I/O端口訪問RTC。
能在IRQ8上發出週期性的中斷,頻率在2HZ~8192HZ之間,可編程
2.2 時間戳計數器TSC
在80x86微處理器中,有一個CLK輸入引線接收外部振盪器的時鐘信號。TSC在每個時鐘信號到來時加1.
TSC是一個64位的時間戳計數器寄存器,彙編指令rdtsc讀這個寄存器。Linux在初始化時系統時必須確定時鐘信號的頻率。
獲得tsc的時鐘頻率:calibrate_tsc()函數通過計算一個大約在5ms的時間間隔內所產生的時鐘信號的個數來算出CPU實際頻率。
Linux通過rdtscll()或rdtscl()用來讀取TSC的事。
與可編程間隔定時器相比,TSC可以獲得更精確的時鐘。
2.3 可編程間隔定時器PIT
使用I/O端口0x40~0x43
LInux給PC的第一個PIT進行編程,使它以大於1000Hz的頻率向IRQ0發出時鐘中斷,即每1ms產生一次時鐘中斷,這個時間間隔叫做一個節拍(tick),它的長度以納秒爲單位存放在tick_nsec變量中。
由setup_pit_timer()進行初始化。在init_pit_timer()中初始化時鐘中斷頻率。
與系統時鐘信號有關的宏定義:
(1)宏定義Hz
在不同的體系機構下,系統時鐘所要求的可編程定時器中斷的頻率,即每秒tick的個數
(2)宏定義CLOCK_TICK_RATE
記錄了不同體系結構下,驅動可編程定時器工作的輸入時鐘頻率
(3)宏定義LATCH
記錄了上述兩個宏定義的比值,用於在內核初始化過程中設置可編程定時器中計數器寄存器counter的初始值。
3 Linux計時體系結構
LInux的計時體系結構是一組與時間流相關的內核數據結構和函數。
功能:
- 更新自系統啓動以來所經過的時間
- 更新時間和日期
- 確定當前進程的執行時間,考慮是否要搶佔
- 更新資源使用統計計數
- 檢查到期的軟定時器
內核有兩個基本的計時函數:
- 保持當前最新的時間
- 計算在當前秒內走過的納秒數
在單處理器系統中,所有定時活動都由IRQ0上的時鐘中斷觸發,包括:
- 在中斷中立即執行的部分
- 作爲下半部分延遲執行的部分
3.1 計時體系結構的數據結構
3.1.1定時器對象(時鐘源)
爲了使用一種統一的方法來處理可能存在的定時器資源,內核使用能夠了“定時器對象”,它是timer_opts類型的一個描述符。其中最重要的兩個方法:
mark_offset:由時鐘中斷處理程序調用,並以適當的數據結構記錄每個節拍到來時的準確時間。
get_offset:使用已記錄的值來計算上一次時鐘中斷(節拍)以來經過的時間。
這兩種方法,使得Linuxd計時體系結構能夠打到子節拍的分辨率,也就是說,內核能夠以比節拍週期更高的精度來測定當前的時間,這種操作被稱爲“定時插補”。
在內核初始化期間,select_timer()函數設置cur_timer指向適當定時器對象(時鐘源)的地址。變量timer_cur存放了某個定時器對應的那個的地址,該定時器是系統可利用的定時器資源中最好的。
3.1.2jiffies變量
一個計數器,用來記錄自系統啓動以來產生的節拍總數。
因爲一秒鐘內產生系統時鐘中斷次數等於宏定義HZ的值,所以變量jiffies的值在一秒內增加HZ。
3.1.3xtime變量
xtime變量存放當前時間和日期,它是一個timespec類型的數據結構。以便內核對某些對象和事件作時間標記,如記錄文件的創建時間、修改時間、上次訪問時間,或者供用戶進程通過系統調用來使用。
基本每個tick更新一次。
3.2 單處理器系統上的計時體系結構
考點:tick_handle_periodic函數的功能(Linux的計時體系結構的功能)
tick_init調用clockevents_tegister_notifier註冊tick_notifier到clockevents_chain上。
Update_wall_time()完成變量xtime的更新。
time_init_hook()來設置系統時鐘中斷處理程序。
在時鐘中斷處理函數中:
會調用tick_init函數,書上很多流程中的函數最終都是被這個函數所調用,流程如下:
4 軟定時器和延遲函數
軟定時器:
- 動態定時器(內核)
- 間隔定時器(可以用戶)
動態定時器:被動態的創建和撤銷,當前活動的動態定時器個數沒有限制
定時器是一種軟件功能,即允許在將來的某個時刻,函數在給定的時間間隔用完時被調用。每個定時器都包含一個字段,表示定時器將需要多長時間纔到期。這個字段的初值就是jiffies的當前值加上合適的節拍數。
注意,對於必須嚴格遵守定時時間的那些實時應用而言,定時器並不適合,因爲定時器的檢查總是由可延遲函數進行。
4.1創建並激活一個動態定時器——init_timer初始化一個time_list對象
- 創建一個新的timer_list對象
- 調用init_timer初始化,並設置定時器要處理的函數和參數
- 設置定時時間
- 使用add_timer加入到合適的鏈表中
4.2動態定時器的數據結構
- 成員變量function:該函數指針變量保存了內核定時器超時後要執行的函數,即定時器超時處理函數。
- 成員變量data:該無符號長整型變量用作定時器超時處理函數的參數。
- 成員變量base:該指針變量表明瞭該內核定時器節點歸屬於系統中哪一個處理器,在使用函數init_timer()初始化內核定時器節點的過程中,將該指針指向了一個每處理器變量tvec_bases的成員變量t_base。
4.3動態定時器的維護
run_timer的主要功能
- 定時器時間表示參數加一
- 處理的定時器去除
- 依次處理到期定時器
動態定時器應用之delayed work
動態定時器應用之schedule_timeout: setup_time_on_stack(&timer, process_timeout, (unsigned long)current); timer時間到了之後,process_timeout函數將當前進程變爲等待態。
4.4延遲函數:
當內核需要等待一個較短的時間間隔,如幾毫秒,通常設備驅動器會等待預先定義的整個微秒直到硬件完成某些操作。這些情況下,內核使用udelay()和ndelay()函數:前者接收一個微秒級的時間間隔作爲它的參數,並在指定的延遲結束後返回,後者與前者類似,但是指定延遲的參數是納秒級的。