Linux内核定时器struct timer_list

摘要:
#如果(BITS_PER_LONG<64)u64get_jiffies_64(无效);#elstatisticinlineu64get_jiffies_64(void){return(u64)jiffies;msecs_to_jiffies()和usecs_to_jiffies)用于将毫秒和微秒转换为jiffies节拍。

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++] = ' '; buf[ret] = '

免责声明:内容来源于网络,仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇OpenCV实现图像变换(python)-仿射变换原理使用ApkTool以及dex2jar对apk进行反编译-更新异常以及解决方案下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Linux设备驱动——内核定时器

内核定时器使用 内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于<Linux/timer.h>和kernel/timer.c文件中。 被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则: 1)没有current指针、不允许访问用户空...

更新与发展 | Alibaba Cloud Linux 2 特性与开发细节揭秘

2019 年 4 月,Alibaba Cloud Linux 2 (Aliyun Linux 2) 正式开源。时至今日,已经走过三个月的里程。在这段时间内,这个刚诞生不久的为阿里云 ECS 环境定制优化的 Linux 操作系统发行版的装机量稳步上升。我们经常接到内部和外部的客户咨询 Alibaba Cloud Linux 2 相关的问题,因此本文将重点介绍...

关于51单片机串口通讯中向上位机发送字符的讨论

在调试串口通讯过程中搞清楚几个配合问题: 使用Keil带的C51库函数puts、printf之前必须置TI=1; 蓝色(一)处可以使用ES=0、ES=1,这时串口中断中(二)处,有无TI=0均可; 如果(一)处不使用ES=0、ES=1,这是串口中断中(二)处,不能使用TI=0; puts发送完字符串后自动发换行符' ',printf需要手工加上换行符;...

stm32基本定时器timer6的原理与使用

/********************基本定时器 TIM 参数定义,只限 TIM6、7************/ /* 一、定时器分类 STM32F1 系列中,除了互联型的产品,共有 8 个定时器,分为基本定时器,通用定时器和高级定时器。基本定时器 TIM6 和 TIM7 是一个 16 位的只能向上计数的定时器,只能定时,没有外部 IO。通用定...

NIO与AIO,同步/异步,阻塞/非阻塞

1.flip(),compact(),与clear()的使用 flip()内部实现,先将limit设为当前位置,再将缓冲区的postion设为0,所以是为将缓冲区的数据写出到其它通道或者get()作准备。 clear()内部实现,将limit设为缓冲区的容量,position设为0,limit的不同为clear()与flip()的区别,所以clear()是...

VC++或QT下 高精度 多媒体定时器

在VC编程中,用SetTimer可以定义一个定时器,到时间了,就响应OnTimer消息,但这种定时器精度太低了。如果需要精度更高一些的定时器(精 确到1ms),可以使用下面的高精度多媒体定时器进行代码优化,可以达到毫秒级的精度,而且使用方便。先要包含头文件"mmsystem.h"和库文 件"winmm.lib"。  虽然Win95下可视化开发工具如VC、D...