Linux内核定时器struct timer_list

一、前言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灯闪烁实例,来讲明定时器的常规用法。

相关文章
相关标签/搜索