在Linux驱动中使用timer定时器

原文(有删改): https://www.cnblogs.com/chen-farsight/p/6226562.html

介绍

内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于kernel/linux/timer.hkernel/timer.c 文件中。

被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则:

  • 没有 current 指针、不允许访问用户空间。因为没有进程上下文,相关代码和被中断的进程没有任何联系。
  • 不能执行休眠(或可能引起休眠的函数)和调度。
  • 任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。

内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。

在SMP系统中,调度函数总是在注册它的同一CPU上运行,以尽可能获得缓存的局域性。

原型定义

struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;
unsigned long expires;
struct tvec_base *base; void (*function)(unsigned long);
unsigned long data; int slack; #ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};

其中 expires 字段表示期望定时器执行的 jiffies 值,到达该 jiffies 值时,将调用 function 函数,并传递 data 作为参数。

当一个定时器被注册到内核之后,entry 字段用来连接该定时器到一个内核链表中。base 字段是内核内部实现所用的。

需要注意的是 expires 的值是32位的,因为内核定时器并不适用于长的未来时间点。

初始化

在使用 struct timer_list 之前,需要初始化该数据结构,确保所有的字段都被正确地设置。初始化有两种方法。

方法一:基于宏定义DEFINE_

DEFINE_TIMER(timer_name, function_name, expires_value, data);

该宏会定义一个名叫 timer_name 内核定时器,并初始化其 function, expires, namebase字段。

方法二:调用接口

void init_timer(struct timer_list *timer); // 再加上赋值
void setup_timer(struct timer_list *timer, (*function)(unsigned long), unsigned long data);

上述init_timer函数将初始化struct timer_list的 entry的next 为 NULL ,并为base指针赋值 :

// timer function
void timer_function(unsigned long arg)
{
struct gpio_led_data *led_dat = (struct gpio_led_data *)arg;
INIT_WORK(&led_dat->delay_work, turn_off_led_work);
schedule_work(&led_dat->delay_work);
printk(KERN_ERR "turn_off_led!\n");
} void mytimer_init(void)
{
// 声明
struct timer_list mytimer;
int dat = 1; // 初始化、赋值
#if 0 // 下面两种等价
init_timer(&(mytimer));
mytimer.function = timer_function;
mytimer.data = (unsigned long)dat;
#else
// setup_timer 方法也可以用于初始化定时器并赋值其成员
setup_timer(&mytimer, timer_function, (unsigned long)dat);
#endif
mytimer.expires = jiffies + (秒数)*HZ;
}

注意,无论用哪种方法初始化,其本质都只是给字段赋值,所以只要在运行 add_timer() 之前,expires, function 和 data 字段都可以直接再修改。

注册/开启计时器

定时器要生效,还必须被连接到内核专门的链表中,这可以通过 void add_timer(struct timer_list *timer) 来实现。

注意,每次add_timer只会执行function一次。如果需要执行的话,还需要重新调用add_timer

重新注册(修改)

要修改一个定时器的调度时间,可以通过调用 mod_timer(struct timer_list *timer, unsigned long expires)mod_timer() 会重新注册定时器到内核,而不管定时器函数是否被运行过。

注销

注销一个定时器,可以通过 del_timer(struct timer_list *timer)del_timer_sync(struct timer_list *timer)

其中 del_timer_sync 是用在 SMP 系统上的(在非SMP系统上,它等于del_timer),当要被注销的定时器函数正在另一个 cpu 上运行时,del_timer_sync() 会等待其运行完,所以这个函数会休眠。

另外还应避免它和被调度的函数争用同一个锁。对于一个已经被运行过且没有重新注册自己的定时器而言,注销函数其实也没什么事可做。

int timer_pending(const struct timer_list *timer);

这个函数用来判断一个定时器是否被添加到了内核链表中以等待被调度运行。注意,当一个定时器函数即将要被运行前,内核会把相应的定时器从内核链表中删除(相当于注销)。

例子

实现每隔一秒向内核log中打印一条信息

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/time.h>
#include <linux/timer.h> static struct timer_list tm;
struct timeval oldtv; void callback(unsigned long arg)
{
struct timeval tv;
char *strp = (char*)arg; printk("%s: %lu, %s\n", __func__, jiffies, strp); do_gettimeofday(&tv);
printk("%s: %ld, %ld\n", __func__,
tv.tv_sec - oldtv.tv_sec, //与上次中断间隔 s
tv.tv_usec- oldtv.tv_usec); //与上次中断间隔 ms oldtv = tv;
tm.expires = jiffies+1*HZ;
add_timer(&tm); //重新开始计时
} static int __init demo_init(void)
{
printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__); init_timer(&tm); //初始化内核定时器 do_gettimeofday(&oldtv); //获取当前时间
tm.function= callback; //指定定时时间到后的回调函数
tm.data = (unsigned long)"hello world"; //回调函数的参数
tm.expires = jiffies+1*HZ; //定时时间, 1*HZ ,这里的 1 代表秒数
add_timer(&tm); //注册定时器 return 0;
} static void __exit demo_exit(void)
{
printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__);
del_timer(&tm); //注销定时器
} module_init(demo_init);
module_exit(demo_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("Farsight");
MODULE_DESCRIPTION("Demo for kernel module");

附录A:和时间相关的函数与变量

----------------------------------------------
linux/jiffies.h
jiffies 计数值:可用 函数 `u64 get_jiffies_64(void)` 获取
----------------------------------------------
asm/param.h HZ : 每秒触发中断的次数
----------------------------------------------
时间值
秒数=(jiffies(new) - jiffies(old))/HZ
jiffies(new) = jiffies(old) + 秒*HZ ----------------------------------------------
linux/delay.h
延时函数
void ssleep(unsigned int seconds);
void msleep(unsigned int msecs);
----------------------------------------------
时间函数
linux/time.h
void do_gettimeofday(struct timeval *tv)

附录B:本文提到的定义原型

关于上面这些宏和函数的定义,具体参见 include/linux/timer.h。

#define init_timer(timer)                       \
__init_timer((timer), 0) #define __init_timer(_timer, _flags) \
do { \
static struct lock_class_key __key; \
init_timer_key((_timer), (_flags), #_timer, &__key); \
} while (0) #define __setup_timer(_timer, _fn, _data, _flags) \
do { \
__init_timer((_timer), (_flags)); \
(_timer)->function = (_fn); \
(_timer)->data = (_data); \
} while (0) #define __setup_timer_on_stack(_timer, _fn, _data, _flags) \
do { \
__init_timer_on_stack((_timer), (_flags)); \
(_timer)->function = (_fn); \
(_timer)->data = (_data); \
} while (0) #define setup_timer(timer, fn, data) \
__setup_timer((timer), (fn), (data), 0) /**
* init_timer_key - initialize a timer
* @timer: the timer to be initialized
* @flags: timer flags
* @name: name of the timer
* @key: lockdep class key of the fake lock used for tracking timer
* sync lock dependencies
*
* init_timer_key() must be done to a timer prior calling *any* of the
* other timer functions.
*/
void init_timer_key(struct timer_list *timer, unsigned int flags,
const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, flags, name, key);
} static void do_init_timer(struct timer_list *timer, unsigned int flags,
const char *name, struct lock_class_key *key)
{
struct tvec_base *base; #ifdef CONFIG_SMP
if (flags & TIMER_DEFERRABLE)
base = tvec_base_deferral;
else
#endif
base = __raw_get_cpu_var(tvec_bases); timer->entry.next = NULL;
timer->base = (void *)((unsigned long)base | flags);
timer->slack = -1;
#ifdef CONFIG_TIMER_STATS
timer->start_site = NULL;
timer->start_pid = -1;
memset(timer->start_comm, 0, TASK_COMM_LEN);
#endif
lockdep_init_map(&timer->lockdep_map, name, key, 0);
} #define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { \
.entry = { .prev = TIMER_ENTRY_STATIC }, \
.function = (_function), \
.expires = (_expires), \
.data = (_data), \
.base = (void *)((unsigned long)&boot_tvec_bases + (_flags)), \
.slack = -1, \
__TIMER_LOCKDEP_MAP_INITIALIZER( \
__FILE__ ":" __stringify(__LINE__)) \
} #define TIMER_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), 0) #define TIMER_DEFERRED_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), TIMER_DEFERRABLE) #define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)

在Linux驱动中使用timer定时器的更多相关文章

  1. Linux驱动中的EPROBE_DEFER是个啥

    ​Linux kernel 驱动中,有不少驱动会引用到 EPROBE_DEFER 这个错误号.比如下面这个例子,对 devm_gpiod_get 的返回值进行判断,如果有错误且错误号不是 -EPRBO ...

  2. linux驱动中printk的使用注意事项

    今天在按键驱动中增加printk(KERN_INFO "gpio_keys_gpio_isr()\n");在驱动加载阶段可以输出调试信息,但驱动加载起来后的信息,在串口端看不到输出 ...

  3. Linux驱动中completion接口浅析(wait_for_complete例子,很好)【转】

    转自:http://blog.csdn.net/batoom/article/details/6298267 completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用 ...

  4. Linux驱动中completion接口浅析(wait_for_complete例子,很好)

    completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用下面的宏静态创建completion:                          DECLARE_CO ...

  5. 为什么linux驱动中变量或者函数都用static修饰?(知乎问题)

    static定义的全局变量 或函数也只能作用于当前的文件. 世界硬件厂商太多,定义static为了防止变量或 函数 重名,定义成static, 就算不同硬件驱动中的 变更 或函数重名了也没关系 .

  6. C# 在类中使用Timer定时器以及延时处理的方法

    我们平时在C#中要用到定时功能时,有自带定时器,一般在定时器里面写函数就行了,现在需要在类里面写了一个定时器,不和界面绑定,一开始的时候感觉没什么思路,然后看了一下界面的设计代码,有了思路,还是很简单 ...

  7. Linux驱动中常用的宏

    .module_i2c_driver(adxl34x_driver)展开为 static int __int adxl34x_driver_init(void) { return i2c_regist ...

  8. Linux驱动中的platform总线分析

    copy from :https://blog.csdn.net/fml1997/article/details/77622860 概述 从Linux2.6内核起,引入一套新的驱动管理和注册机制:pl ...

  9. Linux驱动中获取系统时间

    最近在做VoIP方面的驱动,总共有16个FXS口和FXO口依次初始化,耗用的时间较多.准备将其改为多线程,首先需要确定哪个环节消耗的时间多,这就需要获取系统时间. #include <linux ...

  10. 浅析linux内核中timer定时器的生成和sofirq软中断调用流程(转自http://blog.chinaunix.net/uid-20564848-id-73480.html)

    浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(& ...

随机推荐

  1. DE10-Lite加速度计使用教程

    DE10-Lite加速度计使用教程 1. 概述 DE10-Lite实验板上有一片5轴加速度计芯片ADXL345(通常称为G-sensor).它可以用来测量板子的倾斜角度.本文讲述如何以50次/秒的速度 ...

  2. Java中的读写锁ReentrantReadWriteLock详解,存在一个小缺陷

    写在开头 最近是和java.util.concurrent.locks包下的同步类干上了,素有 并发根基 之称的concurrent包中全是精品,今天我们继续哈,今天学习的主题要由一个大厂常问的Jav ...

  3. CSS样式(第二篇)

    ​ CSS样式(第二篇) 关于定位position: li.widget {position: relative;}相对定位,设定一个参照物. @media (max-width: 980px) .t ...

  4. Java基础 韩顺平老师的 常用类 的部分笔记

    459,八大Wrapper类 包装类的分类 1) 针对八种基本数据类型相应的引用类型-包装类 2) 有了类的特点, 就可以调用类中的方法. 460,装箱和拆箱 package com.hspedu.W ...

  5. JS 实现鼠标框选(页面选择)时返回对应的代码或文本内容

    JS 实现鼠标框选(页面选择)时返回对应的代码或文案内容 一.需求背景 1.项目需求 当用户进行鼠标框选选择了页面上的内容时,把选择的内容进行上报. 2.需求解析 虽然这需求就一句话的事,但是很显然, ...

  6. [NOIP2001 提高组] 数的划分

    个人博客传送锚点:https://www.acwing.com/blog/content/55495/ 传送锚点:https://www.luogu.com.cn/problem/P1025 题目描述 ...

  7. Windows系统命令行的最佳实践

    更多博文请关注:https://blog.bigcoder.cn 每次看到Mac生态中炫酷的命令行工具,我就一脸羡慕,但是奈何财力不足,整不起动辄上万的电脑,搬砖人就只能折腾折腾手里的这台window ...

  8. 将python文件转换成exe可执行文件

    一.安装Pyinstaller pip install pyinstaller(Pyinstaller) 二.找到 .py文件的路径并执行如下命令 pyinstaller -F 要转换的文件.py 三 ...

  9. 鸿蒙HarmonyOS实战-Web组件(基本使用和属性)

    前言 Web是一种基于互联网的技术和资源的网络服务系统.它是指由许多互连的计算机组成的全球性计算机网络,使用户能够通过浏览器访问和交互式使用各种信息和资源,如网页.文档.图片.视频.音频等.通过Web ...

  10. ChatTTS,语气韵律媲美真人的开源TTS模型,文字转语音界的新魁首,对标微软Azure-tts

    前两天 2noise 团队开源了ChatTTS项目,并且释出了相关的音色模型权重,效果确实非常惊艳,让人一听难忘,即使摆在微软的商业级项目Azure-tts面前,也是毫不逊色的. ChatTTS是专门 ...