1、尹能 linux 时间系统的设计与实现 第 1 页 共 18 页 Linux 时间系统的设计与实现学生姓名:xxx 指导老师: XXX摘 要 时间系统是操作系统的重要组成部分 ,无论是在分时操作系统还是在实时操作系统中 ,时间系统都起着极为重要的作用。时间系统的主要功能是负责维护系统时间、计算 CPU 的使用情况、为分时调度提供计时、提供定时器。概述了时间系统的主要功能 ,对 Linux 时间系统的硬件基础进行介绍 ,介绍了 Linux 操作系统中用来表示时间的数据结构。结合对源代码的分析 ,具体描述了 Linux 独特的时钟中断处理过程和时间系统各功能的实现。最后 ,介绍了实时操作系统的特征
2、 ,并针对如何改造时间系统 ,提高 Linux 核心的实时处理能力提出了解决方案。关键词 linux 时间系统;linux-2.4.3.0 源码;Red hat Enterprise linux 5尹能 linux 时间系统的设计与实现 第 2 页 共 18 页 1 引 言1.1 课题背景及意义首先, 是经济问题。中国是个人口大国,将来更要成为网络大国,如果每个人都用正版 windows 那将是一笔不小的开销.linux 就不存在这个问题。 当然, 更不可能每个人都用苹果的 macos。其次,linux 是一个具有优良血统的系统, 它具有 unix 的特点,又具有极好的易用性,安全性,强大的网
3、络服务功能。综合来说,linux 具有良好的发展前景.中国对于推广 linux 具有非常的价值。 1.2 课程设计目的(1)学会使用 vi 编辑器编辑 C 语言程序(2)学会 Linux 环境下 gcc 的使用(3)学会调试工具 GDB 的使用(4)学习重新编译 Linux 内核,理解、掌握 Linux 内核版本和 Linux 发行版本的区别(5) 学会在 linux 中设置,修改,显示时间 1.3 课题技术支持了解 Linux 的时钟由于 Linux 时钟和 Windows 时钟从概念的分类、使用到设置都有很大的不同,所以,搞清楚 Linux 时钟的工作方式与设置操作,不仅对于 Linux
4、初学者有着重大意义,而且对于使用 Linux 服务器的用户来说尤为重要。1.4 可行性分析报告Windows 时钟大家可能十分熟悉了,Linux 时钟在概念上类似 Windows 时钟显示当前系统时间,但在时钟分类和设置上却和 Windows 大相径庭。和Windows 不同的是,Linux 将时钟分为系统时钟(System Clock)和硬件(Real 尹能 linux 时间系统的设计与实现 第 3 页 共 18 页 Time Clock,简称 RTC)时钟两种。系统时间是指当前 Linux Kernel 中的时钟,而硬件时钟则是主板上由电池供电的那个主板硬件时钟,这个时钟可以在 BIOS的
5、“Standard BIOS Feture”项中进行设置。既然 Linux 有两个时钟系统,那么大家所使用的 Linux 默认使用哪种时钟系统呢?会不回出现两种系统时钟冲突的情况呢?这些疑问和担心不无道理。首先,Linux 并没有默认哪个时钟系统。当 Linux 启动时,硬件时钟会去读取系统时钟的设置,然后系统时钟就会独立于硬件运作。从 Linux 启动过程来看,系统时钟和硬件时钟不会发生冲突,但 Linux 中的所有命令(包括函数)都是采用的系统时钟设置。不仅如此,系统时钟和硬件时钟还可以采用异步方式,见图 1 所示,即系统时间和硬件时间可以不同。这样做的好处对于普通用户意义不大,但对于 L
6、inux 网络管理员却有很大的用处。例如,要将一个很大的网络中(跨越若干时区)的服务器同步,假如位于美国纽约的 Linux 服务器和北京的 Linux 服务器,其中一台服务器无须改变硬件时钟而只需临时设置一个系统时间,如要将北京服务器上的时间设置为纽约时间,两台服务器完成文件的同步后,再与原来的时钟同步一下即可。这样系统和硬件时钟就提供了更为灵活的操作。尹能 linux 时间系统的设计与实现 第 4 页 共 18 页 2 实现原理通常,操作系统可以使用三种方法来表示系统的当前时间与日期:最简单的一种方法就是直接用一个64位的计数器来对时钟滴答进行计数。第二种方法就是用一个32位计数器来对秒进行
7、计数,同时还用一个32位的辅助计数器对时钟滴答计数,直至累积到一秒为止。因为32位的秒计数超过136年,因此这种方法直至22世纪都可以让系统工作得很好。第三种方法是按系统启动以来的滴答次数进行计数,而不是相对于某个确定的外部时刻;当读外部后备时钟(如RTC)或用户输入实际时间时,根据当前的滴答次数计算系统当前时间。 21 基本概念 首先,有必要明确一些Linux内核时钟驱动中的基本概念。 (1)时钟周期(clock cycle)的频率:82538254 PIT的本质就是对由晶体振荡器产生的时钟周期进行计数,晶体振荡器在1秒时间内产生的时钟脉冲个数就是时钟周期的频率。Linux用宏CLOCK_T
8、ICK_RATE来表示8254 PIT的输入时钟脉冲的频率(在PC机中这个值通常是1193180HZ),该宏定义在include/asm-i386/timex.h头文件中: #define CLOCK_TICK_RATE 1193180 /* Underlying HZ */ (2)时钟滴答(clock tick):我们知道,当PIT通道0的计数器减到0值时,它就在IRQ0上产生一次时钟中断,也即一次时钟滴答。PIT通道0的计数器的初始值决定了要过多少时钟周期才产生一次时钟中断,因此也就决定了一次时钟滴答的时间间隔长度。 (3)时钟滴答的频率(HZ):也即1秒时间内PIT所产生的时钟滴答次数。
9、类似地,这个值也是由PIT通道0的计数器初值决定的(反过来说,确定了时钟滴答的频率值后也就可以确定8254 PIT通道0的计数器初值)。Linux内核用宏HZ来表示时钟滴答的频率,而且在不同的平台上HZ有不同的定义值。对于ALPHA和IA62平台HZ的值是1024,对于SPARC、MIPS、ARM和i386等平台HZ的值都是100。该宏在i386平台上的定义如下(include/asm-i386/param.h): 尹能 linux 时间系统的设计与实现 第 5 页 共 18 页 #ifndef HZ #define HZ 100 #endif 根据HZ的值,我们也可以知道一次时钟滴答的具体时
10、间间隔应该是(1000msHZ)10ms。 (4)时钟滴答的时间间隔:Linux用全局变量tick来表示时钟滴答的时间间隔长度,该变量定义在kernel/timer.c文件中,如下: long tick = (1000000 + HZ/2) / HZ; /* timer interrupt period */ tick变量的单位是微妙(s),由于在不同平台上宏HZ的值会有所不同,因此方程式tick1000000HZ的结果可能会是个小数,因此将其进行四舍五入成一个整数,所以Linux将tick定义成(1000000HZ2)HZ,其中被除数表达式中的HZ2的作用就是用来将tick值向上圆整成一个整
11、型数。 另外,Linux还用宏TICK_SIZE来作为tick变量的引用别名(alias),其定义如下(archi386/kernel/time.c): #define TICK_SIZE tick (5)宏LATCH:Linux用宏LATCH来定义要写到PIT通道0的计数器中的值,它表示PIT将每隔多少个时钟周期产生一次时钟中断。显然LATCH应该由下列公式计算: LATCH(1秒之内的时钟周期个数)(1秒之内的时钟中断次数)(CLOCK_TICK_RATE)(HZ) 类似地,上述公式的结果可能会是个小数,应该对其进行四舍五入。所以,Linux将LATCH定义为(include/linux/
12、timex.h): /* LATCH is used in the interval timer and ftape setup. */ #define LATCH (CLOCK_TICK_RATE + HZ/2) / HZ) /* For divider */ 类似地,被除数表达式中的HZ2也是用来将LATCH向上圆整成一个整数。尹能 linux 时间系统的设计与实现 第 6 页 共 18 页 22 表示系统当前时间的内核数据结构 作为一种UNIX类操作系统,Linux内核显然采用本节一开始所述的第三种方法来表示系统的当前时间。Linux内核在表示系统当前时间时用到了三个重要的数据结构: 全
13、局变量jiffies:这是一个32位的无符号整数,用来表示自内核上一次启动以来的时钟滴答次数。每发生一次时钟滴答,内核的时钟中断处理函数timer_interrupt()都要将该全局变量jiffies加1。该变量定义在kernel/timer.c源文件中,如下所示: unsigned long volatile jiffies; C语言限定符volatile表示jiffies是一个易改变的变量,因此编译器将使对该变量的访问从不通过CPU内部cache来进行。 全局变量xtime:它是一个timeval结构类型的变量,用来表示当前时间距UNIX时间基准19700101 00:00:00的相对秒数
14、值。结构timeval是Linux内核表示时间的一种格式(Linux内核对时间的表示有多种格式,每种格式都有不同的时间精度),其时间精度是微秒。该结构是内核表示时间时最常用的一种格式,它定义在头文件include/linux/time.h中,如下所示: struct timeval time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ ; 其中,成员tv_sec表示当前时间距UNIX时间基准的秒数值,而成员tv_usec则表示一秒之内的微秒值,且1000000tv_usec0。 Linux内核通过timeval
15、结构类型的全局变量xtime来维持当前时间,该变量定义在kernel/timer.c文件中,如下所示: /* The current time */ volatile struct timeval xtime _attribute_ (aligned (16); 但是,全局变量xtime所维持的当前时间通常是供用户来检索和设置的,尹能 linux 时间系统的设计与实现 第 7 页 共 18 页 而其他内核模块通常很少使用它(其他内核模块用得最多的是jiffies),因此对xtime的更新并不是一项紧迫的任务,所以这一工作通常被延迟到时钟中断的底半部分(bottom half)中来进行。由于bo
16、ttom 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; 全局变量sys_tz:它是一个tim
17、ezone结构类型的全局变量,表示系统当前的时区信息。结构类型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; 23 Linux对TSC的编程实现 Linux用定义
18、在arch/i386/kernel/time.c文件中的全局变量use_tsc来表示内核是否使用CPU的TSC寄存器,use_tsc1表示使用TSC,use_tsc0表示不使用TSC。该变量的值是在time_init()初始化函数中被初始化的(详见下一节)。该变量的定义如下: static int use_tsc; 宏cpu_has_tsc可以确定当前系统的CPU是否配置有TSC寄存器。此外,宏尹能 linux 时间系统的设计与实现 第 8 页 共 18 页 CONFIG_X86_TSC也表示是否存在TSC寄存器。 1 读TSC寄存器的宏操作 x86 CPU的rdtsc指令将TSC寄存器的高3
19、2位值读到EDX寄存器中、低32位读到EAX寄存器中。Linux根据不同的需要,在rdtsc指令的基础上封装几个高层宏操作,以读取TSC寄存器的值。它们均定义在include/asm-i386/msr.h头文件中,如下: #define rdtsc(low,high) _asm_ _volatile_(“rdtsc“ : “=a“ (low), “=d“ (high) #define rdtscl(low) _asm_ _volatile_ (“rdtsc“ : “=a“ (low) : : “edx“) #define rdtscll(val) _asm_ _volatile_ (“rdts
20、c“ : “=A“ (val) 宏rdtsc()同时读取TSC的LSB与MSB,并分别保存到宏参数low和high中。宏rdtscl则只读取TSC寄存器的LSB,并保存到宏参数low中。宏rdtscll读取TSC的当前64位值,并将其保存到宏参数val这个64位变量中。 2 校准TSC 与可编程定时器PIT相比,用TSC寄存器可以获得更精确的时间度量。但是在可以使用TSC之前,它必须精确地确定1个TSC计数值到底代表多长的时间间隔,也即到底要过多长时间间隔TSC寄存器才会加1。Linux内核用全局变量fast_gettimeoffset_quotient来表示这个值,其定义如下(arch/i3
21、86/kernel/time.c): /* Cached *multiplier* to convert TSC counts to microseconds. * (see the equation below). * Equal to 232 * (1 / (clocks per usec) ). 尹能 linux 时间系统的设计与实现 第 9 页 共 18 页 * Initialized in time_init. */ unsigned long fast_gettimeoffset_quotient; 根据上述定义的注释我们可以看出,这个变量的值是通过下述公式来计算的: fast_g
22、ettimeoffset_quotient (232) / (每微秒内的时钟周期个数) 定义在arch/i386/kernel/time.c文件中的函数calibrate_tsc()就是根据上述公式来计算fast_gettimeoffset_quotient的值的。显然这个计算过程必须在内核启动时完成,因此,函数calibrate_tsc()只被初始化函数time_init()所调用。 3 用TSC实现高精度的时间服务 在拥有TSC(TimeStamp Counter)的x86 CPU上,Linux内核可以实现微秒级的高精度定时服务,也即可以确定两次时钟中断之间的某个时刻的微秒级时间值。要确定
23、时刻x的微秒级时间值,就必须确定时刻x距上一次时钟中断产生时刻的时间间隔偏移offset_usec的值(以微秒为单位)。为此,内核定义了以下两个变量: (1)中断服务执行延迟delay_at_last_interrupt:由于从产生时钟中断的那个时刻到内核时钟中断服务函数timer_interrupt真正在CPU上执行的那个时刻之间是有一段延迟间隔的,因此,Linux内核用变量delay_at_last_interrupt来表示这一段时间延迟间隔,其定义如下(arch/i386/kernel/time.c): /* Number of usecs that the last interrupt
24、 was delayed */ static int delay_at_last_interrupt; 关于delay_at_last_interrupt的计算步骤我们将在分析timer_interrupt()函数时讨论。 (2)全局变量last_tsc_low:它表示中断服务timer_interrupt真正在尹能 linux 时间系统的设计与实现 第 10 页 共 18 页 CPU上执行时刻的TSC寄存器值的低32位(LSB)。 显然,通过delay_at_last_interrupt、last_tsc_low和时刻x处的TSC寄存器值,我们就可以完全确定时刻x距上一次时钟中断产生时刻的时
25、间间隔偏移offset_usec的值。实现在arch/i386/kernel/time.c中的函数do_fast_gettimeoffset()就是这样计算时间间隔偏移的,当然它仅在CPU配置有TSC寄存器时才被使用,后面我们会详细分析这个函数。 尹能 linux 时间系统的设计与实现 第 11 页 共 18 页 3 时钟中断的驱动如前所述,82538254 PIT的通道0通常被用来在IRQ0上产生周期性的时钟中断。对时钟中断的驱动是绝大数操作系统内核实现time-keeping的关键所在。不同的OS对时钟驱动的要求也不同,但是一般都包含下列要求内容: 1. 维护系统的当前时间与日期。 2.
26、防止进程运行时间超出其允许的时间。 3. 对CPU的使用情况进行记帐统计。 4. 处理用户进程发出的时间系统调用。 5. 对系统某些部分提供监视定时器。 其中,第一项功能是所有OS都必须实现的基础功能,它是OS内核的运行基础。31 Linux对时钟中断的初始化 Linux对时钟中断的初始化是分为几个步骤来进行的:(1)首先,由init_IRQ()函数通过调用init_ISA_IRQ()函数对中断向量32256所对应的中断向量描述符进行初始化设置。显然,这其中也就把IRQ0(也即中断向量32)的中断向量描述符初始化了。(2)然后,init_IRQ()函数设置中断向量32256相对应的中断门。(3
27、)init_IRQ()函数对PIT进行初始化编程;(4)sched_init()函数对计数器、时间中断的Bottom Half进行初始化。(5)最后,由time_init()函数对Linux内核的时钟中断机制进行初始化。这三个初始化函数都是由init/main.c文件中的start_kernel()函数调用的,如下: asmlinkage void _init start_kernel() 尹能 linux 时间系统的设计与实现 第 12 页 共 18 页 trap_init(); init_IRQ(); sched_init(); time_init(); softirq_init(); (
28、1)init_IRQ()函数对8254 PIT的初始化编程 函数init_IRQ()函数在完成中断门的初始化后,就对8254 PIT进行初始化编程设置,设置的步骤如下:(1)设置8254 PIT的控制寄存器(端口0x43)的值为“01100100”,也即选择通道0、先读写LSB再读写MSB、工作模式2、二进制存储格式。(2)将宏LATCH的值写入通道0的计数器中(端口0x40),注意要先写LATCH的LSB,再写LATCH的高字节。其源码如下所示(arch/i386/kernel/i8259.c): void _init init_IRQ(void) /* * Set the clock to
29、 HZ Hz, we already have a valid * vector now: */ outb_p(0x34,0x43); /* binary, mode 2, LSB/MSB, ch 0 */ outb_p(LATCH /* LSB */ outb(LATCH 8 , 0x40); /* MSB */ 尹能 linux 时间系统的设计与实现 第 13 页 共 18 页 (2)sched_init()对定时器机制和时钟中断的Bottom Half的初始化 函数sched_init()中与时间相关的初始化过程主要有两步:(1)调用init_timervecs()函数初始化内核定时器机
30、制;(2)调用init_bh()函数将BH向量TIMER_BH、TQUEUE_BH和IMMEDIATE_BH所对应的BH函数分别设置成timer_bh()、tqueue_bh()和immediate_bh()函数。如下所示(kernel/sched.c): void _init sched_init(void) init_timervecs(); init_bh(TIMER_BH, timer_bh); init_bh(TQUEUE_BH, tqueue_bh); init_bh(IMMEDIATE_BH, immediate_bh); 尹能 linux 时间系统的设计与实现 第 14 页 共
31、 18 页 4 详细设计4.1 基本操作(1)date 042612492005(2)hwclock w第一步的意思是设置时间,设置完了可以用date命令查看对不对.格式是月日时分年第二步的意思是写入主板的rtc芯片=su -c date -s 月/日/年su -c date -s 时:分:秒=1.在虚拟终端中使用date命令来查看和设置系统时间查看系统时钟的操作:# date设置系统时钟的操作:# date 091713272003.30通用的设置格式:# date 月日时分年.秒2.使用hwclock或clock命令查看和设置硬件时钟查看硬件时钟的操作:# hwclock -show 或#
32、 clock -show2003年09月17日 星期三 13时24分11秒 -0.482735 seconds设置硬件时钟的操作:# hwclock -set -date=“09/17/2003 13:26:00“尹能 linux 时间系统的设计与实现 第 15 页 共 18 页 或者# clock -set -date=“09/17/2003 13:26:00“通用的设置格式:hwclock/clock -set -date=“月/日/年 时:分:秒” 。3.同步系统时钟和硬件时钟Linux系统(使用的是Red hat Enterprise linux 5)默认重启后,硬件时钟和系统时钟同步
33、。如果不大方便重新启动的话(服务器通常很少重启),使用clock或hwclock命令来同步系统时钟和硬件时钟。硬件时钟与系统时钟同步:# hwclock -hctosys或者# clock -hctosys上面命令中,-hctosys表示Hardware Clock to SYStem clock。系统时钟和硬件时钟同步:# hwclock -systohc或者# clock -systohc还可以使用图形化系统设置工具设置时间对于初学者来,使用图形化的时钟设置工具,如Red hat Enterprise linux 5中的日期与时间设置工具,可以在虚拟终端中键“redhat-config-t
34、ime”命令,或者选择“K选单/系统设置/日期与时间”来启动日期时间设置工具。使用该工具不必考虑系统时间和硬件时间,只需从该对话框中设置日期时间,可同时设置、修改系统时钟和硬件时钟。Internet同步时钟设置在Windows XP日期与时间设置中有一项与Internet同步的功能,有了这项功能只要上网便可得到十分准确的时间。Red hat Enterprise linux 5也提供了这样的功能,在日期与时间设置工具对话框中的下部,有一个“启用网络时间协议”的选项,将该项选中就可以使用网络时间协议来同步Linux 系统时钟。选中该项后,其下面的服务器下拉列表框就变为可用状态,可从中选择一个时间
35、服务器作为远程时间服务器。然后单击确定按钮,便可连接所设定的时间服务尹能 linux 时间系统的设计与实现 第 16 页 共 18 页 器,并与之同步时间。4.2 系统测试与实现图4.1.显示当前系统时间图4.2设置系统时间并写入硬件尹能 linux 时间系统的设计与实现 第 17 页 共 18 页 5 总结通过两个星期的课程设计,使我我很大的收获.对 linux 操作系统有了更深的认识,平时使用 window 操作系统都只是用鼠标操作,而 linux 操作系统大都以命令的方式执行. 在 Linux 中有硬件时钟与系统时钟等两种时钟。硬件时钟是指主机板上的时钟设备,也就是通常可在 BIOS 画
36、面设定的时钟。系统时钟则是指 kernel 中的时钟。所有 Linux 相关指令与函数都是读取系统时钟的设定。因为存在两种不同的时钟,那么它们之间就会存在差异。根据不同参数设置,hwclock 命令既可以将硬件时钟同步到系统时钟,也可以将系统时钟同步到硬件时钟。尹能 linux 时间系统的设计与实现 第 18 页 共 18 页 6参考文献1 史芳丽,周亚莉Linux系统中虚拟文件系统内核机制研究J陕西师2 王艳春,崔洪启,等Linux虚拟文件系统分析J长春理工大学学报3 廖光忠Linux虚拟文件系统机制J计算机技术与发展,2006,16(11)4 聂希芸Linux的虚拟文件系统J玉溪师范学院学报,2006, 5 顾喜梅,顾宝根Linux虚拟文件系统实现机制研究J微机发展,2002, 6 夏煜Linux操作系统的文件系统研究D西安:西北工业大学硕士学7 杨立身,王中海Linux虚拟文件系统内核机制研究与改进策略J8 张荣亮,余敏,余文斌Linux文件系统内核机制分析与研究J计算 机与现代化,2007