新闻资讯
Group news
青岛广盛源肥业有限公司    您的位置: 首页  >  新闻资讯  >  正文

Linux时间子系统中低分辨率定时器的原理和实现

2019年10月12日 文章来源:网络整理 热度:104℃ 作者:刘英

利用定时器,我们可以设定在未来的某一时刻,触发一个特定的事件。所谓低分辨率定时器,是指这种定时器的计时单位基于jiffies值的计数,也就是说,它的精度只有1/HZ,假如你的内核配置的HZ是1000,那意味着系统中的低分辨率定时器的精度就是1ms。早期的内核版本中,内核并不支持高精度定时器,理所当然只能使用这种低分辨率定时器,我们有时候把这种基于HZ的定时器机制成为时间轮:time wheel。虽然后来出现了高分辨率定时器,但它只是内核的一个可选配置项,所以直到目前最新的内核版本,这种低分辨率定时器依然被大量地使用着。

1.  定时器的使用方法

在讨论定时器的实现原理之前,我们先看看如何使用定时器。要在内核编程中使用定时器,首先我们要定义一个TIme_list结构,该结构在include/linux/TImer.h中定义:

[cpp] view plain copy

struct TImer_list {  

/* 

* All fields that change during normal runTIme grouped to the 

* same cacheline 

*/  

struct list_head entry;  

unsigned long expires;  

struct tvec_base *base;  

void (*function)(unsigned long);  

unsigned long data;  

int slack;  

......  

};  

entry  字段用于把一组定时器组成一个链表,至于内核如何对定时器进行分组,我们会在后面进行解释。

expires  字段指出了该定时器的到期时刻,也就是期望定时器到期时刻的jiffies计数值。

base  每个cpu拥有一个自己的用于管理定时器的tvec_base结构,该字段指向该定时器所属的cpu所对应tvec_base结构。

function  字段是一个函数指针,定时器到期时,系统将会调用该回调函数,用于响应该定时器的到期事件。

data  该字段用于上述回调函数的参数。

slack  对有些对到期时间精度不太敏感的定时器,到期时刻允许适当地延迟一小段时间,该字段用于计算每次延迟的HZ数。

要定义一个timer_list,我们可以使用静态和动态两种办法,静态方法使用DEFINE_TIMER宏:

#define DEFINE_TIMER(_name, _function, _expires, _data)

该宏将得到一个名字为_name,并分别用_function,_expires,_data参数填充timer_list的相关字段。

如果要使用动态的方法,则可以自己声明一个timer_list结构,然后手动初始化它的各个字段:

[cpp] view plain copy

struct timer_list timer;  

......  

init_timer(&timer);  

timer.function = _function;  

timer.expires = _expires;  

timer.data = _data;  

要激活一个定时器,我们只要调用add_timer即可:

[cpp] view plain copy

add_timer(&timer);  


要修改定时器的到期时间,我们只要调用mod_timer即可:

[cpp] view plain copy

mod_timer(&timer, jiffies+50);  

要移除一个定时器,我们只要调用del_timer即可:

[cpp] view plain copy

del_timer(&timer);  

定时器系统还提供了以下这些API供我们使用:

void add_timer_on(struct timer_list *timer, int cpu);  // 在指定的cpu上添加定时器

int mod_timer_pending(struct timer_list *timer, unsigned long expires);  //  只有当timer已经处在激活状态时,才修改timer的到期时刻

int mod_timer_pinned(struct timer_list *timer, unsigned long expires);  //  当

void set_timer_slack(struct timer_list *time, int slack_hz);  //  设定timer允许的到期时刻的最大延迟,用于对精度不敏感的定时器

int del_timer_sync(struct timer_list *timer);  //  如果该timer正在被处理中,则等待timer处理完成才移除该timer

2.  定时器的软件架构

低分辨率定时器是基于HZ来实现的,也就是说,每个tick周期,都有可能有定时器到期,关于tick如何产生,请参考:Linux时间子系统之四:定时器的引擎:clock_event_device。系统中有可能有成百上千个定时器,难道在每个tick中断中遍历一下所有的定时器,检查它们是否到期?内核当然不会使用这么笨的办法,它使用了一个更聪明的办法:按定时器的到期时间对定时器进行分组。因为目前的多核处理器使用越来越广泛,连智能手机的处理器动不动就是4核心,内核对多核处理器有较好的支持,低分辨率定时器在实现时也充分地考虑了多核处理器的支持和优化。为了较好地利用cache line,也为了避免cpu之间的互锁,内核为多核处理器中的每个cpu单独分配了管理定时器的相关数据结构和资源,每个cpu独立地管理属于自己的定时器。

2.1  定时器的分组

首先,内核为每个cpu定义了一个tvec_base结构指针:

[cpp] view plain copy

static DEFINE_PER_CPU(struct tvec_base *, tvec_bases) = &boot_tvec_bases;  

tvec_base结构的定义如下:

[cpp] view plain copy

struct tvec_base {  

spinlock_t lock;  

struct timer_list *running_timer;  

unsigned long timer_jiffies;  

unsigned long next_timer;  

struct tvec_root tv1;  

struct tvec tv2;  

struct tvec tv3;  

struct tvec tv4;  

struct tvec tv5;  

} ____cacheline_aligned;  

running_timer  该字段指向当前cpu正在处理的定时器所对应的timer_list结构。

上一篇:Linux时间子系统中的定时器的引擎:clock_event_device


下一篇:了解Linux动态频率调节系统CPUFreq等问题

友情链接
Links