Linux kernel之内核定时器

内核定时器 (也称为动态定时器)是内核在以后某一个时刻运行一段程序或进程的基础,软件定时器可以在一个确切的时间点上(更严格地说是一个时间点以后)激活相应的程序段或进程。软件定时器在设备驱动程序中被大量应用以检测设备的状态。

使用一个软件定时器很简单,只需做一些初始化工作,设置一个相对于当前时刻的超时时间和超时处理函数,将其插入到内核定时器队列中即可,设置的超时处理函数会在定时器超时时自动运行。下面介绍如何使用内核定时器和实现内核定时器的内部架构。

1.内核定时器的使用方法

内核定时器由数据结构timer_list表示,该结构表示了一个待处理的延迟任务,我们称该数据结构为内核定时器节点。该数据结构的详细内容请看下面的代码清单。

代码清单--数据结构timer_list

功能简介:该数据结构保存了内核定时器节点的相关信息,包括定时器超时时间和超时处理函数等。

文件:src/include/Linux/timer.h
11  struct timer_list {    
12       struct list_head entry;   
13       unsigned long expires;   
14       void (*function)(unsigned long); 
15       unsigned long data;    
16       struct timer_base_s *base;  
};

成员变量entry:该内核链表表头类型成员变量用于将该内核定时器节点连接到系统中的定时器链表中。

成员变量expires:该无符号长整型变量保存了该定时器的超时时间,用于和系统核心变量jiffies进行比较。

成员变量function:该函数指针变量保存了内核定时器超时后要执行的函数,即定时器超时处理函数。

成员变量data:该无符号长整型变量用作定时器超时处理函数的参数。

成员变量base:该指针变量表明了该内核定时器节点归属于系统中哪一个处理器,在使用函数init_timer()初始化内核定时

                       器节点的过程中,将该指针指向了一个每处理器变量tvec_bases的成员变量t_base。

在了解了内核定时器节点数据结构的相关内容之后,下面来看如何在自己的代码中使用一个内核定时器节点,实现在一段时间后执行一个延迟处理任务。

① 首先,使用下面语句声明一个内核定时器数据结构。

struct timer_list my_timer;

② 使用函数init_timer()对上一步声明的内核定时器结构进行初始化。函数init_timer()主要设置该内核定时器归属系统中哪一个处理,并初始化内核定时器链表指针的next域为NULL。

init_timer(&my_timer);

③ 使用下面的语句来设置内核定时器的超时时间expires、超时处理函数function、超时处理函数所使用的参数data。

my_timer.expires = jiffies + delay; 
my_timer.data = 0;     
my_timer.function = my_function; 

④ 也是最后一步,通过函数add_timer()来激活内核定时器,使用的语句如下:

add_timer(&my_timer);

通过上面4步,我们就创建了一个内核定时器节点my_timer。该内核定时器在当前时刻以后delay个时钟中断后超时,执行超时处理函数my_function,传给超时处理函数的参数为0。

除了上述过程中介绍的内核定时器接口函数之外,内核同时提供了以下接口函数来辅助对内核定时器的操作。

int mod_timer(struct timer_list *timer,unsigned long expires):该函数负责修改内核定时器timer的超时字段expires。该函数可以修改激活和没有激活的内核定时器的超时时间,并把它们都设置为激活状态;返回值为0表示修改的内核定时器在修改之前处于未激活状态,返回值为1表示修改的内核定时器在修改之前处于已激活状态。

int del_timer(structtimer_list* timer)、int del_timer_sync(structtimer_list* timer):这两个函数负责从链表中删除内核定时器timer。它们的区别在于,后者在多处理器系统中会确保其他处理器上没有处理或者处理完毕当前内核定时器timer时才退出。

2.内核定时器架构

与softirq、工作队列两种中断下半部的处理方法类似,每一个内核定时器节点与系统中的处理器通过一个每处理器变量联系起来。内核在文件 src/kernel/timer.c中的第88行使用下面的语句分配了一个名称为tvec_bases、类型为tvec_base_t的每处理器变量。

static DEFINE_PER_CPU(tvec_base_t, tvec_bases);

其中,tvec_base_t是数据结构struct tvec_t_base_s通过语句typedef定义的一个别名,数据结构struct tvec_t_base_s用来记录系统中每一个处理器上待处理内核定时器节点的相关信息。有关该数据结构的详细内容请看下面的代码清单。

代码清单--数据结构tvec_t_base_s

功能简介:该数据结构用于有效组织当前处理器上所有待处理内核定时器节点,以支持快速访问超时内核定时器节点。

该数据结构在同一文件中的第77行开始定义,代码如下。接下来是对这些成员用途的分析、说明。

struct tvec_t_base_s {
struct timer_base_s t_base;
unsigned long timer_jiffies;
tvec_root_t tv1;
tvec_t tv2;
tvec_t tv3;
tvec_t tv4;
tvec_t tv5;
} ____cacheline_aligned_in_smp;

成员变量t_base的数据类型为struct timer_base_s,它在文件src/kernel/timer.c中的第64行开始定义,代码如下:

truct timer_base_s {
spinlock_t lock;
struct timer_list *running_timer;
);

其中,成员变量running_timer记录了正在本地处理器上进行超时处理的内核定时器;另外一个自旋锁成员变量lock用于保护每处理器变量tvec_bases的本地拷贝。

无符号长整型变量timer_jiffies记录了该数据结构中所包含的定时器中最早超时时间,根据该变量可以计算出超时定时器节点所在链表的表头。

变量tv1的类型为tvec_root_t,该数据结构在文件src/kernel/timer.c中的第73行开始定义,代码如下。该数据结构中包含了TVR_SIZE个链表表头指针。

typedef struct tvec_root_s {
struct list_head vec[TVR_SIZE];
} tvec_root_t;

变量tv2、tv3、tv4、tv5的类型为tvec_t,该数据结构在文件src/kernel/timer.c中的第69行开始定义,代码如下:

ypedef struct tvec_s {
struct list_head vec[TVN_SIZE];
)tvec_t;

该数据结构中包含了TVN_SIZE个链表表头指针,其中TVR_SIZE、TVN_SIZE的值在没有选中内核选项 CONFIG_BASE_SMALL时分别为256、64;否则分别为64、16。内核选项CONFIG_BASE_SMALL用于为系统资源匮乏的计算机系统进行优化,选中该内核选项后,可以减小系统核心对内存的使用量。通常个人计算机中不会选中该内核选项。

这5个变量一共包含了256+64×4=512个链表表头,每个链表表头指针指向了一个待处理内核定时器链表。由当前处理器处理的内核定时器根据其超时时间的不同分布在这512个链表上。通过上面的分析,可以用一个形象化的示意图来描述内核定时器的架构,如图所示。

Linux kernel之内核定时器  
内核定时器架构示意图

相关推荐