Linux内核与驱动:7.定时器
在 Linux 驱动开发中,内核定时器(Kernel Timer) 是一种高频率使用的机制,用于在未来的某个时间点触发特定的执行逻辑。与用户态的 sleep 不同,内核定时器是异步的,且运行在中断上下文中。

1.定时器核心概念
Linux 内核通过一个全局变量 jiffies 来记录自系统启动以来产生的节拍数。
-
HZ (Hertz):内核每秒钟产生的节拍数(通常为 100, 250 或 1000)。
-
jiffies:系统开机到目前的节拍总数。每过 1/HZ 秒,jiffies 自动加 1。
-
Expires:定时器的到期时间点,计算公式为:
expires = jiffies + msecs_to_jiffies(1000)
节拍数HZ的修改步骤如下:
1.进入内核源码路径下,输入make menuconfig;
![]()

2.选择Processor type and features进入;

3.选择Timer frequency进入,修改节拍数;

2.定时器核心API
DEFINE_TIMER(name, function) :这是一个宏,用于静态定义并初始化一个定时器。它设定了回调函数,并将其他字段(如到期时间)初始化为 0。
add_timer(timer):将定时器加入内核链表并启动。它要求 timer->expires 必须预先设置好。
mod_timer(timer, expires):驱动开发中最常用的函数,循环定时就靠他,功能:修改定时器的到期时间并重新启动。
del_timer(timer):尝试删除定时器。如果定时器正在运行,它可能删除失败。
del_timer_sync(timer): 最安全的删除方式。它会等待定时器在其他 CPU 上的回调函数执行完毕后再返回。
3.定时器实验
这段代码实现了一个简单的 Linux 字符设备驱动程序,它演示了以下核心功能:
-
动态申请字符设备号、注册字符设备、自动创建设备节点。
-
通过原子变量实现无锁的计数统计。
-
使用内核定时器周期性执行任务(每秒递增原子计数器)。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h> //copy_to_user copy_from_user
#include <linux/string.h> //strlen
#include <linux/wait.h> //等待队列头文件
#include <linux/time.h>
struct cdev_test_dev{
dev_t dev_num;
int major;
int minor;
struct cdev cdev_test;
struct class* class_test;
struct device* device_test;
char kbuf[100];
atomic64_t counter;
};
struct cdev_test_dev dev1;
static void timer_func(struct timer_list *t);
DEFINE_TIMER(timer,timer_func);
static void timer_func(struct timer_list *t)
{
printk("timer_function...\n");
mod_timer(&timer,jiffies_64 + msecs_to_jiffies(1000));
atomic64_inc(&dev1.counter);
}
static int cdev_test_open(struct inode* inode,struct file* filp)
{
printk("cdev_test_open\n");
filp->private_data = &dev1; //将设备结构体指针保存在文件私有数据中
printk("major = %d,minor = %d\n",dev1.major,dev1.minor);
timer.expires = jiffies_64 + msecs_to_jiffies(1000);
add_timer(&timer);
atomic64_set(&dev1.counter,0);
return 0;
}
static ssize_t cdev_test_read(struct file* filp,char __user* buf,size_t count,loff_t* offset)
{
unsigned int counter = 0;
counter = atomic64_read(&dev1.counter);
copy_to_user(buf,&counter,sizeof(counter)); //用于将内核空间的数据复制到用户空间,使用strlen而不是sizeof是为了去掉字符串末尾的'\0'
printk("cdev_test_read\n");
return 0;
}
static ssize_t cdev_test_write(struct file* filp,const char __user* buf,size_t count,loff_t* offset)
{
struct cdev_test_dev* dev = (struct cdev_test_dev*)filp->private_data;
if(dev->minor == 0)
{
copy_from_user(dev->kbuf,buf,count); //用于将用户空间的数据复制到内核空间
printk("cdev_test_write to dev1: %s\n",dev->kbuf);
}
return count;
}
static int cdev_test_release(struct inode* inode,struct file* flip)
{
printk("cdev_test_release\n");
del_timer(&timer);
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = cdev_test_open,
.read = cdev_test_read,
.write = cdev_test_write,
.release = cdev_test_release,
}; //文件操作结构体
static int __init module_cdev_init(void)
{
int ret;
int major,minor;
//1.申请设备号
ret = alloc_chrdev_region(&dev1.dev_num,0,1,"cdev_test1");
if(ret < 0)
{
goto err_alloc;
}
dev1.major = MAJOR(dev1.dev_num);
dev1.minor = MINOR(dev1.dev_num);
printk("major = %d,minor = %d\n",dev1.major,dev1.minor);
//2.初始化字符设备结构体
cdev_init(&dev1.cdev_test,&fops);
dev1.cdev_test.owner = THIS_MODULE;
//3.注册字符设备
ret = cdev_add(&dev1.cdev_test,dev1.dev_num,1);
if(ret < 0)
{
goto err_add;
}else
{
printk("cdev_add success\n");
}
//4.自动创建设备节点(创建类和设备)
dev1.class_test = class_create(THIS_MODULE,"cdev_test_class");
if(IS_ERR(dev1.class_test))
{
ret = PTR_ERR(dev1.class_test);
goto err_class;
}
dev1.device_test = device_create(dev1.class_test,NULL,dev1.dev_num,NULL,"cdev_test_device");
if(IS_ERR(dev1.device_test))
{
ret = PTR_ERR(dev1.device_test);
goto err_device;
}
return 0;
err_device:
class_destroy(dev1.class_test);
err_class:
cdev_del(&dev1.cdev_test);
err_add:
unregister_chrdev_region(dev1.dev_num,1);
err_alloc:
return ret;
}
static void __exit module_cdev_exit(void)
{
//1.删除字符设备
cdev_del(&dev1.cdev_test);
//2.释放设备号
unregister_chrdev_region(dev1.dev_num,1);
//3.销毁设备节点和类
device_destroy(dev1.class_test,dev1.dev_num);
class_destroy(dev1.class_test);
//4.释放定时器
del_timer_sync(&timer);
printk("cdev_test exit\n");
}
module_init(module_cdev_init);
module_exit(module_cdev_exit);
MODULE_LICENSE("GPL");
注意:1.新版本Linux内核, timer_func(struct timer_list *t)函数的参数必须是struct timer_list *t,不能填void,更不能不填,在内核C中:如果一个函数不需要任何参数,必须显式地在括号内写上void。
2.目前的版本有内核崩溃风险:add_timer 不能对已经在运行的定时器使用。如果用户 A 打开了设备,定时器开始运行;此时用户 B 再打开,第二次调用 add_timer 会导致内核由于链表破坏而触发 Oops 或 Panic。
改进,实现互斥访问,只能有一个设备被打开:
(1)在结构体中增加一个原子变量available:
struct cdev_test_dev {
...
atomic_t available; // 1 表示可用,0 表示被占用
};
(2)在module_init中初始化为1:
atomic_set(&dev1.available, 1);
(3)修改 open 和 release
使用 atomic_dec_and_test(减 1 并测试是否为 0)来实现逻辑:
static int cdev_test_open(struct inode* inode, struct file* filp) {
// 尝试获取设备:available 从 1 变成 0
if (!atomic_dec_and_test(&dev1.available)) {
atomic_inc(&dev1.available); // 恢复原值
return -EBUSY; // 返回设备忙错误码
}
filp->private_data = &dev1;
mod_timer(&timer, jiffies + msecs_to_jiffies(1000));
atomic64_set(&dev1.counter, 0);
return 0;
}
static int cdev_test_release(struct inode* inode, struct file* flip) {
del_timer_sync(&timer);
atomic_inc(&dev1.available); // 释放设备:available 变回 1
return 0;
}
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)