内核定时器概念
在 Linux 内核中,定时器是用来管理和调度延迟操作的机制。Linux 内核提供了几种不同类型的定时器,每种定时器都有不同的特点和用途。以下是一些主要的定时器类型和相关概念:
-
POSIX 定时器:
- 这是一个基于 POSIX 标准的定时器,提供了一种用户空间的定时器接口。通过
timer_create()
、timer_settime()
和timer_gettime()
等系统调用,用户空间程序可以创建和管理定时器。
- 这是一个基于 POSIX 标准的定时器,提供了一种用户空间的定时器接口。通过
-
高精度定时器 (High Resolution Timers):
- 高精度定时器可以提供比普通定时器更高的时间精度。它们使用
hrtimer
API 来实现,适用于需要非常精确定时的场景,比如实时系统。
- 高精度定时器可以提供比普通定时器更高的时间精度。它们使用
-
软定时器 (Soft Timers):
- 软定时器是内核中的定时器,用于调度执行内核中的延迟操作。它们主要通过
mod_timer()
和del_timer()
等 API 进行管理。软定时器通常用于处理网络数据包或其他延迟任务。
- 软定时器是内核中的定时器,用于调度执行内核中的延迟操作。它们主要通过
-
延迟队列 (Delay Queues):
- 延迟队列是一种数据结构,用于在内核中排队等待延迟处理的任务。通过
init_timer()
和add_timer()
等函数,可以将任务添加到延迟队列中,等待指定时间后执行。
- 延迟队列是一种数据结构,用于在内核中排队等待延迟处理的任务。通过
-
定时器回调:
- 当定时器到期时,会调用相应的回调函数来执行预定的操作。回调函数通常会在内核态执行,进行一些需要延迟处理的任务。
定时器在内核中的实现涉及到多种机制,包括定时器队列、定时器中断以及内核调度机制等。具体使用和实现细节可以参考 Linux 内核文档和源代码。
在Linux驱动模块中使用定时器
在 Linux 驱动模块中使用定时器,通常有两种主要方式:使用软定时器和高精度定时器。
软定时器(Soft Timers)
软定时器在内核驱动中使用时,通常涉及到 timer_list
结构和相关 API。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/init.h>
static struct timer_list my_timer;
void timer_callback(struct timer_list *t)
{
printk(KERN_INFO "Timer callback function called.\n");
}
static int __init my_module_init(void)
{
printk(KERN_INFO "Module initialized.\n");
printk(KERN_INFO "System HZ value: %d\n", HZ);
// 初始化定时器
timer_setup(&my_timer, timer_callback, 0);
// 设置定时器超时值(例如 500ms)
mod_timer(&my_timer, jiffies + msecs_to_jiffies(500));
return 0;
}
static void __exit my_module_exit(void)
{
// 删除定时器
del_timer(&my_timer);
printk(KERN_INFO "Module exited.\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marxist");
MODULE_DESCRIPTION("A simple Linux driver with a timer.");
关键点说明:
timer_setup
函数用于初始化timer_list
结构体并设置回调函数。mod_timer
函数用于设置定时器的超时时间。del_timer
函数用于删除定时器,防止模块卸载时定时器仍在执行。
效果如图:
其中 printk(KERN_INFO "System HZ value: %d\n", HZ);
输出当前系统的时钟中断频率,与jiffies 相关
jiffies 含义
在 Linux 内核中,jiffies
是一个用于表示系统启动以来的时间的全局计数器,它以“滴答”(tick)为单位。jiffies
是内核中时间管理的一个基本概念,广泛用于定时器和延迟操作。
定义:jiffies
是一个计数器,表示自系统启动以来经过的“时钟滴答”数。每个滴答的时间长度由系统的时钟中断频率决定,通常是 1 毫秒(即 1000 Hz)或 10 毫秒(即 100 Hz),但具体取决于内核配置。
单位:jiffies
的单位是系统时钟滴答。系统时钟滴答的频率(即每秒钟中断的次数)由内核配置中的 HZ
参数决定。例如,如果 HZ
设置为 1000,那么每秒会有 1000 个滴答,每个滴答大约是 1 毫秒。
计算时间:使用 jiffies
可以计算时间间隔。例如,如果你要设置一个定时器在 500 毫秒后触发,可以使用以下代码:
mod_timer(&my_timer, jiffies + msecs_to_jiffies(500));
这里,msecs_to_jiffies
函数将 500 毫秒转换为相应的 jiffies
数量。
溢出:由于 jiffies
是一个 unsigned long
类型的变量,它可能会在长时间运行后溢出。在现代 Linux 内核中,jiffies
的溢出问题一般不会对大多数应用造成问题,因为内核设计考虑了这个问题并进行了处理。
转换函数:
jiffies_to_msecs(jiffies)
:将jiffies
转换为毫秒。jiffies_to_timespec(jiffies)
:将jiffies
转换为timespec
结构。msecs_to_jiffies(milliseconds)
:将毫秒转换为jiffies
。
高精度定时器(High Resolution Timers)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/init.h>
static struct hrtimer my_hrtimer;
static enum hrtimer_restart hrtimer_callback(struct hrtimer *timer)
{
printk(KERN_INFO "High resolution timer callback function called.\n");
return HRTIMER_NORESTART; // 不重复定时
}
static int __init my_module_init(void)
{
printk(KERN_INFO "Module initialized.\n");
// 初始化高精度定时器
hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
my_hrtimer.function = hrtimer_callback;
// 设置定时器超时值(例如 500ms)
ktime_t ktime = ktime_set(0, 500 * 1000000); // 500ms
hrtimer_start(&my_hrtimer, ktime, HRTIMER_MODE_REL);
return 0;
}
static void __exit my_module_exit(void)
{
// 删除高精度定时器
int ret = hrtimer_cancel(&my_hrtimer);
if (ret)
printk(KERN_INFO "The high resolution timer was still active.\n");
printk(KERN_INFO "Module exited.\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marxist");
MODULE_DESCRIPTION("A simple Linux driver with a high resolution timer.");
关键点说明:
hrtimer_init
函数用于初始化高精度定时器。hrtimer_start
函数用于设置高精度定时器的超时时间。ktime_set
用于创建高精度时间值。hrtimer_cancel
函数用于取消高精度定时器。
效果如下:
实现倒计时字符设备驱动
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/atomic.h>
#define DEVICE_NAME "countdown_device"
static atomic_t counter = ATOMIC_INIT(0);
static int major_number;
static struct class *my_class = NULL;
static struct device *my_device = NULL;
static struct cdev mydev; // 声明 cdev 结构体
static struct timer_list my_timer; // 声明定时器
static struct semaphore my_semaphore; // 定义信号量
static int count_val = 30; // 倒计时初始值
// 定时器回调函数
void timer_callback(struct timer_list *t)
{
count_val= atomic_read(&counter);
// 每秒 -1
if (count_val > 0)
{
atomic_dec(&counter);
printk("mytimer : %d",atomic_read(&counter));
// 重新设置定时器以在 1 秒后触发
mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
}
else count_val = -1; // 代表结束
}
static int device_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device opened\n");
// 初始化定时器 ,并开启定时任务
timer_setup(&my_timer, timer_callback, 0);
mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
return 0;
}
static int device_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device closed\n");
// 删除定时器
del_timer_sync(&my_timer);
return 0;
}
static ssize_t device_read(struct file *filp, char __user *buffer, size_t len, loff_t *offset)
{
int count_val_copy;
count_val_copy = atomic_read(&counter);
printk("begin read new val: %d", count_val_copy);
// 将数据从内核空间拷贝到用户空间
if (copy_to_user(buffer, &count_val_copy, sizeof(count_val_copy)))
{
printk("copy_to_user error");
return -EFAULT;
}
return sizeof(count_val_copy);
}
static ssize_t device_write(struct file *filp, const char __user *buffer, size_t len, loff_t *offset)
{
char buf[64];
int new_value;
// 获取信号量
if (down_interruptible(&my_semaphore))
{
return -ERESTARTSYS;
}
// 从用户空间复制数据
if (copy_from_user(buf, buffer, len))
{
up(&my_semaphore);
return -EFAULT;
}
buf[len] = '\0';
if (sscanf(buf, "%d", &new_value) == 1)
{
count_val = new_value; // 从用户空间获取到倒计时的值
}
printk("write new val is %d",count_val);
// 释放信号量
up(&my_semaphore);
//重新给原子变量赋值
atomic_set(&counter, count_val);
return len;
}
static struct file_operations fops = {
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write,
};
static int __init test_init(void)
{
int retval;
dev_t dev;
printk(KERN_INFO "module init success\n");
// 初始化信号量
sema_init(&my_semaphore, 1);
// 1. 动态分配主次设备号
retval = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
if (retval < 0)
{
printk(KERN_ERR "Failed to allocate major number\n");
goto fail_alloc_chrdev_region;
}
major_number = MAJOR(dev);
printk(KERN_INFO "major number is: %d, minor number is: %d\n", major_number, MINOR(dev));
// 2. 初始化 cdev 结构体并添加到内核
cdev_init(&mydev, &fops);
retval = cdev_add(&mydev, dev, 1);
if (retval < 0)
{
printk(KERN_ERR "Failed to add cdev\n");
goto fail_cdev_add;
}
// 3. 创建设备类
my_class = class_create(THIS_MODULE, "my_class");
if (IS_ERR(my_class))
{
printk(KERN_ERR "Failed to create class\n");
retval = PTR_ERR(my_class);
goto fail_class_create;
}
// 4. 申请设备,内核空间就会通知用户空间的 udev 进行创建设备,驱动程序本身自己是创建不了文件的!
my_device = device_create(my_class, NULL, dev, NULL, DEVICE_NAME);
if (IS_ERR(my_device))
{
printk(KERN_ERR "Failed to create device\n");
retval = PTR_ERR(my_device);
goto fail_device_create;
}
printk(KERN_INFO "my_char_device: module loaded\n");
return 0;
fail_device_create:
class_destroy(my_class);
fail_class_create:
cdev_del(&mydev);
fail_cdev_add:
unregister_chrdev_region(dev, 1);
fail_alloc_chrdev_region:
return retval;
}
static void __exit test_exit(void)
{
dev_t dev = MKDEV(major_number, 0);
if (my_device)
device_destroy(my_class, dev);
if (my_class)
class_destroy(my_class);
cdev_del(&mydev);
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "my_char_device: module unloaded\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("Marxist");
MODULE_LICENSE("GPL");
用户端
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define DEVICE_PATH "/dev/countdown_device"
int main()
{
int fd;
char buffer[64];
int count_val = 60; // 倒计时初始值
ssize_t bytes_written, bytes_read;
int res_val;
// 打开设备文件
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0)
{
perror("Failed to open the device");
return errno;
}
// 写入倒计时初始值
snprintf(buffer, sizeof(buffer), "%d", count_val);
bytes_written = write(fd, buffer, strlen(buffer));
if (bytes_written < 0)
{
perror("Failed to write to the device");
close(fd);
return errno;
}
printf("Written %d to the device\n", count_val);
// 进行倒计时读取输出
while (1)
{
// 读取当前倒计时值
bytes_read = read(fd, &res_val, sizeof(res_val));
if (bytes_read < 0)
{
perror("Failed to read from the device");
close(fd);
return errno;
}
// 打印当前倒计时值
printf("Current countdown value: %d\n", res_val);
// 休眠1秒
sleep(1);
// 判断倒计时是否结束
if (res_val <= 0)
{
printf("Countdown finished.\n");
break;
}
}
// 关闭设备文件
close(fd);
return 0;
}
效果如图:在内核中,使用定时器每秒对原子变量的值减-1