Linux内部的时钟处理机制全面剖析

硬件

PC 机里常见的时钟硬件有以下这些。

RTC (Real Time Clock,实时时钟)

人们需要知道时间的时候,可以看看钟表。计算机系统中钟表类似的硬件就是外部时钟。它依靠主板上的电池,在系统断电的情况下,也能维持时钟的准确性。计算机需要知道时间的时候,就需要读取该时钟。

在 x86 体系中,这个时钟一般被称为 Real Time Clock。RTC 是主板上的一个 CMOS 芯片,比如 Motorola 146818,该芯片独立于 CPU 和其他芯片,可以通过 0x70 和 0x71 端口操作 RTC。RTC 可以周期性地在 IRQ 8 上触发中断,但精度很低,从 2HZ 到 8192HZ。

以 Motorola 146818 为例,软件可以通过 I/O 指令读写以下这些值:

图 2. Motorola 146818
Motorola 146818

可以看到,RTC 能提供精确到秒的实时时间值。

TSC (Time Stamp Counter)

CPU 执行指令需要一个外部振荡器产生时钟信号,从 CLK 管脚输入。x86 提供了一个 TSC 寄存器,该寄存器的值在每次收到一个时钟信号时加一。比如 CPU 的主频为 1GHZ,则每一秒时间内,TSC 寄存器的值将增加 1G 次,或者说每一个纳秒加一次。x86 还提供了 rtdsc 指令来读取该值,因此 TSC 也可以作为时钟设备。TSC 提供了比 RTC 更高精度的时间,即纳秒级的时间精度。

PIT (Programmable Interval Timer)

PIT 是 Programmable Interval Timer 的缩写,该硬件设备能定时产生中断。早期的 PIT 设备是 8254,现在多数可以集成在 Intel 的 I/O Control Hub 电路中,可以通过端口 0x40~0x43 访问 PIT。系统利用 PIT 来产生周期性的时钟中断,时钟中断通过 8259A 的 IRQ0 向 CPU 报告。它的精度不高,其入口 clock 的频率为 1MHz,理论上能产生的最高时钟频率略小于 0.5MHz。实际系统往往使用 100 或者 1000Hz 的 PIT。

HPET (High Precision Event Timer)

PIT 的精度较低,HPET 被设计来替代 PIT 提供高精度时钟中断(至少 10MHz)。它是由微软和 Intel 联合开发的。一个 HPET 包括了一个固定频率的数值增加的计数器以及 3 到 32 个独立的计时器,这每一个计时器有包涵了一个比较器和一个寄存器(保存一个数值,表示触发中断的时机)。每一个比较器都比较计数器中的数值和寄存器中的数值,当这两个数值相等时,将产生一个中断。

APIC Timer (Advanced Programmable Interrupt Controller Timer)

APIC ("Advanced Programmable Interrupt Controller") 是早期 PIC 中断控制器的升级,主要用于多处理器系统,用来支持复杂的中断控制以及多 CPU 之间的中断传递。APIC Timer 集成在 APIC 芯片中,用来提供高精度的定时中断,中断频率至少可以达到总线频率。系统中的每个 CPU 上都有一个 APIC Timer,而 PIT 则是由系统中所有的 CPU 共享的。Per CPU 的 Timer 简化了系统设计,目前 APIC Timer 已经集成到了所有 Intel x86 处理器中。




http://www.xuebuyuan.com/388444.html

 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 ):

o   
独立于整个计算机系统(例如:
CPU 
和其他 chip 

o   
内核利用其获取系统当前时间和日期

·        
Time Stamp Counter
 TSC ):

o   
 Pentium 起,提供一个寄存器 TSC,用来累计每一次外部振荡器产生的时钟信号

o   
通过指令 rdtsc 访问这个寄存器

o   
比起 PITTSC 可以提供更精确的时间测量

·        
Programmable
Interval Timer 
 PIT ):

o   
时间测量设备

o   
内核使用的产生时钟中断的设备,产生的时钟中断依赖于硬件的体系结构,慢的为 10 ms 一次,快的为 1 ms 一次

·        
High Precision
Event Timer 
 HPET ):

o   
PIT  RTC 的替代者,和之前的计时器相比,HPET 提供了更高的时钟频率(至少10 MHz )以及更宽的计数器宽度(64位)

o   
一个 HPET 包括了一个固定频率的数值增加的计数器以及332个独立的计时器,这每一个计时器有包涵了一个比较器和一个寄存器(保存一个数值,表示触发中断的时机)。每一个比较器都比较计数器中的数值和寄存器中的数值,当这两个数值相等时,将产生一个中断

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
数据结构:

通知链有四种类型,

1.原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞

2.可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞

3.原始通知链( Raw notifier chains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护

4.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
运作机制

通知链的运作机制包括两个角色:

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.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(省略了部分和主要功能无关的内容,以后代码同样方式处理)

注:本文中所有代码均来自于Linux2.6.25 源代码

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 函数

清单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 的实现):

1. 
初始化时钟源信息( struct
clocksource 
类型的变量),并将其添加到时钟源链表中,即 clocksource_list 链表(参见图2-1)。

2. 
初始化时钟事件设备信息(
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;

}

为了说明这个问题,不妨假设系统中使用的是 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;

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值