Linux内部的时钟处理机制全面剖析Word文档格式.docx
- 文档编号:16525034
- 上传时间:2022-11-24
- 格式:DOCX
- 页数:23
- 大小:93.89KB
Linux内部的时钟处理机制全面剖析Word文档格式.docx
《Linux内部的时钟处理机制全面剖析Word文档格式.docx》由会员分享,可在线阅读,更多相关《Linux内部的时钟处理机制全面剖析Word文档格式.docx(23页珍藏版)》请在冰豆网上搜索。
对硬件设备的抽象,描述时钟源信息
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):
通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞
1可阻塞通知链(Blockingnotifierchains):
通知链元素的回调函数在进程上下文中运行,允许阻塞
1原始通知链(Rawnotifierchains):
对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护
1SRCU通知链(SRCUnotifierchains):
可阻塞通知链的一种变体
所以对应了四种通知链头结构:
structatomic_notifier_head:
原子通知链的链头
structblocking_notifier_head:
可阻塞通知链的链头
structraw_notifier_head:
原始通知链的链头
structsrcu_notifier_head:
SRCU通知链的链头
通知链元素的类型:
structnotifier_block:
通知链中的元素,记录了当发出通知时,应该执行的操作(即回调函数)
链头中保存着指向元素链表的指针。
通知链元素结构则保存着回调函数的类型以及优先级,参见notifier.h文件。
2.2.2运作机制
通知链的运作机制包括两个角色:
1被通知者:
对某一事件感兴趣一方。
定义了当事件发生时,相应的处理函数,即回调函数。
但需要事先将其注册到通知链中(被通知者注册的动作就是在通知链中增加一项)。
1通知者:
事件的通知者。
当检测到某事件,或者本身产生事件时,通知所有对该事件感兴趣的一方事件发生。
他定义了一个通知链,其中保存了每一个被通知者对事件的处理函数(回调函数)。
通知这个过程实际上就是遍历通知链中的每一项,然后调用相应的事件处理函数。
包括以下过程:
1通知者定义通知链
1被通知者向通知链中注册回调函数
1当事件发生时,通知者发出通知(执行通知链中所有元素的回调函数)
被通知者调用notifier_chain_register函数注册回调函数,该函数按照优先级将回调函数加入到通知链中。
注销回调函数则使用notifier_chain_unregister函数,即将回调函数从通知链中删除。
2.2.1节讲述的4种通知链各有相应的注册和注销函数,但是他们最终都是调用上述两个函数完成注册和注销功能的。
有兴趣的读者可以自行查阅内核代码。
通知者调用notifier_call_chain函数通知事件的到达,这个函数会遍历通知链中所有的元素,然后依次调用每一个的回调函数(即完成通知动作)。
2.2.1节讲述的4种通知链也都有其对应的通知函数,这些函数也都是最终调用notifier_call_chain函数完成事件的通知。
由以上的叙述,“通知链”技术可以概括为:
事件的被通知者将事件发生时应该执行的操作通过函数指针方式保存在链表(通知链)中,然后当事件发生时通知者依次执行链表中每一个元素的回调函数完成通知。
2.3时钟初始化
内核初始化部分(start_kernel函数)和时钟相关的过程主要有以下几个:
1tick_init()
2init_timers()
3hrtimers_init()
4time_init()
其中函数hrtimers_init()和高精度时钟相关(本文暂不介绍这部分内容)。
下面将详细介绍剩下三个函数。
2.3.1tick_init函数
函数tick_init()很简单,调用clockevents_register_notifier函数向clockevents_chain通知链注册元素:
tick_notifier。
这个元素的回调函数指明了当时钟事件设备信息发生变化(例如新加入一个时钟事件设备等等)时,应该执行的操作,该回调函数为tick_notify(参见2.4节)。
2.3.2init_timers函数
函数init_timers()的实现如清单2-1(省略了部分和主要功能无关的内容,以后代码同样方式处理)
注:
本文中所有代码均来自于Linux2.6.25源代码
单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的实现):
5初始化时钟源信息(structclocksource类型的变量),并将其添加到时钟源链表中,即clocksource_list链表(参见图2-1)。
6初始化时钟事件设备信息(structclock_event_device类型的变量),并向通知链clockevents_chain发布通知:
一个时钟事件设备要被添加到系统中。
在通知(执行回调函数)结束后,该时钟事件设备被添加到时钟事件设备链表中,即clockevent_devices链表(参见图2-1)。
有关通知链的内容参见2.2节。
需要注意的是在初始化时钟事件设备时,全局变量global_clock_event被赋予了相应的值。
该变量保存着系统中当前正在使用的时钟事件设备(保存了系统当前使用的硬件时钟中断发生时,要执行的中断处理函数的指针)。
2.4硬件时钟处理过程
由2.3.3可知硬件时钟中断的处理函数保存在静态变量irq0中,其定义如清单2-4
单2-4变量irq0定义
staticstructirqactionirq0={
.handler=timer_event_interrupt,
.flags=IRQF_DISABLED|IRQF_IRQPOLL|IRQF_NOBALANCING,
.mask=CPU_MASK_NONE,
.name="
timer"
};
由定义可知:
函数timer_event_interrupt为时钟中断处理函数,其定义如清单2-5
清单2-5timer_event_interrupt函数
staticirqreturn_ttimer_event_interrupt(intirq,void*dev_id)
add_pda(irq0_irqs,1);
global_clock_event->
event_handler(global_clock_event);
returnIRQ_HANDLED;
为了说明这个问题,不妨假设系统中使用的是hpet时钟。
由2.3.3节可知global_clock_event指向hpet时钟事件设备(hpet_clockevent)。
查看hpet_enable函数的代码并没有发现有对event_handler成员的赋值。
所以继续查看时钟事件设备加入事件的处理函数tick_notify,该函数记录了当时钟事件设备发生变化(例如,新时钟事件设备的加入)时,执行那些操作(参见2.3.1节),代码如清单2-6
清单2-6tick_notify函数
staticinttick_notify(structnotifier_block*nb,unsignedlongreason,void*dev)
switch(reason){
caseCLOCK_EVT_NOTIFY_ADD:
returntick_check_new_device(dev);
returnNOTIFY_OK;
由代码可知:
对于新加入时钟事件设备这个事件,将会调用函数tick_check_new_device。
顺着该函数的调用序列向下查找。
tick_set_periodic_handler函数将时钟事件设备的event_handler成员赋值为tick_handle_periodic函数的地址。
由此可知,函数tick_handle_periodic为硬件时钟中断发生时,真正的运行函数。
函数tick_handle_periodic的处理过程分成了以下两个部分:
1全局处理:
整个系统中的信息处理
2局部处理:
局部于本地CPU的处理
总结一下,一次时钟中断发生后,OS主要执行的操作(tick_handle_periodic):
全局处理(仅在一个CPU上运行):
3更新jiffies_64
4更新xtimer和当前时钟源信息等
5根据tick计算avenrun负载
局部处理(每个CPU都要运行):
根据当前在用户态还是核心态,统计当前进程的时间:
用户态时间还是核心态时间
唤醒TIMER_SOFTIRQ软中断
唤醒RCU软中断
调用scheduler_tick(更新进程时间片等等操作,更多内容参见参考文献)
profile_tick函数调用
以上就介绍完了硬件时钟的处理过程,下面来看软件时钟。
3、软件时钟处理
这里所说“软件时钟”指的是软件定时器(SoftwareTimers),是一个软件上的概念,是建立在硬件时钟基础之上的。
它记录了未来某一时刻要执行的操作(函数),并使得当这一时刻真正到来时,这些操作(函数)能够被按时执行。
举个例子说明:
它就像生活中的闹铃,给闹铃设定振铃时间(未来的某一时间)后,当时间(相当于硬件时钟)更新到这个振铃时间后,闹铃就会振铃。
这个振铃时间好比软件时钟的到期时间,振铃这个动作好比软件时钟到期后要执行的函数,而闹铃时间更新好比硬件时钟的更新。
实现软件时钟原理也比较简单:
每一次硬件时钟中断到达时,内核更新的jiffies,然后将其和软件时钟的到期时间进行比较。
如果jiffies等于或者大于软件时钟的到期时间,内核就执行软件时钟指定的函数。
接下来的几节会详细介绍Linux2.6.25是怎么实现软件时钟的。
3.1相关数据结构
structtimer_list:
软件时钟,记录了软件时钟的到期时间以及到期后要执行的操作。
具体的成员以及含义见表3-1。
structtvec_base:
用于组织、管理软件时钟的结构。
在SMP系统中,每个CPU有一个。
具体的成员以及含义参见表3-2。
表3-1structtimer_list主要成员
域名
类型
描述
entry
structlist_head
所在的链表
expires
unsignedlong
到期时间,以tick为单位
function
void(*)(unsignedlong)
回调函数,到期后执行的操作
data
回调函数的参数
base
structtvec_base*
记录该软件时钟所在的structtvec_base变量
表3-2structtvec_base类型的成员
lock
spinlock_t
用于同步操作
running_timer
structtimer_list*
正在处理的软件时钟
timer_jiffies
当前正在处理的软件时钟到期时间
tv1
structtvec_root
保存了到期时间从timer_jiffies到timer_jiffies+
之间(包括边缘值)的所有软件时钟
tv2
structtvec
保存了到期时间从timer_jiffies+
到timer_jiffies+
之间(包括边缘值)的所有软件时钟
tv3
保存了到期时间从timer_jiffies+
tv4
tv5
一个tick表示的时间长度为两次硬件时钟中断发生时的时间间隔
其中tv1的类型为structtvec_root,tv2~tv5的类型为structtvec,清单3-1显示它们的定义清单3-1structtvec_root和structtvec的定义
structtvec{
structlist_headvec[TVN_SIZE];
structtvec_root{
structlist_headvec[TVR_SIZE];
可见它们实际上就是类型为structlist_head的数组,其中TVN_SIZE和TVR_SIZE在系统没有配置宏CONFIG_BASE_SMALL时分别被定义为64和256。
3.2数据结构之间的关系
图3-1显示了以上数据结构之间的关系
从图中可以清楚地看出:
软件时钟(structtimer_list,在图中由timer表示)以双向链表(structlist_head)的形式,按照它们的到期时间保存相应的桶(tv1~tv5)中。
tv1中保存了相对于timer_jiffies下256个tick时间内到期的所有软件时钟;
tv2中保存了相对于timer_jiffies下256*64个tick时间内到期的所有软件时钟;
tv3中保存了相对于timer_jiffies下256*64*64个tick时间内到期的所有软件时钟;
tv4中保存了相对于timer_jiffies下256*64*64*64个tick时间内到期的所有软件时钟;
tv5中保存了相对于timer_jiffies下256*64*64*64*64个tick时间内到期的所有软件时钟。
具体的说,从静态的角度看,假设timer_jiffies为0,那么tv1[0]保存着当前到期(到期时间等于timer_jiffies)的软件时钟(需要马上被处理),tv1[1]保存着下一个tick到达时,到期的所有软件时钟,tv1[n](0<
=n<
=255)保存着下n个tick到达时,到期的所有软件时钟。
而tv2[0]则保存着下256到511个tick之间到期所有软件时钟,tv2[1]保存着下512到767个tick之间到期的所有软件时钟,tv2[n](0<
=63)保存着下256*(n+1)到256*(n+2)-1个tick之间到达的所有软件时钟。
tv3~tv5依次类推。
从上面的说明中可以看出:
软件时钟是按照其到期时间相对于当前正在处理的软件时钟的到期时间(timer_jiffies的数值)保存在structtvec_base变量中的。
而且这个到期时间的最大相对值(到期时间-timer_jiffies)为0xffffffffUL(tv5最后一个元素能够表示的相对到期时间的最大值)。
一个tick的长度指的是两次硬件时钟中断发生之间的时间间隔
还需要注意的是软件时钟的处理是局部于CPU的,所以在SMP系统中每一个CPU都保存一个类型为structtvec_base的变量,用来组织、管理本CPU的软件时钟。
从图中也可以看出structtvec_base变量是per-CPU的(关于per-CPU的变量原理和使用参见参考资料)。
由于以后的讲解经常要提到每个CPU相关的structtvec_base变量,所以为了方便,称保存软件时钟的structtvec_base变量为该软件时钟的base,或称CPU的base。
3.3添加或删除软件时钟
在了解了软件时钟的数据组织关系之后,现在来看一下如何添加以及删除一个软件时钟。
3.3.1添加软件时钟
在Linux内核中要添加一个软件时钟,首先必须分配structtimer_list类型的变量,然后调用函数add_timer()将该软件时钟添加到相应调用add_timer函数的CPU的base中。
Add_timer是对函数__mod_timer()的一层包装。
函数__mod_timer()的代码如清单3-2:
清单3-2__mod_timer函数
int__mod_timer(structtimer_list*timer,unsignedlongexpires)
structtvec_base*base,*new_base;
unsignedlongflags;
intret=0;
base=lock_timer_base(timer,&
flags);
if(timer_pending(timer)){
detach_timer(timer,0);
ret=1;
new_base=__get_cpu_var(tvec_bases);
if(base!
=new_base){
if(li
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux 内部 时钟 处理 机制 全面 剖析