《Linux设备驱动程序》(第三版) 第5章 定时器和时间管理

第5章 定时器和时间管理

5.1 内核中的时间表示
5.1.1 jiffies计数器

jiffies 是Linux内核中用于记录系统启动以来的时钟滴答数的全局变量。时钟滴答是系统定时器中断的频率,不同的系统可能有不同的滴答频率。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/jiffies.h>

// 模块加载函数
static int __init jiffies_demo_init(void) {
    // 获取当前的jiffies值
    unsigned long current_jiffies = jiffies;
    printk(KERN_INFO "Current jiffies value: %lu\n", current_jiffies);
    return 0;
}

// 模块卸载函数
static void __exit jiffies_demo_exit(void) {
    printk(KERN_INFO "Module unloaded.\n");
}

module_init(jiffies_demo_init);
module_exit(jiffies_demo_exit);
MODULE_LICENSE("GPL");
  • unsigned long current_jiffies = jiffies;:获取当前的 jiffies 值并存储在 current_jiffies 变量中。
  • printk(KERN_INFO "Current jiffies value: %lu\n", current_jiffies);:打印当前的 jiffies 值。%lu 是用于格式化输出无符号长整型的格式说明符。
5.1.2 时间换算

由于 jiffies 表示的是时钟滴答数,通常需要将其换算为实际的时间单位(如秒、毫秒)。内核提供了一些宏来进行这种换算。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/jiffies.h>
#include <linux/time.h>

// 模块加载函数
static int __init time_conversion_demo_init(void) {
    // 获取当前的jiffies值
    unsigned long current_jiffies = jiffies;
    // 将jiffies换算为秒
    unsigned long seconds = jiffies_to_seconds(current_jiffies);
    // 将jiffies换算为毫秒
    unsigned long milliseconds = jiffies_to_msecs(current_jiffies);

    printk(KERN_INFO "Current jiffies: %lu, seconds: %lu, milliseconds: %lu\n",
           current_jiffies, seconds, milliseconds);
    return 0;
}

// 模块卸载函数
static void __exit time_conversion_demo_exit(void) {
    printk(KERN_INFO "Module unloaded.\n");
}

module_init(time_conversion_demo_init);
module_exit(time_conversion_demo_exit);
MODULE_LICENSE("GPL");
  • unsigned long seconds = jiffies_to_seconds(current_jiffies);:使用 jiffies_to_seconds 宏将 jiffies 值换算为秒。
  • unsigned long milliseconds = jiffies_to_msecs(current_jiffies);:使用 jiffies_to_msecs 宏将 jiffies 值换算为毫秒。
5.2 定时器
5.2.1 定时器的使用

Linux内核提供了定时器机制,允许驱动程序在未来的某个时间点执行特定的函数。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>

// 定义定时器处理函数
static void timer_callback(unsigned long data) {
    // data:传递给定时器处理函数的参数
    printk(KERN_INFO "Timer callback executed. Data passed: %ld\n", data);
}

// 模块加载函数
static int __init timer_demo_init(void) {
    struct timer_list my_timer;
    // 初始化定时器
    setup_timer(&my_timer, timer_callback, 42);
    // 设置定时器在1000个jiffies后触发
    my_timer.expires = jiffies + 1000;
    // 添加定时器到内核定时器队列
    add_timer(&my_timer);

    printk(KERN_INFO "Timer set up. Will expire in 1000 jiffies.\n");
    return 0;
}

// 模块卸载函数
static void __exit timer_demo_exit(void) {
    struct timer_list my_timer;
    // 初始化定时器(与加载时相同,只是为了确保结构一致)
    setup_timer(&my_timer, timer_callback, 42);
    // 从内核定时器队列中删除定时器
    del_timer_sync(&my_timer);
    printk(KERN_INFO "Timer removed. Module unloaded.\n");
}

module_init(timer_demo_init);
module_exit(timer_demo_exit);
MODULE_LICENSE("GPL");
  • static void timer_callback(unsigned long data):定时器到期时执行的回调函数。data 是在设置定时器时传递给该函数的参数。
  • setup_timer(&my_timer, timer_callback, 42);:初始化定时器 my_timer,指定回调函数为 timer_callback,并传递参数 42
  • my_timer.expires = jiffies + 1000;:设置定时器在当前 jiffies 值加上 1000 个滴答后到期。
  • add_timer(&my_timer);:将定时器添加到内核定时器队列。
  • del_timer_sync(&my_timer);:从内核定时器队列中删除定时器,并等待所有可能正在执行的定时器回调函数完成。
5.2.2 定时器的实现原理

Linux内核定时器基于系统的时钟中断实现。当系统时钟中断发生时,内核检查是否有定时器到期。如果有,内核调用相应的定时器回调函数。定时器的实现涉及到内核的定时器队列管理,每个定时器都被插入到一个按到期时间排序的队列中。

5.2.3 高分辨率定时器

高分辨率定时器提供了比普通定时器更高的时间精度。它适用于对时间精度要求较高的应用场景,如音频、视频处理等。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/hrtimer.h>

// 定义高分辨率定时器的回调函数
static enum hrtimer_restart my_hrtimer_callback(struct hrtimer *timer) {
    // timer:指向高分辨率定时器结构的指针
    printk(KERN_INFO "High - resolution timer callback executed.\n");
    // 重新启动定时器,设置下一次到期时间
    hrtimer_forward_now(timer, ktime_set(2, 0));
    return HRTIMER_RESTART;
}

// 模块加载函数
static int __init hrtimer_demo_init(void) {
    struct hrtimer my_hrtimer;
    // 初始化高分辨率定时器
    hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    // 设置定时器的到期时间为2秒后
    my_hrtimer.expires = ktime_set(2, 0);
    // 设置定时器的回调函数
    my_hrtimer.function = my_hrtimer_callback;
    // 启动高分辨率定时器
    hrtimer_start(&my_hrtimer, my_hrtimer.expires, HRTIMER_MODE_REL);

    printk(KERN_INFO "High - resolution timer set up. Will expire in 2 seconds.\n");
    return 0;
}

// 模块卸载函数
static void __exit hrtimer_demo_exit(void) {
    struct hrtimer my_hrtimer;
    // 初始化高分辨率定时器(与加载时相同,只是为了确保结构一致)
    hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    // 取消高分辨率定时器
    hrtimer_cancel(&my_hrtimer);
    printk(KERN_INFO "High - resolution timer removed. Module unloaded.\n");
}

module_init(hrtimer_demo_init);
module_exit(hrtimer_demo_exit);
MODULE_LICENSE("GPL");
  • static enum hrtimer_restart my_hrtimer_callback(struct hrtimer *timer):高分辨率定时器到期时执行的回调函数。timer 是指向高分辨率定时器结构的指针。
  • hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);:初始化高分辨率定时器 my_hrtimer,使用单调时钟 CLOCK_MONOTONIC,设置为相对时间模式。
  • my_hrtimer.expires = ktime_set(2, 0);:设置定时器的到期时间为2秒后。ktime_set 用于创建一个表示时间的 ktime 结构体,第一个参数为秒数,第二个参数为纳秒数。
  • my_hrtimer.function = my_hrtimer_callback;:设置定时器的回调函数为 my_hrtimer_callback
  • hrtimer_start(&my_hrtimer, my_hrtimer.expires, HRTIMER_MODE_REL);:启动高分辨率定时器,指定到期时间和模式。
  • hrtimer_cancel(&my_hrtimer);:取消高分辨率定时器。
5.3 延迟执行
5.3.1 短延迟

对于短延迟,内核提供了 udelayndelay 函数,分别用于微秒级和纳秒级的延迟。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>

// 模块加载函数
static int __init short_delay_demo_init(void) {
    printk(KERN_INFO "Starting short delay.\n");
    // 延迟1000微秒
    udelay(1000);
    printk(KERN_INFO "Short delay completed.\n");
    return 0;
}

// 模块卸载函数
static void __exit short_delay_demo_exit(void) {
    printk(KERN_INFO "Module unloaded.\n");
}

module_init(short_delay_demo_init);
module_exit(short_delay_demo_exit);
MODULE_LICENSE("GPL");
  • udelay(1000);:使当前线程延迟1000微秒。
5.3.2 长延迟

对于长延迟,不推荐使用 udelayndelay,因为它们会占用CPU资源。可以使用 msleep 函数,它会使当前进程睡眠指定的毫秒数,让出CPU资源。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>

// 模块加载函数
static int __init long_delay_demo_init(void) {
    printk(KERN_INFO "Starting long delay.\n");
    // 延迟1000毫秒
    msleep(1000);
    printk(KERN_INFO "Long delay completed.\n");
    return 0;
}

// 模块卸载函数
static void __exit long_delay_demo_exit(void) {
    printk(KERN_INFO "Module unloaded.\n");
}

module_init(long_delay_demo_init);
module_exit(long_delay_demo_exit);
MODULE_LICENSE("GPL");
  • msleep(1000);:使当前进程睡眠1000毫秒。
5.4 时间测量
5.4.1 获取当前时间

内核提供了多种获取当前时间的方法,如 ktime_getgetnstimeofday

#include <linux/init.h>
#include <linux/module.h>
#include <linux/ktime.h>
#include <linux/time.h>

// 模块加载函数
static int __init get_current_time_demo_init(void) {
    struct timespec64 ts;
    // 使用ktime_get获取当前时间
    ktime_t now = ktime_get();
    // 将ktime转换为timespec64
    ktime_to_timespec64(now, &ts);
    printk(KERN_INFO "Current time using ktime_get: %lld seconds, %ld nanoseconds\n",
           (long long)ts.tv_sec, ts.tv_nsec);

    // 使用getnstimeofday获取当前时间
    getnstimeofday(&ts);
    printk(KERN_INFO "Current time using getnstimeofday: %lld seconds, %ld nanoseconds\n",
           (long long)ts.tv_sec, ts.tv_nsec);
    return 0;
}

// 模块卸载函数
static void __exit get_current_time_demo_exit(void) {
    printk(KERN_INFO "Module unloaded.\n");
}

module_init(get_current_time_demo_init);
module_exit(get_current_time_demo_exit);
MODULE_LICENSE("GPL");
  • ktime_t now = ktime_get();:使用 ktime_get 获取当前时间,返回一个 ktime 结构体。
  • ktime_to_timespec64(now, &ts);:将 ktime 结构体转换为 timespec64 结构体,以便更方便地处理时间。
  • getnstimeofday(&ts);:使用 getnstimeofday 获取当前时间,直接填充 timespec64 结构体。
5.4.2 时间差计算

可以通过获取两个时间点,然后计算它们之间的差值来测量时间间隔。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/ktime.h>

// 模块加载函数
static int __init time_difference_demo_init(void) {
    ktime_t start, end;
    // 获取开始时间
    start = ktime_get();

    // 模拟一些工作
    for (int i = 0; i < 1000000; i++);

    // 获取结束时间
    end = ktime_get();

    // 计算时间差
    ktime_t diff = ktime_sub(end, start);
    // 将时间差转换为纳秒
    unsigned long long diff_ns = ktime_to_ns(diff);

    printk(KERN_INFO "Time difference: %llu nanoseconds\n", diff_ns);
    return 0;
}

// 模块卸载函数
static void __exit time_difference_demo_exit(void) {
    printk(KERN_INFO "Module unloaded.\n");
}

module_init(time_difference_demo_init);
module_exit(time_difference_demo_exit);
MODULE_LICENSE("GPL");
  • start = ktime_get();:获取开始时间。
  • end = ktime_get();:获取结束时间。
  • ktime_t diff = ktime_sub(end, start);:使用 ktime_sub 计算两个 ktime 之间的差值。
  • unsigned long long diff_ns = ktime_to_ns(diff);:将时间差转换为纳秒。

以下是对《Linux设备驱动程序》(第三版)第5章中内核定时器应用场景的详细代码示例及注释:

5.5 内核定时器的应用场景
5.5.1 周期性任务

在Linux内核中,定时器常用于执行周期性任务。以下代码展示了如何使用内核定时器实现一个简单的周期性任务,例如每 Njiffies 打印一条消息。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>

// 定义定时器处理函数
static void periodic_task_callback(unsigned long data) {
    // data: 传递给定时器处理函数的参数,这里未使用,但形参必须存在
    printk(KERN_INFO "Periodic task is running.\n");

    // 获取当前定时器对象,以便重新设置到期时间
    struct timer_list *timer = (struct timer_list *)data;
    // 重新设置定时器,让其在当前jiffies值加上1000个jiffies后再次触发
    mod_timer(timer, jiffies + 1000);
}

// 模块加载函数
static int __init periodic_task_init(void) {
    struct timer_list my_timer;

    // 初始化定时器
    // &my_timer: 指向要初始化的定时器结构体
    // periodic_task_callback: 定时器到期时要执行的回调函数
    // (unsigned long)&my_timer: 传递给回调函数的参数,这里传递定时器自身的地址
    setup_timer(&my_timer, periodic_task_callback, (unsigned long)&my_timer);

    // 设置定时器首次到期时间为当前jiffies值加上1000个jiffies
    my_timer.expires = jiffies + 1000;

    // 将定时器添加到内核定时器队列
    add_timer(&my_timer);

    printk(KERN_INFO "Periodic task has been set up.\n");
    return 0;
}

// 模块卸载函数
static void __exit periodic_task_exit(void) {
    struct timer_list my_timer;

    // 初始化定时器,确保结构与加载时一致
    setup_timer(&my_timer, periodic_task_callback, (unsigned long)&my_timer);

    // 从内核定时器队列中删除定时器,并等待所有可能正在执行的定时器回调函数完成
    del_timer_sync(&my_timer);

    printk(KERN_INFO "Periodic task has been removed. Module unloaded.\n");
}

module_init(periodic_task_init);
module_exit(periodic_task_exit);
MODULE_LICENSE("GPL");
5.5.2 超时处理

内核定时器还可用于实现超时处理。例如,在等待设备响应时,如果在指定时间内设备未响应,则执行相应的超时处理逻辑。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>

// 定义一个标志,表示设备是否响应
int device_responded = 0;

// 定义定时器处理函数
static void timeout_callback(unsigned long data) {
    // data: 传递给定时器处理函数的参数,这里未使用,但形参必须存在
    if (!device_responded) {
        printk(KERN_INFO "Device did not respond within the timeout. Performing timeout action.\n");
        // 这里可以添加具体的超时处理逻辑,例如重置设备等
    }
}

// 模拟设备响应函数
void device_response(void) {
    device_responded = 1;
    printk(KERN_INFO "Device has responded.\n");
}

// 模块加载函数
static int __init timeout_init(void) {
    struct timer_list my_timer;

    // 初始化定时器
    // &my_timer: 指向要初始化的定时器结构体
    // timeout_callback: 定时器到期时要执行的回调函数
    // 0: 传递给回调函数的参数,这里未使用
    setup_timer(&my_timer, timeout_callback, 0);

    // 设置定时器到期时间为当前jiffies值加上5000个jiffies
    my_timer.expires = jiffies + 5000;

    // 将定时器添加到内核定时器队列
    add_timer(&my_timer);

    printk(KERN_INFO "Timeout has been set up. Waiting for device response.\n");
    return 0;
}

// 模块卸载函数
static void __exit timeout_exit(void) {
    struct timer_list my_timer;

    // 初始化定时器,确保结构与加载时一致
    setup_timer(&my_timer, timeout_callback, 0);

    // 从内核定时器队列中删除定时器,并等待所有可能正在执行的定时器回调函数完成
    del_timer_sync(&my_timer);

    printk(KERN_INFO "Timeout has been removed. Module unloaded.\n");
}

module_init(timeout_init);
module_exit(timeout_exit);
// 假设这里有一个外部机制可以调用device_response函数,模拟设备响应

在上述代码中,timeout_callback 函数在定时器到期时检查 device_responded 标志。如果设备未响应(device_responded0),则打印超时消息并可执行相应的超时处理逻辑。device_response 函数模拟设备响应,将 device_responded 标志设置为 1。在实际应用中,device_response 函数可能由设备驱动的中断处理程序或其他与设备交互的逻辑调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

请向我看齐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值