1、前言

Linux内核中的定时器是一个很常用的功能,某些需要周期性处理的工作都需要用到定时器。在Linux内核中,使用定时器功能比较简单,需要提供定时器的超时时间和超时后需要执行的处理函数。

2、常用API接口

在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节拍数进行比较。

/*
* 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节拍数。

Linux内核中使用struct timer_list结构体表示内核定时器,该结构体的定义在文件include/linux/timer.h中:

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链表的元素节点;

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), ) #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), )

宏init_timer用于初始化传入的timer定时器,当我们定义了一个timer_list结构体,可以使用该宏进行定时器初始化。

#define __setup_timer(_timer, _fn, _data, _flags)            \
do { \
__init_timer((_timer), (_flags)); \
(_timer)->function = (_fn); \
(_timer)->data = (_data); \
} while () #define setup_timer(timer, fn, data) \
__setup_timer((timer), (fn), (data), )

宏__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。

3、实例说明

接下来,将通过一个简单的实例来说明在驱动程序中如何去使用定时器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());
} 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();
/* 设置定时器超时调用函数以及传递的参数 */
setup_timer(&pdata->timer, timer_function, (unsigned long)pdata);
/* 启动定时器 */
add_timer(&pdata->timer); ....
return ;
} 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 >;  //设备的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 - , "enable");
else
ret = snprintf(buf, PAGE_SIZE - , "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 ( == 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 ( == 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 ; ret:
mutex_unlock(&pdata->mutex_lock);
return strlen(buf);
}
static DEVICE_ATTR(ctrl, , 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 - , "timer-led-gpio: GPIO_%d",
pdata->led_gpio - );
buf[ret++] = '\n';
buf[ret] = '\0'; return ret;
}
static DEVICE_ATTR(gpio, , 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 - , "%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, , &pdata->timer_peroid);
if (ret < ) {
dev_err(dev, "failed to convert string for timer peroid\n");
return -EINVAL;
} return strlen(buf);
}
static DEVICE_ATTR(timer_peroid, , 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", , &pdata->led_flag);
if (pdata->led_gpio < ) {
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, );
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 = ;
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 ; 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 ;
} 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 > timer_peroid

使用下面的命令启动或者定时器:

##启动定时器
# echo "enable" > ctrl ##关闭定时器
# echo "disable" > ctrl

驱动能正常工作的话,使用启动定时器命令后,LED灯会随一定的周期进行闪烁,另外,通过在sysfs文件系统中导出设备的属性文件,可以很容易的到达控制我们设备的要求,而且能够非常方便地完成我们的设备的控制。

4、小结

本篇文章主要介绍了Linux内核中的定时器struct timer_list结构体,并简单介绍了关于定时器常用的API接口,最后,通过一个简单的LED灯闪烁实例,来说明定时器的常规用法。

Linux内核定时器struct timer_list的更多相关文章

  1. 芯灵思Sinlinx A64开发板Linux内核定时器编程

    开发平台 芯灵思Sinlinx A64 内存: 1GB 存储: 4GB 开发板详细参数 https://m.tb.cn/h.3wMaSKm 开发板交流群 641395230 Linux 内核定时器是内 ...

  2. 全志A33开发板Linux内核定时器编程

    开发平台 * 芯灵思SinlinxA33开发板 淘宝店铺: https://sinlinx.taobao.com/ 嵌入式linux 开发板交流 QQ:641395230 Linux 内核定时器是内核 ...

  3. 芯灵思SinlinxA33开发板Linux内核定时器编程

    开发平台 * 芯灵思SinlinxA33开发板 淘宝店铺: https://sinlinx.taobao.com/ 嵌入式linux 开发板交流 QQ:641395230 Linux 内核定时器是内核 ...

  4. 模仿linux内核定时器代码,用python语言实现定时器

    大学无聊的时候看过linux内核的定时器,如今已经想不起来了,也不知道当时有没有看懂,如今想要模仿linux内核的定时器.用python写一个定时器,已经想不起来它的设计原理了.找了一篇blog,li ...

  5. Linux内核——定时器和时间管理

    定时器和时间管理 系统定时器是一种可编程硬件芯片.它能以固定频率产生中断.该中断就是所谓的定时器中断.它所相应的中断处理程序负责更新系统时间,还负责执行须要周期性执行的任务. 系统定时器和时钟中断处理 ...

  6. Linux内核 - 定时器

    #include <linux/timer.h> //头文件 struct timer_list mytimer; //定义变量 static void my_timer(unsigned ...

  7. linux内核--定时器API

    /**<linux/timer.h> 定时器结构体 struct timer_list { ........ unsigned long expires; --内核希望定时器执行的jiff ...

  8. Linux内核定时器

    Linux使用struct    timer_list来描述一个定时器. 重要成员: expires:定时时长 *function:超时执行函数名使用流程: 1.定义定时器变量 /*定义定时器变量结构 ...

  9. linux 内核定时器

    无论何时你需要调度一个动作以后发生, 而不阻塞当前进程直到到时, 内核定时器是给你 的工具. 这些定时器用来调度一个函数在将来一个特定的时间执行, 基于时钟嘀哒, 并且 可用作各类任务; 例如, 当硬 ...

随机推荐

  1. vs扩展和更新插件的开发

    一.调试 以 MinimalisticView.vsix (https://github.com/poma/MinimalisticView) 为例. 正如 | Marketplace 上介绍的,这个 ...

  2. HttpHelper之我见

    前几月一直用一个Http的访问类去调用WebApi,说句实话最开始没觉有什么,一是技术老,二是觉得比较简单,但是最近我一直关注云开发和AI这块儿微软技术,看到云平台调用API大多类似,所以回想这个早年 ...

  3. JPA笔记4 ManyToMany

    package many_to_many; import java.util.HashSet; import java.util.Set; import javax.persistence.Entit ...

  4. c# Hashtable Synchronized vs SyncRoot

    Synchronized vs SyncRoot 我们知道,在.net的一些集合类型中,譬如Hashtable和ArrayList,都有Synchronized静态方法和SyncRoot实例方法,他们 ...

  5. Android培训准备资料之UI一些相似控件和控件一些相似属性之间的区别

    这一篇博客主要收集五大布局中的一些相似控件和控件一些相似属性之间的区别 ImageView ImageButton Button 三者有啥区别? (1)Button继承自TextView,ImageV ...

  6. vue---v-model的详细解答

    1.v-model:双向数据绑定的实现原理     等同于一个  v-bind  加   v-on <div id="app"> <!-- <input t ...

  7. Nginx02(环境配置以及基本使用)

    一:Nginx环境配置 反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet ...

  8. 基于图的异常检测(三):GraphRAD

    基于图的异常检测(三):GraphRAD 风浪 一个快乐的数据玩家/风控/图挖掘 24 人赞同了该文章 论文:<GraphRAD: A Graph-based Risky Account Det ...

  9. 进化后的const分析

    C语言中的const const修饰的变量是只读的,本质还是变量 const修饰的局部变量在栈上分配空间 const修饰的全局变量在只读存储区分配空间 const只在编译期有用,在运行期无用 注意:c ...

  10. 201871010118-唐敬博 《面向对象程序设计(java)》第十五周学习总结

    博文正文开头格式:(2分) 项目 内容 这个作业属于哪个课程 <https://www.cnblogs.com/nwnu-daizh/> 这个作业的要求在哪里 <https://ww ...