news 2026/5/16 15:36:34

Linux内核与驱动:7.定时器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/16 15:34:18

Yi-Coder-1.5B游戏开发:Unity脚本智能生成

Yi-Coder-1.5B游戏开发&#xff1a;Unity脚本智能生成 1. 引言 想象一下&#xff0c;你正在开发一款Unity游戏&#xff0c;脑子里有个绝妙的玩法创意&#xff0c;却卡在了代码实现上。传统的游戏开发需要你一行行敲代码&#xff0c;调试逻辑&#xff0c;反复修改——这个过程…

作者头像 李华
网站建设 2026/4/9 6:35:09

cv_unet_image-matting图像抠图场景实战:复杂背景人像处理技巧

cv_unet_image-matting图像抠图场景实战&#xff1a;复杂背景人像处理技巧 1. 引言&#xff1a;为什么需要专业的人像抠图工具&#xff1f; 在日常工作中&#xff0c;我们经常遇到需要将人像从复杂背景中分离出来的需求。无论是电商产品图、证件照制作还是创意设计&#xff0…

作者头像 李华
网站建设 2026/4/9 6:29:12

30分钟搭建个人AI助手:OpenClaw对接千问3.5-35B-A3B-FP8全记录

30分钟搭建个人AI助手&#xff1a;OpenClaw对接千问3.5-35B-A3B-FP8全记录 1. 缘起&#xff1a;为什么选择OpenClaw千问组合&#xff1f; 上周整理电脑文件时&#xff0c;发现Downloads文件夹堆积了2000多个未分类文件。手动整理耗时费力&#xff0c;突然想到&#xff1a;能否…

作者头像 李华
网站建设 2026/4/9 6:28:20

算法调度问题中的代价模型与优化方法的技术5

算法调度问题概述定义与基本概念&#xff1a;任务调度、资源分配、目标函数典型应用场景&#xff1a;云计算、分布式系统、实时系统核心挑战&#xff1a;多目标权衡、动态环境、不确定性代价模型的设计与分析代价模型的组成&#xff1a;时间代价、资源代价、经济代价常见模型分…

作者头像 李华
网站建设 2026/4/11 3:47:17

使用Phi-4-mini-reasoning优化SpringBoot微服务:架构设计与代码审查实战

使用Phi-4-mini-reasoning优化SpringBoot微服务&#xff1a;架构设计与代码审查实战 1. 引言&#xff1a;当AI遇见微服务开发 最近在重构一个电商平台的SpringBoot微服务时&#xff0c;我遇到了一个典型困境&#xff1a;随着业务复杂度提升&#xff0c;代码审查变得越来越耗时…

作者头像 李华