在Linux驅動中使用timer定時器
原文(有刪改): https://www.cnblogs.com/chen-farsight/p/6226562.html
介紹
內核定時器是內核用來控制在未來某個時間點(基於jiffies)調度執行某個函數的一種機制,其實現位於kernel/linux/timer.h
和kernel/timer.c
文件中。
被調度的函數肯定是異步執行的,它類似於一種“軟件中斷”,而且是處於非進程的上下文中,所以調度函數必須遵守以下規則:
- 沒有 current 指針、不允許訪問用戶空間。因為沒有進程上下文,相關代碼和被中斷的進程沒有任何聯系。
- 不能執行休眠(或可能引起休眠的函數)和調度。
- 任何被訪問的數據結構都應該針對並發訪問進行保護,以防止競爭條件。
內核定時器的調度函數運行過一次后就不會再被運行了(相當於自動注銷),但可以通過在被調度的函數中重新調度自己來周期運行。
在SMP系統中,調度函數總是在注冊它的同一CPU上運行,以盡可能獲得緩存的局域性。
原型定義
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;
unsigned long expires;
struct tvec_base *base;
void (*function)(unsigned long);
unsigned long data;
int slack;
#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
其中 expires 字段表示期望定時器執行的 jiffies 值,到達該 jiffies 值時,將調用 function 函數,並傳遞 data 作為參數。
當一個定時器被注冊到內核之后,entry 字段用來連接該定時器到一個內核鏈表中。base 字段是內核內部實現所用的。
需要注意的是 expires 的值是32位的,因為內核定時器並不適用於長的未來時間點。
初始化
在使用 struct timer_list 之前,需要初始化該數據結構,確保所有的字段都被正確地設置。初始化有兩種方法。
方法一:基於宏定義DEFINE_
DEFINE_TIMER(timer_name, function_name, expires_value, data);
該宏會定義一個名叫 timer_name 內核定時器,並初始化其 function
, expires
, name
和base
字段。
方法二:調用接口
void init_timer(struct timer_list *timer); // 再加上賦值
void setup_timer(struct timer_list *timer, (*function)(unsigned long), unsigned long data);
上述init_timer函數將初始化struct timer_list的 entry的next 為 NULL ,並為base指針賦值 :
// timer function
void timer_function(unsigned long arg)
{
struct gpio_led_data *led_dat = (struct gpio_led_data *)arg;
INIT_WORK(&led_dat->delay_work, turn_off_led_work);
schedule_work(&led_dat->delay_work);
printk(KERN_ERR "turn_off_led!\n");
}
void mytimer_init(void)
{
// 聲明
struct timer_list mytimer;
int dat = 1;
// 初始化、賦值
#if 0 // 下面兩種等價
init_timer(&(mytimer));
mytimer.function = timer_function;
mytimer.data = (unsigned long)dat;
#else
// setup_timer 方法也可以用於初始化定時器並賦值其成員
setup_timer(&mytimer, timer_function, (unsigned long)dat);
#endif
mytimer.expires = jiffies + (秒數)*HZ;
}
注意,無論用哪種方法初始化,其本質都只是給字段賦值,所以只要在運行 add_timer() 之前,expires, function 和 data 字段都可以直接再修改。
注冊/開啟計時器
定時器要生效,還必須被連接到內核專門的鏈表中,這可以通過 void add_timer(struct timer_list *timer)
來實現。
注意,每次add_timer只會執行function
一次。如果需要執行的話,還需要重新調用add_timer
。
重新注冊(修改)
要修改一個定時器的調度時間,可以通過調用 mod_timer(struct timer_list *timer, unsigned long expires)
。mod_timer()
會重新注冊定時器到內核,而不管定時器函數是否被運行過。
注銷
注銷一個定時器,可以通過 del_timer(struct timer_list *timer)
或 del_timer_sync(struct timer_list *timer)
。
其中 del_timer_sync
是用在 SMP 系統上的(在非SMP系統上,它等於del_timer),當要被注銷的定時器函數正在另一個 cpu 上運行時,del_timer_sync()
會等待其運行完,所以這個函數會休眠。
另外還應避免它和被調度的函數爭用同一個鎖。對於一個已經被運行過且沒有重新注冊自己的定時器而言,注銷函數其實也沒什么事可做。
int timer_pending(const struct timer_list *timer);
這個函數用來判斷一個定時器是否被添加到了內核鏈表中以等待被調度運行。注意,當一個定時器函數即將要被運行前,內核會把相應的定時器從內核鏈表中刪除(相當於注銷)。
例子
實現每隔一秒向內核log中打印一條信息
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/time.h>
#include <linux/timer.h>
static struct timer_list tm;
struct timeval oldtv;
void callback(unsigned long arg)
{
struct timeval tv;
char *strp = (char*)arg;
printk("%s: %lu, %s\n", __func__, jiffies, strp);
do_gettimeofday(&tv);
printk("%s: %ld, %ld\n", __func__,
tv.tv_sec - oldtv.tv_sec, //與上次中斷間隔 s
tv.tv_usec- oldtv.tv_usec); //與上次中斷間隔 ms
oldtv = tv;
tm.expires = jiffies+1*HZ;
add_timer(&tm); //重新開始計時
}
static int __init demo_init(void)
{
printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__);
init_timer(&tm); //初始化內核定時器
do_gettimeofday(&oldtv); //獲取當前時間
tm.function= callback; //指定定時時間到后的回調函數
tm.data = (unsigned long)"hello world"; //回調函數的參數
tm.expires = jiffies+1*HZ; //定時時間, 1*HZ ,這里的 1 代表秒數
add_timer(&tm); //注冊定時器
return 0;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__);
del_timer(&tm); //注銷定時器
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Farsight");
MODULE_DESCRIPTION("Demo for kernel module");
附錄A:和時間相關的函數與變量
----------------------------------------------
linux/jiffies.h
jiffies 計數值:可用 函數 `u64 get_jiffies_64(void)` 獲取
----------------------------------------------
asm/param.h
HZ : 每秒觸發中斷的次數
----------------------------------------------
時間值
秒數=(jiffies(new) - jiffies(old))/HZ
jiffies(new) = jiffies(old) + 秒*HZ
----------------------------------------------
linux/delay.h
延時函數
void ssleep(unsigned int seconds);
void msleep(unsigned int msecs);
----------------------------------------------
時間函數
linux/time.h
void do_gettimeofday(struct timeval *tv)
附錄B:本文提到的定義原型
關於上面這些宏和函數的定義,具體參見 include/linux/timer.h。
#define init_timer(timer) \
__init_timer((timer), 0)
#define __init_timer(_timer, _flags) \
do { \
static struct lock_class_key __key; \
init_timer_key((_timer), (_flags), #_timer, &__key); \
} while (0)
#define __setup_timer(_timer, _fn, _data, _flags) \
do { \
__init_timer((_timer), (_flags)); \
(_timer)->function = (_fn); \
(_timer)->data = (_data); \
} while (0)
#define __setup_timer_on_stack(_timer, _fn, _data, _flags) \
do { \
__init_timer_on_stack((_timer), (_flags)); \
(_timer)->function = (_fn); \
(_timer)->data = (_data); \
} while (0)
#define setup_timer(timer, fn, data) \
__setup_timer((timer), (fn), (data), 0)
/**
* init_timer_key - initialize a timer
* @timer: the timer to be initialized
* @flags: timer flags
* @name: name of the timer
* @key: lockdep class key of the fake lock used for tracking timer
* sync lock dependencies
*
* init_timer_key() must be done to a timer prior calling *any* of the
* other timer functions.
*/
void init_timer_key(struct timer_list *timer, unsigned int flags,
const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, flags, name, key);
}
static void do_init_timer(struct timer_list *timer, unsigned int flags,
const char *name, struct lock_class_key *key)
{
struct tvec_base *base;
#ifdef CONFIG_SMP
if (flags & TIMER_DEFERRABLE)
base = tvec_base_deferral;
else
#endif
base = __raw_get_cpu_var(tvec_bases);
timer->entry.next = NULL;
timer->base = (void *)((unsigned long)base | flags);
timer->slack = -1;
#ifdef CONFIG_TIMER_STATS
timer->start_site = NULL;
timer->start_pid = -1;
memset(timer->start_comm, 0, TASK_COMM_LEN);
#endif
lockdep_init_map(&timer->lockdep_map, name, key, 0);
}
#define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { \
.entry = { .prev = TIMER_ENTRY_STATIC }, \
.function = (_function), \
.expires = (_expires), \
.data = (_data), \
.base = (void *)((unsigned long)&boot_tvec_bases + (_flags)), \
.slack = -1, \
__TIMER_LOCKDEP_MAP_INITIALIZER( \
__FILE__ ":" __stringify(__LINE__)) \
}
#define TIMER_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), 0)
#define TIMER_DEFERRED_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), TIMER_DEFERRABLE)
#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)