定时器struct timer_list的高级使用

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

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(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文件系统中导出设备的属性文件,可以很容易的到达控制我们设备的要求,而且能够非常方便地完成我们的设备的控制。
4、小结

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 定时器 timer_list 是一个内核数据结构,用于管理内核中的定时器。它是一个双向链表,每个节点表示一个定时器timer_list 的定义位于 `<linux/timer.h>` 头文件中。 每个 timer_list 节点的定义如下: ```c struct timer_list { struct list_head entry; // 定时器节点的链表指针 unsigned long expires; // 定时器的到期时间 void (*function)(unsigned long); // 定时器回调函数 unsigned long data; // 传递给回调函数的参数 struct tvec_base *base; // 定时器所属的时间轮 int slack; // 定时器的松弛时间 }; ``` 其中,`entry` 是一个 `list_head` 结构,用于将节点连接到定时器链表中。`expires` 表示定时器的到期时间,以 jiffies 单位表示。`function` 是定时器的回调函数,在定时器到期时被调用。`data` 是传递给回调函数的参数。`base` 表示定时器所属的时间轮,`slack` 是定时器的松弛时间,用于处理定时器的精度。 在使用 timer_list 时,可以使用以下函数进行初始化和操作: - `timer_setup(struct timer_list *timer, void (*function)(unsigned long), unsigned int flags)`:初始化一个定时器,并指定回调函数和标志。 - `init_timer(struct timer_list *timer)`:初始化一个定时器。 - `add_timer(struct timer_list *timer)`:将定时器添加到定时器链表中。 - `del_timer(struct timer_list *timer)`:从定时器链表中删除定时器。 - `mod_timer(struct timer_list *timer, unsigned long expires)`:修改定时器的到期时间。 这些函数可以通过 `<linux/timer.h>` 头文件中的宏来调用。通过操作 timer_list,可以实现在 Linux 内核中的定时器功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值