简介

介绍了内核定时器,相关的数据结构,简单的使用方法,做了一个实验,开启一个2s的定时器

代码

https://github.com/liodegwin/kernel/tree/main/timer

感谢和参考

《linux内核设计与实现》11.7章节

https://wangquan.blog.csdn.net/article/details/122968025?spm=1001.2101.3001.6650.8&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-8-122968025-blog-128086137.pc_relevant_multi_platform_whitelistv4&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-8-122968025-blog-128086137.pc_relevant_multi_platform_whitelistv4&utm_relevant_index=12

https://blog.csdn.net/cyhhh/article/details/126778268?spm=1001.2101.3001.6650.17&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-17-126778268-blog-52022787.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-17-126778268-blog-52022787.pc_relevant_default&utm_relevant_index=18

定时器结构

一个定时器用一个timer_list结构表示

https://elixir.bootlin.com/linux/v5.15.82/source/include/linux/timer.h#L11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct hlist_node entry;
unsigned long expires;
void (*function)(struct timer_list *);
u32 flags;

#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};

重要成员

expires:超时事件,是绝对时间,也就是内核的jiffies值,当jiffies等于或者大于expires的时候,function指向的定时器函数就会被调用

function:指向一个定时器回调函数,4.15以下版本的内核,中function的原型是void(*function)(unsignedlong);直到4.15之后,入口参数改变了

flags:详细描述定时器配置,可选项如下

https://elixir.bootlin.com/linux/v5.15.82/source/include/linux/timer.h#L64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* @TIMER_DEFERRABLE: A deferrable timer will work normally when the
* system is busy, but will not cause a CPU to come out of idle just
* to service it; instead, the timer will be serviced when the CPU
* eventually wakes up with a subsequent non-deferrable timer.
*
* @TIMER_IRQSAFE: An irqsafe timer is executed with IRQ disabled and
* it's safe to wait for the completion of the running instance from
* IRQ handlers, for example, by calling del_timer_sync().
*
* Note: The irq disabled callback execution is a special case for
* workqueue locking issues. It's not meant for executing random crap
* with interrupts disabled. Abuse is monitored!
*
* @TIMER_PINNED: A pinned timer will not be affected by any timer
* placement heuristics (like, NOHZ) and will always expire on the CPU
* on which the timer was enqueued.
*
* Note: Because enqueuing of timers can migrate the timer from one
* CPU to another, pinned timers are not guaranteed to stay on the
* initialy selected CPU. They move to the CPU on which the enqueue
* function is invoked via mod_timer() or add_timer(). If the timer
* should be placed on a particular CPU, then add_timer_on() has to be
* used.
*/
#define TIMER_CPUMASK 0x0003FFFF
#define TIMER_MIGRATING 0x00040000
#define TIMER_BASEMASK (TIMER_CPUMASK | TIMER_MIGRATING)
#define TIMER_DEFERRABLE 0x00080000
#define TIMER_PINNED 0x00100000
#define TIMER_IRQSAFE 0x00200000
#define TIMER_INIT_FLAGS (TIMER_DEFERRABLE | TIMER_PINNED | TIMER_IRQSAFE)
#define TIMER_ARRAYSHIFT 22
#define TIMER_ARRAYMASK 0xFFC00000struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct hlist_node entry;
unsigned long expires;
void (*function)(struct timer_list *);
u32 flags;

#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};

定时器函数

初始化定时器

在低版本的内核中,初始化定时器使用函数init_timer,但是在高版本内核中已经没有了。取而代之的是使用

timer_setup

https://elixir.bootlin.com/linux/v5.19.17/source/include/linux/timer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* timer_setup - prepare a timer for first use
* @timer: the timer in question
* @callback: the function to call when timer expires
* @flags: any TIMER_* flags
*
* Regular timer initialization should use either DEFINE_TIMER() above,
* or timer_setup(). For timers on the stack, timer_setup_on_stack() must
* be used and must be balanced with a call to destroy_timer_on_stack().
*/
#define timer_setup(timer, callback, flags) \
__init_timer((timer), (callback), (flags))
extern int mod_timer(struct timer_list *timer, unsigned long expires);

第一个参数是一个指向定时器的指针,第二个函数是回调函数,第三个参数是定时器的flags

注意,调用timer_setup并不会开始计时,只是初始化而已

开启定时器

调用add_timer之后,定时器就开始计时了

void add_timer(struct timer_list *timer)

修改定时器

使用mod_timer来修改定时器的超时时间,当调用后,定时器也会开始计时

int mod_timer(struct timer_list *timer, unsigned long expires);

删除定时器

使用del_timer来删除定时器

int del_timer(struct timer_list * timer);

实验

开启了一个2s的定时器,在模块安装的时候,开始,在模块卸载的时候,定时器也被删除,可以通过dmesg内核信息查看到打印信息

代码

https://github.com/liodegwin/kernel/tree/main/timer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/timer.h>

static struct timer_list timer_1;
void timer_func(struct timer_list* ptl){

printk("in %s\n",__FUNCTION__ );
mod_timer(&timer_1, jiffies+msecs_to_jiffies(2000));
}
static int __init tiemr_test_init(void)
{
printk("In %s %s %s\n",__FILE__,__FUNCTION__,__LINE__);


timer_1.expires = jiffies + msecs_to_jiffies(2000);
timer_setup(&timer_1, timer_func, 0);

add_timer(&timer_1);

return 0;
}
static void __exit tiemr_test_exit(void)
{
printk("In %s %s %s\n",__FILE__,__FUNCTION__,__LINE__);
del_timer(&timer_1);
}
module_init(tiemr_test_init);
module_exit(tiemr_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liodeGwin@gmail.com");

实验结果

观察看每2s输出一次打印信息直到模块被卸载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[ 174.376654] In /home/liode/nfs_dir/tmp/timer_test_1.c tiemr_test_init (efault)
[ 176.429446] in timer_func
[ 178.477630] in timer_func
[ 180.525649] in timer_func
[ 182.573641] in timer_func
[ 184.621682] in timer_func
[ 186.669393] in timer_func
[ 188.717528] in timer_func
[ 190.765575] in timer_func
[ 192.813704] in timer_func
[ 194.861760] in timer_func
[ 196.909732] in timer_func
[ 198.957690] in timer_func
[ 201.005811] in timer_func
[ 203.053696] in timer_func
[ 205.101819] in timer_func
[ 207.149806] in timer_func
[ 209.197712] in timer_func
[ 211.245846] in timer_func
[ 213.293587] in timer_func
[ 215.334934] in timer_func
[ 217.369458] in timer_func
[ 217.428013] In /home/liode/nfs_dir/tmp/timer_test_1.c tiemr_test_exit (efault)

注意事项

  1. 如果在定时器还在开始的时候,卸载掉模块,会导致整个系统崩溃,我试了试在exit里面没有调用del_timer,就会导致系统崩溃

  2. 在源代码中的宏__LINE__没有被正确解析成行号,而是变成了错误(efault),原因未知

  3. 定时器在不同版本的内核中产生了较大的变化,相应的代码被部分的改变了,需要根据需求使用不同的函数