一、前言node
Linux內核中的定時器是一個很經常使用的功能,某些須要週期性處理的工做都須要用到定時器。在Linux內核中,使用定時器功能比較簡單,須要提供定時器的超時時間和超時後須要執行的處理函數。linux
二、經常使用API接口ios
在Linux內核中使用全局變量jiffies來記錄系統從啓動以來的系統節拍數,當系統內核啓動的時候,會將該jiffies初始化爲0,該定義在kernel/include/linux/jiffies.h文件中,以下:框架
extern u64 __jiffy_data jiffies_64; extern unsigned long volatile __jiffy_data jiffies; #if (BITS_PER_LONG < 64) u64 get_jiffies_64(void); #else static inline u64 get_jiffies_64(void) { return (u64)jiffies; } #endif
在上面的代碼中,jiffies_64與jiffies變量相似,jiffies_64用於64位的系統,而jiffies用於32位系統,Linux內核使用HZ表示每秒的節拍數,使用jiffies/HZ能夠得到系統已經運行的時間,單位爲秒。函數
/* time_is_before_jiffies(a) return true if a is before jiffies */ #define time_is_before_jiffies(a) time_after(jiffies, a) /* time_is_after_jiffies(a) return true if a is after jiffies */ #define time_is_after_jiffies(a) time_before(jiffies, a) /* time_is_before_eq_jiffies(a) return true if a is before or equal to jiffies*/ #define time_is_before_eq_jiffies(a) time_after_eq(jiffies, a) /* time_is_after_eq_jiffies(a) return true if a is after or equal to jiffies*/ #define time_is_after_eq_jiffies(a) time_before_eq(jiffies, a)
上面的四個宏能夠用於與當前系統的jiffies節拍數進行比較。ui
/* * Convert various time units to each other: */ extern unsigned int jiffies_to_msecs(const unsigned long j); extern unsigned int jiffies_to_usecs(const unsigned long j); extern unsigned long msecs_to_jiffies(const unsigned int m); extern unsigned long usecs_to_jiffies(const unsigned int u);
Linux內核中還提供了相關的API函數用於jiffies節拍數和毫秒或者微秒之間進行轉換,jiffies_to_msecs()和jiffies_to_usecs()用於將傳入的jiffies轉換爲對應得毫秒和微秒,msecs_to_jiffies()和usecs_to_jiffies()用於將毫秒和微秒轉換爲jiffies節拍數。this
Linux內核中使用struct timer_list結構體表示內核定時器,該結構體的定義在文件include/linux/timer.h中:spa
struct timer_list { /* * All fields that change during normal runtime grouped to the * same cacheline */ struct hlist_node entry; unsigned long expires; void (*function)(unsigned long); unsigned long data; u32 flags; #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
結構成員介紹:指針
entry:鏈入hlist鏈表的元素節點;code
expires:該定時器的超時時間,單位爲節拍數;
function:須要定時處理的函數指針;
data:傳遞給function函數的參數。
接下來,簡單介紹一下經常使用的定時器API函數接口:
#define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { \ .entry = { .next = TIMER_ENTRY_STATIC }, \ .function = (_function), \ .expires = (_expires), \ .data = (_data), \ .flags = (_flags), \ __TIMER_LOCKDEP_MAP_INITIALIZER( \ __FILE__ ":" __stringify(__LINE__)) \ }
宏__TIMER_INITIALIZER用於初始化一個定時器,主要是對其內部的成員進行一系列的賦值操做。
#define TIMER_INITIALIZER(_function, _expires, _data) \ __TIMER_INITIALIZER((_function), (_expires), (_data), 0) #define DEFINE_TIMER(_name, _function, _expires, _data) \ struct timer_list _name = \ TIMER_INITIALIZER(_function, _expires, _data)
宏TIMER_INITIALIZER實際上是__TIMER_INITIALIZER的進一步封裝,DEFINE_TIMER則是定義一個名爲_name的定時器,並對其完成內部成員的初始化。
void init_timer_key(struct timer_list *timer, unsigned int flags, const char *name, struct lock_class_key *key); #define __init_timer(_timer, _flags) \ init_timer_key((_timer), (_flags), NULL, NULL) #define init_timer(timer) \ __init_timer((timer), 0)
宏init_timer用於初始化傳入的timer定時器,當咱們定義了一個timer_list結構體,能夠使用該宏進行定時器初始化。
#define __setup_timer(_timer, _fn, _data, _flags) \ do { \ __init_timer((_timer), (_flags)); \ (_timer)->function = (_fn); \ (_timer)->data = (_data); \ } while (0) #define setup_timer(timer, fn, data) \ __setup_timer((timer), (fn), (data), 0)
宏__setup_timer用於初始化定時器,並對timer_list結構體的成員進行設置,包括function函數指針、data和flag標誌,而宏setup_timer則是對宏__setup_timer的進一步封裝,其中flag成員設置爲0。
/** * timer_pending - is a timer pending? * @timer: the timer in question * * timer_pending will tell whether a given timer is currently pending, * or not. Callers must ensure serialization wrt. other operations done * to this timer, eg. interrupt contexts, or other CPUs on SMP. * * return value: 1 if the timer is pending, 0 if not. */ static inline int timer_pending(const struct timer_list * timer) { return timer->entry.pprev != NULL; }
函數timer_pending()用於判斷傳入的timer定時器是否被掛起,若是返回值爲1,則當前的定時器已經被掛起。
extern void add_timer(struct timer_list *timer);
當咱們對定時器完成初始化,以及內部成員的賦值後,能夠使用add_timer()函數向內核註冊定時器,當定時器在內核註冊後,便開始運行。
extern int del_timer(struct timer_list * timer); #ifdef CONFIG_SMP extern int del_timer_sync(struct timer_list *timer); #else # define del_timer_sync(t) del_timer(t) #endif
函數del_timer()用於刪除內核中已經註冊的定時器,在多處理器系統中,定時器可能會在其它處理器上運行,所以,在調用del_timer()函數刪除定時器要先等待其它處理器的定時器處理函數退出,del_timer_sync()函數是del_timer()函數的同步版本,會等待其它處理器處理完定時處理函數再刪除,del_timer_sync()不能用於中斷上下文。
extern int mod_timer(struct timer_list *timer, unsigned long expires);
參數:
timer:要修改超時時間的定時器結構指針;
expires:修改後的超時時間。
返回值:返回0表示定時器未被激活,返回1表示定時器已被激活。
關於定時器timer_list的經常使用API接口基本這些,更詳細的內容能夠查看文件include/linux/timer.h。
三、實例說明
接下來,將經過一個簡單的實例來講明在驅動程序中如何去使用定時器struct timer_list,該實例爲經過定時器去控制LED燈的點亮和熄滅,使用內核中platform_driver的框架去實現,並在對應的sysfs設備節點中導出屬性文件ctrl、gpio和timer_peroid,在Linux的應用層對ctrl進行讀寫能實現定時器的打開和關閉,對gpio進行讀,可以顯示對應的GPIO號,對timer_peroid進行寫可以控制定時器的週期,該文件的值以毫秒爲單位。
先來看一下內核定時器的通常使用思路,以下:
#include <linux/module.h> #include <linux/init.h> #include <linux/timer.h> ... struct device_drvdata { struct timer_list timer; ... }; /* 定時器超時調用此函數 */ static void timer_function(unsigned long data) { struct device_drvdata *pdata = (struct device_drvdata *)data; /* 定時器的處理代碼 */ ... /* 從新設置超時值並啓動定時器 */ mod_timer(pdata->timer, jiffies + msecs_to_jiffies(1000)); } static int __init device_init(void) { struct device_drvdata *pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; /* 設備的其它處理代碼 */ ... /* 定時器初始化 */ init_timer(&pdata->timer); /* 設置超時時間 */ pdata->timer.expires = jiffies + msecs_to_jiffies(2000); /* 設置定時器超時調用函數以及傳遞的參數 */ setup_timer(&pdata->timer, timer_function, (unsigned long)pdata); /* 啓動定時器 */ add_timer(&pdata->timer); .... return 0; } static void __exit device_exit(void) { /* 設備的其它處理代碼 */ ... /* 刪除定時器 */ del_timer(&pdata->timer); ... } module_init(device_init); module_exit(device_exit);
上面的代碼只是定時器的大概使用思路,也就是須要對嵌入的定時器進行初始化,而後實現定時功能函數,對其進行設置後,而後再經過add_timer()函數添加到系統中啓動運行。
接下來給出實例說明的具體實現過程,以下:
首先,由於要用到GPIO口,經過設備樹進行GPIO的定義,以下:
timer_led { status = "okay"; compatible = "timer-led"; //和驅動匹配的屬性值 dev,name = "timer-led"; gpio-label = "timer_led_gpio"; gpios = <&msm_gpio 97 0>; //設備的GPIO引腳 };
接下來是驅動代碼的實現,使用了內核中platform_driver框架,以下:
#include <linux/module.h> #include <linux/init.h> #include <linux/timer.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #include <linux/err.h> #include <linux/string.h> #include <linux/mutex.h> #define FALSE 0 #define TRUE 1 struct timer_led_drvdata { const char *dev_name; const char *gpio_label; int led_gpio; enum of_gpio_flags led_flag; struct timer_list timer; unsigned int timer_peroid; bool timer_state; bool led_state; struct mutex mutex_lock; };
static void timer_led_function(unsigned long data) { struct timer_led_drvdata *pdata = (struct timer_led_drvdata *)data; if (pdata->led_state) { gpio_set_value(pdata->led_gpio, FALSE); pdata->led_state = FALSE; } else { gpio_set_value(pdata->led_gpio, TRUE); pdata->led_state = TRUE; } mod_timer(&pdata->timer, jiffies + msecs_to_jiffies(pdata->timer_peroid)); } static ssize_t ctrl_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; struct timer_led_drvdata *pdata = dev_get_drvdata(dev); if (pdata->timer_state) ret = snprintf(buf, PAGE_SIZE - 2, "enable"); else ret = snprintf(buf, PAGE_SIZE - 2, "disable"); buf[ret++] = '\n'; buf[ret] = '\0'; return ret; } static ssize_t ctrl_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct timer_led_drvdata *pdata = dev_get_drvdata(dev); struct timer_list *timer = &pdata->timer; mutex_lock(&pdata->mutex_lock); if (0 == strncmp(buf, "enable", strlen("enable"))) { if (!pdata->timer_state) { timer->expires = jiffies + msecs_to_jiffies(pdata->timer_peroid); add_timer(timer); pdata->timer_state = TRUE; goto ret; } } else if (0 == strncmp(buf, "disable", strlen("disable"))) { if (pdata->timer_state) { if (gpio_get_value(pdata->led_gpio)) { gpio_set_value(pdata->led_gpio, FALSE); pdata->led_state = FALSE; } del_timer_sync(timer); pdata->timer_state = FALSE; goto ret; } } mutex_unlock(&pdata->mutex_lock); return 0; ret: mutex_unlock(&pdata->mutex_lock); return strlen(buf); } static DEVICE_ATTR(ctrl, 0644, ctrl_show, ctrl_store); static ssize_t gpio_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; struct timer_led_drvdata *pdata = dev_get_drvdata(dev); ret = snprintf(buf, PAGE_SIZE - 2, "timer-led-gpio: GPIO_%d", pdata->led_gpio - 911); buf[ret++] = '\n'; buf[ret] = '\0'; return ret; } static DEVICE_ATTR(gpio, 0444, gpio_show, NULL); static ssize_t timer_peroid_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; struct timer_led_drvdata *pdata = dev_get_drvdata(dev); ret = snprintf(buf, PAGE_SIZE - 2, "%d", pdata->timer_peroid); buf[ret++] = '\n'; buf[ret] = '\0'; return ret; } static ssize_t timer_peroid_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct timer_led_drvdata *pdata = dev_get_drvdata(dev); int ret; ret = kstrtouint(buf, 0, &pdata->timer_peroid); if (ret < 0) { dev_err(dev, "failed to convert string for timer peroid\n"); return -EINVAL; } return strlen(buf); } static DEVICE_ATTR(timer_peroid, 0644, timer_peroid_show, timer_peroid_store); static struct attribute *timer_led_attr[] = { &dev_attr_ctrl.attr, &dev_attr_gpio.attr, &dev_attr_timer_peroid.attr, NULL }; static const struct attribute_group attr_group = { .attrs = timer_led_attr, }; static int timer_led_probe(struct platform_device *pdev) { int ret; struct timer_led_drvdata *pdata; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; printk("[%s]==========timer_led driver probe start==========\n", __func__); if (!np) return -ENODEV; pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; platform_set_drvdata(pdev, pdata); /* parse device tree node */ ret = of_property_read_string(np, "dev,name", &pdata->dev_name); if (ret) { dev_err(dev, "failed to read property of dev,name\n"); goto fail1; } ret = of_property_read_string(np, "gpio-label", &pdata->gpio_label); if (ret) { dev_err(dev, "failed to read property of gpio-label\n"); goto fail1; } pdata->led_gpio = of_get_named_gpio_flags(np, "gpios", 0, &pdata->led_flag); if (pdata->led_gpio < 0) { dev_err(dev, "failed to read property of gpio\n"); goto fail1; } /* init gpio */ if (gpio_is_valid(pdata->led_gpio)) { ret = gpio_request_one(pdata->led_gpio, pdata->led_flag, pdata->gpio_label); if (ret) { dev_err(dev, "failed to request the gpio\n"); goto fail1; } ret = gpio_direction_output(pdata->led_gpio, 0); if (ret) { dev_err(dev, "failed to set gpio direction output\n"); goto fail2; } ret = gpio_export(pdata->led_gpio, false); if (ret) { dev_err(dev, "failed to export gpio in sysfs\n"); goto fail2; } } else { dev_err(dev, "the gpio of timer-led is not valid\n"); goto fail1; } mutex_init(&pdata->mutex_lock); /* timer init here */ init_timer(&pdata->timer); setup_timer(&pdata->timer, timer_led_function, (unsigned long)pdata); pdata->timer_state = FALSE; pdata->timer_peroid = 1000; pdata->led_state = FALSE; /* create attribute files */ ret = sysfs_create_group(&dev->kobj, &attr_group); if (ret) { dev_err(dev, "Failed to create attribute files\n"); goto fail2; } printk("[%s]==========timer_led driver probe over==========\n", __func__); return 0; fail2: gpio_free(pdata->led_gpio); fail1: kfree(pdata); return ret; } static int timer_led_remove(struct platform_device *pdev) { struct timer_led_drvdata *pdata = platform_get_drvdata(pdev); if (gpio_is_valid(pdata->led_gpio)) gpio_free(pdata->led_gpio); del_timer_sync(&pdata->timer); kfree(pdata); return 0; } static struct of_device_id timer_led_of_match[] = { { .compatible = "timer-led", }, { }, }; static struct platform_driver timer_led_driver = { .probe = timer_led_probe, .remove = timer_led_remove, .driver = { .name = "timer_led_driver", .owner = THIS_MODULE, .of_match_table = of_match_ptr(timer_led_of_match), }, }; module_platform_driver(timer_led_driver); MODULE_AUTHOR("HLY"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Driver for the timer led");
代碼比較簡單,沒啥好說的,驅動加載時timer_led_probe()函數會調用,應用層則是經過sysfs中設備的屬性文件進行操做。
接下來是實現效果,當驅動加載完成後,生成相應的設備節點和屬性文件,以下:
因爲使用的內核中的platform_driver驅動框架,經過uevent屬性文件,能看到整個設備節點的相關信息,在該設備節點中,咱們本身添加的設備屬性文件也成功生成,內容以下所示:
在驅動程序中,對定時器的初始化週期爲1000毫秒,定時器默認爲關閉,經過使用下面的命令可修改定時器週期:
##設置定時器週期爲500毫秒 # echo 500 > timer_peroid
使用下面的命令啓動或者定時器:
##啓動定時器 # echo "enable" > ctrl ##關閉定時器 # echo "disable" > ctrl
驅動能正常工做的話,使用啓動定時器命令後,LED燈會隨必定的週期進行閃爍,另外,經過在sysfs文件系統中導出設備的屬性文件,能夠很容易的到達控制咱們設備的要求,並且可以很是方便地完成咱們的設備的控制。
四、小結
本篇文章主要介紹了Linux內核中的定時器struct timer_list結構體,並簡單介紹了關於定時器經常使用的API接口,最後,經過一個簡單的LED燈閃爍實例,來講明定時器的常規用法。