||
突兀地说高精度定时器,感觉摸不着头脑,至少初学者会茫然,但是从字面上来理解,很简单,定时器嘛,精度高点,然后,就没有然后了。其实差不多就是这么回事,只是里面涉及到了一些别的细节上的问题。
工欲善其事必先利其器,在开始讲之前,我们先利一下器:
² 相关的用到的几个源代码文件以及其路径如下:
Hrtimers.txt (linux-3.2.12\documentation\timers)
Hrtimer.c (linux-3.2.12\kernel)
Hrtimer.h (linux-3.2.12\include\linux)
² 单纯的在高精度定时器模式下操作高精度定时器,整个操作框架如下:
初始化hrtimer_init,通过hetimer结构体设置相关数据,比如定时时长等->开启定时器hrtimer_start->运行高精度定时器hrtimer_run_queues->触发中断,调用中断回调函数,hrtimer_interrupt->移除高精度定时器remove_hrtimer.
读者现在脑子里有一个框架,具体驱动细节下文将一一阐述。
先概述一下,可能会有些生僻的东西在里面难理解,不要紧,后面会有相关的代码和例子来解释。
Ø 高精度定时器按照时间在一棵红黑树上排序。
Ø 他们独立于周期时钟,采用纳秒时间戳而非jiffies的时间规格。
先把Linux代码里面的高精度定时器相关的文档拿出来,看看他的介绍,然后我再解释一下,文档路径:Hrtimers.txt (linux-3.2.12\documentation\timers)
文档内容……说实话文档有点长,而且Linux的文档维护度不是很高,我从里面找了几句话翻译出来,然后解释一下:
Ø This patch introduces a new subsystem for high-resolution kernel timers.这句话里的patch这个单词有点意思,他是说高精度定时器作为一个补丁包被安装到系统里的,在2.6.16之前是没有这个概念的。
Ø 第二点,英文太长就不贴了,是说为什么要用高精度定时器,因为每个系统都存在定时器,当然精度不高,相比较而言就称之为低精度定时器。说白了就是需要高精度。
Ø 第三点,高精度定时器还有个特点,就是他的框架在编译的时候是在内核里,但是如果没有配置高精度定时器,那么高精度定时器是按照普通的定时器来运行。
Ø 最后一点,高精度定时器是采用红黑树算法实现的,而普通的定时器是采用时间轮循算法实现的.
Ø 另外,文档中还解释了很多比如时钟源、数据结构、红黑树等等问题,这些问题在下面分开阐述。
一、相关的数据结构
高分辨率定时器所涉及的数据结构,我们从这几大方面考虑:
关于来源:就是说这个时钟是怎么来的,在hrtimer.h中定义了一个结构体,代码如下:
struct hrtimer_clock_base {
struct hrtimer_cpu_base *cpu_base;
int index; //用于区分时钟的属性(一共有两种,下面将会提及)
clockid_t clockid; //每个CPU所支持的时钟的ID
struct timerqueue_head active; //正在启用的定时器的红黑树根节点
ktime_t resolution; //时钟的分辨率,纳秒
ktime_t (*get_time)(void); //用来恢复当前时钟
ktime_t softirq_time; //在软中断中运行高精度定时器队列的时间
ktime_t offset; //修改定时器时钟的额偏移量
};
关于上面的几个元素,,有些东西解释一下。
高分辨率定时器可以基于两种时钟(时钟基础,clock base):一种是单调时钟(CLOCK_MONOTONIC),在系统启动时,从0开始;另一种时钟(CLOCK_REALTIME)表示系统的实际时间。上文的结构体struct hrtimer_clock_base中,index元素就是用来区分是CLOCK_MONOTONIC还是CLOCK_REALTIME时钟的。对于系统的每一个CPU都提供了一个包含这两种时钟基础的数据结构,每一个时候总时钟基础都有一个红黑树,来排序所有待决的高精度定时器,而每个CPU都提供两个时钟基础(单调时钟和实际时间),所有定时器都按过期时间在红黑树上排序,如果定时器已经到期但其处理程序回调函数尚未执行,则从红黑树迁移到一个链表中。在调整实时时钟的时候,会造成存储在CLOCK_REALTIME时钟基础上的定时器的过期时间值与当前实际时间之间的偏差。offset字段有助于修正这种情况,他表示定时器需要校正的偏移量。由于这只是一种临时效应,很少发生。
在认识时钟源之前,我们其实还应该认识一个结构体struct hrtimer,代码如下:
struct hrtimer {
struct timerqueue_node node; //定时器队列节点, 同时还管理 node.expires,高精度定时器的绝对失效时间在其内部的算法这个时间和定时器基于的时钟有关(就是上文说的那两种时基).
ktime_t _softexpires; //绝对的最早的到期时间
enum hrtimer_restart (*)(struct hrtimer *); //定时器到期回调函数
struct hrtimer_clock_base *base; //指向时基的指针(每个CPU,每个时钟)
unsigned long state; //状态信息,用来看位值
#ifdef CONFIG_TIMER_STATS
int start_pid; //定时器统计区域存储的开始计时的任务的pid
void *start_site; //定时器存放当前定时的开始值
char start_comm[16]; //定时器统计区域名称开始计时的存储过程
#endif
};
以上这个结构体,对于用户来说,只需关心三点,第一是字段,这个是定时器失效后的回调函数,第二是expires,这个表示到期时间,第三是最后一句话,高精度定时器结构体的使用必须经过 hrtimer_init()函数的初始化,hrtimer_init()函数属于应用接口,所以放在后面说。这里还有一个问题,也是高精度定时器的核心的问题,就是红黑树在高精度定时器的应用问题,当然现在讲这个有点早,但是先让读者心里有这么个底,Linux的传统定时器通过时间轮算法实现(timer.c),但hrtimer通过红黑树算法实现。在struct hrtimer里面有一个node域,类型为struct timerqueue_node,这个域代表了hrtimer在红黑树中的位置,注意一下,我参考的源代码是3.2.12版本,在2.6.X版本中这个域的格式为struct rb_node。这里先跟读者打声招呼,有这么回事,等在将具体用法时,我们在说说是如何实现的。
两个重要的结构体说完了,由于要兼容多核处理器,因此会涉及到每个CPU的时基,结构体struct hrtimer_cpu_base就是用来定义每个CPU的时钟的,目前每个CPU只对应于单调时钟和实时时钟,结构体如下:
struct hrtimer_cpu_base { //单个CPU时基结构体
raw_spinlock_t lock; //锁定相关的时基和定时器,自旋锁
unsigned long active_bases; //用活动的定时器标记基础的位字段
#ifdef CONFIG_HIGH_RES_TIMERS
ktime_t expires_next; //将要到期的下一个时间的绝对时间
int hres_active; //高分辨率模式的状态,布尔变量
int hang_detected; //最新的被发现的挂起的高精度定时器中断
unsigned long nr_events; //高精度定时器中断总数
unsigned long nr_retries; //高精度定时器中断重试总数
unsigned long nr_hangs; //高精度定时器中断挂起总数
ktime_t max_hang_time; //在高精度定时器中断触发最长时间
#endif
struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES]; //此CPU时基指针
};
上面的三个结构体应该是最基础的,定义了高精度定时器相关的功能和元素,并且每一个CPU都全套一个定义的结构体,然后初始化hrtimers。
基本的结构体讲完了,下面开始讲API接口。
首先是配置并初始化hrtimers的API。在开始的时候我们讲struct hrtimer的时候,提到要使用struct hrtimer要先初始化,函数声明代码如下:
void hrtimer_init(struct hrtimer *timer, clockid_t clock_id,
enum hrtimer_mode mode) //给定时钟初始化定时器
{
debug_init(timer, clock_id, mode);
__hrtimer_init(timer, clock_id, mode);
}
以上函数实现了一个高精度定时器的初始化,下面是相关元素的解释:
/**
* hrtimer_init – 给定时钟初始化定时器
* @timer: 将要被初始化的定时器
* @clock_id: 将要被用到的时钟
* @mode: 定时器模式 abs/rel
*/
mode可以使用五个常数,如下:
enum hrtimer_mode {
HRTIMER_MODE_ABS = 0x0, /* 时间是绝对的 */
HRTIMER_MODE_REL = 0x1, /*时间是相对的 */
HRTIMER_MODE_PINNED = 0x02, /* 定时器被绑定到CPU */
HRTIMER_MODE_ABS_PINNED = 0x02,
HRTIMER_MODE_REL_PINNED = 0x03,
};
hrtimer_init()函数里面调用了__hrtimer_init()函数,下面是该函数的原型:
static void __hrtimer_init(struct hrtimer *timer, clockid_t clock_id,enum hrtimer_mode mode)
{
struct hrtimer_cpu_base *cpu_base;
int base;
memsettimer, 0, sizeof(struct hrtimer));
cpu_base = &__raw_get_cpu_var(hrtimer_bases);
if (clock_id == CLOCK_REALTIME && mode != HRTIMER_MODE_ABS)
clock_id = CLOCK_MONOTONIC;
base = hrtimer_clockid_to_base(clock_id);
timer->base = &cpu_base->clock_base[base];
timerqueue_init(&timer->node);
#ifdef CONFIG_TIMER_STATS
timer->start_site = NULL;
timer->start_pid = -1;
memset(timer->start_comm, 0, TASK_COMM_LEN);
#endif
}
__hrtimer_init()函数调用了struct hrtimer_cpu_base结构体对CPU进行相关的初始化,并使用memset()函数,原型如下:
void *memset(void *s, int c, size_t n)
{
int i;
char *ss = s;
for (i = 0; i < n; i++)
ss = c;
return s;
}
这个函数清空了memory里面的东西,完成了初始化,memset(timer, 0, sizeof(struct hrtimer))。
在这里注意一下,还是前面说到的一个问题,我用的源代码是3.2.12的,而2.6.X的源代码里所提供的,其实只有两个常数。