Linux 中的 时钟和定时测量
Linux Operating Systems Analysis2 举例
Operating Systems Analysis3 定时测量 Linux 内核提供两种主要的定时测量 获得当前的时间和日期 系统调用: time(), ftime() 以及 gettimeofday() 维持定时器 settimer(), alarm() 定时测量是由基于固定频率振荡器和计数器的 几个硬件电路完成的
Operating Systems Analysis4 主要内容 定时的硬件设备 Linux 内核中与时间有关的程序 实现 CPU 分时、更新系统时间、维护软定时器 与定时测量相关的系统调用及相关服务例程
Operating Systems Analysis5 硬时钟 80x86 体系结构上,内核必须显式的与四种时 钟打交道 实时时钟 Real time clock , RTC 时间戳计数器 Time stamp counter , TSC 可编程间隔定时器 Programmable interval timer, PIT SMP 系统上的本地 APIC 定时器 用于跟踪 当前时间 产生周期性的时钟中断, 用于计时
Operating Systems Analysis6 实时时钟 RTC 基本上所有的 PC 都包含实时时钟 独立于 CPU 与所有其他芯片 依靠一个独立的小电池供电给 RTC 中的振荡器 即使关闭 PC 电源,还会继续运转 与 CMOS RAM 往往集成在一个芯片内 例如: Motorala 能在 IRQ8 上发出周期性的中断,频率在 2HZ~8192 之间 可以对其编程实现一个闹钟
Operating Systems Analysis7 Linux 本身只使用 RTC 获得时间和日期 对应的设备文件为 /dev/rtc 可以通过设备文件对其编程 内核通过 0x70 和 0x71 两个端口访问 RTC 系统管理员可以通过执行时钟程序设置时钟
Operating Systems Analysis8 时间戳计数器 TSC 在 80x86 微处理器中,有一个 CLK 输入引线 接收外部振荡器的时钟信号 从 pentium 开始,很多 80x86 微处理器都引入 了一个 TSC 一个 64 位的、用作时间戳计数器的寄存器 它在每个时钟信号( CLK )到来时 +1 例如时钟频率 400MHz 的微处理器, TSC 每 2.5ns 就 +1 rdtsc 指令用于读该寄存器
Operating Systems Analysis9 与后面介绍的可编程间隔定时器相比, TSC 可 以获得更精确的时钟 为此, Linux 在系统初始化的时候必须确定时钟信 号 CLK 的频率(即 CPU 的实际频率) calibrate_tsc 根据在一个相对较长的时间间隔内(约 50ms )所发生 的 TSC 计数的个数进行计算 那个间隔由可编程间隔定时器给出 由于只在系统初始化的时候运行一次,因此本程序可以 执行较长时间,而不会引起问题
Operating Systems Analysis10 可编程间隔定时器 PIT 经过适当编程后,可以周期性的给出时钟中断 通常是 8254 CMOS 芯片 使用 I/O 端口 0x40~0x43 Linux 将 PIT 编程为: 100Hz 通过 IRQ0 发出时钟中断 每 10ms 产生一次时钟中断,即一个 tick
Operating Systems Analysis11 Tick 的长短 短 优点:分辨率高 缺点:需要较多的 CPU 时间处理,会导致用户程序 运行变慢 适用于非常强大的机器,这种机器能够承担较大的 系统开销 Tick 的设置是一个折中,例如 在大多数惠普的 Alpha 和 Intel 的 IA-64 上约 1ms 产生 一个 tick (每秒 1024 个时钟中断) Rawhide Alpha 工作站采用更高( 1200tick/ 秒)
Operating Systems Analysis12 在 Linux 中,下列宏决定时钟中断频率 每秒钟时钟中断的个数,即每秒 tick 的个数 8254 芯片的内部振荡器频率,每秒多少次 对 8254 分频,获得 HZ 所需的时钟
Operating Systems Analysis13 在 init_IRQ() 中初始化时钟中断频率 init_IRQ 此后,只要允许处理时钟中断,约每 10ms 就会产生一个时钟中断 1tick 约为 10ms
Operating Systems Analysis14 如何计算 CPU 的时钟频率 CLK Linux 在初始化的时候,利用可编程间隔定时 器获得 CPU 的频率 观察 calibrate_tsc() ,了解如何计算 CPU 的频 率 calibrate_tsc 已知: PIT 的频率 未知: CLK 频率 方法:统计在 PIT 已知的一段时间内( 50ms ), CLK 发生了多少次;然后计算出 CLK 频率(次数 /50ms )
Operating Systems Analysis15 Linux 的计时体系结构 Linux 要周期性的执行一些任务,例如 更新系统自启动以来所经过的时间 更新时间和日期 确定进程运行了多久 检查每个软定时器是否已经到期
Operating Systems Analysis16 在单处理器系统中,所有定时活动都由 IRQ0 上的时钟中断触发,包括 在中断中立即执行的部分,和 作为下半部分延迟执行的部分
Operating Systems Analysis17 PIT 的时钟中断处理例程 Linux 初始化时由 time_init() 建立 IRQ0 对应的中 断处理函数 time_init 将 irq0 作为 irq_desc 的第一项 的中断处理函数
Operating Systems Analysis18 如果有 TSC ,那么就得到时钟中断处理延迟, 以给用户提供更精确的时钟 该函数会调用 do_timer 进一步处理
Operating Systems Analysis19 do_timer 全局变量,存放自系统启动 以来的时钟节拍数 32 位 约 497 天会溢出(回归为 0 ) 检查当前进程对时间片的使用 情况 激活下半部分 如果 tq_timer 非空,还要激活相关的下半部分处理
Operating Systems Analysis20 update_process_times 更新时间片 视需要进行调度 统计当前进程对 CPU 时间的使用 情况
Operating Systems Analysis21 TIMER_BH 下半部分 当时钟中断处理例程运行结束并返回时,会立 即处理下半部分 更新系统日期和时间,计算当前的系统负载 维护软定时器处理
Operating Systems Analysis22 更新时间和日期 用户程序从下面这个变量中获得当前时间和日 期 存放从 1970 年 1 月 1 日凌晨 0 点 以来经过的所有秒数 最后一秒已经过去的微秒数 取值范围: 0~999999
Operating Systems Analysis23 系统初始化时, time_init() 初始化时间和日期 time_init 观察 get_cmos_time() get_cmos_time 获得 coms 时间 一旦完成, Linux 不再需要 RTC , 依靠下半部分维护 xtime
Operating Systems Analysis24 更新 xtime 上一次 xtime 更新后的 jiffies
Operating Systems Analysis25 软定时器 定时器是一种软件功能,它允许在将来的某个 时刻调用某个函数 大多数设备驱动程序利用定时器完成一些特殊 工作 软盘驱动程序在软盘暂时不被访问时就关闭设备的 发动机 并行打印机利用定时器检测错误的打印机情况
Operating Systems Analysis26 Linux 中存在两类定时器: 动态定时器 内核使用 间隔定时器 由进程在用户态创建 注意:由于软定时器在下半部分处理,内核不能保 证定时器正好在时钟到期的时候被执行,会存在延 迟,不适用于实时应用
Operating Systems Analysis27 动态定时器 动态定时器被动态的创建和撤销,当前活动的 动态定时器个数没有限制 数据结构: 系统使用 512 个双向链表维护动态定时器 定时器到期时要执行的函数 函数使用的参数 到期时间
Operating Systems Analysis28 创建并激活一个动态定时器 创建一个新的 timer_list 对象 调用 init_timer 初始化,并设置定时器要处理的 函数和参数 设置定时时间 使用 add_timer 加入到合适的链表中 通常定时器只能执行一次,如果要周期性的执 行,必须再次将其加入链表
Operating Systems Analysis29 动态定时器的处理 为提高处理动态定时器的效率,必须给定时器 排序,并使用合适的数据结构 Linux 根据 expires 的值,维护这样的数据结构
Operating Systems Analysis30 =64, 64 个双向链表,包含了未来某个时间段内的 动态定时器 index 指向当前应当用来更新上一级定时器的链表 ( =256 ), 256 个双向链表,每个表示对应 时钟到期时的动态定时器链表 Index 表示当前节拍对应的那个链表 未来 个节拍内的定时器 每 256 个节拍内的定时器为 1 个链表 共 64 个 未来 个节拍内的定时器 每 2 14 个节拍内的定时器为 1 个链表 共 64 个 未来 个节拍内的定时器 每 2 20 个节拍内的定时器为 1 个链表 共 64 个 未来 个节拍内的定时器 每 2 26 个节拍内的定时器为 1 个链表 共 64 个 一点点不同:最后一个链表中的 定时器的时间可以任意大
Operating Systems Analysis31 run_timer_list 下半部分 timer_bh() 调用 run_timer_list() 检查到 期的动态定时器,包括: 执行动态定时器 更新链表 观察 run_timer_list() run_timer_list
Operating Systems Analysis32 动态定时器的应用 使用 schedule_timeout() 可以使进程被延迟 (睡眠一段时间) 观察 schedule_timeout() 并看一个内核应用实 例 schedule_timeout
Operating Systems Analysis33 与定时测量相关的系统调用 time() 返回从 1970 年 1 月 1 日凌晨 0 点开始的秒数 ftime() 返回从 1970 年 1 月 1 日凌晨 0 点开始的秒数以及最后 一秒的毫秒数 数据结构为 timeb gettimeofday() 返回从 1970 年 1 月 1 日凌晨 0 点开始的秒数 对应于 sys_gettimeofday()
Operating Systems Analysis34 settimer() 间隔定时器 频率:周期性的触发定时器(若为 0 ,只触发一次) alarm() 引起 SIGALARM 信号
Operating Systems Analysis35 与时钟相关的命令 date :显示或者更改系统时钟 使用 time 获得时钟 使用 ctime 改变时钟格式
Operating Systems Analysis36 Project 5 在用户态编写一个程序,该程序设定一个定时 器,在时间到期的时候做出某种可观察的响应 方法不限 分析你的程序的实际执行借助了内核的哪些机 制