Linux时间子系统1(基于Linux6.6)---timer_list介绍
一、概述
在Linux内核中,timer_list
是定时器机制相关的重要数据结构。在内核中用于处理时间相关的任务,如延时执行、周期性任务以及超时处理等。定时器在内核中广泛应用于调度、驱动程序、网络协议栈、文件系统等方面。
内核节点来表示TimerList统计信息。
/proc/timer_list,打印per_cpu的hrtimer_bases信息以及基于此的timer列表,包括三种时钟MONOTONIC/REALTIME/BOOTTIME;以及Broadcast Tick Device和Per CPU Tick Device信息。
二、Timer_list
2.1、节点创建
kernel/time/timer_list.c中init_timer_list_procfs创建/proc/timer_list节点。
static int __init init_timer_list_procfs(void)
{
struct proc_dir_entry *pe;
pe = proc_create_seq_private("timer_list", 0400, NULL, &timer_list_sops,
sizeof(struct timer_list_iter), NULL);
if (!pe)
return -ENOMEM;
return 0;
}
__initcall(init_timer_list_procfs);
static int timer_list_show(struct seq_file *m, void *v)
{
struct timer_list_iter *iter = v;
if (iter->cpu == -1 && !iter->second_pass)
timer_list_header(m, iter->now);
else if (!iter->second_pass)
print_cpu(m, iter->cpu, iter->now);
#ifdef CONFIG_GENERIC_CLOCKEVENTS
else if (iter->cpu == -1 && iter->second_pass)
timer_list_show_tickdevices_header(m);
else
print_tickdevice(m, tick_get_device(iter->cpu), iter->cpu);
#endif
return 0;
}
下面根据一个示例来分析:
Timer List Version: v0.8
HRTIMER_MAX_CLOCK_BASES: 8
now at 348005493993702 nsecs
cpu: 0-----------------------print_cpuCPU0的信息如下,从clock0到clock2
clock 0:---------------------clock0作为MONOTONIC使用ktime_get获取当前时间,是timerkeeper的xtime和wall_to_monotonic之和。
.base: ffff9270aa21c000
.index: 0
.resolution: 1 nsecs
.get_time: ktime_get
.offset: 0 nsecs------------和MONOTONIC相比的offset。
active timers:--------------print_active_timers打印所有的hrtimer,按照时间排列,timerqueue_iterate_next读取rbtree的next。
--依次是:序列号、hrtimer、timer状态、hrtimer超时函数、进程名/进程号
--超时绝对时间和相对时间。
active timers:
#0: <ffffbb12013cfeb0>, hrtimer_wakeup, S:01
# expires at 348005494451191-348005494501191 nsecs [in 457489 to 507489 nsecs]
#1: <ffff9270aa21c520>, tick_sched_timer, S:01
# expires at 348005494627251-348005494627251 nsecs [in 633549 to 633549 nsecs]
#2: <ffffbb1203c67a90>, hrtimer_wakeup, S:01
# expires at 348005496929532-348005496979532 nsecs [in 2935830 to 2985830 nsecs]
#3: <ffffbb120085fde8>, hrtimer_wakeup, S:01
# expires at 348005497678070-348005497728070 nsecs [in 3684368 to 3734368 nsecs]
#4: <ffffbb120164fde8>, hrtimer_wakeup, S:01
# expires at 348005497683499-348005497733499 nsecs [in 3689797 to 3739797 nsecs]
#5: <ffffbb1202e5fde8>, hrtimer_wakeup, S:01
# expires at 348005498946047-348005498996047 nsecs [in 4952345 to 5002345 nsecs]
#6: <ffffbb12013f7de8>, hrtimer_wakeup, S:01
# expires at 348005498954370-348005499004370 nsecs [in 4960668 to 5010668 nsecs]
#7: <ffffbb1203aa7d08>, hrtimer_wakeup, S:01
# expires at 348005501518861-348005501568861 nsecs [in 7525159 to 7575159 nsecs]
#8: <ffffbb12035a7d08>, hrtimer_wakeup, S:01
# expires at 348005515269901-348005515319901 nsecs [in 21276199 to 21326199 nsecs]
#9: <ffffbb12039a7a90>, hrtimer_wakeup, S:01
# expires at 348005573723599-348005573823598 nsecs [in 79729897 to 79829896 nsecs]
#10: <ffffbb1201607eb0>, hrtimer_wakeup, S:01
# expires at 348005727135370-348005727185370 nsecs [in 233141668 to 233191668 nsecs]
clock 1:
.base: ffff9270aa21c040
.index: 1
.resolution: 1 nsecs
.get_time: ktime_get_real
.offset: 1733968404430557217 nsecs
active timers:
#0: <ffffbb1201767d08>, hrtimer_wakeup, S:01
# expires at 1734316409963709613-1734316409963759613 nsecs [in 39158694 to 39208694 nsecs]
#1: <ffffbb1208e97d08>, hrtimer_wakeup, S:01
# expires at 1734316410350643000-1734316410350693000 nsecs [in 426092081 to 426142081 nsecs]
#2: <ffffbb1200b4bd08>, hrtimer_wakeup, S:01
# expires at 1734316410505412000-1734316410505462000 nsecs [in 580861081 to 580911081 nsecs]
#3: <ffffbb1200d37d08>, hrtimer_wakeup, S:01
# expires at 1734316790547733147-1734316790547783147 nsecs [in 380623182228 to 380623232228 nsecs]
#4: <ffffbb120080fd08>, hrtimer_wakeup, S:01
# expires at 1734317622914085000-1734317622914135000 nsecs [in 1212989534081 to 1212989584081 nsecs]
#5: <ffffbb1200d7fd08>, hrtimer_wakeup, S:01
# expires at 1734401090496983400-1734401090497033400 nsecs [in 84680572432481 to 84680572482481 nsecs]
clock 2:
.base: ffff9270aa21c080
.index: 2
.resolution: 1 nsecs
.get_time: ktime_get_boottime
.offset: 0 nsecs
active timers:
-------------------------------------------------------------------hrtimer_cpu_base部分,参照结构体解释----------------------------------------------------------------------
.expires_next : 86212955000000 nsecs---------CPU层次看hrtimer信息,可以在上面找到对应的最近一次expires
.hres_active : 1
.nr_events : 4648009
.nr_retries : 4467
.nr_hangs : 0
.max_hang_time : 0 nsecs
---------------------------------------------------------------------tick_sched部分,参照结构体解释-----------------------------------------------------------------------------
.nohz_mode : 2--------------------------------------nohz_mode对应NOHZ_MODE_INACTIVE(0)、NOHZ_MODE_LOWRES(1)、NOHZ_MODE_HIGHRES(2)。
.idle_tick : 86212945000000 nsecs
.tick_stopped : 0
.idle_jiffies : 17182588
.idle_calls : 4632242
.idle_sleeps : 4573348
.idle_entrytime : 86212940790186 nsecs
.idle_waketime : 86212940790186 nsecs
.idle_exittime : 86212940790186 nsecs
.idle_sleeptime : 84593571589272 nsecs
.iowait_sleeptime: 0 nsecs
.last_jiffies : 17182588
.next_jiffies : 17182604
.idle_expires : 86213020000000 nsecs
jiffies: 17182590
Tick Device: mode: 1-----------------------Broadcast TickDevice
Broadcast device
Clock Event Device: <NULL>
tick_broadcast_mask: 00000000
tick_broadcast_oneshot_mask: 00000000
Tick Device: mode: 1---------------------per_cpu TickDevice,详细信息参照clock_event_device结构体解释。
Per CPU device: 0
Clock Event Device: xxx_ap_timer0
max_delta_ns: 660764185443
min_delta_ns: 15385
mult: 13958644
shift: 32
mode: 3
next_event: 86212955000000 nsecs
set_next_event: xxx_timer_set_next_event
set_mode: xxx_timer_set_mode
event_handler: hrtimer_interrupt
retries: 165992
2.2、timer_list 数据结构
timer_list
是内核中定时器的核心数据结构。它用于表示一个定时器以及与该定时器相关的回调函数。每个 timer_list
结构体都包含了定时器的基本信息,如到期时间、回调函数、定时器状态等。
timer_list 结构体定义
struct timer_list {
struct hlist_node entry; // 定时器链表节点,用于定时器队列管理
unsigned long expires; // 定时器到期的时间(相对于系统启动时间)
void (*function)(struct timer_list *); // 定时器到期时的回调函数
unsigned long data; // 用户数据,可以传递给回调函数
int base; // 该定时器所属的定时器基础结构(定时器队列)
};
字段说明:
entry
: 定时器结构体在定时器链表中的位置。定时器通过哈希链表(hlist_node
)或红黑树(在高精度定时器中)链接起来。expires
: 定时器的到期时间,以系统启动时间为基准的时间戳(单位是“jiffies”)。jiffies
是内核时间的计时单位,它是一个全局计数器,表示自系统启动以来的“节拍”数。function
: 定时器到期时调用的回调函数。这个回调函数会在定时器到期时执行用户指定的操作。data
: 用户可以通过这个字段传递额外的数据给回调函数,通常是指向某个对象或结构体的指针。base
: 定时器所属的定时器基础结构(用于管理多个定时器的定时器队列)。
2.3、定时器的创建与操作
-
初始化定时器: 内核通过
init_timer()
或timer_setup()
来初始化定时器,并指定定时器到期时执行的回调函数。
-
void timer_setup(struct timer_list *timer, void (*function)(struct timer_list *), unsigned long flags);
-
启动定时器: 定时器通过
mod_timer()
函数启动,并设定定时器的到期时间。 -
int mod_timer(struct timer_list *timer, unsigned long expires);
-
删除定时器: 定时器通过
del_timer()
删除,取消定时器的调用。
-
int del_timer(struct timer_list *timer);
2.4、定时器工作机制
内核定时器机制的核心是基于 jiffies
的定时器队列管理。定时器到期后,系统会通过内核的定时器中断处理程序调用定时器的回调函数。
三、举例应用
1. 基本的定时器使用例子:
我们将实现一个简单的例子,创建一个定时器,定时器到期时触发一个回调函数,该函数将在定时器到期后打印一条消息。
步骤1:定义一个定时器回调函数
这个回调函数将在定时器到期时被调用。我们会简单地打印一条消息,表示定时器触发。
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/jiffies.h>
static struct timer_list my_timer; // 定义一个定时器
// 定时器到期时的回调函数
void timer_callback(struct timer_list *t)
{
pr_info("Timer expired at jiffies: %ld\n", jiffies);
}
步骤2:初始化和启动定时器
在模块的初始化函数中,我们将初始化定时器并设置定时器的到期时间。假设我们让定时器在 2 秒后到期(在 Linux 中,jiffies
是一个内核时间单位,通常 1 个 jiffy
是 1/100秒,具体取决于系统配置)。
static int __init my_module_init(void)
{
pr_info("Module initialized\n");
// 初始化定时器
timer_setup(&my_timer, timer_callback, 0); // 传入回调函数
// 设置定时器到期时间:当前时间 + 2秒
mod_timer(&my_timer, jiffies + msecs_to_jiffies(2000)); // 2000ms = 2秒
return 0;
}
步骤3:模块清理
在模块卸载时,我们需要删除定时器,以防止在模块卸载后定时器回调仍然被调用。
static void __exit my_module_exit(void)
{
pr_info("Module exited\n");
// 删除定时器,防止定时器回调函数在模块卸载后被调用
del_timer_sync(&my_timer);
}
步骤4:模块定义
module_init(my_module_init); // 模块初始化函数
module_exit(my_module_exit); // 模块退出函数
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux Developer");
MODULE_DESCRIPTION("A simple timer_list example");
2. 详细解释
timer_setup
函数
void timer_setup(struct timer_list *timer, void (*function)(struct timer_list *), unsigned long flags);
timer
: 指向timer_list
结构的指针,这是我们要初始化的定时器。function
: 指定定时器到期时要调用的回调函数。flags
: 标志参数,通常设置为 0,表示普通定时器。
timer_setup
函数用于初始化 timer_list
结构,并为定时器设置回调函数。
mod_timer
函数
int mod_timer(struct timer_list *timer, unsigned long expires);
timer
: 需要设置的定时器。expires
: 定时器到期时间,单位为jiffies
。我们通过jiffies + msecs_to_jiffies(2000)
来设置定时器 2 秒后触发。
mod_timer
函数会根据当前的系统时间(jiffies
)设置定时器的到期时间。如果定时器已经存在,它将重新设置定时器的到期时间。
del_timer_sync
函数
int del_timer_sync(struct timer_list *timer);
timer
: 指向要删除的定时器。
del_timer_sync
函数会删除定时器,并等待定时器的回调函数执行完毕。如果回调函数正在执行,它会等待执行完成再删除定时器。这通常用于确保定时器在删除时不会被触发。
3. 模块加载与卸载
加载这个内核模块时,定时器将在 2 秒后触发,执行回调函数并打印消息。当卸载模块时,定时器会被删除。
# 编译并加载模块
make
sudo insmod timer_example.ko
# 查看内核日志,检查定时器回调输出
dmesg | tail
# 卸载模块
sudo rmmod timer_example
4. 扩展应用场景:周期性定时器
除了一次性定时器,你还可以使用周期性定时器来实现重复触发的任务。例如,如果你需要每 1 秒钟执行一次任务,可以设置一个周期性定时器。
周期性定时器实现
void periodic_timer_callback(struct timer_list *t)
{
pr_info("Periodic Timer expired at jiffies: %ld\n", jiffies);
// 重新启动定时器,设置周期为 1 秒
mod_timer(t, jiffies + msecs_to_jiffies(1000));
}
在 my_module_init
中初始化周期性定时器:
static int __init my_module_init(void)
{
pr_info("Module initialized\n");
// 初始化定时器
timer_setup(&my_timer, periodic_timer_callback, 0);
// 启动定时器,1秒后首次触发
mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
return 0;
}
这样,定时器将每 1 秒触发一次,直到模块被卸载。
5. 总结
timer_list
是 Linux 内核中实现定时器功能的核心数据结构。你可以用它来实现定时任务、超时机制以及周期性任务。- 定时器回调函数 在定时器到期时被调用,它通常用于处理延时任务或超时操作。
mod_timer
用于启动或重设定时器。del_timer_sync
用于删除定时器,确保删除过程中不会再触发回调函数。