Chinaunix首页 | 论坛 | 博客
  • 博客访问: 256860
  • 博文数量: 63
  • 博客积分: 179
  • 博客等级: 入伍新兵
  • 技术积分: 342
  • 用 户 组: 普通用户
  • 注册时间: 2010-06-27 20:29
文章分类

全部博文(63)

文章存档

2019年(2)

2013年(5)

2012年(53)

2011年(3)

分类:

2012-06-11 20:47:38

/*--------------------------------------
  欢迎转载: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) |
给主人留下些什么吧!~~