1.内核定时器
1.1.内核定时器简介和系统时钟
Linux内核定时器是一个基于系统时钟的软件模拟定时器。Linux内核定时器使用很简单,只需要提供超时时间和定时处理函数即可,当超时时间到了以后设置的定时器处理函数就会执行。
在使用Linux内核定时器要注意一点,内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想实现周期性定时,那么就需要在定时处理函数中重新开启定时器。
之前大家学习过ucos系统,我们知道ucos系统是需要一个硬件定时器来提供系统时钟。同样,Linux系统要运行,也得需要硬件定时器来提供系统时钟。硬件定时器提供时钟源,时钟源的频率可以设置,设置好以后就周期性的产生定时中断,系统使用定时中断来计时。而中断周期性产生的频率就是系统频率,也叫做节拍率。Linux内核默认采用100Hz作为系统频率。在使用内核定时器时,经常使用以下两个标识符:
HZ:是一个宏,表示每秒的系统节拍数,即每秒进入多少次定时器中断。
jiffies:是一个全局变量,记录系统从启动以来的系统节拍数,系统启动的时候将jiffies初始化为0。
1.2.内核定时器核心数据结构
Linux内核使用timer_list结构体描述一个定时器,该结构体在include\linux\timer.h文件中定义。
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;
unsigned long expires; /* 定时器超时时间,单位是节拍数 */ jiffies+5*HZ
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,function,data这三个必要成员。如果想同时定时多段时间,就定义多个定时器。实现必须的成员。
注:Linux内核定时器是基于未来某个时间点(就是用jiffies来表示)来触发超时处理函数。
expires成员变量表示超时时间,单位为节拍数。比如,现在需要定义一个周期为两秒的定时器,那么这个定时器的超时时间就是jiffies + 2*HZ。
注意: 如果内核定时器要设置超时时间为几微秒,几毫秒,怎么办?
10毫秒:jiffies + 0.01*HZ。这样写不准确!建议使用以下两个函数:
unsigned long usecs_to_jiffies(const unsigned int u);
作用:把微秒转换成时钟节拍
参数:u--时间微秒
返回:对应的时钟节拍数量
unsigned long msecs_to_jiffies(const unsigned int m);
作用:把毫秒转换成时钟节拍
参数:m--时间毫秒
返回:对应的时钟节拍数量
1.3.内核定时器使用步骤
1.构造定时器对象
头文件:#include <linux/timer.h>
宏原型:init_timer(timer)
宏功能:只是对struct timer_list结构成员进行了一些基础初始化的操作,function,expires,data还需要用户自己填充,相当于只剩下三个必要成员没有初始化。
宏参数:struct timer_list结构变量的地址
头文件:#include <linux/timer.h>
宏原型:setup_timer(timer, fn, data)
宏功能:设置定时器中的function,data和一些基础成员,expires并没有初始化,需要用户自己进行初始化(直接赋值)。
宏参数: timer struct timer_list结构体变量的地址
fn 内核定时器超时处理函数
data 传给定时器超时处理函数的参数
2.启动定时器
调用add_timer函数用于向Linux内核注册定时器,当注册定时器以后,定时器就会开始运行。
头文件: #include <linux/timer.h>
函数原型:void add_timer(struct timer_list *timer);
函数功能:向内核注册一个定时器,注册后会马上开始计时。
函数参数:要注册的定时器
函数返回值:无
3.修改定时器
mod_timer函数用于修改定时器定时值,如果定时器还没有激活的话,mod_timer会激活定时器。
头文件: #include <linux/timer.h>
函数原型:int mod_timer(struct timer_list *timer, unsigned long expires);
函数功能:修改定时器定时时间值,并且重新激活该定时器。执行完成后定时器会马上启动定时。
函数参数: timer 要修改超时时间的定时器
expires 修改后的超时时间
函数返回值:无
4.删除定时器
del_timer函数用于删除一个定时器,不管定时器有没有被激活(有没有被启动)。
头文件: #include <linux/timer.h>
函数原型:int del_timer(struct timer_list * timer);
函数功能:从内核定时链表上删除指定的定时器,删除后就不会再执行绑定的函数
函数参数:要删除的定时器
函数返回值:返回0表示定时器还没被激活,返回1表示定时器已经被激活
注:
不能在超时回调函数中调用该函数,调用del_timer删除定时器前会先等待定时器处理函数执行完毕!
1.4.内核定时器使用示例
使用过程示例:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/timer.h> //内核定时器所需头文件
struct timer_list mytimer; //定义一个内核定时器结构体变量
void timer_handler(unsigned long data) //超时回调函数
{
printk("time out\n");
//printk("data = %d\n",data);
mod_timer(&mytimer,jiffies + HZ*2);
}
static int __init xxx_init(void)
{
printk("xxx_init\n");
init_timer(&mytimer); //对结构体进行基础初始化
setup_timer(&mytimer,timer_handler,100); //设置结构体的函数指针和参数
//mod_timer(&mytimer,jiffies + HZ*2); //修改定时器定时时间值,并且重新注册
mytimer.expires = jiffies + HZ*2; //设置结构体的超时时间
add_timer(&mytimer); //注册定时器到内核
return 0;
}
static void __exit xxx_exit(void)
{
printk("xxx is exit\n");
del_timer(&mytimer); //若使用了循环定时,在卸载函数中未调用该函数,注销驱动时内核崩溃
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");