第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 短延迟
对于短延迟,内核提供了 udelay
和 ndelay
函数,分别用于微秒级和纳秒级的延迟。
#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 长延迟
对于长延迟,不推荐使用 udelay
或 ndelay
,因为它们会占用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_get
和 getnstimeofday
。
#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内核中,定时器常用于执行周期性任务。以下代码展示了如何使用内核定时器实现一个简单的周期性任务,例如每 N
个 jiffies
打印一条消息。
#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_responded
为 0
),则打印超时消息并可执行相应的超时处理逻辑。device_response
函数模拟设备响应,将 device_responded
标志设置为 1
。在实际应用中,device_response
函数可能由设备驱动的中断处理程序或其他与设备交互的逻辑调用。