Linux驱动开发—Linux内核定时器概念和使用详解,实现基于定时器的字符驱动

内核定时器概念

在 Linux 内核中,定时器是用来管理和调度延迟操作的机制。Linux 内核提供了几种不同类型的定时器,每种定时器都有不同的特点和用途。以下是一些主要的定时器类型和相关概念:

  1. POSIX 定时器

    • 这是一个基于 POSIX 标准的定时器,提供了一种用户空间的定时器接口。通过 timer_create()timer_settime()timer_gettime() 等系统调用,用户空间程序可以创建和管理定时器。
  2. 高精度定时器 (High Resolution Timers)

    • 高精度定时器可以提供比普通定时器更高的时间精度。它们使用 hrtimer API 来实现,适用于需要非常精确定时的场景,比如实时系统。
  3. 软定时器 (Soft Timers)

    • 软定时器是内核中的定时器,用于调度执行内核中的延迟操作。它们主要通过 mod_timer()del_timer() 等 API 进行管理。软定时器通常用于处理网络数据包或其他延迟任务。
  4. 延迟队列 (Delay Queues)

    • 延迟队列是一种数据结构,用于在内核中排队等待延迟处理的任务。通过 init_timer()add_timer() 等函数,可以将任务添加到延迟队列中,等待指定时间后执行。
  5. 定时器回调

    • 当定时器到期时,会调用相应的回调函数来执行预定的操作。回调函数通常会在内核态执行,进行一些需要延迟处理的任务。

定时器在内核中的实现涉及到多种机制,包括定时器队列、定时器中断以及内核调度机制等。具体使用和实现细节可以参考 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

在这里插入图片描述

  • 25
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Trump. yang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值