/*--------------------------------------
欢迎转载:http://kylinux.cublog.cn
---------------------------------------*/
一:前言
时钟是整个操作系统的脉搏,它为进程的时间片调度,定时事件提供了依据.另外,用户空间的很多操作都依赖于时钟,例如select.poll,make. 操作系统管理的时间为分两种,一种称为当前时间,也即我们日常生活所用的时间.这个时间一般保存在CMOS中.主板中有特定的芯片为其提供计时依据.另外一种时间称为相对时间.例如系统运行时间.显然对计算机而然,相对时间比当前时间更为重要.
二:与时钟有关的硬件处理.
1):实时时钟(RTC)
该时钟独立于CPU和其它芯片.即使PC断电,该时钟还是继续运行.该计时由一块单独的芯片处理,并把时钟值存放CMOS.该时间可参在IRQ8上周期性的产生时间信号.频率在2Hz ~ 8192Hz之间.但在linux中,只是用RTC来获取当前时间.
2):时间戳计时器(TSC)
CPU附带了一个64位的时间戳寄存器,当时钟信号到来的时候.该寄存器内容自动加1
3):可编程中断定时器(PIC)
该设备可以周期性的发送一个时间中断信号.发送中断信号的间隔可以对其进行编程控制.在linux系统中,该中断时间间隔由HZ表示.这个时间间隔也被称为一个节拍(tick).
在 ./include/asm-i386/param.h 定义
10#ifndef HZ
11#define HZ 100
12#endif
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触发的.
6)内核在2.6引入hrtimer,增强了clock event模型。 在当前内核里,有3种中断源将自己注册为一个clock_event_device结构:
struct clock_event_device {
const char *name;
unsigned int features;
unsigned long max_delta_ns;
unsigned long min_delta_ns;
unsigned long mult;
int shift;
int rating;
int irq;
cpumask_t cpumask;
int (*set_next_event)(unsigned long evt,
struct clock_event_device *);
void (*set_mode)(enum clock_event_mode mode,
struct clock_event_device *);
void (*event_handler)(struct clock_event_device *);
void (*broadcast)(cpumask_t mask);
struct list_head list;
enum clock_event_mode mode;
ktime_t next_event;
}; |
最重要的是set_next_event(), event_handler(). 前者是设置下一个clock事件的触发条件, 一般就是往clock device里重设一下定时器. 后者是event handler, 事件处理函数. 该处理函数会在时钟中断ISR里被调用. 如果这个clock用来做为ticker时钟, 那么handler的执行和之前kernel的时钟中断ISR基本相同, 类似timer_tick(). 事件处理函数可以在运行时动态替换, 这就给kernel一个改变整个时钟中断处理方式的机会, 也就给highres tick及dynamic tick一个动态挂载的机会.
1) LAPIC Timer (每个CPU一个lapic_events)
arch/x86/kernel/apic_32.c, setup_APIC_timer():
clockevents_register_device(levt);
嗯,lapic_clockevent是每个lapic_events的默认值,每个CPU都在setup_APIC_Timer中用它对lapic_events
进行初始化。
2) HPET
arch/x86/kernel/hpet.c, hpet_legacy_clockevent_register():
clockevents_register_device(&hpet_clockevent);
3) PIT
arch/x86/kernel/i8253.c, setup_pit_timer():
clockevents_register_device(&pit_clockevent);
同时,系统中有一个唯一的global_clock_event指针,从代码上来看,它或者被赋值为&pit_clockevent,或者
被赋值为hpet_clockevent。 我的理解是,只要CONFIG_HPET_TIMER=y,那么就是后者。 而且,LAPIC Timer只
能是一种Local Interrupt Source,因此不可能用来给global_clock_event赋值。
7)
和硬件计时器(相关的数据结构主要有两个:
-
struct clocksource :对硬件设备的抽象,描述时钟源信息
-
struct clock_event_device :时钟的事件信息,包括当硬件时钟中断发生时要执行那些操作(实际上保存了相应函数的指针)。本文将该结构称作为“时钟事件设备”。
上述两个结构内核源代码中有较详细的注解,分别位于文件 clocksource.h 和 clockchips.h 中。需要特别注意的是结构
clock_event_device 的成员 event_handler
,它指定了当硬件时钟中断发生时,内核应该执行那些操作,也就是真正的时钟中断处理函数。
Linux 内核维护了两个链表,分别存储了系统中所有时钟源的信息和时钟事件设备的信息。这两个链表的表头在内核中分别是 clocksource_list 和 clockevent_devices 。图2-1显示了这两个链表。
三:时钟中断相关代码分析time_init()是时钟初始化函数,他由asmlinkage void __init start_kernel()调用.具体代码如下://时钟中断初始化 /*
* This is called directly from init code; we must delay timer setup in the
* HPET case as we can't make the decision to turn on HPET this early in the
* boot process.
*
* The chosen time_init function will usually be hpet_time_init, above, but
* in the case of virtual hardware, an alternative function may be substituted.
*/
void __init time_init(void)
{
tsc_init();
late_time_init = choose_time_init();
}
|
内核中检查0号中断的中断源为HPET还是PIT的代码流程是:
start_kernel() > time_init() > choose_time_init() == hpet_time_init:
void __init hpet_time_init(void)
{
if (!hpet_enable())
setup_pit_timer();
//注册时钟中断处理函数
time_init_hook();
}
注意,如果HPET失败,则使用PIT。 做完这个选择之后进行0号中断的相关设置。
-> HPET的情况:
hpet_enable() > hpet_legacy_clockevent_register() :
clockevents_register_device(&hpet_clockevent);
global_clock_event = &hpet_clockevent;
-> PIT的情况:
setup_pit_timer() :
clockevents_register_device(&pit_clockevent);
global_clock_event = &pit_clockevent;
-> 设置完中断源后:
time_init_hook():
/**
* static struct irqaction irq0 = {
* .handler = timer_interrupt,
* .flags = IRQF_DISABLED | IRQF_NOBALANCING | IRQF_IRQPOLL,
* .mask = CPU_MASK_NONE,
* .name = "timer"
* };
*/
irq0.mask = cpumask_of_cpu(0);
setup_irq(0, &irq0);
可见, 不管是其中断源是HPET还是PIT, 0号中断只能由CPU0来处理,其处理例程为timer_interrupt。
注:在start_kernel()>timekeeping_init()已队系统时间进行了初始化,而在2.6以前版本中初始化时间是在time_init()函数中初始化.
**接着我会介绍中断处理程序,请接着看。
参考资料:
linux内核设计与实现
http://blog.ccidnet.com/blog-htm-do-showone-uid-84561-type-blog-itemid-258809.html
http://blog.chinaunix.net/u2/61477/showart_481379.html
http://hi.baidu.com/80695073/blog/item/9c170f55be0ae5c1b745ae5b.html
http://blog.chinaunix.net/u1/51562/showart_509400.html
阅读(1340) | 评论(0) | 转发(0) |