目錄
內核時間管理
系統節拍率
高/低節拍率的優缺點
jiffies 節拍數
時間繞回
時間轉換函數
內核定時器
timer_list 結構體
定時器API函數
init_timer 函數
add_timer 函數
del_timer 函數
del_timer_sync 函數
mod_timer 函數
Linux 內核短延時函數
內核時間管理
Linux 內核中有大量的函數需要時間管理,比如周期性的調度程序、延時程序,最常用的就是定時器。
系統節拍率
硬件定時器提供時鐘源,時鐘源的頻率可以設置, 設置好以后就周期性的產生定時中斷,系統使用定時中斷來計時。
中斷周期性產生的頻率就是系統頻率,也叫做節拍率(tick rate)。
在編譯 Linux 內核的時候可以通過圖形化界面設置系統節拍率,按照如下路徑打開配置界面:
-> Kernel Features? ?-> Timer frequency (<choice> [=y])
選中“Timer frequency”,可選的系統節拍率如下:
默認情況下選擇 100Hz。
設置好以后打開 Linux 內核源碼根目錄下的.config 文件,可以找到相關宏定義:
宏定義CONFIG_HZ 為 100, Linux 內核會使用 CONFIG_HZ 來設置自己的系統時鐘。
打開文件 include/asm-generic/param.h,有如下內容:
# undef HZ
# define HZ CONFIG_HZ
# define USER_HZ 100
# define CLOCKS_PER_SEC (USER_HZ)
定義了一個宏 HZ,表示一秒的節拍數,也就是頻率。
宏 HZ 就是 CONFIG_HZ,因此 HZ=100。
高/低節拍率的優缺點
-
??高節拍率??:通常 ≥1000Hz(如1000Hz=1ms/次)
-
??低節拍率??:通常 ≤100Hz(如100Hz=10ms/次)
高節拍率會提高系統時間精度,但也會導致中斷的產生更加頻繁,從而加劇系統的負擔。
所以需要根據項目的實際情況,選擇合適的系統節拍率。
jiffies 節拍數
Linux 內核使用全局變量 jiffies 來記錄系統從啟動以來的系統節拍數,系統啟動的時候會將 jiffies 初始化為 0,。
jiffies 定義在文件 include/linux/jiffies.h 中,定義如下:
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
jiffies_64 用于 64 位系統,而 jiffies 用于 32 位系統:
HZ 表示每秒的節拍數, jiffies 表示系統運行的 jiffies 節拍數,所以 jiffies/HZ 就是系統運行時間,單位為秒。
時間繞回
不管是 32 位還是 64 位的 jiffies,都有溢出的風險,溢出以后會重新從 0 開始計數。
存儲限制??:
-
32位
jiffies
:最大值?0xFFFFFFFF
(約49.7天溢出) -
64位
jiffies
:理論永不溢出(5.8 億年,實際內核仍做防御性編程)
// 32位無符號整數溢出示例
u32 jiffies = 0xFFFFFFFE;
jiffies += 3; // 結果變為1(非5)
Linux 內核提供了API 函數來處理繞回:
我們要判斷某段代碼執行時間有沒有超時,可以使用如下所示代碼:
unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超時的時間點 *//*************************************
具體的代碼
************************************//* 判斷有沒有超時 */
if(time_before(jiffies, timeout)) {/* 超時未發生 */
} else {/* 超時發生 */
}
timeout 就是超時時間點,通過函數 time_before 來判斷 jiffies 是否小于 timeout,如果小于的話就表示沒有超時。
時間轉換函數
Linux 內核提供了幾個 jiffies 和 ms、 us、 ns 之間的轉換函數,如表:
這些函數使用的示例代碼如下:
#include <linux/jiffies.h>
#include <linux/ktime.h>void time_conversion_demo(void) {unsigned long jiffies_value;u64 nanoseconds;unsigned int milliseconds, microseconds;/* 示例1:jiffies轉時間單位 */jiffies_value = jiffies; // 獲取當前jiffies值// 轉換為毫秒/微秒/納秒milliseconds = jiffies_to_msecs(jiffies_value);microseconds = jiffies_to_usecs(jiffies_value);nanoseconds = jiffies_to_nsecs(jiffies_value);printk(KERN_INFO "Current jiffies: %lu\n", jiffies_value);printk(KERN_INFO "Converted to: %u ms, %u us, %llu ns\n", milliseconds, microseconds, nanoseconds);/* 示例2:時間單位轉jiffies */milliseconds = 1000; // 1秒microseconds = 1000000; // 1秒nanoseconds = 1000000000; // 1秒// 轉換為jiffiesjiffies_value = msecs_to_jiffies(milliseconds);printk(KERN_INFO "%u ms = %lu jiffies\n", milliseconds, jiffies_value);jiffies_value = usecs_to_jiffies(microseconds);printk(KERN_INFO "%u us = %lu jiffies\n", microseconds, jiffies_value);jiffies_value = nsecs_to_jiffies(nanoseconds);printk(KERN_INFO "%llu ns = %lu jiffies\n", nanoseconds, jiffies_value);/* 示例3:混合使用場景 */// 設置2秒后的超時點(考慮HZ可能不同)unsigned long timeout = jiffies + msecs_to_jiffies(2000);printk(KERN_INFO "Timeout at jiffies: %lu\n", timeout);
}
內核定時器
Linux 內核定時器采用系統時鐘來實現,內核定時器并不是周期性運行的,超時以后就會自動關閉。
因此如果想要實現周期性定時,那么就需要在定時處理函數中重新開啟定時器。
timer_list 結構體
Linux 內核使用 timer_list 結構體表示內核定時器。
timer_list 定義在文件include/linux/timer.h 中,定義如下:
struct timer_list {struct list_head entry; // 鏈表節點(用于加入定時器隊列)unsigned long expires; // 超時時間(基于jiffies的絕對時間)struct tvec_base *base; // 關聯的定時器管理基類void (*function)(unsigned long); // 回調函數指針unsigned long data; // 傳遞給回調函數的參數int slack; // 允許的時間誤差(優化用)
};
典型使用流程如下:
定時器API函數
linux 內核定時器常用的 API 函數如下:
init_timer 函數
init_timer 函數負責初始化 timer_list 類型變量,當我們定義了一個 timer_list 變量以后一定要先用 init_timer 初始化一下。
init_timer 函數原型如下:
void init_timer(struct timer_list *timer)
- timer:要初始化定時器。
add_timer 函數
add_timer 函數用于向 Linux 內核注冊定時器,使用 add_timer 函數向內核注冊定時器以后,定時器就會開始運行。
add_timer函數原型如下:
void add_timer(struct timer_list *timer)
- timer:要注冊的定時器。
del_timer 函數
del_timer 函數用于刪除一個定時器,不管定時器有沒有被激活,都可以使用此函數刪除。
在多處理器系統上,定時器可能會在其他的處理器上運行,因此在調用 del_timer 函數刪除定時器之前要先等待其他處理器的定時處理器函數退出。
del_timer 函數原型如下:
int del_timer(struct timer_list * timer)
- timer:要刪除的定時器。
- 返回值: 0,定時器還沒被激活; 1,定時器已經激活。
del_timer_sync 函數
del_timer_sync 函數是 del_timer 函數的同步版,會等待其他處理器使用完定時器再刪除, del_timer_sync 不能使用在中斷上下文中。
del_timer_sync 函數原型如下所示:
int del_timer_sync(struct timer_list *timer)
- timer:要刪除的定時器。
- 返回值: 0,定時器還沒被激活; 1,定時器已經激活。
mod_timer 函數
mod_timer 函數用于修改定時值,如果定時器還沒有激活的話, mod_timer 函數會激活定時器。
mod_timer? 函數原型如下:
int mod_timer(struct timer_list *timer, unsigned long expires)
- timer:要修改超時時間(定時值)的定時器。
- expires:修改后的超時時間。
- 返回值: 0,調用 mod_timer 函數前定時器未被激活;
- ? ? ? ? ? ? ? ?1,調用 mod_timer 函數前定時器已被激活。
內核定時器一般的使用流程如下所示:
struct timer_list timer; /* 定義定時器 *//* 定時器回調函數 */
void function(unsigned long arg) {/** 定時器處理代碼*//* 如果需要定時器周期性運行的話就使用 mod_timer* 函數重新設置超時值并且啟動定時器。*/mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
}/* 初始化函數 */
void init(void) {init_timer(&timer); /* 初始化定時器 */timer.function = function; /* 設置定時處理函數 */timer.expires = jffies + msecs_to_jiffies(2000); /* 超時時間 2 秒 */timer.data = (unsigned long)&dev; /* 將設備結構體作為參數 */add_timer(&timer); /* 啟動定時器 */
}/* 退出函數 */
void exit(void) {del_timer(&timer); /* 刪除定時器 *//* 或者使用 */del_timer_sync(&timer);
}
Linux 內核短延時函數
在?Linux 內核種,提供了毫秒、微秒和納秒延時函數,如表:
使用示例如下:
#include <linux/delay.h> // 必須包含的頭文件void hardware_operations(void)
{/* 硬件寄存器寫入后需要穩定時間 */writel(0x55AA, reg_addr);// 納秒級延時(通常用于信號電平穩定)ndelay(100); // 延遲100納秒/* 發送啟動命令 */writel(0xCC33, cmd_reg);// 微秒級延時(適合短時等待)udelay(50); // 延遲50微秒/* 檢查設備狀態 */while (!(readl(status_reg) & READY_BIT)) {// 毫秒級延時(較長等待)mdelay(1); // 每次循環延遲1毫秒}// 更長的延時(不推薦在原子上下文使用)mdelay(100); // 延遲100毫秒
}