一、Linux内核定时器编程本系列文章所编写的驱动源码仓库,欢迎Star~
https://github.com/Mculover666/linux_driver_study。
Linux内核中,在时钟中断发生后会唤醒 TIMER_SOFTIRQ 软中断,运行当前处理器上到期的所有定时器。
在linux设备驱动编程中,可以利用Linux内核中提供的一组函数或数据结构来完成定时器触发工作或者完成周期性的事务,并且不用关心具体的软件定时器究竟对应怎样的内核和硬件行为。
1. timer_listLinux内核使用 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;
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
};
当定时器到期后,function成员将被执行,data成员是传入回调函数的参数,expires成员表示定时器超时时间,单位是节拍数。
2. 初始化定时器#define init_timer(timer) __init_timer((timer), 0)
该函数会初始化 timer_list 的entry的next为NULL,并给base指针赋值。
3. 增加定时器void add_timer(struct timer_list *timer);
该函数用于向Linux内核注册定时器,将定时器加入到内核动态定时器链表中。注册定时器之后,定时器就会开始运行。
4. 删除定时器int del_timer(struct timer_list *timer);
该函数用于删除一个定时器。
除此之外,还有一个函数也用来删除一个定时器,但会同步等待其被处理完,所以该API的调用不能发生在中断上下文中。
5. 修改定时器的超时时间int mod_timer(struct timer_list *timer, unsigned long expires);6. 关于定时器的超时时间
定时器的超时时间往往是在目前 jiffies 的基础上添加一个时延,若为HZ,则表示延迟1s。
xxx_timer.expires = jiffies+delay
在定时器处理函数中,在完成相应的工作后,往往会延后 expires 并将定时器再次添加到内核定时器链表中,以便定时器能再次被触发。
二、使用示例——秒字符设备编写一个second字符设备,在被打开的时候初始化一个定时器并将其添加到内核中,每秒输出一次当前的jiffies。
1. 编写基本模块#include2. 编写字符设备驱动框架#include #include static int __init second_module_init(void) { return 0; } static void __exit second_module_exit(void) { } module_init(second_module_init); module_exit(second_module_exit); MODULE_AUTHOR("Mculover666"); MODULE_LICENSE("GPL");
引入头文件:
#include#include #include
封装全局变量:
struct second_dev {
dev_t dev;
struct cdev *cdev;
struct class *class;
struct device *device0;
};
static struct second_dev second;
编写字符设备驱动框架:
static int second_open(struct inode *inode, struct file *fp)
{
return 0;
}
static int second_read(struct file *fp, char __user *buf, size_t size, loff_t *off)
{
return 0;
}
static int second_write(struct file *fp, const char __user *buf, size_t size, loff_t *off)
{
return 0;
}
static int second_release(struct inode *inode, struct file *fp)
{
return 0;
}
static struct file_operations second_fops = {
.owner = THIS_MODULE,
.open = second_open,
.read = second_read,
.write = second_write,
.release = second_release,
};
static int __init second_module_init(void)
{
int ret;
//分配cdev设备号
ret = alloc_chrdev_region(&second.dev, 0, 1, "second");
if (ret != 0) {
printk("alloc_chrdev_region fail!");
return -1;
}
//初始化cdev
second.cdev = cdev_alloc();
if (!second.cdev) {
printk("cdev_alloc fail!");
return -1;
}
//设置fop操作函数
second.cdev->owner = THIS_MODULE;
second.cdev->ops = &second_fops;
//注册cdev
cdev_add(second.cdev, second.dev, 1);
// 创建设备类
second.class = class_create(THIS_MODULE, "second_class");
if (!second.class) {
printk("class_create fail!");
return -1;
}
//创建设备节点
second.device0 = device_create(second.class, NULL, second.dev, NULL, "second0");
if (IS_ERR(second.device0)) {
printk("device_create device0 fail!");
return -1;
}
return 0;
}
static void __exit second_module_exit(void)
{
// 将设备从内核删除
cdev_del(second.cdev);
// 释放设备号
unregister_chrdev_region(second.dev, 1);
// 删除设备节点
device_destroy(second.class, second.dev);
// 删除设备类
class_destroy(second.class);
}
3. 编写定时器程序
引入头文件:
#include
添加全局变量:
struct second_dev {
dev_t dev;
struct cdev *cdev;
struct class *class;
struct device *device0;
struct timer_list timer;
atomic_t counter;
};
编写定时器中断函数:
static void second_timer_handler(unsigned long arg)
{
// 重启定时器
mod_timer(&second.timer, jiffies + HZ);
// 秒计数
atomic_inc(&second.counter);
printk(KERN_INFO "current jiffies is %ldn", jiffies);
}
在open函数中,初始化定时器并注册到内核中,开始运行:
static int second_open(struct inode *inode, struct file *fp)
{
// 初始化定时器,超时时间1s
init_timer(&second.timer);
second.timer.expires = jiffies + HZ;
second.timer.function = second_timer_handler;
// 初始化秒计数值
atomic_set(&second.counter, 0);
// 注册定时器到内核,开始运行
add_timer(&second.timer);
return 0;
}
在release函数中,从定时器删除定时器:
static int second_release(struct inode *inode, struct file *fp)
{
// 将定时器从内核删除
del_timer(&second.timer);
return 0;
}
此时,编译驱动模块,加载到内核中,可以看到对应的设备节点:
#include#include #include #include int main(int argc, char *argv[]) { int fd; // 检查参数 if (argc != 2) { printf("usage: ./test_second [device]n"); return -1; } fd = open(argv[1], O_RDWR); while (1) { } }
编译:
arm-linux-gnueabihf-gcc test_second.c -o test_second
运行测试:
引入头文件:
#include
优化open函数:
static int second_open(struct inode *inode, struct file *fp)
{
// 初始化定时器,超时时间1s
init_timer(&second.timer);
second.timer.expires = jiffies + HZ;
second.timer.function = second_timer_handler;
// 初始化秒计数值
atomic_set(&second.counter, 0);
// 注册定时器到内核,开始运行
add_timer(&second.timer);
fp->private_data = &second;
return 0;
}
编写read函数:
static int second_read(struct file *fp, char __user *buf, size_t size, loff_t *off)
{
int ret;
int sec;
struct second_dev *dev = (struct second_dev *)fp->private_data;
sec = atomic_read(&dev->counter);
ret = copy_to_user(buf, &sec, sizeof(sec));
return 0;
}
重新编译驱动模块,加载到系统中。
6. 应用读取秒计数值优化应用程序,读取按键值:
#include#include #include #include int main(int argc, char *argv[]) { int fd; int ret; int sec; // 检查参数 if (argc != 2) { printf("usage: ./test_second [device]n"); return -1; } fd = open(argv[1], O_RDWR); while (1) { ret = read(fd, &sec, sizeof(sec)); if (ret < 0) { printf("read sec failn"); } printf("sec is %dn", sec); sleep(1); } }
重新编译,运行,测试结果如下:



