打印

Linux驱动学习笔记之序言&高精度定时器

[复制链接]
4104|15
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
dianzijiangren|  楼主 | 2012-5-15 13:07 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 dianzijiangren 于 2012-5-15 21:19 编辑


在一座山面前,谁都不知道如何开路,我愿意帮后来者指明一条充满乐趣的道路.Linux是个庞大的工程,许多人在门里自娱自乐.许多老人,将门槛高高的立起,享受着美餐.确切说,他们很少奉献.无论是对开源代码贡献,还是技术的共享精神,他们都违背了.本文的作者是我在Linux源代码方面的第一个徒弟.我没有让他去学驱动,也没有让他去学应用.我更没有让他去读苦涩的进程调度.或许,或许现在还不是时候.我选择了一个轻量级的设计,让他去研读.但是这个东西又有很重要的地位.能快速增进学习的成就感和技术水平.
hrtimer是一个比较新的模块,他在内核代码树上面还是一根年轻的枝叶,但是能合并到Linux代码库中,说明他有很强壮的生命力.一切优美的东西,都来源于看起来不是那么神秘的设计,比如红黑树,比如定时器.但是如何将这两个组成一个具有生命力的娃娃,是大师的功力.
我一直让本文作者写的深入浅出,作为第一篇**,我也不必要要求的太过于苛刻.我相信,以后大家提出的改动应该更能推动他写的更好.我也不希望读者仅仅浏览一下本文就浅尝辄止.这毕竟是我在工作中看到一些深恶痛绝的垃圾设计之后,才想让大家深刻理解Linux作为一个代码库的美好.你完全可以把这一块扣下来,写到你的系统里面去,这是何等美好的一件事情呢?

icecut


2012
5
                                                                                             北京
作者前言

2009年9月,我考入了大学,电子信息工程专业,学校是师范类,工科方面薄弱程度可想而知,和很多的普通工科生一样,每天就是上课,睡觉玩游戏,生活就这么混下去,一天一天的混。2010年2月份,买了台笔记本,回校后,在二手书摊凑巧买了本《微型计算机原理与接口技术》,五块钱吧,然后自此便一发不可收拾,整天折腾电脑,当然,那时候是没有目的的折腾。直到有一天,在图书馆看到将Linux的书,便拿下来翻着看,Ubuntu9.04,是我在电脑上装的第一个Linux系统,自那以后,几乎天天泡在图书馆里,看Linux方面的书,而与此同时,自学了几款单片机,对于器件底层有了一定的了解和认识。
应该是2011年下旬,在21IC上发了自己的简历,看到icecut前辈给我留言,并邀请我加入他的技术交流群。春节的时候,和群里的前辈们聊了很多,icecut前辈愿意收我为徒弟,并得知我想学习Linux驱动设计,至此,便开始了我的驱动学习生涯。有一个师傅带着我,这是何等的荣幸,又是何等的幸运,我看到周围的很多人如今依然在浑浑噩噩的生活着,上课、睡觉、玩游戏、找妹子……青春本可以不如此荒芜。现在大三了,很多人都选择考研,或者说很多人都不得不考研,不考,下来能干吗?但是考研又是何等的残酷,我不想说教育如何如何,凡事靠自己,这是我一直相信的,至于外部的东西,何必去在意那么多。
谨以此笔记献给所有热爱生活的人们,感谢师傅和各位前辈的指点,也希望看到我笔记的前辈们,在百忙中留言指点晚辈,拜托了!由于我的水平有限,笔记更新可能缓慢,请谅解!

dianzijiangren


2012/5

                                        济南

相关帖子

沙发
icecut| | 2012-5-15 13:51 | 只看该作者
原野之狼,给他合并一下帖子

使用特权

评论回复
板凳
icecut| | 2012-5-15 13:53 | 只看该作者
地板
dianzijiangren|  楼主 | 2012-5-15 20:59 | 只看该作者
原野之狼,给他合并一下帖子
icecut 发表于 2012-5-15 13:51
貌似不能合并,字数超了

使用特权

评论回复
5
dianzijiangren|  楼主 | 2012-5-15 21:20 | 只看该作者
本帖最后由 dianzijiangren 于 2012-5-16 10:34 编辑

                                           高精度定时器


突兀地说高精度定时器,感觉摸不着头脑,至少初学者会茫然,但是从字面上来理解,很简单,定时器嘛,精度高点,然后,就没有然后了。其实差不多就是这么回事,只是里面涉及到了一些别的细节上的问题。
工欲善其事必先利其器,在开始讲之前,我们先利一下器:
²
相关的用到的几个源代码文件以及其路径如下:
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
(*function)(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
};
以上这个结构体,对于用户来说,只需关心三点,第一是字段function,这个是定时器失效后的回调函数,第二是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接口。

使用特权

评论回复
6
dianzijiangren|  楼主 | 2012-5-15 21:21 | 只看该作者
首先是配置并初始化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的源代码里所提供的,其实只有两个常数。
二、相关的接口代码
定时器初始化之后,进行设定定时器的到期时间,并启动定时器,函数声明代码hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode),timer代表将要被添加的定时器,tim代表到期时间,mode代表定时器模式。如果启动成功,则返回0,否则返回1。
如果要取消一个设置好的定时器,可以使用int hrtimer_cancel(struct hrtimer *timer)和int hrtimer_try_to_cancel(struct hrtimer *timer),这两个函数的区别是,后者提供了额外的返回值-1,如果定时器当前正在执行因而无法停止,则返回-1.在这种情况下,hrtimer_cancel会一直等处理程序执行完毕。另外,如果定时器处于未激活状态,两个函数的返回值都是0,如果处于激活状态(即状态为HRTIMER_STATE_INACTIVE或者HRTIMER_STATE_ENQUEUED),二者都返回1(读者务必注意,在2.6.X版本的源代码中,处于激活状态的两个常数是HRTIMER_STATE_PENDING或者HRTIMER_STATE_ENQUEUED,无非改了模样了,这个注意一下就好)。
如果要重启一个取消的定时器,可以使用static inline int hrtimer_restart(struct hrtimer *timer)。
上面讲的几个函数应该是最基本的,也没什么大的讲头,下面的函数才是高精度定时器的精彩之处,也就四高精度定时器的到期机制和回调函数的运行方式,运用红黑树的方法既节省资源又提高效率。
在讲精彩之处之前,首先要跟读者说一下几个补充知识,其实前面我们也已经提到了,无非这里重复和补充一下:
Ø
高精度定时器按照时间在一棵红黑树上排序。
Ø
他们独立于周期时钟,采用纳秒时间戳而非jiffies的时间规格。
Ø
This patch introduces a new subsystem for high-resolution kernel timers.这句话里的patch这个单词有点意思,他是说高精度定时器作为一个补丁包被安装到系统里的,在2.6.16之前是没有这个概念的。
Ø
高精度定时器的框架在编译的时候是在内核里,但是如果没有配置高精度定时器,那么高精度定时器是按照普通的定时器来运行。
Ø
最后一点,高精度定时器是采用红黑树算法实现的,而普通的定时器是采用时间轮循算法实现的.
根据上面的几点,我们可以知道高精度定时器框架总是有一部分会编译到内核中去的,即使禁止了对高分辨率定时器的支持。在这种情况下,高分辨率定时器的到期是有一个低分辨率时钟驱动的。这避免了代码复制,因为高分辨率定时器的用户,在没有高分辨率计时能力的系统上,无需对时间相关代码提供一个额外的版本。这种情况下,仍然会采用高分辨率框架,但只是以低分辨率运行。即使高分辨率定时器支持已经编译到内核中,但在启动时只提供了低分辨率计时功能,这与上述情况是相同的。因此我们在这里讲的高分辨率定时器,要分成两种情况,一种是高分辨率模式下的高分辨率定时器,另一种是高分辨率模式下的低分辨率定时器。

使用特权

评论回复
7
dianzijiangren|  楼主 | 2012-5-15 21:24 | 只看该作者
本帖最后由 dianzijiangren 于 2012-5-15 21:50 编辑

前面我们讲了定时器的初始化和打开,下面讲定时器中断相关,这里我们只讲在高分辨率模式下的hrtimer的工作情况。
首先我们假定一个高分辨率时钟已经设置好而且正在运行,而向高分辨率模式的迁移已经完全完成,那么接下来,一般会发生以下情形:负责高分辨率定时器的时钟事件设备引发中断,调用hrtimer_interrupt(struct clock_event_device *dev)作为事件处理程序,该函数负责选中所有到期的定时器,然后直接执行中断程序,当然也可能将其转移到过期链表中(如果他们可以在软中断上下文执行的话——这一段接下来会说)。执行中断程序后,接下来重新对硬件进行编程,说白了有点类似于单片机的清除中断标志,当然这里还做了很多的事情,不光是清楚中断标志,还包括对硬件的重新编程和初始化,使得在下一个待决定时器到期时可以引发中断,然后将引发static void run_hrtimer_softirq(struct softirq_action *h)处理到期链表上所有定时器的中断函数。下面讨论一下相关的代码的特性和机制。首先考虑的是hrtimer_interrupt函数,下面贴上它的源代码(务必注意这里的代码版本是以Linux3.2.12为准):
void hrtimer_interrupt(struct clock_event_device *dev)
{

struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);

ktime_t expires_next, now, entry_time, delta;

int i, retries = 0;

BUG_ON(!cpu_base->hres_active);

cpu_base->nr_events++;

dev->next_event.tv64 = KTIME_MAX;

entry_time = now = ktime_get();
retry:

expires_next.tv64 = KTIME_MAX;

raw_spin_lock(&cpu_base->lock);

cpu_base->expires_next.tv64 = KTIME_MAX;

for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {

struct hrtimer_clock_base *base;

struct timerqueue_node *node;

ktime_t basenow;

if (!(cpu_base->active_bases & (1 << i)))

continue;

base = cpu_base->clock_base + i;

basenow = ktime_add(now, base->offset);

while ((node = timerqueue_getnext(&base->active))) {

struct hrtimer *timer;

timer = container_of(node, struct hrtimer, node);

if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer)) {

ktime_t expires;

expires = ktime_sub(hrtimer_get_expires(timer),


base->offset);

if (expires.tv64 < expires_next.tv64)

expires_next = expires;

break;

}

__run_hrtimer(timer, &basenow);

}

}

cpu_base->expires_next = expires_next;

raw_spin_unlock(&cpu_base->lock);

if (expires_next.tv64 == KTIME_MAX ||


!tick_program_event(expires_next, 0)) {

cpu_base->hang_detected = 0;

return;

}

now = ktime_get();

cpu_base->nr_retries++;

if (++retries < 3)

goto retry;

cpu_base->nr_hangs++;

cpu_base->hang_detected = 1;

delta = ktime_sub(now, entry_time);

if (delta.tv64 > cpu_base->max_hang_time.tv64)

cpu_base->max_hang_time = delta;

if (delta.tv64 > 100 * NSEC_PER_MSEC)

expires_next = ktime_add_ns(now, 100 * NSEC_PER_MSEC);

else

expires_next = ktime_add(now, delta);

tick_program_event(expires_next, 1);

printk_once(KERN_WARNING "hrtimer: interrupt took %llu ns\n",


ktime_to_ns(delta));
}
话说有点长啊……把它分成几部分来看,首先进行的是一些必要的初始化配置:

struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);

ktime_t expires_next, now, entry_time, delta;

int i, retries = 0;

BUG_ON(!cpu_base->hres_active);

cpu_base->nr_events++;

dev->next_event.tv64 = KTIME_MAX;

entry_time = now = ktime_get();
retry:

expires_next.tv64 = KTIME_MAX;

raw_spin_lock(&cpu_base->lock);
以上几行程序就是用来初始化的,接下来,其到期时间保存在expires_next中。最初将该变量设置为KTIME_MAX,是表明没有下一个定时器。函数的主要工作是遍历所有的时基(两种时钟都遍历),代码如下:

for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {

struck hrtimer_clock_base *base;

struct timerqueue_node *node;

ktime_t basenow;

if (!(cpu_base->active_bases & (1 << i)))

continue;

base = cpu_base->clock_base + i;

basenow = ktime_add(now, base->offset);
上面那段代码没什么亮点吧,最后一句basenow就是表示当前时间,base->offset仅在已经重新调整了实时时钟的时候是非零,其他的时候不会影响到单调时钟基础。从base->first开始,即可以获得红黑树中到期的结点,代码如下:

while ((node = timerqueue_getnext(&base->active))) {
struct hrtimer *timer;
timer = container_of(node, struct hrtimer, node);
if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer)) {
ktime_t expires;
expires = ktime_sub(hrtimer_get_expires(timer),
base->offset);
if (expires.tv64 < expires_next.tv64)
expires_next = expires;
break;
}
__run_hrtimer(timer, &basenow);
}
这里面涉及到另一个东西——红黑树,其实在之前我们已经说了,高精度定时器之所以精度高,就是因为定时器引入了二叉树算法,确切点说是红黑树算法,红黑树算饭的算法的源代码文件及位置如下:
Rbtree.c (linux-3.2.12\lib)
Rbtree.h (linux-3.2.12\include\linux)
Rbtree.txt (linux-3.2.12\documentation)
在这里不细细讲解红黑树,只是简单的提一下相关的性质吧:
l
每个结点或是红的或是黑的;
l
根节点是黑的;
l
每个叶子节点(NIL)是黑的;
l
如果一个结点是红的,则他的两个儿子都是黑的;
l
对于每个结点,从该结点到其子孙节点的结点的所有路径上包含相同数目的黑结点。
一个二叉查找树,如果符合上述特点,那么他就是红黑树。说的太玄乎了……其实引入红黑树的目的就是让计时更加精确而已,这里我们只关心以下几个结构体:
struct rb_root
{
struct rb_node *rb_node;
};
struct rb_node
{
unsigned long
rb_parent_color;
#define
RB_RED
0
#define
RB_BLACK
1
struct rb_node *rb_right;
struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
可能有人会诧异,就这俩?!我的答案是:嗯,就这俩。struct rb_root定义树的根,struct rb_node定义树的结点,然后再就是几个常用的API函数,这里就不细细说了,毕竟红黑树涉及的东西太多了。
高精度定时器之所以引入红黑树,就是为了更加精确,这个说起来可能很难理解,我们先说说二叉查找树,二叉查找树有个性质,左孩子的关键字≤父结点≤右孩子的关键字,这样的规定就大大加快了查找的效率,降低了算法的复杂度,同样的,在二叉查找树上再加上一些规定,可以将查找效率更大的提高,这样,可以更加精确的计时,这是高精度定时器引入红黑树的核心思想。
关于红黑树,就说这么些,继续说高精度定时器。
如果下一个定时器到期时间在未来,那么可以停止处理,离开while循环。但需要记住该到期时间,以便在稍后对时钟事件设备重新编程。
如果当前的定时器已经到期,那么在允许在软中断上下文执行处理程序的情况下,会将该定时器移动到回调链表。continue确保了处理代码转向下一个到期的候选定时器;如果不允许在软中断上下文执行定时器的处理程序,那么将直接在硬件中断上下文中执行定时器回调函数。
回调处理程序通过timer->function(timer)来执行(原型在linux-3.2.12\include\linux\ Hrtimer.h中,注意这个大的for循环里面有struct hrtimer *timer;这一句)。如果处理函数返回HRTIMER_RESTART,请求重启定时器,那么通过enqueue_hrtimer来完成该请求。在处理程序已经执行后,可以清除HRTIMER_STATE_CALLBACK标志。在已经选择了所有时钟基础的待决定时器之后,内核需要对时钟事件设备重新编程,以便在下一个定时器到期时依法中断。另外,我们前面说的,中断引发多了,不能同时处理的,就挂到连边上等待,处理完这个,再从链表上弄些一个来处理,直到链表为空。
最后还需要一个步骤,就是引发软中断执行待决定时器的回调函数,该软中断的处理程序是:
static void run_hrtimer_softirq(struct softirq_action *h)
{
hrtimer_peek_ahead_timers();
}
从本质上来看,该函数将遍历所有待决定的链表,hrtimer_peek_ahead_timers();代码如下:
void hrtimer_peek_ahead_timers(void)
{
unsigned long flags;
local_irq_save(flags);
__hrtimer_peek_ahead_timers();
local_irq_restore(flags);
}
在hrtimer_peek_ahead_timers()函数的注释里写的很清楚,英文翻译如下:
hrtimer_peek_ahead_timers——run soft-expired timers now现在运行软中断执行待决定时期
hrtimer_peek_ahead_timers会监视目前的cpu的定时器队列,检查是否有任何过期的软中断的定时器。如有此类定时器存在,他们立即运行,然后从定时器队列移除。
移除定时器的函数为remove_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base)。
大致上,到此已经讲完了了高精度定时器在高精度模式下的整个使用过程。


dianzijiangren

QQ:704519331

使用特权

评论回复
8
原野之狼| | 2012-5-15 22:19 | 只看该作者
支持原创!
这是一篇技术含量比较高的**,LZ在Q群里也很活跃。
长江后浪推前浪呀~  
继续加油:victory:

使用特权

评论回复
9
原野之狼| | 2012-5-15 22:21 | 只看该作者
原野之狼,给他合并一下帖子
icecut 发表于 2012-5-15 13:51

貌似本论坛没有合并的功能,反正我是没有找到相关的操作~

使用特权

评论回复
10
dianzijiangren|  楼主 | 2012-5-16 10:31 | 只看该作者
支持原创!
这是一篇技术含量比较高的**,LZ在Q群里也很活跃。
长江后浪推前浪呀~  
继续加油:victory:
原野之狼 发表于 2012-5-15 22:19

谢谢前辈鼓励和栽培:lol

使用特权

评论回复
11
dong_abc| | 2012-5-16 12:46 | 只看该作者
在linux版块发这个帖子比较好。

使用特权

评论回复
12
icecut| | 2012-5-16 14:01 | 只看该作者
11# dong_abc

这个应该写给单片机.原因是单片机就2-3个时钟中断源,很多时候都不够用.所以.应该学习一下

使用特权

评论回复
13
ayl439| | 2012-6-14 09:31 | 只看该作者
mark

使用特权

评论回复
14
dianzijiangren|  楼主 | 2013-7-18 17:06 | 只看该作者
ayl439 发表于 2012-6-14 09:31
mark

~_~

使用特权

评论回复
15
duyiwm| | 2014-2-19 14:28 | 只看该作者
我想把mini2440开发板配置成高精度定时器  应该如何配置?   

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:终于,我也改行到互联网了

18

主题

558

帖子

3

粉丝