Linux定时器实现
使用场景:
延后执行某个动作
定期查询硬件状态
…
内核相关时间概念
-
HZ
通过 CONFIG_HZ 来设置,范围为 100 ~ 1000HZ 决定了系统时钟中断发生的频率, HZ 值不能设置太大或太小
-
jiffies
记录内核自启动以来的节拍数全局变量 jiffies 用来记录子系统启动以来产生的节拍总数。启动时内核将该变量
初始化为0, 此后每次时钟中断处理程序都会增加该变量的值
因为 1 秒内时钟中断的次数等于 HZ ,jiffies 一秒以内增加的值也等于 HZ
定时器代码解读
- 定时器相关函数
struct timer_list mytimer;
// 内核中每一个timer都用一个timer_list结构体来描述
struct timer_list {
// 这是 Kernel 3.0.8 中对于 timer_list 的描述
// 与 当前编译环境 5.3.28 不同
// 详细信息可以参考 kernel 4.15 kernel/timer/timer.c 等代码
struct list_head entry; // 双向链表
/*
* struct list_head {
* struct list_head *next, *prev;
* };
*/
unsigned long expires; // 超时时间
struct tvec_base *base;
// 时钟管理的结构体,表示这个结构体是隶属于这个base来管理的
void (*function)(unsigned long); // 超时执行的动作
unsigned long data; // 作为参数传给函数
int slack;
#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP // 是否定义需要查看 .config 文件
struct lockdep_map lockdep_map;
#endif
};
// setup_timer 函数
// 在 include/linux/timer.h 中声明
linux定时器使用实例
- 内核注册定时器最终都会通过调用 internal_add_timer 来实现,具体工作方式:
- 如果定时器在接下来的 0~255 个 jiffies 中到期,则将定时器添加到 v1
- 如果定时器在接下来的 255 * 64 个 jiffies 中到期,则将定时器添加到 v2
- 如果定时器在接下来的 255 * 64 * 64 个 jiffies 中到期,则将定时器添加到 v3
- 如果定时器在接下来的 255 * 64 * 64 * 64 个jiffies 中到期,则将定时器添加到 v4
- 如果有更大的超时时间,则利用0xffffffff来计算 hash ,然后插入到 v5 (这个只会出现在64bit的系统中)
定时器的调度与处理的过程
/*
* 参考 init/main.c 文件中 start_kernel函数
*/
asmlinkage __visible void __init start_kernel(void); //具体内容见内核代码
// 定时器对应初始化语句
init_timers();
// 在 kernel/timer.c 中定义
void __init init_timers(void)
{
init_timer_cpus();
open_softirq(TIMER_SOFTIRQ, run_timer_softirq); // 注册软中断
}
在 linux 内核中将中断处理程序分为两个部分:上半部 / 下半部
- 上半部
中断处理程序——完成实时性要求高的操作
实现必要的代码,对实时性要求高,太过繁琐容易造成丢中断 - 下半部
繁琐的、占用时间长的代码在下半部中实现
下半部代码可以被硬件中断打断