Linux的时钟处理机制
“乔哆啦不吃虾”通过精心收集,向本站投稿了6篇Linux的时钟处理机制,下面是小编精心整理后的Linux的时钟处理机制,仅供参考,大家一起来看看吧。
篇1:Linux的时钟处理机制
在 Linux 操作系统中,很多活动都和时间有关,例如:进程调度和网络处理 等等,所以说,了解 Linux 操作系统中的时钟处理机制有助于更好地了解 Linux 操作系统的运作方式。本文分析了 Linux 2.6.25 内核的时钟处理机制, 首先介绍了在计算机系统中的一些硬件计时器,然后重点介绍了 Linux 操作系 统中的硬件时钟和软件时钟的处理过程以及软件时钟的应用。最后对全文进行了 总结。
1 计算机系统中的计时器
在计算机系统中存在着许多硬件计时器,例如 Real Timer Clock ( RTC ) 、Time Stamp Counter ( TSC ) 和 Programmable Interval Timer ( PIT ) 等等。
这部分内容不是本文的中点,这里仅仅简单介绍几种,更多内容参见参考文 献:
Real Timer Clock ( RTC ):
独立于整个计算机系统(例如: CPU 和其他 chip )
内核利用其获取系统当前时间和日期
Time Stamp Counter ( TSC ):
从 Pentium 起,提供一个寄存器 TSC,用来累计每一次外部振荡器产生的时 钟信号
通过指令 rdtsc 访问这个寄存器
比起 PIT,TSC 可以提供更精确的时间测量
Programmable Interval Timer ( PIT ):
时间测量设备
内核使用的产生时钟中断的设备,产生的时钟中断依赖于硬件的体系结构, 慢的为 10 ms 一次,快的为 1 ms 一次
High Precision Event Timer ( HPET ):
PIT 和 RTC 的替代者,和之前的计时器相比,HPET 提供了更高的时钟频率 (至少10 MHz )以及更宽的计数器宽度(64位)
一个 HPET 包括了一个固定频率的数值增加的计数器以及3到32个独立的计时 器,这每一个计时器有包涵了一个比较器和一个寄存器(保存一个数值,表示触 发中断的时机)。每一个比较器都比较计数器中的数值和寄存器中的数值,当这 两个数值相等时,将产生一个中断
2 硬件时钟处理
这里所说的硬件时钟处理特指的是硬件计时器时钟中断的处理过程。
2.1 数据结构
和硬件计时器(本文又称作硬件时钟,区别于软件时钟)相关的数据结构主 要有两个:
struct clocksource :对硬件设备的抽象,描述时钟源信息
struct clock_event_device :时钟的事件信息,包括当硬件时钟中断发生 时要执行那些操作(实际上保存了相应函数的指针)。本文将该结构称作为 “时钟事件设备”。
上述两个结构内核源代码中有较详细的注解,分别位于文件 clocksource.h 和 clockchips.h 中。需要特别注意的是结构 clock_event_device 的成员 event_handler ,它指定了当硬件时钟中断发生时,内核应该执行那些操作,也 就是真正的时钟中断处理函数。 在2.3节“时钟初始化”部分会介绍 它真正指向哪个函数。
Linux 内核维护了两个链表,分别存储了系统中所有时钟源的信息和时钟事 件设备的信息。这两个链表的表头在内核中分别是 clocksource_list 和 clockevent_devices 。图2-1显示了这两个链表。
图2-1 时钟源链表和时钟事件链表
2.2 通知链技术( notification chain )
在时钟处理这部分中,内核用到了所谓的“通知链( notification chain )”技术。所以在介绍时钟处理过程之前先来了解下“通知链 ”技术。
在 Linux 内核中,各个子系统之间有很强的相互关系,一些被一个子系统生 成或者被探测到的事件,很可能是另一个或者多个子系统感兴趣的,也就是说这 个事件的获取者必须能够通知所有对该事件感兴趣的子系统,并且还需要这种通 知机制具有一定的通用性。基于这些, Linux 内核引入了“通知链 ”技术。
2.2.1 数据结构:
通知链有四种类型,
原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事 件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞
可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在 进程上下文中运行,允许阻塞
原始通知链( Raw notifier chains ):对通知链元素的回调函数没有任何 限制,所有锁和保护机制都由调用者维护
SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体
所以对应了四种通知链头结构:
struct atomic_notifier_head :原子通知链的链头
struct blocking_notifier_head :可阻塞通知链的链头
struct raw_notifier_head :原始通知链的链头
struct srcu_notifier_head : SRCU 通知链的链头
通知链元素的类型:
struct notifier_block :通知链中的元素,记录了当发出通知时,应该执 行的操作(即回调函数)
链头中保存着指向元素链表的指针。通知链元素结构则保存着回调函数的类 型以及优先级,参见 notifier.h 文件。
2.2.2 运作机制
通知链的运作机制包括两个角色:
被通知者:对某一事件感兴趣一方。定义了当事件发生时,相应的处理函数 ,即回调函数。但需要事先将其注册到通知链中(被通知者注册的动作就是在通 知链中增加一项)。
通知者:事件的通知者。当检测到某事件,或者本身产生事件时,通知所有 对该事件感兴趣的一方事件发生。他定义了一个通知链,其中保存了每一个被通 知者对事件的处理函数(回调函数)。通知这个过程实际上就是遍历通知链中的 每一项,然后调用相应的事件处理函数。
包括以下过程:
通知者定义通知链
被通知者向通知链中注册回调函数
当事件发生时,通知者发出通知(执行通知链中所有元素的回调函数)
整个过程可以看作是“发布——订阅”模型(参见参 考资料)
被通知者调用 notifier_chain_register 函数注册回调函数,该函数按照优 先级将回调函数加入到通知链中 。注销回调函数则使用 notifier_chain_unregister 函数,即将回调函数从通知链中删除。2.2.1节讲 述的4种通知链各有相应的注册和注销函数,但是他们最终都是调用上述两个函 数完成注册和注销功能的。有兴趣的读者可以自行查阅内核代码。
通知者调用 notifier_call_chain 函数通知事件的到达,这个函数会遍历通 知链中所有的元素,然后依次调用每一个的回调函数(即完成通知动作)。 2.2.1节讲述的4种通知链也都有其对应的通知函数,这些函数也都是最终调用 notifier_call_chain 函数完成事件的通知。
更多关于通知链的内容,参见参考文献。
由以上的叙述,“通知链”技术可以概括为:事件的被通知者将 事件发生时应该执行的操作通过函数指针方式保存在链表(通知链)中,然后当 事件发生时通知者依次执行链表中每一个元素的回调函数完成通知。
2.3 时钟初始化
内核初始化部分( start_kernel 函数)和时钟相关的过程主要有以下几个 :
tick_init
init_timers()
hrtimers_init()
time_init()
其中函数 hrtimers_init() 和高精度时钟相关(本文暂不介绍这部分内容) 。下面将详细介绍剩下三个函数。
2.3.1 tick_init 函数
函数 tick_init() 很简单,调用 clockevents_register_notifier 函数向 clockevents_chain 通知链注册元素: tick_notifier。这个元素的回调函数指 明了当时钟事件设备信息发生变化(例如新加入一个时钟事件设备等等)时,应 该执行的操作,该回调函数为 tick_notify (参见2.4节)。
2.3.2 init_timers 函数
函数 init_timers() 的实现如清单2-1(省略了部分和
主要功能无关的内容,以后代码同样方式处理)
清单2-1 init_timers 函数
void __init init_timers(void)
{
int err = timer_cpu_notify(&timers_nb, (unsigned long) 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.3 time_init 函数
函数 time_init 的实现如清单2-2
清单2-2 time_init 函数
void __init time_init(void)
{
……
init_tsc_clocksource();
late_time_init = choose_time_init();
}
函数 init_tsc_clocksource 初始化 tsc 时钟源。choose_time_init 实际 是函数 hpet_time_init ,其代码清单2-3
清单2-3 hpet_time_init 函数
void __init hpet_time_init(void)
{
if (!hpet_enable())
setup_pit_timer();
setup_irq(0, &irq0);
}
函数 hpet_enable 检测系统是否可以使用 hpet 时钟,如果可以则初始化 hpet 时钟。否则初始化 pit 时钟。最后设置硬件时钟发生时的处理函数(参见 2.4节)。
初始化硬件时钟这个过程主要包括以下两个过程(参见 hpet_enable 的实现 ):
初始化时钟源信息( struct clocksource 类型的变量),并将其添加到时 钟源链表中,即 clocksource_list 链表(参见图2-1)。
初始化时钟事件设备信息( struct clock_event_device 类型的变量),并 向通知链 clockevents_chain 发布通知:一个时钟事件设备要被添加到系统中 。在通知(执行回调函数)结束后,该时钟事件设备被添加到时钟事件设备链表 中,即 clockevent_devices 链表(参见图2-1)。有关通知链的内容参见2.2节 。
需要注意的是在初始化时钟事件设备时,全局变量 global_clock_event 被 赋予了相应的值。该变量保存着系统中当前正在使用的时钟事件设备(保存了系 统当前使用的硬件时钟中断发生时,要执行的中断处理函数的指针)。
2.4 硬件时钟处理过程
由2.3.3可知硬件时钟中断的处理函数保存在静态变量 irq0 中,其定义如清 单2-4
清单2-4 变量irq0定义
static struct irqaction irq0 = {
.handler = timer_event_interrupt,
.flags = IRQF_DISABLED | IRQF_IRQPOLL | IRQF_NOBALANCING,
.mask = CPU_MASK_NONE,
.name = “timer”
};
由定义可知:函数 timer_event_interrupt 为时钟中断处理函数,其定义如 清单2-5
清单2-5 timer_event_interrupt 函数
static irqreturn_t timer_event_interrupt(int irq, void *dev_id)
{
add_pda(irq0_irqs, 1);
global_clock_event->event_handler(global_clock_event);
return IRQ_HANDLED;
}
从代码中可以看出:函数 timer_event_interrupt 实际上调用的是 global_clock_event 变量的 event_handler 成员。那 event_handler 成员指 向哪里呢?
为了说明这个问题,不妨假设系统中使用的是 hpet 时钟。由2.3.3节可知 global_clock_event 指向 hpet 时钟事件设备( hpet_clockevent )。查看 hpet_enable 函数的代码并没有发现有对 event_handler 成员的赋值。所以继 续查看时钟事件设备加入事件的处理函数 tick_notify ,该函数记录了当时钟 事件设备发生变化(例如,新时钟事件设备的加入)时,执行那些操作(参见 2.3.1节),代码如清单2-6
清单2-6 tick_notify 函数
static int tick_notify(struct notifier_block *nb, unsigned long reason, void *dev)
{
switch (reason) {
case CLOCK_EVT_NOTIFY_ADD:
return tick_check_new_device(dev);
……
return NOTIFY_OK;
}
由代码可知:对于新加入时钟事件设备这个事件,将会调用函数 tick_check_new_device 。顺着该函数的调用序列向下查找。 tick_set_periodic_handler 函数将时钟事件设备的 event_handler 成员赋值 为 tick_handle_periodic 函数的地址。由此可知,函数 tick_handle_periodic 为硬件时钟中断发生时,真正的运行函数。
函数 tick_handle_periodic 的处理过程分成了以下两个部分:
全局处理:整个系统中的信息处理
局部处理:局部于本地 CPU 的处理
总结一下,一次时钟中断发生后, OS 主要执行的操作( tick_handle_periodic ):
全局处理(仅在一个 CPU 上运行):
更新 jiffies_64
更新 xtimer 和当前时钟源信息等
根据 tick 计算 avenrun 负载
局部处理(每个 CPU 都要运行):
根据当前在用户态还是核心态,统计当前进程的时间:用户态时间还是核心 态时间
唤醒 TIMER_SOFTIRQ 软中断
唤醒 RCU 软中断
调用 scheduler_tick (更新进程时间片等等操作,更多内容参见参考文献 )
profile_tick 函数调用
以上就介绍完了硬件时钟的处理过程,下面来看软件时钟。
3 软件时钟处理
这里所说“软件时钟”指的是软件定时器( Software Timers ) ,是一个软件上的概念,是建立在硬件时钟基础之上的。它记录了未来某一时刻 要执行的操作(函数),并使得当这一时刻真正到来时,这些操作(函数)能够 被按时执行。举个例子说明:它就像生活中的闹铃,给闹铃设定振铃时间(未来 的某一时间)后,当时间(相当于硬件时钟)更新到这个振铃时间后,闹铃就会 振铃。这个振铃时间好比软件时钟的到期时间,振铃这个动作好比软件时钟到期 后要执行的函数,而闹铃时间更新好比硬件时钟的更新。
实现软件时钟原理也比较简单:每一次硬件时钟中断到达时,内核更新的 jiffies ,然后将其和软件时钟的到期时间进行比较。如果 jiffies 等于或者 大于软件时钟的到期时间,内核就执行软件时钟指定的函数。
接下来的几节会详细介绍 Linux2.6.25 是怎么实现软件时钟的。
3.1 相关数据结构
struct timer_list :软件时钟,记录了软件时钟的到期时间以及到期后要 执行的操作。具体的成员以及含义见表3-1。
struct tvec_base :用于组织、管理软件时钟的结构。在 SMP 系统中,每 个 CPU 有一个。具体的成员以及含义参见表3-2。
表3-1 struct timer_list 主要成员
域名类型描述 entrystruct list_head所在的链表 expiresunsigned long到期时间,以 tick 为单位 functionvoid (*)(unsigned long)回调函数,到期后执行的操作 dataunsigned long回调函数的参数 basestruct tvec_base *记录该软件时钟所在的 struct tvec_base 变量表3-2 struct tvec_base 类型的成员
域名类型描述 lockspinlock_t用于同步操作 running_timerstruct timer_list *正在处理的软件时钟 timer_jiffiesunsigned long当前正在处理的软件时钟到期时间 tv1struct tvec_root保存了到期时间从 timer_jiffies 到 timer_jiffies + 之间 (包括边缘值)的所有软件时钟 tv2struct tvec保存了到期时间从 timer_jiffies + 到 timer_jiffies +之间 (包括边缘值)的 所有软件时钟 tv3struct tvec保存了到期时间从 timer_jiffies +到 timer_jiffies +之间 (包括边缘值)的所有软件时钟 tv4struct tvec保存了到期时间从 timer_jiffies +到 timer_jiffies +之间 (包括边缘值)的所有软件时钟 tv5struct tvec保存了到期时间从 timer_jiffies +到 timer_jiffies +之间 (包括边缘值)的所有软件时钟其中 tv1 的类型为 struct tvec_root ,tv 2~ tv 5的类型为 struct tvec ,清单3-1显示它们的定义
清单3-1 struct tvec_root 和 struct tvec 的定义
struct tvec {
struct list_head vec[TVN_SIZE];
};
struct tvec_root {
struct list_head vec[TVR_SIZE];
};
可见它们实际上就是类型为 struct list_head 的数组,其中 TVN_SIZE 和 TVR_SIZE 在系统没有配置宏 CONFIG_BASE_SMALL 时分别被定义为64和256。
3.2 数据结构之间的关系
图3-1显示了以上数据结构之间的关系:
从图中可以清楚地看出:软件时钟( struct timer_list ,在图中由 timer 表示)以双向链表( struct list_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<= n <=63) 保存着下256*(n+1)到256*(n+2)-1个 tick 之间到达的所有软件时钟。 tv3~tv5 依次类推。
从上面的说明中可以看出:软件时钟是按照其到期时间相对于当前正在处理 的软件时钟的到期时间( timer_jiffies 的数值)保存在 struct tvec_base 变量中的。而且这个到期时间的最大相对值(到期时间 - timer_jiffies )为 0xffffffffUL ( tv5 最后一个元素能够表示的相对到期时间的最大值)。
还需要注意的是软件时钟的处理是局部于 CPU 的,所以在 SMP 系统中每一 个 CPU 都保存一个类型为 struct tvec_base 的变量,用来组织、管理本 CPU 的软件时钟。从图中也可以看出 struct tvec_base 变量是 per-CPU 的(关于 per-CPU 的变量原理和使用参见参考资料)。
由于以后的讲解经常要提到每个 CPU 相关的 struct tvec_base 变量,所以 为了方便,称保存软件时钟的 struct tvec_base 变量为该软件时钟的 base , 或称 CPU 的 base 。
3.3 添加或删除软件时钟
在了解了软件时钟的数据组织关系之后,现在来看一下如何添加以及删除一 个软件时钟。
3.3.1 添加软件时钟
在 Linux 内核中要添加一个软件时钟,首先必须分配 struct timer_list 类型的变量,然后调用函数 add_timer() 将该软件时钟添加到相应调用 add_timer 函数的 CPU 的 base 中。 Add_timer 是对函数 __mod_timer() 的 一层包装。函数 __mod_timer() 的代码如清单3-2:
清单3-2 __mod_timer 函数
int __mod_timer(struct timer_list *timer, unsigned long expires)
{
struct tvec_base *base, *new_base;
unsigned long flags;
int ret = 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 (likely(base->running_timer != timer)) {
/* See the comment in lock_timer_base() */
timer_set_base(timer, NULL);
spin_unlock(&base->lock);
base = new_base;
spin_lock(&base->lock);
timer_set_base(timer, base);
}
}
timer->expires = expires;
internal_add_timer(base, timer);
spin_unlock_irqrestore(&base->lock, flags);
return ret;
}
代码解释:
取得软件时钟所在 base 上的同步锁( struct tvec_base 变量中的自旋锁 ),并返回该软件时钟的 base ,保存在 base 变量中
如果该软件时钟处在 pending 状态(在 base 中,准备执行),则卸载该软 件时钟
取得本 CPU 上的 base 指针(类型为 struct tvec_base* ),保存在 new_base 中
如果 base 和 new_base 不一样,也就是说软件时钟发生了迁移(从一个 CPU 中移到了另一个 CPU 上),那么如果该软件时钟的处理函数当前没有在迁 移之前的那个 CPU 上运行,则先将软件时钟的 base 设置为 NULL ,然后再将 该软件时钟的 base 设置为 new_base ,
否则,跳到5。
设置软件时钟的到期时间
调用 internal_add_timer 函数将软件时钟添加到软件时钟的 base 中(本 CPU 的 base )
释放锁
这里有必要详细说明一下软件时钟如何被添加到软件时钟的 base 中的(添 加到本 CPU base 的 tv1~tv5 里面),因为这是软件时钟处理的基础。来看函 数 internal_add_timer 函数的实现,如清单3-3
清单3-3 internal_add_timer 函数
static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
{
unsigned long expires = timer->expires;
unsigned long idx = expires - base->timer_jiffies;
struct list_head *vec;
if (idx < TVR_SIZE) {
int i = expires & TVR_MASK;
vec = base->tv1.vec + i;
} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
int i = (expires >> TVR_BITS) & TVN_MASK;
vec = base->tv2.vec + i;
} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
vec = base->tv3.vec + i;
} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
vec = base->tv4.vec + i;
} else if ((signed long) idx < 0) {
vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
} else {
int i;
if (idx > 0xffffffffUL) {
idx = 0xffffffffUL;
expires = idx + base->timer_jiffies;
}
i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
vec = base->tv5.vec + i;
}
list_add_tail(&timer->entry, vec);
}
代码解释:
计算该软件时钟的到期时间和 timer_jiffies (当前正在处理的软件时钟的 到期时间)的差值,作为索引保存到 idx 变量中。
判断 idx 所在的区间,在
[0, ]或 者( , 0) (该软件时钟已经到期),则将要添加到 tv1 中
[, ],则将要添加到 tv2 中
[, ],则将要添加到 tv3 中
[, ],则将要添加到 tv4 中
[, ),则将要添加到 tv5 中,但实际上最大值为 0xffffffffUL
计算所要加入的具体位置(哪个链表中,即 tv1~tv5 的哪个子链表,参考图 3-1)
最后将其添加到相应的链表中
从这个函数可以得知,内核中是按照软件时钟到期时间的相对值(相对于 timer_jiffies 的值)将软件时钟添加到软件时钟所在的 base 中的。
3.3.2 删除软件时钟
内核可调用 del_timer 函数删除软件时钟, del_timer 的代码如清单3- 4
清单3-4 del_timer 函数
int del_timer(struct timer_list *timer)
{
struct tvec_base *base;
unsigned long flags;
int ret = 0;
……
if (timer_pending(timer)) {
base = lock_timer_base(timer, &flags);
if (timer_pending(timer)) {
detach_timer(timer, 1);
ret = 1;
}
spin_unlock_irqrestore(&base->lock, flags);
}
return ret;
}
代码解释:
检测该软件时钟是否处在 pending 状态(在 base 中,准备运行),如果不 是则直接函数返回
如果处于 pending 状态,则获得锁
再次检测软件时钟是否处于 pending 状态(该软件时钟可能被卸载了),不 是则释放锁然后函数返回
如果还是 pending 状态,则将其卸载,之后释放锁,函数返回
如果在 SMP 系统中,则需使用 del_timer_sync 函数来删除软件时钟。在讲 解 del_timer_sync 函数之前,先来看下 try_to_del_timer_sync 函数的实现 (该函数被 del_timer_sync 函数使用),其代码如清单3-5
清单3-5 try_to_del_timer_sync 函数
int try_to_del_timer_sync(struct timer_list *timer)
{
struct tvec_base *base;
unsigned long flags;
int ret = -1;
base = lock_timer_base(timer, &flags);
if (base->running_timer == timer)
goto out;
ret = 0;
if (timer_pending(timer)) {
detach_timer(timer, 1);
ret = 1;
}
out:
spin_unlock_irqrestore(&base->lock, flags);
return ret;
}
该函数检测当前运行的软件时钟是不是该软件时钟,如果是,则函数返回-1 ,表明目前不能删除该软件时钟;如果不是检测该软件时钟是否处于 pending 状态,如果不是,则函数返回0,表明软件时钟已经被卸载,如果处于 pending 状态再把软件时钟卸载,函数返回1,表明成功卸载该软件时钟。
接下来,再来看看函数 del_timer_sync 定义,如清单3-6
清单3-6 del_timer_sync 函数
int del_timer_sync(struct timer_list *timer)
{
for (;;) {
int ret = try_to_del_timer_sync(timer);
if (ret >= 0)
return ret;
cpu_relax();
}
}
del_timer_sync 函数无限循环试图卸载该软件时钟,直到该软件时钟能够被 成功卸载。从其实现中可以看出:如果一个软件时钟的处理函数正在执行时,对 其的卸载操作将会失败。一直等到软件时钟的处理函数运行结束后,卸载操作才 会成功。这样避免了在 SMP 系统中一个 CPU 正在执行软件时钟的处理函数,而 另一个 CPU 则要将该软件时钟卸载所引发的问题。
3.3 时钟的软中断处理
软件时钟的处理是在时钟的软中断中进行的。
3.3.1 软中断初始化
软中断的一个重要的处理时机是在每个硬件中断处理完成后(参见 irq_exit 函数),且由2.4节的内容可知:在硬件时钟中断处理中,会唤醒时钟的软中断 ,所以每次硬件时钟中断处理函数执行完成后都要进行时钟的软中断处理。和时 钟相关的软中断是 TIMER_SOFTIRQ ,其处理函数为 run_timer_softirq ,该函 数用来处理所有的软件时钟。这部分初始化代码在函数 init_timers 中进行, 如清单3-7
清单3-7 init_timers 函数
void __init init_timers(void)
{
……
open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);
}
3.3.2 处理过程
函数 run_timer_softirq 所作的工作就是找出所有到期的软件时钟,然后依 次执行其处理函数。其代码如清单3-8
清单3-8 run_timer_softirq函数
static void run_timer_softirq(struct softirq_action *h)
{
struct tvec_base *base = __get_cpu_var(tvec_bases);
hrtimer_run_pending();
if (time_after_eq(jiffies, base->timer_jiffies))
__run_timers(base);
}
函数首先获得到本地 CPU 的 base 。然后检测如果 jiffies
大于等于 timer_jiffies ,说明可能已经有软件时钟到期了,此
时就要进行软件时钟的处理,调用函数 __run_timers 进行处
理。如果 jiffies 小于 timer_jiffies ,表明没有软件时钟到期,
则不用对软件时钟进行处理。函数返回。
接下来看一下函数 __run_timers 都作了些什么,如清单3-9
清单3-9 __run_timers函数
static inline void __run_timers(struct tvec_base *base)
{
……
spin_lock_irq(&base->lock);
while (time_after_eq(jiffies, base->timer_jiffies)) {
……
int index = base->timer_jiffies & TVR_MASK;
if (!index &&
(!cascade(base, &base->tv2, INDEX(0))) &&
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3));
++base->timer_jiffies;
list_replace_init(base->tv1.vec + index, &work_list);
while (!list_empty(head)) {
……
timer = list_first_entry(head, struct timer_list,entry);
fn = timer->function;
data = timer->data;
……
set_running_timer(base, timer);
detach_timer(timer, 1);
spin_unlock_irq(&base->lock);
{
int preempt_count = preempt_count();
fn(data);
……
}
spin_lock_irq(&base->lock);
}
}
set_running_timer(base, NULL);
spin_unlock_irq(&base->lock);
}
代码解释:
获得 base 的同步锁
如果 jiffies 大于等于 timer_jiffies (当前正要处理的软件时钟的到期 时间,说明可能有软件时钟到期了),就一直运行3~7,否则跳转至8
计算得到 tv1 的索引,该索引指明当前到期的软件时钟所在 tv1 中的链表 (结构参见3.2节),代码:
int index = base->timer_jiffies & TVR_MASK;
调用 cascade 函数对软件时钟进行必要的调整(稍后会介绍调整的过程)
使得 timer_jiffies 的数值增加1
取出相应的软件时钟链表
遍历该链表,对每个元素进行如下操作
设置当前软件时钟为 base 中正在运行的软件时钟(即保存当前软件时钟到 base-> running_timer 成员中)
将当前软件时钟从链表中删除,即卸载该软件时钟
释放锁,执行软件时钟处理程序
再次获得锁
设置当前 base 中不存在正在运行的软件时钟
释放锁
3.3.3 软件时钟调整过程
函数 cascade 用于调整软件时钟(这个调整过程是指:将马上就要到期的软 件时钟从其所在的链表中删除,重新计算到期时间的相对值(到期时间 - timer_jiffies ),然后根据该值重新插入到 base 中)。注意到在软件时钟处 理过程中,每次都是从 tv1 中取出一个链表进行处理,而不是从 tv2~tv5 中取 ,所以对软件时钟就要进行必要的调整。
在讲解 cascade 函数之前,再从直观上理解下为什么需要进行调整。所有软 件时钟都是按照其到期时间的相对值(相对于 timer_jiffies )被调加到 base 中的。但是 timer_jiffies 的数值都会在处理中增加1(如3.3.2节所示),也 就是说这个相对值会随着处理发生变化,当这个相对值小于等于256时,就要将 软件时钟从 tv2~tv5 中转移到 tv1 中( tv1 中保存着下256个 tick 内到期的 所有软件时钟)。
函数 cascade 的实现如清单3-10
清单3-10 cascade 函数
static int cascade(struct tvec_base *base, struct tvec *tv, int index)
{
struct timer_list *timer, *tmp;
struct list_head tv_list;
list_replace_init(tv->vec + index, &tv_list);
list_for_each_entry_safe(timer, tmp, &tv_list, entry) {
……
internal_add_timer(base, timer);
}
return index;
}
该函数根据索引,取出相应的 tv ( tv2~tv5 )中的链表,然后遍历链表每 一个元素。按照其到期时间重新将软件时钟加入到软件时钟的 base 中。该函数 返回 tv 中被调整的链表索引值(参见图3-1)。
清单3-9中调整软件时钟的代码如下:
int index = base->timer_jiffies & TVR_MASK;
if (!index &&
(!cascade(base, &base->tv2, INDEX(0))) &&
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3));
这部分代码表明:如果 index 有0再到0时( index 是对 timer_jiffies 取 模),说明时间已经过了256个 tick ,这时要把 tv2 中软件时钟转移到 tv1 中。如果 index 和第一个 cascade 函数的返回值都从0再到到0时,说明时间已 经过了256*64个 tick ,这时要把 tv3 中软件时钟转移到 tv1 或者 tv2 中。 之后的调整过程依次类推。
3.4 自我激活
软件时钟可分为两种类型:
仅仅激活一次
激活多次或者周期性激活
多次激活的实现机制就是要在软件时钟处理函数中重新设置软件时钟的到期 时间为将来的一个时间,这个过程通过调用 mod_timer 函数来实现。该函数的 实现如清单3-11
清单3-11 mod_timer 函数
int mod_timer(struct timer_list *timer, unsigned long expires)
{
……
if (timer->expires == expires && timer_pending(timer))
return 1;
return __mod_timer(timer, expires);
}
从代码中可以看出,该函数实际上调用 __mod_timer 函数(参见3.3.1节) 来调整软件时钟的到期时间。
3.5 软件时钟的应用
软件时钟的处理是在处理软中断时触发的,而软中断的处理又会紧接着硬件 中断处理结束而进行,并且系统会周期地产生时钟中断(硬件中断),这样,软 件时钟的处理至少会在系统每一次时钟中断处理完成后触发(如果软件时钟的到 期时间大于系统当前的 jiffies ,表明时间未到期,则不会调用保存在软件时 钟中的函数,但此时的确提供了处理软件时钟的时机)。从这点上看,软件时钟 会有较快的相应——一旦时间到期,保存在软件时钟中的函数会将快 地被调用(在时钟软中断中被调用,参见3.3.2节)。所以内核中凡是需要隔一 段时间间隔后作指定操作的过程都通过软件时钟完成。例如大部分设备驱动程序 使用软件时钟探测异常条件、软盘驱动程序利用软件时钟关闭有一段时间没有被 访问软盘的设备马达、进程的定时睡眠( schedule_timeout 函数)和网络超时 重传等等。
本节主要通过介绍进程的定时睡眠( schedule_timeout 函数)和网络超时 重传来说明软件时钟的应用。
3.5.1 进程的定时睡眠
函数 schedule_timeout 的代码如清单3-12
清单3-12 函数 schedule_timeout
signed long __sched schedule_timeout(signed long timeout)
{
struct timer_list timer;
unsigned long expire;
……
expire = timeout + jiffies;
setup_timer(&timer, process_timeout, (unsigned long) current);
__mod_timer(&timer, expire);
schedule();
del_singleshot_timer_sync(&timer);
timeout = expire - jiffies;
out:
return timeout < 0 ? 0 : timeout;
}
函数 schedule_timeout 定义了一个软件时钟变量 timer ,在计算到期时间 后初始化这个软件时钟:设置软件时钟当时间到期时的处理函数为 process_timeout ,参数为当前进程描述符,设置软件时钟的到期时间为 expire 。之后调用 schedule() 函数。此时当前进程睡眠,交出执行权,内核 调用其它进程运行。但内核在每一个时钟中断处理结束后都要检测这个软件时钟 是否到期。如果到期,将调用 process_timeout 函数,参数为睡眠的那个进程 描述符。 process_timeout 函数的代码如清单3-13。
清单3-13 函数 process_timeout
static void process_timeout(unsigned long __data)
{
wake_up_process((struct task_struct *)__data);
}
函数 process_timeout 直接调用 wake_up_process 将进程唤醒。当内核重 新调用该进程执行时,该进程继续执行 schedule_timeout 函数,执行流则从 schedule 函数中返回,之后调用 del_singleshot_timer_sync 函数将软件时钟 卸载,然后函数 schedule_timeout 结束。函数 del_singleshot_timer_sync 是实际上就是函数 del_timer_sync (参见3.3.2节),如清单3-14
清单3-14 函数del_singleshot_timer_sync
#define del_singleshot_timer_sync(t) del_timer_sync(t)
以上就是进程定时睡眠的实现过程。接下来介绍的是软件时钟在网络超时重 传上的应用。
3.5.2 网路超时重传
对于 TCP 协议而言,如果某次发送完数据包后,并超过一定的时间间隔还没 有收到这次发送数据包的 ACK 时, TCP 协议规定要重新发送这个数据包。
在 Linux2.6.25 的内核中,这种数据的重新发送使用软件时钟来完成。这个 软件时钟保存在面向连接的套接字(对应内核中 inet_connection_sock 结构) 中。对这个域的初始在函数 tcp_init_xmit_timers 中,如清单3-15
清单3-15 函数 tcp_init_xmit_timers 、函数 inet_csk_init_xmit_timers 和函数 setup_timer
void tcp_init_xmit_timers(struct sock *sk)
{
inet_csk_init_xmit_timers(sk,
&tcp_write_timer, &tcp_delack_timer,
&tcp_keepalive_timer);
}
void inet_csk_init_xmit_timers(struct sock *sk,
void (*retransmit_handler)(unsigned long),
void (*delack_handler)(unsigned long),
void (*keepalive_handler)(unsigned long))
{
struct inet_connection_sock *icsk = inet_csk(sk);
setup_timer(&icsk->icsk_retransmit_timer, retransmit_handler,
(unsigned long)sk);
……
}
static inline void setup_timer(struct timer_list * timer,
void (*function)(unsigned long),
unsigned long data)
{
timer->function = function;
timer->data = data;
init_timer(timer);
}
在函数 inet_csk_init_xmit_timers 中,变量 icsk 就是前面提到的面向连 接的套接字,其成员 icsk_retransmit_timer 则为实现超时重传的软件时钟。 该函数调用 setup_timer 函数将函数 tcp_write_timer (参考函数 tcp_init_xmit_timers )设置为软件时钟 icsk->icsk_retransmit_timer 当时间到期后的处理函数。初始化的时候并没有设置该软件时钟的到期时间。
在 TCP 协议具体的一次数据包发送中,函数 tcp_write_xmit 用来将数据包 从 TCP 层发送到网络层,如清单3-16。
清单3-16 tcp_write_xmit 函数
static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
……
if (unlikely(tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC)))
break;
tcp_event_new_data_sent(sk, skb);
……
return !tp->packets_out && tcp_send_head(sk);
}
注意该函数中加粗的函数,其中 tcp_transmit_skb 函数是真正将数据包由 TCP 层发送到网络层中的函数。数据发送后,将调用函数 tcp_event_new_data_sent ,而后者又会调用函数 inet_csk_reset_xmit_timer 来设置超时软件时钟的到期时间。
当函数 tcp_event_new_data_sent 结束之后,处理超时的软件时钟已经设置 好了。内核会在每一次时钟中断处理完成后检测该软件时钟是否到期。如果网络 真的超时,没有 ACK 返回,那么当该软件时钟到期后内核就会执行函数 tcp_write_timer 。函数 tcp_write_timer 将进行数据包的重新发送,并重新 设置超时重传软件时钟的到期时间。
4 总结
本文介绍了 Linux 内核的时钟处理机制。首先简单介绍了系统的硬件计时器 ,然后重点介绍了硬件时钟的处理过程和软件时钟的处理过程以及软件时钟的应 用。
篇2:Linux中断处理之时钟中断(一)
一:前言
时钟是整个操作系统的脉搏,它为进程的时间片调度,定时事件提供了依据.另外,用户空间的很多操作都依赖于时钟,例如select.poll,make.操作系统管理的时间为分两种,一种称为当前时间,也即我们日常生活所用的时间.这个时间一般保存在CMOS中.主板中有特定的芯片为其提供计时依据.另外一种时间称为相对时间.例如系统运行时间.显然对计算机而然,相对时间比当前时间更为重要.
二:与时钟有关的硬件处理.
1):实时时钟(RTC)
该时钟独立于CPU和其它芯片.即使PC断电,该时钟还是继续运行.该计时由一块单独的芯片处理,并把时钟值存放CMOS.该时间可参在IRQ8上周期性的产生时间信号.频率在2Hz ~ 8192Hz之间.但在linux中,只是用RTC来获取当前时间.
2):时间戳计时器(TSC)
CPU附带了一个64位的时间戳寄存器,当时钟信号到来的时候.该寄存器内容自动加1
3):可编程中断定时器(PIC)
该设备可以周期性的发送一个时间中断信号.发送中断信号的间隔可以对其进行编程控制.在linux系统中,该中断时间间隔由HZ表示.这个时间间隔也被称为一个节拍(tick).
4):CPU本地定时器
在处理器的本地APIC还提供了另外的一定定时设备.CPU本地定时器也可以单次或者周期性的产生中断信号.与上次描述的PIC相比.它有以下几点的区别:
APIC本地计时器是32位.而PIC是16位.由此APIC本地计时器可以提供更低频率的中断信号
本地APIC只把中断信号发送给本地CPU.而PIC发送的中断信号任何CPU都可以处理
APIC定时器是基于总线时钟信号的.而PIC有自己的内部时钟振荡器
5):高精度计时器(HPET)
在linux2.6中增加了对HPET的支持.HPET是一种由微软和intel联合开发的新型定时芯片.该设备有一组寄时器,每个寄时器对应有自己的时钟信号,时钟信号到来的时候就会自动加1.
实际上,在intel多理器系统与单处理器系统还有所不同:
在单处理系统中.所有计时活动过由PIC产生的时钟中断信号触发的
在多处理系统中,所有普通活动是由PIC产生的中断触发.所有具体的CPU活动,都由本地APIC触发的.
三:时钟中断相关代码分析
time_init()是时钟初始化函数,他由asmlinkage void __init start_kernel()调用.具体代码如下:
//时钟中断初始化
void __init time_init(void)
{
//如果定义了HPET
#ifdef CONFIG_HPET_TIMER
if (is_hpet_capable()) {
/*
* HPET initialization needs to do memory-mapped io. So, let
* us do a late initialization after mem_init().
*/
late_time_init = hpet_time_init;
return;
}
#endif
//从cmos 中取得实时时间
xtime.tv_sec = get_cmos_time();
//初始化wall_to_monotonic
wall_to_monotonic.tv_sec = -xtime.tv_sec;
xtime.tv_nsec = (INITIAL_JIFFIES % HZ) * (NSEC_PER_SEC / HZ);
wall_to_monotonic.tv_nsec = -xtime.tv_nsec;
//选择一个合适的定时器
cur_timer = select_timer();
printk(KERN_INFO “Using %s for high-res timesourcen”,cur_timer->name);
//注册时间中断信号处理函数
time_init_hook();
}
该函数从cmos取得了当前时间.并为调整时间精度选择了合适的定时器
转入time_init_hook():
void __init time_init_hook(void)
{
//注册中断处理函数
setup_irq(0, &irq0);
}
Irq0定义如下:
static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, “timer”, NULL, NULL};
对应的中断处理函数为:timer_interrupt():
irqreturn_t timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
//因为该函数会修改xtime的值,为避免多处理器竞争.先加锁
write_seqlock(&xtime_lock);
//记录上一次时间中断的精确时间.做调准时钟用
cur_timer->mark_offset();
do_timer_interrupt(irq, NULL, regs);
//解锁
write_sequnlock(&xtime_lock);
return IRQ_HANDLED;
}
核心处理函数为 do_timer_interrupt():
static inline void do_timer_interrupt(int irq, void *dev_id,
struct pt_regs *regs)
{
#ifdef CONFIG_X86_IO_APIC
if (timer_ack) {
spin_lock(&i8259A_lock);
outb(0x0c, PIC_MASTER_OCW3);
/* Ack the IRQ; AEOI will end it automatically. */
inb(PIC_MASTER_POLL);
spin_unlock(&i8259A_lock);
}
#endif
do_timer_interrupt_hook(regs);
//如果要进行时间同步,那就隔一段时间把当前时间写回coms
if ((time_status & STA_UNSYNC) == 0 &&
xtime.tv_sec > last_rtc_update + 660 &&
(xtime.tv_nsec / 1000)
>= USEC_AFTER - ((unsigned) TICK_SIZE) / 2 &&
(xtime.tv_nsec / 1000)
<= USEC_BEFORE + ((unsigned) TICK_SIZE) / 2) {
/* horrible...FIXME */
if (efi_enabled) {
if (efi_set_rtc_mmss(xtime.tv_sec) == 0)
last_rtc_update = xtime.tv_sec;
else
last_rtc_update = xtime.tv_sec - 600;
} else if (set_rtc_mmss(xtime.tv_sec) == 0)
last_rtc_update = xtime.tv_sec;
else
last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */
}
#ifdef CONFIG_MCA
if( MCA_bus ) {
/* The PS/2 uses level-triggered interrupts. You can't
turn them off, nor would you want to (any attempt to
enable edge-triggered interrupts usually gets intercepted by a
special hardware circuit). Hence we have to acknowledge
the timer interrupt. Through some incredibly stupid
design idea, the reset for IRQ 0 is done by setting the
high bit of the PPI port B (0x61). Note that some PS/2s,
notably the 55SX, work fine if this is removed. */
irq = inb_p( 0x61 ); /* read the current state */
outb_p( irq|0x80, 0x61 ); /* reset the IRQ */
}
#endif
}
我们忽略选择编译部份,转到do_timer_interrupt_hook()
static inline void do_timer_interrupt_hook(struct pt_regs *regs)
{
do_timer(regs);
/*
* In the SMP case we use the local APIC timer interrupt to do the
* profiling, except when we simulate SMP mode on a uniprocessor
* system, in that case we have to call the local interrupt handler.
*/
#ifndef CONFIG_X86_LOCAL_APIC
//更新内核代码监管器,
Linux中断处理之时钟中断(一)
。在每次时钟中断的时候。取得每一次中断前的esp,进而可以得到运行的函//数地址。这样就可以统计运行时间最长的函内核函数区域。以便于内核管理者优化
profile_tick(CPU_PROFILING, regs);
#else
if (!using_apic_timer)
smp_local_timer_interrupt(regs);
#endif
}
这里有几个重要的操作.先看do_timer():
void do_timer(struct pt_regs *regs)
{
// 更新jiffies计数.jiffies_64与jiffies在链接的时候,实际是指向同一个区域
jiffies_64++;
#ifndef CONFIG_SMP
/* SMP process accounting uses the local APIC timer */
//更新当前运行进程的与时钟相关的信息
update_process_times(user_mode(regs));
#endif
//更新当前时间.xtime的更新
update_times();
}
Update_process_times代码如下:
void update_process_times(int user_tick)
{
struct task_struct *p = current;
int cpu = smp_processor_id(), system = user_tick ^ 1;
update_one_process(p, user_tick, system, cpu);
//激活时间软中断
run_local_timers();
//减少时间片。这个函数涉及到的东西过多,等到进程调度的时候再来分析。请关注本站更新*^_^*
scheduler_tick(user_tick, system);
}
先看update_one_process():
static void update_one_process(struct task_struct *p, unsigned long user,
unsigned long system, int cpu)
{
do_process_times(p, user, system);
//检查进程的定时器
do_it_virt(p, user);
do_it_prof(p);
} 在这里简单介绍一下do_it_virt()与do_it_prof():
这两个函数主要检查用户空间的进程定时器是否到期.在进程的内存描述符有相关的字段.如下:
struct task_struct{
⋯⋯
unsigned long it_real_value, it_prof_value,it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_incr;
struct timer_list real_timer;
⋯⋯
}
(1)真实间隔定时器(ITIMER_REAL):这种间隔定时器在启动后,不管进程是否运行,每个时钟滴答都将其间隔计数器减1。当减到0值时,内核向进程发送SIGALRM信号。结构类型task_struct中的成员it_real_incr则表示真实间隔定时器的间隔计数器的初始值,而成员it_real_value则表示真实间隔定时器的间隔计数器的当前值。由于这种间隔定时器本质上与上一节的内核定时器时一样的,因此Linux实际上是通过real_timer这个内嵌在task_struct结构中的内核动态定时器来实现真实间隔定时器ITIMER_REAL的。
(2)虚拟间隔定时器ITIMER_VIRT:也称为进程的用户态间隔定时器。结构类型task_struct中成员it_virt_incr和it_virt_value分别表示虚拟间隔定时器的间隔计数器的初始值和当前值,二者均以时钟滴答次数位计数单位。当虚拟间隔定时器启动后,只有当进程在用户态下运行时,一次时钟滴答才能使间隔计数器当前值it_virt_value减1。当减到0值时,内核向进程发送SIGVTALRM信号(虚拟闹钟信号),并将it_virt_value重置为初值it_virt_incr。具体请见7.4.3节中的do_it_virt()函数的实现。
(3)PROF间隔定时器ITIMER_PROF:进程的task_struct结构中的it_prof_value和it_prof_incr成员分别表示PROF间隔定时器的间隔计数器的当前值和初始值(均以时钟滴答为单位)。当一个进程的PROF间隔定时器启动后,则只要该进程处于运行中,而不管是在用户态或核心态下执行,每个时钟滴答都使间隔计数器it_prof_value值减1。当减到0值时,内核向进程发送SIGPROF信号,并将it_prof_value重置为初值it_prof_incr.
Do_process_times():
static inline void do_process_times(struct task_struct *p,
unsigned long user, unsigned long system)
{
unsigned long psecs;
//p->utime:在用户空间所花的时间
psecs = (p->utime += user);
//p->stime:在系统空间所花的时间
psecs += (p->stime += system);
//如果运行的时间片到达
if (psecs / HZ >= p->rlim[RLIMIT_CPU].rlim_cur) {
/* Send SIGXCPU every second.. */
//每秒发送一个SIGXCPU
if (!(psecs % HZ))
send_sig(SIGXCPU, p, 1);
/* and SIGKILL when we go over max.. */
//发送SIGKILL
if (psecs / HZ >= p->rlim[RLIMIT_CPU].rlim_max)
send_sig(SIGKILL, p, 1);
}
}
该函数检查当前进程的时间片是否到达,如果到达就给当前进程发送SIGKILL和SIGXCPU
do_it_virt()/do_it_prof()检查过程的定时器是否到期.如果到期就给进程发送相应的信号:
static inline void do_it_virt(struct task_struct * p, unsigned long ticks)
{
unsigned long it_virt = p->it_virt_value;
if (it_virt) {
it_virt -= ticks;
if (!it_virt) {
it_virt = p->it_virt_incr;
//发送SIGVTALRM
send_sig(SIGVTALRM, p, 1);
}
p->it_virt_value = it_virt;
}
}
返回到update_process_times()的其它函数:
run_local_timers()
void run_local_timers(void)
{
raise_softirq(TIMER_SOFTIRQ);
}
激活时间软中断.这个函数我们在IRQ中断中已经分析过了,不再赘述
我们在do_timer()还漏掉了一个函数:
static inline void update_times(void)
{
unsigned long ticks;
//wall_jiffies:上一次更新的值
ticks = jiffies - wall_jiffies;
if (ticks) {
wall_jiffies += ticks;
//更新xtime
update_wall_time(ticks);
}
//统计TASK_RUNNING TASK_UNINTERRUPTIBLE进程数量
calc_load(ticks);
}
四:定时器
在模块的编写过程中,我们经常使用定时器来等待一段时间之后再来执行某一个操作,
为方便分析,写了下列一段测试程序:
#include
#include
#include
#include
#include
#include
MODULE_LICENSE(“GPL”);
void test_timerfuc(unsigned long x)
{
printk(“Eric xiao test ......n”);
}
//声明一个定个器
struct timer_list test_timer = TIMER_INITIALIZER(test_timerfuc, 0, 0);
int kernel_test_init()
{
printk(“test_initn”);
//修改定时器到期时间。为3个HZ。一个HZ产生一个时钟中断
mod_timer(&test_timer,jiffies+3*HZ);
//把定时器加入时钟软中断处理链表
add_timer(&test_timer);
}
int kernel_test_exit()
{
printk(“test_exitn”);
return 0;
}
module_init(kernel_test_init);
module_exit(kernel_test_exit);
上面的例子程序比较简单,我们从这个例子开始研究linux下的定时器实现。
TIMER_INITIALIZER():
1):TIMER_INITIALIZER()用来声明一个定时器,它的定义如下:
#define TIMER_INITIALIZER(_function, _expires, _data) {
.function = (_function),
.expires = (_expires),
.data = (_data),
.base = NULL,
.magic = TIMER_MAGIC,
.lock = SPIN_LOCK_UNLOCKED,
}
Struct timer_list定义如下:
struct timer_list {
//用来形成链表
struct list_head entry;
//定始器到达时间
unsigned long expires;
spinlock_t lock;
unsigned long magic;
//定时器时间到达后,所要运行的函数
void (*function)(unsigned long);
//定时器函数对应的参数
unsigned long data;
//挂载这个定时器的tvec_t_base_s.这个结构我们等会会看到
struct tvec_t_base_s *base;
};
从上面的过程中我们可以看到TIMER_INITIALIZER()只是根据传入的参数初始化了struct timer_list结构.并把magic 成员初始化成TIMER_MAGIC
2): mod_timer():修改定时器的到时时间
int mod_timer(struct timer_list *timer, unsigned long expires)
{
//如果该定时器没有定义fuction
BUG_ON(!timer->function);
//判断timer的magic是否为TIMER_MAGIC.如果不是,则将其修正为TIMER_MAGIC
check_timer(timer);
//如果要调整的时间就是定时器的定时时间而且已经被激活,则直接返回
if (timer->expires == expires && timer_pending(timer))
return 1;
//调用_mod_timer().呆会再给出分析
return __mod_timer(timer, expires);
}
3): add_timer()用来将定时器挂载到定时软中断队列,激活该定时器
static inline void add_timer(struct timer_list * timer)
{
__mod_timer(timer, timer->expires);
}
可以看到mod_timer与add_timer 最后都会调用__mod_timer().为了分析这个函数,我们先来了解一下定时系统相关的数据结构.
tvec_bases: per cpu变量,它的定义如下:
static DEFINE_PER_CPU(tvec_base_t, tvec_bases) = { SPIN_LOCK_UNLOCKED };
由此可以看到tves_bases的数型数据为teves_base_t.数据结构的定义如下:
typedef struct tvec_t_base_s tvec_base_t;
struct tvec_t_base_s的定义:
struct tvec_t_base_s {
spinlock_t lock;
//上一次运行计时器的jiffies 值
unsigned long timer_jiffies;
struct timer_list *running_timer;
//tv1 tv2 tv3 tv4 tv5是五个链表数组
tvec_root_t tv1;
tvec_t tv2;
tvec_t tv3;
tvec_t tv4;
tvec_t tv5;
} ____cacheline_aligned_in_smp;
Tves_root_t与tvec_t的定义如下:
#define TVN_BITS 6
#define TVR_BITS 8
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)
typedef struct tvec_s {
struct list_head vec[TVN_SIZE];
} tvec_t;
typedef struct tvec_root_s {
struct list_head vec[TVR_SIZE];
} tvec_root_t;
系统规定定时器最大超时时间间隔为0xFFFFFFFF.即为一个32位数.即使在64位系统上.如果超过此值也会将其强制设这oxFFFFFFFF(这在后面的代码分析中可以看到).内核最关心的就是间隔在0~255个HZ之间的定时器.次重要的是间隔在255~1<<(8+6)之间的定时器.第三重要的是间隔在1<<(8+6) ~ 1<<(8+6+6)之间的定器.依次往下推.也就是把32位的定时间隔为份了五个部份.1个8位.4个6位.所以内核定义了五个链表数组.第一个链表数组大小为8位大小,也即上面定义的 #define TVR_SIZE (1 << TVR_BITS).其它的四个数组大小为6位大小.即上面定义的#define TVN_SIZE (1 << TVN_BITS)
在加入定时器的时候,按照时间间隔把定时器加入到相应的数组即可.了解这点之后,就可以来看__mod_timer()的代码了:
//修改timer或者新增一个timer都会调用此接口
int __mod_timer(struct timer_list *timer, unsigned long expires)
{
tvec_base_t *old_base, *new_base;
unsigned long flags;
int ret = 0;
//入口参数检测
BUG_ON(!timer->function);
check_timer(timer);
spin_lock_irqsave(&timer->lock, flags);
//取得当前CPU对应的tvec_bases
new_base = &__get_cpu_var(tvec_bases);
repeat:
//该定时器所在的tvec_bases.对于新增的timer.它的base字段为NULL
old_base = timer->base;
/*
* Prevent deadlocks via ordering by old_base < new_base.
*/
//在把timer从当前tvec_bases摘下来之前,要充分考虑好竞争的情况
if (old_base && (new_base != old_base)) {
//按次序获得锁
if (old_base < new_base) {
spin_lock(&new_base->lock);
spin_lock(&old_base->lock);
} else {
spin_lock(&old_base->lock);
spin_lock(&new_base->lock);
}
/*
* The timer base might have been cancelled while we were
* trying to take the lock(s):
*/
//如果timer->base != old_base.那就是说在Lock的时候.其它CPU更改它的值
//那就解锁.重新判断
if (timer->base != old_base) {
spin_unlock(&new_base->lock);
spin_unlock(&old_base->lock);
goto repeat;
}
} else {
//old_base == NULl 或者是 new_base==old_base的情况
//获得锁
spin_lock(&new_base->lock);
//同理,在Lock的时候timer会生了改变
if (timer->base != old_base) {
spin_unlock(&new_base->lock);
goto repeat;
}
}
/*
* Delete the previous timeout (if there was any), and install
* the new one:
*/
//将其从其它的tvec_bases上删除.注意运行到这里的话,说话已经被Lock了
if (old_base) {
list_del(&timer->entry);
ret = 1;
}
//修改它的定时器到达时间
timer->expires = expires;
//将其添加到new_base中
internal_add_timer(new_base, timer);
//修改base字段
timer-base = new_base;
//操作完了,解锁
if (old_base && (new_base != old_base))
spin_unlock(&old_base->lock);
spin_unlock(&new_base->lock);
spin_unlock_irqrestore(&timer->lock, flags);
return ret;
}
篇3:Dephi 消息处理机制.net
Delphi MI LY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'“>中的消息 消息是 Windows 发出的一个通知,它告诉应用程序某个事件发生了,在 Delphi 中,大多数情况下 Windows 的消息被封装在 VCL 的事件中,
DelphiMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'”>中的消息
消息是Windows发出的一个通知,它告诉应用程序某个事件发生了。在Delphi中,大多数情况下Windows的消息被封装在VCL的事件中,我们只需处理相应的VCL事件就可以了,但如果我们需要编写自己的控件、截获或过滤消息就必须深入研究Win32的消息处理机制。在Delphi中消息以TMessage记录的方式定义。打开Message.pas文件,我们可以看到Tmessage是这样定义的:type
TMessage = packed record
Msg: Cardinal;
case Integer of
0: ( WParam: Longint;
LParam: Longint;
Result: Longint);
1: ( WParamLo: Word;
WParamHi: Word;
LParamLo: Word;
LParamHi: Word;
ResultLo: Word;
ResultHi: Word);
end;
其中,Msg是区别于其他消息的常量值,这些常量值可以是Windows单元中预定义的常量,也可以是用户自己定义的常量。Wparam通常是一个与消息有关的常量值,也可以是窗口或控件的句柄。LParam通常是一个指向内存中数据的指针。
Result是消息处理的返回值。Wparam、Lparam和Result都是32位的,如果想访问其中的低16位或高16位可以分别使用WparamLo、WparamHi、LParamLo、LparamHi、ResultLo和ResultHi。在Delphi中除了通用的Tmessage外,还为每个Windows定义了一个特殊的消息记录。我们可以浏览Message.pas文件,下面是键盘的消息记录:TWMKey = packed recordMsg: Cardinal;
CharCode: Word;
Unused: Word;
KeyData: Longint;
Result: Longint;
与键盘相关的消息如:WM_KEYDOWN、WM_KEYUP、WM_CHAR、WM_SYSKEYDOWN WM_SYSKEYUP、WM_SYSCHAR的记录也被定义为TWMkey。在Message.pas文件中有以下声明:
TWMChar=TWMkey;TWMKeyDown=
TWMkey;TWMKeyUp=TWMkey;TWMSys
-KeyDown=TWMkey; TWMSysKeyUp=
TWMkey;TWMSysChar=TWMkey;
消息的发送
消息处理就是定义应用程序如何响应Windows的消息,
在Delphi中每一个消息都有自己的处理过程,它必须是一个对象中的方法,且只能传递一个Tmessage或其他特殊的消息记录,方法声明后要有一个message命令,后接一个在0到32767之间的常量。
前面我们提到的消息都是标准的Windows消息(WM_X),除此之外还有VCL内部消息、通知消息和用户自定义消息。VCL内部消息通常以“CM_”开头,用于管理VCL内部的事物。如果改变了某个属性值或组件的其他一些特性后,需要通过内部消息将该变化通知其他组件。例如,激活输入焦点消息是向被激活的或被停用的组件发送的,用于接受或放弃输入焦点。
另外还有通知消息,一个窗口内的子控件发生了一些事情,需要通知父窗口,这是通过通知消息实现的。它只适用于标准的窗口控件,如按钮、列表框、编辑框等等。打开Message.pas文件,在标准的Windows后就是通知消息的声明:const
{$EXTERNALSYM BN_CLICKED}
BN_CLICKED = 0;
{$EXTERNALSYM BN_PAINT}
BN_PAINT = 1;
{$EXTERNALSYM BN_HILITE}
BN_HILITE = 2;
以上是按钮的通知消息,分别表示用户单击了按钮、按钮应当重画、用户加亮了按钮。
用户也可以自己定义消息、给自己发送消息和编写消息处理过程。消息的常量值为WM_USER+100到$7FFF,这个范围是Windows为用户自定义消息保留的。
Delphi消息的发送有三种方法:
1.Tcontrol类的Perform对象方法。可以向任何一个窗体或控件发送消息,只需要知道窗体或控件的实例。其声明如下:function Tcontrol.Perform(Msg:Cardinal;Wparam,Lparam:Longint):Longint
2.Windows的API函数SendMessage()和Postmessage()。其声明如下:
function SendMessage(hWnd: HWND; Msg: UINT;wParam:WPARAM; lParam: LPARAM):LRESULT;stdcall;function PostMessage(hWnd: HWND; Msg: UINT;wParam: WPARAM; lParam:LPARAM):LRESULT;stdcall
PostMessage函数将消息添加到应用程序的消息队列中去。应用程序的消息循环会从消息队列中提取登记的该消息,再发送到相应的窗口中。
SendMessage函数可以越过消息队列直接向窗口过程发送。所以当Windows需要立刻返回值时使用SendMessage,当需要不同的应用程序依次处理消息时使用PostMessage。而Perform从本质上和SendMessage相似,它们直接向窗口过程发送。SendMessage、Postmessage函数只需要知道窗口的句柄就可以发送消息,所以它们可以向非Delphi窗体发送一条消息,但而Perform必须知道窗体或控件的实例。共3页: 1 [2] [3] 下一页
原文转自:www.ltesting.net
篇4:保险合同快速处理机制评析及建议经济论文
【摘要】保险合同理赔纠纷的传统处理主要包括诉讼和仲裁两种方式。但这两种处理方式都存在明显效率低、高成本的特点,尤其不适宜处理案情简单、标的额较小的保险合同纠纷。随着保险消费者快速、低成本处理纠纷的呼声日益高涨,中国保险监督管理委员会开展了大量探索,中国保险监督管理委员会在各省市的派出机构也指导当地保险行业协会结合实际,大胆实践,摸索出若干种新的保险合同纠纷快速调处模式。
【关键词】保险;合同纠纷;快速调处
由于保险合同是一种高度格式化和专业化的合同,在订立和履行过程中,经常发生因投保、续保、理赔而发生纠纷。传统的纠纷解决方式主要是诉讼和仲裁。但是这两种保险合同纠纷处理方式也具有不可避免的缺陷:
首先,纠纷处理费用高,很多小额保险合同纠纷的保险人考虑到成本与效益的比例关系,不得不放弃应有权益。
其次,纠纷处理时间长。被保险人经常在漫长的诉讼和仲裁程序面前望而却步,放弃了维权的努力。
再次,纠纷处理专业性差。从实际情况看,部分法官和仲裁员保险知识普遍比较薄弱,案件的质量难以保证。
最后,执行有难度。某些保险公司虽然一审败诉,但为了迫使被保险人对一审判决作出让步,有意提起二审,以合法形式拖延履行赔付义务,甚至在判决生效后也不积极履行判决书,迫使被保险人交纳申请执行费。
伴随着我国加入时间贸易组织后保险业的快速发展,尤其在《国务院关于保险业改革发展的若干意见》(国发〔〕23号)发布后,建立一种新型的保险合同纠纷快速处理机制成为保险业发展十分急迫的任务。
1保险合同纠纷快速处理机制相关的五种模式
上海模式。依据《人民调解工作若干规定》设立人民调解委员会解决保险合同纠纷。可见上海模式在法律上应归属人们调解制度范畴。
甘肃模式。由于采用了设立仲裁委员会分会的形式,所以法律程序上应归属于仲裁范畴。
安徽模式、山东模式各有特色,但均未明确归属的法律制度范畴。
中国保险监督管理委员会推荐模式。在保监会推荐模式中,并没有明确规定纠纷快速处理机制应采用哪种法律模式,但在“处理机制的运行模式”部分规定“结合我国保险业的实际,调处机构采用调解模式……此外,为提高处理机制的效率,有条件的地区可以采用调解与裁决相结合的模式处理保险合同纠纷。”
以上五种模式的关系。前四种均为省级保险行业协会制定,第五种模式,制定主体虽然是中国保险监督管理委员会,但在该文件已明确其性质为“指导意见”所以,这五种模式相互平行,没有效力等级区分。
纠纷解决是广义的司法制度组成部分。保监会通知不具有立法效力,各地保险行业协会在没有法律规定的前提下,仅仅依据通知建立新的就纠纷解决机制,不但难以与现有制度衔接,也破坏了司法制度的统一性。
篇5:保险合同快速处理机制评析及建议经济论文
2.1保险合同纠纷快速处理机构和人员
(1)保监会模式。规定“可以在保险行业协会成立调解处理机构(以下简称“调处机构”)”。在具体案件的调处过程中,“被保险人对调处人员有选择权。涉案保险公司的员工应当回避”
(2)甘肃模式。设立的仲裁委员会分会,实际是仲裁机构的组成部分,但是聘请了若干保险业工作人员作为仲裁员。
(3)上海模式。①调解委员会置备有调解员名册,供争议各方查阅。②调解人员的选定基本上参照了《中华人民共和国仲裁法》的规定。
(4)安徽模式。①裁决员在主裁人领导下,负责具体裁决和调解工作,并实行回避制度。②裁决可以采用裁决员或者裁决组的形式。但对裁决组的人数未作规定。
(5)山东模式。①纠纷调解工作由本会办公室从本会成员中指定调解员组成调解小组进行。②调解纠纷涉及调解员任职保险公司的、调解员与申请人有亲属关系或利害关系的、调解员任职的律师事务所内有人受聘于当事保险公司的,该调解员回避。
综合以上情况,有几点重要问题的对比:
1)是否需要采取合议方式处理纠纷。从以上模式看,有的没有规定,有的规定必须采取合议方式,有的提供了合议和独任两种模式供实践中选择。采取独任方式更有利于时限该制度的设立目的,采用合议制容易失去快速处理机制的优势。
2)回避范围问题。而保险合同纠纷快速处理机制的处理结果,无论是调解还是裁决,均对被保险人一方没有强制约束力,被保险人一方可以继续采取其他方式维权。所以,当调处人员与争议的保险合同没有直接厉害关系的时候,可以不回避,这也切合我国保险业从业人员流动较大的现实。
2.2案件处理时限
(1)保监会模式。调处工作应当自立案之日起20日内结案,经争议各方同意,可以适当延长,但最长不得超过10日。
(2)甘肃模式。依据仲裁法律法规和仲裁规则。
(3)上海模式。调解工作应当自立案之日起三十日内结案。
(4)安徽模式。对于裁决纠纷,裁决员或裁决组原则上应当在收到有关材料之日起五个工作日内裁决完毕。
(5)山东规则。调解纠纷应自受理立案后30个工作日内完成。
2.3 案件处理经费:保险行业协会是社会团体,处理合同纠纷不是法律赋予的职责,国家没有拨款,因此决定这项制度存续的关键问题之一是经费问题。
(1)保监会周延礼主席在回答网友提问时表示“原则上我们不提倡收取被保险人的调解费用,但对保险公司一方,各地区可以根据具体情况来讨论是否收取费用”
(2)上海模式。调解员因调解而可能发生的'费用,由争议各方在调解意向书中约定分担和垫付的比例。
(3)安徽模式。规定了经费的来源包括“保险行业协会划拨的费用”“参加裁决机制的会员公司交纳的费用。”“其他合法收入(如咨询费等)。”
(4)山东模式。“调解纠纷不向申请人收取任何费用。”同时,保险公司在《保险索赔纠纷调解承诺书》中承诺“同意承担调解委员会开展工作所发生的费用。”
保险行业协会的性质是“行业自律组织”,不是经营主体,其收费除了会费以外,应当具有合法的理由和依据。在上海模式中,采取的是人民调解委员会制,而按照国务院《人民调解委员会组织条例》第十一条“人民调解委员会调解民间纠纷不收费”。这就使上海模式下,保险同业工会调解收费涉嫌违反法律法规。
3几点建议
在当前保险市场诚信问题突出的情况下,建立一套科学合理的合同纠纷解决机制是保险机构和保险监管者需要共同面对的复杂艰巨任务。笔者认为,建立该制度必须从根本上把握以下几个关键法律问题:
从宏观上,将保险合同纠纷快速处理机制置身于我国司法体系之中,成为其有机组成部分,照搬国外模式往往脱离中国司法体制的现状,造成目前保险合同纠纷快速处理机制不能适应中国实际,缺乏生命力,甚至在很多省市根本无法开展或开展后形同虚设的现状。这种脱离中国实际的做法表现为以下几个方面:
一是有的保险合同纠纷快速解决机制定位不清。有的试点地区将其定位为仲裁机构;有的试点地区将其定位为人民调解制度,但在具体规定上又违反了人民调解制度具体规定;还有很多地区根本没有定位,在机制设立的文件中以保监会通知为根据,缺乏牢固的制度根基。
二是现有的探索还不能取得保险公司的信任。保险公司普遍对纠纷处理机制抱有戒备。1.试点规定不利于保险公司。大部分规定无论胜负,均由保险公司承担费用。2.诉讼和仲裁可以约束双方当事人,而保险合同纠纷快速处理机制下作出的处理仅仅约束保险公司一方,保险公司在处理后不得再通过诉讼仲裁寻求保护,被保险人则可以不受约束,即可以选择接受该处理结果,也可以反悔并通过诉讼仲裁获得更多利益。3.“强制裁决”涉嫌违法。国务院《人民调解委员会组织条例》、司法部《人民调解工作若干规定》中均强调了人民调解需要遵循自愿原则,当事人不接受调解的,不可以强行调解,调解协议应当双方自愿,不可以强制裁决。
三是被保险人也对这种机制充满了怀疑:1.处理纠纷的人员大部分都是各保险公司的在职和退休工作人员,其公正性受到怀疑。2.部分保险公司不参加保险合同纠纷快速处理机制,这些公司的被保险人不能获得快速处理机制的保护。
为了获得双方信任,必须公平合理设定双方权利义务。伤害任何一方基本权利和不公平待遇都会使这种实践丧失生命力。
笔者建议:
第一:在收费问题上,如果将机制定位为人民调解制度,则不应当收费;如果将制度定位为仲裁制度,则按照相关规定收费。
第二:在处理机制上,充分利用人民调解机制的制度资源建立保险合同纠纷快速处理机制,避免在探索中失去制度根基。
第三:充分尊重合同双方意见,取消强制裁决和剥夺一方诉讼权的规定,只有在双赢的前提下,才能充分体现调处机制的优越性。
第四:终止各地区保险合同纠纷快速处理机制的探索,建立全国统一的模式,以适应全国保险统一市场的要求。
参考文献
[1] 王利明.司法改革研究.法律出版社,版
[2] 左为民,李玉福.中国司法制度.中国政法大学出版社,版
[3] 中国保险监督管理委员会.中国保险市场年报,版
[4] 杨华柏.保险业法制年度报告.2006.法律出版社,版
[5] 陈依维.保险知识百问百答.机械工业出版社,版
[6] 贾林青.保险法案例分析.中国人民大学出版社,20版
[7]李宏勃.法治现代化进程中的人民信访.清华大学出版社,2007年
篇6:模糊信息的复合型处理机制
模糊信息的复合型处理机制
通过水平截集将模糊信息局部清晰化是生产线优化调度等实际领域中常用的方法.该文针对模糊信息之间的差异度量问题,在分析了现有度量方法不足的'基础上,引入了一种描述水平可靠程度的参数--水平可靠性函数,并从模糊数的水平截集和隶属函数出发,给出了几种水平可靠性函数; 进而通过可靠统计量建立了一种带有可靠程度的衡量模糊数之间差异的度量机制,并结合实例给出了建立这种度量机制的方法和步骤.分析结果表明这种度量机制是现有模糊度量的完善,并将为模糊信息处理的决策和优化提供理论基础.
作 者:李法朝 刘民 吴澄 作者单位:李法朝(清华大学,自动化系,北京,100084;河北科技大学,理学院,石家庄,050018)刘民,吴澄(清华大学,自动化系,北京,100084)
刊 名:清华大学学报(自然科学版) ISTIC EI PKU英文刊名:JOURNAL OF TSINGHUA UNIVERSITY(SCIENCE AND TECHNOLOGY) 年,卷(期):2003 43(3) 分类号:O159 TP14 关键词:模糊数 可靠性函数 可靠统计量 可靠程度 度量【Linux的时钟处理机制】相关文章:
2.认识时钟说课稿
3.《认识时钟》教案
4.时钟团结作文
5.生命时钟诗歌
8.巧解时钟问题
10.合作机制范文






文档为doc格式