Linux时钟处理机制ChinaUnix操作系统频道.docx
《Linux时钟处理机制ChinaUnix操作系统频道.docx》由会员分享,可在线阅读,更多相关《Linux时钟处理机制ChinaUnix操作系统频道.docx(24页珍藏版)》请在冰豆网上搜索。
![Linux时钟处理机制ChinaUnix操作系统频道.docx](https://file1.bdocx.com/fileroot1/2022-10/29/0bdebdcf-64a2-45f7-ad53-93105593aa34/0bdebdcf-64a2-45f7-ad53-93105593aa341.gif)
Linux时钟处理机制ChinaUnix操作系统频道
Linux时钟处理机制
2008年09月10日16:
00来源:
ChinaUnix文档频道作者:
HonestQiao编辑:
周荣茂
级别:
初级
赵健博(zhaojianbo@),硕士,中国科学院计算技术研究所
2008年9月11日
在Linux操作系统中,很多活动都和时间有关,例如:
进程调度和网络处理等等。
所以说,了解Linux操作系统中的时钟处理机制有助于更好地了解Linux操作系统的运作方式。
本文分析了Linux2.6.25内核的时钟处理机制,首先介绍了在计算机系统中的一些硬件计时器,然后重点介绍了Linux操作系统中的硬件时钟和软件时钟的处理过程以及软件时钟的应用。
最后对全文进行了总结。
1计算机系统中的计时器
在计算机系统中存在着许多硬件计时器,例如RealTimerClock(RTC)、TimeStampCounter(TSC)和ProgrammableIntervalTimer(PIT)等等。
这部分内容不是本文的中点,这里仅仅简单介绍几种,更多内容参见参考文献:
∙RealTimerClock(RTC):
o独立于整个计算机系统(例如:
CPU和其他chip)
o内核利用其获取系统当前时间和日期
∙TimeStampCounter(TSC):
o从Pentium起,提供一个寄存器TSC,用来累计每一次外部振荡器产生的时钟信号
o通过指令rdtsc访问这个寄存器
o比起PIT,TSC可以提供更精确的时间测量
∙ProgrammableIntervalTimer(PIT):
o时间测量设备
o内核使用的产生时钟中断的设备,产生的时钟中断依赖于硬件的体系结构,慢的为10ms一次,快的为1ms一次
∙HighPrecisionEventTimer(HPET):
oPIT和RTC的替代者,和之前的计时器相比,HPET提供了更高的时钟频率(至少10MHz)以及更宽的计数器宽度(64位)
o一个HPET包括了一个固定频率的数值增加的计数器以及3到32个独立的计时器,这每一个计时器有包涵了一个比较器和一个寄存器(保存一个数值,表示触发中断的时机)。
每一个比较器都比较计数器中的数值和寄存器中的数值,当这两个数值相等时,将产生一个中断
2硬件时钟处理
这里所说的硬件时钟处理特指的是硬件计时器时钟中断的处理过程。
2.1数据结构
和硬件计时器(本文又称作硬件时钟,区别于软件时钟)相关的数据结构主要有两个:
∙structclocksource:
对硬件设备的抽象,描述时钟源信息
∙structclock_event_device:
时钟的事件信息,包括当硬件时钟中断发生时要执行那些操作(实际上保存了相应函数的指针)。
本文将该结构称作为“时钟事件设备”。
上述两个结构内核源代码中有较详细的注解,分别位于文件clocksource.h和clockchips.h中。
需要特别注意的是结构clock_event_device的成员event_handler,它指定了当硬件时钟中断发生时,内核应该执行那些操作,也就是真正的时钟中断处理函数。
在2.3节“时钟初始化”部分会介绍它真正指向哪个函数。
Linux内核维护了两个链表,分别存储了系统中所有时钟源的信息和时钟事件设备的信息。
这两个链表的表头在内核中分别是clocksource_list和clockevent_devices。
图2-1显示了这两个链表。
图2-1时钟源链表和时钟事件链表
2.2通知链技术(notificationchain)
在时钟处理这部分中,内核用到了所谓的“通知链(notificationchain)”技术。
所以在介绍时钟处理过程之前先来了解下“通知链”技术。
在Linux内核中,各个子系统之间有很强的相互关系,一些被一个子系统生成或者被探测到的事件,很可能是另一个或者多个子系统感兴趣的,也就是说这个事件的获取者必须能够通知所有对该事件感兴趣的子系统,并且还需要这种通知机制具有一定的通用性。
基于这些,Linux内核引入了“通知链”技术。
2.2.1数据结构:
通知链有四种类型,
1.原子通知链(Atomicnotifierchains):
通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞
2.可阻塞通知链(Blockingnotifierchains):
通知链元素的回调函数在进程上下文中运行,允许阻塞
3.原始通知链(Rawnotifierchains):
对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护
4.SRCU通知链(SRCUnotifierchains):
可阻塞通知链的一种变体
所以对应了四种通知链头结构:
∙structatomic_notifier_head:
原子通知链的链头
∙structblocking_notifier_head:
可阻塞通知链的链头
∙structraw_notifier_head:
原始通知链的链头
∙structsrcu_notifier_head:
SRCU通知链的链头
通知链元素的类型:
∙structnotifier_block:
通知链中的元素,记录了当发出通知时,应该执行的操作(即回调函数)
链头中保存着指向元素链表的指针。
通知链元素结构则保存着回调函数的类型以及优先级,参见notifier.h文件。
2.2.2运作机制
通知链的运作机制包括两个角色:
1.被通知者:
对某一事件感兴趣一方。
定义了当事件发生时,相应的处理函数,即回调函数。
但需要事先将其注册到通知链中(被通知者注册的动作就是在通知链中增加一项)。
2.通知者:
事件的通知者。
当检测到某事件,或者本身产生事件时,通知所有对该事件感兴趣的一方事件发生。
他定义了一个通知链,其中保存了每一个被通知者对事件的处理函数(回调函数)。
通知这个过程实际上就是遍历通知链中的每一项,然后调用相应的事件处理函数。
包括以下过程:
1.通知者定义通知链
2.被通知者向通知链中注册回调函数
3.当事件发生时,通知者发出通知(执行通知链中所有元素的回调函数)
整个过程可以看作是“发布——订阅”模型(参见参考资料)
被通知者调用notifier_chain_register函数注册回调函数,该函数按照优先级将回调函数加入到通知链中。
注销回调函数则使用notifier_chain_unregister函数,即将回调函数从通知链中删除。
2.2.1节讲述的4种通知链各有相应的注册和注销函数,但是他们最终都是调用上述两个函数完成注册和注销功能的。
有兴趣的读者可以自行查阅内核代码。
通知者调用notifier_call_chain函数通知事件的到达,这个函数会遍历通知链中所有的元素,然后依次调用每一个的回调函数(即完成通知动作)。
2.2.1节讲述的4种通知链也都有其对应的通知函数,这些函数也都是最终调用notifier_call_chain函数完成事件的通知。
更多关于通知链的内容,参见参考文献。
由以上的叙述,“通知链”技术可以概括为:
事件的被通知者将事件发生时应该执行的操作通过函数指针方式保存在链表(通知链)中,然后当事件发生时通知者依次执行链表中每一个元素的回调函数完成通知。
2.3时钟初始化
内核初始化部分(start_kernel函数)和时钟相关的过程主要有以下几个:
1.tick_init()
2.init_timers()
3.hrtimers_init()
4.time_init()
其中函数hrtimers_init()和高精度时钟相关(本文暂不介绍这部分内容)。
下面将详细介绍剩下三个函数。
2.3.1tick_init函数
函数tick_init()很简单,调用clockevents_register_notifier函数向clockevents_chain通知链注册元素:
tick_notifier。
这个元素的回调函数指明了当时钟事件设备信息发生变化(例如新加入一个时钟事件设备等等)时,应该执行的操作,该回调函数为tick_notify(参见2.4节)。
2.3.2init_timers函数
注:
本文中所有代码均来自于Linux2.6.25源代码
函数init_timers()的实现如清单2-1(省略了部分和
主要功能无关的内容,以后代码同样方式处理)
清单2-1init_timers函数
void__initinit_timers(void)
{
interr=timer_cpu_notify(&timers_nb,(unsignedlong)CPU_UP_PREPARE,
(void*)(long)smp_processor_id());
……
register_cpu_notifier(&timers_nb);
open_softirq(TIMER_SOFTIRQ,run_timer_softirq,NULL);
}
代码解释:
∙初始化本CPU上的软件时钟相关的数据结构,参见3.2节
∙向cpu_chain通知链注册元素timers_nb,该元素的回调函数用于初始化指定CPU上的软件时钟相关的数据结构
∙初始化时钟的软中断处理函数
2.3.3time_init函数
函数time_init的实现如清单2-2
清单2-2time_init函数
void__inittime_init(void)
{
……
init_tsc_clocksource();
late_time_init=choose_time_init();
}
函数init_tsc_clocksource初始化tsc时钟源。
choose_time_init实际是函数hpet_time_init,其代码清单2-3
清单2-3hpet_time_init函数
void__inithpet_time_init(void)
{
if(!
hpet_enable())
setup_pit_timer();
setup_irq(0,&irq0);
}
函数hpet_enable检测系统是否可以使用hpet时钟,如果可以则初始化hpet时钟。
否则初始化pit时钟。
最后设置硬件时钟发生时的处理函数(参见2.4节)。
初始化硬件时钟这个过程主要包括以下两个过程(参见hpet_enable的实现):
1.初始化时钟源信息(structclocksource类型的变量),并将其添加到时钟源链表中,即clocksource_list链表(参见图2-1)。
2.初始化时钟事件设备信息(structclock_event_device类型的变量),并向通知链clockevents_chain发布通知:
一个时钟事件设备要被添加到系统中。
在通知(执行回调函数)结束后,该时钟事件设备被添加到时钟事件设备链表中,即clockevent_devices链表(参见图2-1)。
有关通知链的内容参见2.2节。
需要注意的是在初始化时钟事件设备时,全局变量global_cl