软件意义的定时器依赖于硬件定时器来实现,内核在时钟中断发生后检测各定时器是否到期,到期后定时器处理函数将作为软中断在底半部执行。实质上,时钟中断处理程序会唤起TIMER_SOFTIRQ软中断,运行当前处理器上到期的所有定时器。
1.系统节拍的选择
在进行内核编译之前,利用命令"make menuconfig"命令来裁剪内核时,可以选择系统的节拍:
-> Kernel Features
-> Timer frequency (<choice> [=y])
出现界面:
可选的系统节拍率为 100Hz、 200Hz、 250Hz、 300Hz、 500Hz 和1000 Hz,默认情况下选择 100Hz。打开根目录下的.config文件,可以看到默认的节拍率为100 Hz:
CONFIG_HZ_100=y
# CONFIG_HZ_200 is not set
# CONFIG_HZ_250 is not set
# CONFIG_HZ_300 is not set
# CONFIG_HZ_500 is not set
# CONFIG_HZ_1000 is not set
CONFIG_HZ=100
Linux 内核会使用 CONFIG_HZ 来设置自己的系统时钟。打开文件 include/asmgeneric/param.h,有如下内容:
# undef HZ
# define HZ CONFIG_HZ /* Internal kernel timer frequency */
# define USER_HZ 100 /* some user interfaces are */
# define CLOCKS_PER_SEC (USER_HZ) /* in "ticks" like times() */
#endif /* __ASM_GENERIC_PARAM_H */
问题:100Hz 是可选的节拍率里面最小的。为什么不选择大一点的呢?
答:(1)高节拍率会提高系统时间精度,如果采用 100Hz 的节拍率,时间精度就是 10ms,采用
1000Hz 的话时间精度就是 1ms,精度提高了 10 倍。高精度时钟的好处有很多,对于那些对时
间要求严格的函数来说,能够以更高的精度运行,时间测量也更加准确。(2)高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担, 1000Hz 和 100Hz的系统节拍率相比,系统要花费 10 倍的“精力”去处理中断。中断服务函数占用处理器的时间增加。
Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会
将 jiffies 初始化为 0, jiffies 定义在文件 include/linux/jiffies.h 中,定义如下:
/*
* The 64-bit value is not atomic - you MUST NOT read it
* without sampling the sequence number in jiffies_lock.
* get_jiffies_64() will do this for you as appropriate.
*/
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
jiffies_64 和 jiffies 其实是同一个东西, jiffies_64 用于 64 位系统,而 jiffies 用于 32 位系统。为了兼容不同的硬件, jiffies 其实就是 jiffies_64 的低 32 位。
不管是 32 位还是 64 位的 jiffies,都有溢出的风险,溢出以后会重新从 0 开始计数,相当于绕回来了,因此有些资料也将这个现象也叫做绕回。假如 HZ 为最大值 1000 的时候, 32 位的 jiffies 只需要 49.7 天就发生了绕回,对于 64 位的 jiffies 来说大概需要5.8 亿年才能绕回,因此 jiffies_64 的绕回忽略不计。处理 32 位 jiffies 的绕回显得尤为重要,Linux 内核提供了几个宏定义来处理绕回。
/*
* These inlines deal with timer wrapping correctly. You are
* strongly encouraged to use them
* 1. Because people otherwise forget
* 2. Because if the timer wrap changes in future you won't have to
* alter your driver code.
*
* time_after(a,b) returns true if the time a is after time b.
*
* Do this with "<0" and ">=0" to only test the sign of the result. A
* good compiler would generate better code (and a really good compiler
* wouldn't care). Gcc is currently neither.
*/
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)((b) - (a)) < 0))
#define time_before(a,b) time_after(b,a)
#define time_after_eq(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)((a) - (b)) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)
为了方便开发, Linux 内核提供了几个 jiffies 和 ms、 us、 ns 之间的转换函数:
int jiffies_to_msecs(const unsigned long j); //毫秒
int jiffies_to_usecs(const unsigned long j); //微秒
u64 jiffies_to_nsecs(const unsigned long j); //纳秒
long msecs_to_jiffies(const unsigned int m);
long usecs_to_jiffies(const unsigned int u);
unsigned long nsecs_to_jiffies(u64 n);
2.内核定时器相关的数据结构
Linux 内核使用 timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中,定义如下(省略掉条件编译):
struct timer_list {
struct list_head entry;
unsigned long expires; /* 定时器超时时间,单位是节拍数 */
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数 */
unsigned long data; /* 要传递给 function 函数的参数 */
int slack;
};
一个timer_list结构体对象对应一个定时器。当定时器到期后,超时处理函数function()将会被执行。tiemr_list 结构体的expires 成员变量表示超时时间,单位为节拍数。比如我们现在需要定义一个周期为 2 秒的定时器,那么这个定时器的超时时间就是 jiffies+(2*HZ),因此 expires=jiffies+(2*HZ)。 function 就是定时器超时以后的定时处理函数,我们要做的工作就放到这个函数里面,需要我们编写这个定时处理函数。
定义好定时器以后还需要通过一系列的 API 函数来初始化此定时器,这些函数如下:
(1) init_timer 函数:负责初始化 timer_list 类型变量;
#define init_timer(timer) \
__init_timer((timer), 0)
#define __init_timer(_timer, _flags) \
init_timer_key((_timer), (_flags), NULL, NULL)
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)
{
timer->entry.pprev = NULL;
timer->flags = flags | raw_smp_processor_id();
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);
}
(2) add_timer 函数:用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行。
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);
}
(3)del_timer 函数:用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。
int del_timer(struct timer_list *timer)
{
struct tvec_base *base;
unsigned long flags;
int ret = 0;
debug_assert_init(timer);
timer_stats_timer_clear_start_info(timer);
if (timer_pending(timer)) {
base = lock_timer_base(timer, &flags);
ret = detach_if_pending(timer, base, true);
spin_unlock_irqrestore(&base->lock, flags);
}
return ret;
}
(4)del_timer_sync 函数:会等待其他处理器使用完定时器再删除,del_timer_sync 不能使用在中断上下文中。
int del_timer_sync(struct timer_list *timer)
(5)mod_timer 函数:用于修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器!
int mod_timer(struct timer_list *timer, unsigned long expires)
{
expires = apply_slack(timer, expires);
/*
* This is a common optimization triggered by the
* networking code - if the timer is re-modified
* to be the same thing then just return:
*/
if (timer_pending(timer) && timer->expires == expires)
return 1;
return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
}
内核定时器一般的使用流程如下所示:
struct timer_list timer; /* 定义定时器 */
/* 定时器回调函数 */
void function(unsigned long arg)
{
/*
* 定时器处理代码
*/
/* 如果需要定时器周期性运行的话就使用 mod_timer
* 函数重新设置超时值并且启动定时器。
*/
mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
}
/* 初始化函数 */
void init(void)
{
init_timer(&timer); /* 初始化定时器 */
timer.function = function; /* 设置定时处理函数 */
timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */
add_timer(&timer); /* 启动定时器 */
}
/* 退出函数 */
void exit(void)
{
del_timer(&timer); /* 删除定时器 */
/* 或者使用 */
del_timer_sync(&timer);
}
实验
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define TIMER_CNT 1 /* 设备号个数 */
#define TIMER_NAME "timer" /* 名字 */
#define CLOSE_CMD (_IO(0XEF, 0x1)) /* 关闭定时器 */
#define OPEN_CMD (_IO(0XEF, 0x2)) /* 打开定时器 */
#define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 设置定时器周期命令 */
#define LEDON 1 /* 开灯 */
#define LEDOFF 0 /* 关灯 */
/* timer设备结构体 */
struct timer_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* key所使用的GPIO编号 */
int timeperiod; /* 定时周期,单位为ms */
struct timer_list timer;/* 定义一个定时器*/
spinlock_t lock; /* 定义自旋锁 */
};
struct timer_dev timerdev; /* timer设备 */
/*
* @description : 初始化LED灯IO,open函数打开驱动的时候
* 初始化LED灯所使用的GPIO引脚。
* @param : 无
* @return : 无
*/
static int led_init(void)
{
int ret = 0;
timerdev.nd = of_find_node_by_path("/gpioled");
if (timerdev.nd== NULL) {
return -EINVAL;
}
timerdev.led_gpio = of_get_named_gpio(timerdev.nd ,"led-gpio", 0);
if (timerdev.led_gpio < 0) {
printk("can't get led\r\n");
return -EINVAL;
}
/* 初始化led所使用的IO */
gpio_request(timerdev.led_gpio, "led"); /* 请求IO */
ret = gpio_direction_output(timerdev.led_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
return 0;
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int timer_open(struct inode *inode, struct file *filp)
{
int ret = 0;
filp->private_data = &timerdev; /* 设置私有数据 */
timerdev.timeperiod = 1000; /* 默认周期为1s */
ret = led_init(); /* 初始化LED IO */
if (ret < 0) {
return ret;
}
return 0;
}
/*
* @description : ioctl函数,
* @param - filp : 要打开的设备文件(文件描述符)
* @param - cmd : 应用程序发送过来的命令
* @param - arg : 参数
* @return : 0 成功;其他 失败
*/
static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct timer_dev *dev = (struct timer_dev *)filp->private_data;
int timerperiod;
unsigned long flags;
switch (cmd) {
case CLOSE_CMD: /* 关闭定时器 */
del_timer_sync(&dev->timer);
break;
case OPEN_CMD: /* 打开定时器 */
spin_lock_irqsave(&dev->lock, flags);
timerperiod = dev->timeperiod;
spin_unlock_irqrestore(&dev->lock, flags);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));
break;
case SETPERIOD_CMD: /* 设置定时器周期 */
spin_lock_irqsave(&dev->lock, flags);
dev->timeperiod = arg;
spin_unlock_irqrestore(&dev->lock, flags);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));
break;
default:
break;
}
return 0;
}
/* 设备操作函数 */
static struct file_operations timer_fops = {
.owner = THIS_MODULE,
.open = timer_open,
.unlocked_ioctl = timer_unlocked_ioctl,
};
/* 定时器回调函数 */
void timer_function(unsigned long arg)
{
struct timer_dev *dev = (struct timer_dev *)arg;
static int sta = 1;
int timerperiod;
unsigned long flags;
sta = !sta; /* 每次都取反,实现LED灯反转 */
gpio_set_value(dev->led_gpio, sta);
/* 重启定时器 */
spin_lock_irqsave(&dev->lock, flags);
timerperiod = dev->timeperiod;
spin_unlock_irqrestore(&dev->lock, flags);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
}
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init timer_init(void)
{
/* 初始化自旋锁 */
spin_lock_init(&timerdev.lock);
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (timerdev.major) { /* 定义了设备号 */
timerdev.devid = MKDEV(timerdev.major, 0);
register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME); /* 申请设备号 */
timerdev.major = MAJOR(timerdev.devid); /* 获取分配号的主设备号 */
timerdev.minor = MINOR(timerdev.devid); /* 获取分配号的次设备号 */
}
/* 2、初始化cdev */
timerdev.cdev.owner = THIS_MODULE;
cdev_init(&timerdev.cdev, &timer_fops);
/* 3、添加一个cdev */
cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);
/* 4、创建类 */
timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
if (IS_ERR(timerdev.class)) {
return PTR_ERR(timerdev.class);
}
/* 5、创建设备 */
timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);
if (IS_ERR(timerdev.device)) {
return PTR_ERR(timerdev.device);
}
/* 6、初始化timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */
init_timer(&timerdev.timer);
timerdev.timer.function = timer_function;
timerdev.timer.data = (unsigned long)&timerdev;
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit timer_exit(void)
{
gpio_set_value(timerdev.led_gpio, 1); /* 卸载驱动的时候关闭LED */
del_timer_sync(&timerdev.timer); /* 删除timer */
#if 0
del_timer(&timerdev.tiemr);
#endif
/* 注销字符设备驱动 */
gpio_free(timerdev.led_gpio);
cdev_del(&timerdev.cdev);/* 删除cdev */
unregister_chrdev_region(timerdev.devid, TIMER_CNT); /* 注销设备号 */
device_destroy(timerdev.class, timerdev.devid);
class_destroy(timerdev.class);
}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");