定时器和时间管理
jiffies
全局变量用于自系统启动以来产生的节拍总数
基础术语
- 硬件时钟 内核依赖底层硬件提供时间基准,主要有两种:
-
实时时钟 (RTC - Real Time Clock): 这是一个主板上的独立芯片,自带电池供电。即使计算机关机,它也会继续走时。它的主要作用是在系统开机时为内核提供初始的墙上时间(Wall Time)。
-
系统定时器 (System Timer / OS Timer): 比如 x86 架构下的 PIT、HPET 或 Local APIC Timer。它以固定的频率向 CPU 发送周期性的硬件中断(时钟中断)。这是内核跳动的“心脏”
- 软件时钟(关键概念)内核利用硬件提供的时钟中断,在软件层面上维护了几个极其重要的概念:
- HZ (赫兹): 定义了系统定时器一秒钟内触发时钟中断的次数。例如,如果 HZ = 1000,意味着每秒有 1000 次时钟中断。
- Tick (滴答): 两次连续时钟中断之间的时间间隔。Tick = 秒。如果 HZ 是 1000,那么一个 Tick 就是 1 毫秒。
- Jiffies: 这是一个全局变量,记录了自系统启动以来发生的 Tick 总数。每次发生时钟中断,jiffies 就会加 1。内核极其依赖它来计算时间流逝和处理低精度超时。
- 墙上时间 (Wall Time): 即我们人类日常看到的当前日期和时间。内核内部通常通过 xtime(或现代内核中的特定结构)来维护,它记录了自 1970 年 1 月 1 日以来的秒数和纳秒数
硬件时钟跳动一次内核所发生的事
1.体系结构部分 由于系统架构的不同(arm,x86)其处理硬件时钟的中断的方式不同,所以内核会有针对其架构的中断代码,主要内容为:
- 获得xtime_lock,
- 应该系统时钟,告诉底层硬件中断信号我收到了
- 周期性的核对墙上时钟和rtc时钟是否相同
- 调用tick_periodic(),这个函数就与具体架构无关了
2.体系结构无关部分
- 更新 iffies_64,xtime
- 调用scheduler_tick(),检查进程时间片是否用完(触发调度)
定时器怎么用
内核定时器的生命周期和固定 API 主要包含以下几个步骤:
1. 定义定时器结构体
首先,你需要定义一个 timer_list 结构体变量。
#include <linux/timer.h>
struct timer_list my_timer;
2. 初始化定时器:timer_setup()
在给定时器设定时间之前,必须先初始化它,把它和你的处理函数绑定起来。
- 现代 API (>= Linux 4.15):
void timer_setup(struct timer_list *timer,
void (*callback)(struct timer_list *),
unsigned int flags);
回调函数的参数直接就是这个定时器的指针。这比老版本更安全,你可以通过 from_timer 宏反向推导出包含这个定时器的外层结构体。
- 老版本 API (旧书上常见的
init_timer或setup_timer): 旧版本中,你需要手动设置.function和.data(通常用来传递自定义参数),现在这种方式已经被彻底淘汰。
3. 设置超时时间并激活:add_timer()
初始化完成后,你需要告诉内核这个定时器什么时候触发,并把它加入到内核的时间轮中。
// 设置超时时间:当前时间 (jiffies) 加上你想等待的 Tick 数
// 比如等待 2 秒:jiffies + msecs_to_jiffies(2000)
my_timer.expires = jiffies + msecs_to_jiffies(2000);
// 将定时器加入内核链表,正式激活
add_timer(&my_timer);
4. 修改定时器:mod_timer() (最常用的 API)
在实际开发中,mod_timer() 比 add_timer() 用得更频繁。它的作用是改变一个定时器的超时时间,并激活它。
哪怕这个定时器还没有被 add_timer() 激活过,或者它已经超时执行完了,你都可以直接用 mod_timer()。
// 把定时器的时间修改为从现在起 1 秒后触发
mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
5. 删除定时器:del_timer() 与 del_timer_sync()
当你不再需要这个定时器(比如卸载模块或者关闭设备)时,必须把它从内核中删掉,否则内核去执行一个已经被释放的内存地址上的函数,会导致系统立刻崩溃(Kernel Panic)。
del_timer(&my_timer);尝试停止定时器。如果定时器已经超时或尚未激活,返回 0;如果成功停掉了一个正在等待的定时器,返回 1。del_timer_sync(&my_timer);(强烈推荐) 这是多核 SMP 时代最常用的删除函数。它不仅会从时间轮中删除定时器,还会等待。如果这个定时器的回调函数正在其他 CPU 核心上运行,del_timer_sync()会一直阻塞等待它运行结束。这保证了模块卸载时的绝对安全。
💡 完整的使用示例 (现代内核风格)
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
// 1. 定义定时器
static struct timer_list my_timer;
// 定时器回调函数(注意参数是 timer_list 指针)
static void my_timer_callback(struct timer_list *timer)
{
printk(KERN_INFO "定时器响了!当前 jiffies: %lu\n", jiffies);
// 如果你想让它变成周期性定时器,可以在这里再次 mod_timer
// mod_timer(timer, jiffies + msecs_to_jiffies(1000));
}
static int __init my_module_init(void)
{
printk(KERN_INFO "模块加载...\n");
// 2. 初始化定时器
timer_setup(&my_timer, my_timer_callback, 0);
// 3. 激活定时器(设置为 3 秒后触发)
my_timer.expires = jiffies + msecs_to_jiffies(3000);
add_timer(&my_timer);
return 0;
}
static void __exit my_module_exit(void)
{
// 5. 安全地删除定时器
del_timer_sync(&my_timer);
printk(KERN_INFO "模块卸载...\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");