【前言】
最近在工程中接觸到調用 Linux 內核函數配置定時器實現 LED 閃爍效果的代碼。對定時器的使用方法寫個簡單小結。
【概述】
定時器的用法不復雜。調用過程分為以下幾步:
1、聲明一個 timer。
2、初始化 timer。
3、完善定時中斷服務函數。
4、注冊 timer 到定時器鏈表。
5、重新注冊 timer。
6、提前停止定時器 timer。(非必需步驟)
【函數調用】
1、聲明一個新的定時器可以使用語句 struct timer_list newTimer;
2、初始化定時器就是要為 timer 設定中斷服務函數及其參數、超時時間等值。
代碼示例:
setup_timer(newTimer, fn, data); // 實際上是一個宏。timer 是要設置的定時器,fn 是定時中斷服務函數,data 是傳遞給 fn 的參數。
newTimer.expires = jiffies + 10 * HZ; // 設定 10 秒後發生定時中斷
工程代碼示例:setup_timer(&trigger_data->timer, netdev_trig_timer, (unsigned long)trigger_data);
3、向中斷服務函數中添加的代碼應當位於上鎖/解鎖操作之間。
代碼示例:
static void netdev_trig_timer(unsigned long arg) // 我的工程中實際使用的回調函數,即 setup_timer 函數中的 fn 參數。根據需要不同,這個函數可以不同。中斷服務在運行時需要上鎖,運行結束時解鎖。
{
struct led_netdev_data *trigger_data = (struct led_netdev_data *)arg;
... // 變量聲明
write_lock(&trigger_data->lock); // 寫操作上鎖
... // 中斷服務函數主體
mod_timer(&trigger_data->timer, jiffies + trigger_data->interval); // 為定時器 timer 重新注冊。
write_unlock(&trigger_data->lock);
}
4、配置好定時器後,需要將定時器添加到定時器鏈表中才會生效。一旦將定時器添加進鏈表,定時器便開始工作了。
函數原型:
void add_timer(struct timer_list *timer); // 一般只在初始化結束後調用,之後的周期性添加工作一般使用 mod_timer() 函數完成
5、定時器配在置好後只運行一次,執行完中斷服務函數後定時器就會自動銷毀。
所以,如果想要實現周期性定時中斷就必須在中斷服務程序的結尾重新給定時器寫入超時值。
常用 mod_timer() 函數來完成這一步驟,其源碼如下:
int mod_timer(struct timer_list *timer, unsigned long expires)
{
int ret;
unsigned long flags;
spin_lock_irqsave(&timerlist_lock, flags);
timer->expires = expires; // 重新寫入超時值
ret = detach_timer(timer); // 停止定時器運行,相當於 del_timer()
internal_add_timer(timer); // 對定時器進行注冊,相當於 add_timer()
spin_unlock_irqrestore(&timerlist_lock, flags);
return ret;
}
需要注意,上面的源碼意味著該函數也會將一個處於未激活狀態的定時器激活。因此,使用了 mod_timer() 就無需再單獨調用 add_timer() 了。
代碼示例:
mod_timer(&trigger_data->timer, jiffies + trigger_data->interval);
6、如果想要在定時器中斷發生前停止定時器,可以使用 del_timer() 函數。
函數原型:
int del_timer(struct timer_list *timer);
在多 CPU 平台上可能發生要停止的定時器正在其它 CPU 上運行的情況,此時應該使用 del_timer_sync() 函數。
函數原型:
int del_timer_sync(struct timer_list *timer);
【相關概念】
jiffies 是一個類型為 unsigned long 的全局變量,表示系統啟動之後時鐘芯片產生的節拍總數。
Hz 每秒鐘 jiffies 增加的數值。從 2.5版本內核開始這個參數的內核空間默認值修改為 1000,而以前的版本裡該值是 100。可以在內核源碼根目錄下的 linux-4.3/.config 文件中查看 Hz 的內核默認值,命令 cat .config | grep -i config_hz。文件 /linux-4.3/include/asm-generic/param.h 中則有更詳細的記錄。
所以,如果想要知道系統已經運行了多長時間,可以按照 jiffies/Hz 的方式進行計算,從而獲得系統運行了多少秒。
在 64位 系統中,直接在代碼裡使用 jeffies 變量只能訪問到 低32位 的值。要獲取完全 64位 數值需要調用 get_jiffies_64() 函數。
【延伸閱讀】
[1]
《linux 內核定時器 timer_list》
[2]
《linux驅動之內核定時器驅動設計》
[3]
《定時器使用和延遲執行》