Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1185834
  • 博文数量: 232
  • 博客积分: 7563
  • 博客等级: 少将
  • 技术积分: 1930
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-21 11:17
文章分类

全部博文(232)

文章存档

2011年(17)

2010年(90)

2009年(66)

2008年(59)

分类: LINUX

2008-05-22 16:39:51

一、主要内容:

从内核实现的角度分析:
  •  Linux 2.4.0内核的时钟中断
  • 内核对时间的表示
二、内核主要需要两种类型的时间:
  • 在内核运行期间持续记录当前的时间和日期,以使内核对某些文件、对象等作时间标记(就是我们通常所说“"时间戳”),或者供用户通过时间syscall进行检索。
  • 定时器(维持固定周期),以提醒内核或用户一段时间已经过去了
因此,时间在内核中很重要,它是驱动内核运行的“起搏器”。

三、时钟类型:

1、PC机中的时间是由三种时钟硬件提供的,并且都是基于固定频率的晶体振荡器来提供时钟方波信号输入的:
  • 实时时钟(RTC)
  • 可编程间隔定时器(PTI: Programmable  Interval Timer)
  • 时间戳计数器(TSC: Time Stamp Counter)
2、时钟硬件介绍:
 
   2.1 实时时钟RTC:

2.1.1  
自从IBM PC AT以来,所有的PC机就都包含了一个叫实时时钟的时钟芯片,它是通过主板上的电池来供电的,而不是通过PC机的电源供电,因此:在PC机断电后,RTC仍然能够运行,继续保持时间。最常见的RTC芯片是MC146818(Motorola)和DS12887(maxim),DS12887完全兼容于MC146818,并有一定的扩展。
    Linux内核对RTC的唯一用途就是把RTC用作“离线”或“后台”的时间与日期维护器。当Linux内核启动时,它从RTC中读取时间与日期的基准 值。然后再运行期间内核就完全抛开RTC,从而以软件的形式维护系统的当前时间与日期,并在需要时将时间回写到RTC芯片中。
   
2.1.2  相关内核代码:
  •  与MC146818 RTC对应的设备驱动程序实现在include/linux/rtc.h和drivers/char/rtc.c文件中,对应的设备文件是/dev/rtc;
  • Linux在include/linux/mc146818rtc.h和include/asm-i386/mc146818rtc.h头文件中分别定义了mc146818 RTC芯片各寄存器的含义以及RTC芯片在i386平台上的I/O端口操作。
  • 通用的RTC接口则声明在include/linux/rtc.h头文件中。 
2.1.3 内核对RTC的操作:

如前所述,Linux内核与RTC进行互操作的时机只有两个:
(1)内核在启动时从RTC中读取启动时的时间与日期 , Linux内核在arch/i386/kernel/time.c文件中实现了函数get_cmos_time()来进行对RTC的第一种操作。显然,get_cmos_time()函数仅仅在内核启动时被调用一次。;
(2)内核在需要时将时间与日期回写到RTC中,Linux则同样在arch/i386/kernel/time.c文件中实现了函数set_rtc_mmss(),以支持向RTC中回写当前时间与日期。

 
       2) 可编程间隔定时器PIT:

      每个PC机中都有一个PIT,以通过IRQ0产生周期性的时钟中断信号。当前使用最普遍的是Intel 8254 PIT芯片,它的I/O端口地址是0x40~0x43。
    
      3) 时间戳计数器TSC:
     从Pentium开始,所有的Intel 80x86 CPU就都又包含一个64位的时间戳记数器(TSC)的寄存器。该寄存器实际上是一个不断增加的计数器,它在CPU的每个时钟信号到来时加1(也即每一个clock-cycle输入CPU时,该计数器的值就加1)。
     利用CPU的TSC,操作系统通常可以得到更为精准的时间度量。假如clock-cycle的频率是400MHZ,那么TSC就将每2.5纳秒增加一次。

四、Linux对时间的表示:

1、通常,操作系统可以使用三种方法来表示系统的当前时间与日期:

    ①最简单的一种方法就是直接用一个64位的计数器来对时钟滴答进行计数。
    ②第二种方法就是用一个32位计数器来对秒进行计数,同时还用一个32位的辅助计数器对时钟滴答计数,直至累积到一秒为止。因为232超过136年,因此这种方法直至22世纪都可以让系统工作得很好。
    ③第三种方法也是按时钟滴答进行计数,但是是相对于系统启动以来的滴答次数而不是相对于相对于某个确定的外部时刻;当读外部后备时钟(如RTC)或用户输入实际时间时,根据当前的滴答次数计算系统当前时间。

UNIX类操作系统通常采用第三种方法来维护系统的时间和日期。作为一种UNIX类操作系统,Linux当然也是采用第三种方式来表示系统的当前时间的。

2、Linux内核时钟驱动中的一些基本概念:
  • 时钟周期(clock cycle)的频率:  晶体振荡器在1秒时间内产生的时钟脉冲个数就是时钟周期的频率。8253/8254 PIT的本质就是对由晶体振荡器产生的时钟周期进行计数,Linux用宏CLOCK_TICK_RATE来表示8254 PIT的输入时钟脉冲的频率(在PC机中这个值通常是1193180HZ),该宏定义在include/asm-i386/timex.h头文件中:
    #define CLOCK_TICK_RATE 1193180 /* Underlying HZ */
  • 时钟滴答(clock tick):当PIT通道0的计数器减到0值时,它就在IRQ0上产生一次时钟中断,也即一次时钟滴答。PIT通道0的计数器的初始值决定了要过多少时钟周期才产生一次时钟中断,因此也就决定了一次时钟滴答的时间间隔长度。
  • 时钟滴答的频率(HZ):也即1秒时间内PIT所产生的时钟滴答次数,也就是PIT1秒内发生的时钟中断次数。类似地,这个值也是由PIT通道0的计数器初值决定的(反过来说,确定了时钟滴答的频率值后也就可以确定8254 PIT通道0的计数器初值)。Linux内核用宏HZ来表示时钟滴答的频率,而且在不同的平台上HZ有不同的定义值。对于ALPHA和IA62平台HZ的值是1024,对于 SPARC、MIPS、ARM和i386等平台HZ的值都是100。该宏在i386平台上的定义如下(include/asm- i386/param.h):
    #ifndef HZ
    #define HZ 100
    #endif
    根据HZ的值,我们也可以知道一次时钟滴答的具体时间间隔应该是(1000ms/HZ)=10ms。即每10ms发生一次时钟中断。
  • 时钟滴答的时间间隔:Linux用全局变量tick来表示时钟滴答的时间间隔长度,该变量定义在kernel/timer.c文件中,如下:
    long tick = (1000000 + HZ/2) / HZ; /* timer interrupt period */
    tick 变量的单位是微妙(μs),由于在不同平台上宏HZ的值会有所不同,因此方程式tick=1000000÷HZ的结果可能会是个小数,因此将其进行四舍五 入成一个整数,所以Linux将tick定义成(1000000+HZ/2)/HZ,其中被除数表达式中的HZ/2的作用就是用来将tick值向上圆整成 一个整型数。
    另外,Linux还用宏TICK_SIZE来作为tick变量的引用别名(alias),其定义如下(arch/i386/kernel/time.c):
    #define TICK_SIZE tick
  • 宏LATCH:Linux用宏LATCH来定义要写到PIT通道0的计数器中的值,它表示PIT将每隔多少个时钟周期产生一次时钟中断。显然LATCH应该由下列公式计算:
    LATCH=(1秒之内的时钟周期个数)÷(1秒之内的时钟中断次数)=(CLOCK_TICK_RATE)÷(HZ)
    类似地,上述公式的结果可能会是个小数,应该对其进行四舍五入。所以,Linux将LATCH定义为(include/linux/timex.h):
    /* LATCH is used in the interval timer and ftape setup. */
    #define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ) /* For divider */
    类似地,被除数表达式中的HZ/2也是用来将LATCH向上圆整成一个整数。
3、Linux内核在表示系统当前时间时用到的三个重要数据结构:
  • 全局变量jiffies(瞬间的含义):这是一个32位的无符号整数,用来表示自内核上一次启动以来的时钟滴答次数(也是时钟中断次数)。每发生一次时钟滴答,内核的时钟中断处理函数timer_interrupt()都要将该全局变量jiffies加1。该变量定义在kernel/timer.c源文件中,如下所示:
    unsigned long volatile jiffies;
    C语言限定符volatile表示jiffies是一个易该变的变量,因此编译器将使对该变量的访问从不通过CPU内部cache来进行。
  • 全局变量xtime: 它是一个timeval结构类型的变量,用来表示当前时间距UNIX时间基准1970-01-01 00:00:00的相对秒数值。Linux内核通过timeval结构类型的全局变量xtime来维持当前时间,该变量定义在kernel/timer.c文件中,如下所示:
    /* The current time */
    volatile struct timeval xtime __attribute__ ((aligned (16)));
    但 是,全局变量xtime所维持的当前时间通常是供用户来检索和设置的,而其他内核模块通常很少使用它(其他内核模块用得最多的是jiffies),因此对 xtime的更新并不是一项紧迫的任务,所以这一工作通常被延迟到时钟中断的底半部分(bottom half)中来进行。由于bottom half的执行时间带有不确定性,因此为了记住内核上一次更新xtime是什么时候,Linux内核定义了一个类似于jiffies的全局变量 wall_jiffies,来保存内核上一次更新xtime时的jiffies值。时钟中断的底半部分每一次更新xtime的时侯都会将 wall_jiffies更新为当时的jiffies值。全局变量wall_jiffies定义在kernel/timer.c文件中:
    /* jiffies at the most recent update of wall time */
    unsigned long wall_jiffies;
下面介绍一下结构体timeval,它是Linux内核表示时间的一种格式(Linux内核对时间的表示有多种格式,每种格式都有不同的时间精度),其时间精度是微秒。该结构是内核表示时间时最常用的一种格式,它定义在头文件include/linux/time.h中,如下所示:
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
其中,成员tv_sec表示当前时间距UNIX时间基准的秒数值,而成员tv_usec则表示一秒之内的微秒值,且1000000>tv_usec>=0。

  • 全局变量sys_tz: 它是一个timezone结构类型的全局变量,表示系统当前的时区信息。结构类型timezone定义在include/linux/time.h头文件中,如下所示:
    struct timezone {
    int tz_minuteswest; /* minutes west of Greenwich */
    int tz_dsttime; /* type of dst correction */
    };
    基于上述结构,Linux在kernel/time.c文件中定义了全局变量sys_tz表示系统当前所处的时区信息,如下所示:
    struct timezone sys_tz;
未完待续...


参考资料:
   Linux内核的时钟中断机制
   http://blog.csdn.net/zxg623/archive/2007/09/20/1792224.aspx


































阅读(1963) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~