永利国际网站【Linux操作系统分析】定时测量——RTC,TSC,PIT,jiffies,计时体系结构,延迟函数

System.currentTimeMillis()在java中是最常用的获取系统时间的方法,它返回的是1970年1月1日0点到现在经过的毫秒数。

在 Linux
操作系统中,很多活动都和时间有关,例如:进程调度和网络处理等等。所以说,了解
Linux 操作系统中的时钟处理机制有助于更好地了解 Linux
操作系统的运作方式。本文分析了 Linux 2.6.25
内核的时钟处理机制,首先介绍了在计算机系统中的一些硬件计时器,然后重点介绍了
Linux
操作系统中的硬件时钟和软件时钟的处理过程以及软件时钟的应用。最后对全文进行了总结。

1 基本概念

定时机制连同一些更可见的内核活动(如检查超时)来驱使进程切换。

两种主要的定时测量:

  • 保存当前的时间和日期,以便能通过time(),
    ftime()和gettimeofday()系统调用把它们返回给用户程序。
  • 维持定时器,这种机制能够告诉内核或用户程序某一时间间隔已经过去了。

定时测量是由基于固定频率振荡器和计数器的几个硬件电路完成的。

在系统性能优化的过程中,定位问题的过程发现它似乎有较大性能损耗,所以本文对System.currentTimeMillis()做性能分析。

◆1、计算机系统中的计时器

2 时钟和定时器电路

时钟电路用于跟踪当前时间和产生精确的时间度量。

定时器电路由内核编程,所以它们以udingde,预先定义的频率发出中断。

时钟电路的分类

  • 用于跟踪当前时间
    • 实时时钟RTC
    • 时间戳计数器TSC

  • 产生周期性的时钟中断,用于计时
    • 可编程间隔定时器PIT

在两个配置不同和操作系统不同的linux系统上分别单线程测试调用频率为1ms,100ms的情况,查看对cpu的性能损耗。测试环境比较干净,测试代码为简单的for循环调用。

在计算机系统中存在着许多硬件计时器,例如 Real Timer Clock RTC )、Time
Stamp Counter TSC ) 和 Programmable Interval Timer PIT ) 等等。

2.1 实时时钟RTC——IRQ8上产生中断

当PC被切断电源,RTC还继续工作。

内核通过0x70和0x71I/O端口访问RTC。

能在IRQ8上发出周期性的中断,频率在2HZ~8192HZ之间,可编程

对比结果如下

这部分内容不是本文的中点,这里仅仅简单介绍几种,更多内容参见参考文献:

2.2 时间戳计数器TSC

在80×86微处理器中,有一个CLK输入引线接收外部振荡器的时钟信号。TSC在每个时钟信号到来时加1.

TSC是一个64位的时间戳计数器寄存器,汇编指令rdtsc读这个寄存器。Linux在初始化时系统时必须确定时钟信号的频率。

永利国际网站 1

获得tsc的时钟频率:calibrate_tsc()函数通过计算一个大约在5ms的时间间隔内所产生的时钟信号的个数来算出CPU实际频率。

Linux通过rdtscll()或rdtscl()用来读取TSC的事。

与可编程间隔定时器相比,TSC可以获得更精确的时钟。

机器配置 操作系统 调用频率 CPU使用率
8核CPU Debian 2.6.26-29 1ms 2%~5%
1核VCPU Debian 2.6.32-41 1ms 2%
8核CPU Debian 2.6.26-29 100ms 1%
1核VCPU Debian 2.6.32-41 100ms 1%
  • Real Timer Clock RTC ):
    • 独立于整个计算机系统例如: CPU 和其他 chip )
    • 内核利用其获取系统当前时间和日期
  • Time Stamp Counter TSC ):
    • 从 Pentium 起,提供一个寄存器
      TSC,用来累计每一次外部振荡器产生的时钟信号
    • 通过指令 rdtsc 访问这个寄存器
    • 比起 PIT,TSC 可以提供更精确的时间测量
  • Programmable Interval Timer PIT ):
    • 时间测量设备
    • 内核使用的产生时钟中断的设备,产生的时钟中断依赖于硬件的体系结构,慢的为
      10 ms 一次,快的为 1 ms 一次
  • High Precision Event Timer HPET ):
    • PIT 和 RTC 的替代者,和之前的计时器相比,HPET
      提供了更高的时钟频率至少10 MHz )以及更宽的计数器宽度64位)
    • 一个 HPET
      包括了一个固定频率的数值增加的计数器以及3到32个独立的计时器,这每一个计时器有包涵了一个比较器和一个寄存器保存一个数值,表示触发中断的时机)。每一个比较器都比较计数器中的数值和寄存器中的数值,当这两个数值相等时,将产生一个中断

2.3 可编程间隔定时器PIT

使用I/O端口0x40~0x43

LInux给PC的第一个PIT进行编程,使它以大于1000Hz的频率向IRQ0发出时钟中断,即每1ms产生一次时钟中断,这个时间间隔叫做一个节拍(tick),它的长度以纳秒为单位存放在tick_nsec变量中。

永利国际网站 2

由setup_pit_timer()进行初始化。在init_pit_timer()中初始化时钟中断频率。

与系统时钟信号有关的宏定义:

(1)宏定义Hz

在不同的体系机构下,系统时钟所要求的可编程定时器中断的频率,即每秒tick的个数

(2)宏定义CLOCK_TICK_RATE

记录了不同体系结构下,驱动可编程定时器工作的输入时钟频率

(3)宏定义LATCH

记录了上述两个宏定义的比值,用于在内核初始化过程中设置可编程定时器中计数器寄存器counter的初始值。

数据说明:

Linux
操作系统中,很多活动都和时间有关,例如:进程调度和网络处理等等。所以说,了解
Linux 操作系统中的时钟处理机制有助于更好…

3 Linux计时体系结构

LInux的计时体系结构是一组与时间流相关的内核数据结构和函数。

功能:

  • 更新自系统启动以来所经过的时间
  • 更新时间和日期
  • 确定当前进程的执行时间,考虑是否要抢占
  • 更新资源使用统计计数
  • 检查到期的软定时器

内核有两个基本的计时函数:

  • 保持当前最新的时间
  • 计算在当前秒内走过的纳秒数

在单处理器系统中,所有定时活动都由IRQ0上的时钟中断触发,包括:

  • 在中断中立即执行的部分
  • 作为下半部分延迟执行的部分
  1. 极端情况下1ms调用1次,8核CPU消耗点大概在2~5%左右。

  2. 调用频率为100ms时,CPU基本在1%左右。

  3. 数据说明单单一个System.currentTimeMillis()高频率调用还是有一定CPU消耗的。对一个毫秒级的接口来说这个性能损耗不算小。

3.1 计时体系结构的数据结构

所以在高并发的接口中还是应该尽量避免高频调用。

3.1.1定时器对象(时钟源)

为了使用一种统一的方法来处理可能存在的定时器资源,内核使用能够了“定时器对象”,它是timer_opts类型的一个描述符。其中最重要的两个方法:

mark_offset:由时钟中断处理程序调用,并以适当的数据结构记录每个节拍到来时的准确时间。

get_offset:使用已记录的值来计算上一次时钟中断(节拍)以来经过的时间。

这两种方法,使得Linuxd计时体系结构能够打到子节拍的分辨率,也就是说,内核能够以比节拍周期更高的精度来测定当前的时间,这种操作被称为“定时插补”。

在内核初始化期间,select_timer()函数设置cur_timer指向适当定时器对象(时钟源)的地址。变量timer_cur存放了某个定时器对应的那个的地址,该定时器是系统可利用的定时器资源中最好的。

永利国际网站 3

针对System.currentTimeMillis()性能不好的原因分析,有一篇很好的文章The
slow
currentTimeMillis(),它直接从系统级、源码、汇编语言各个层次全方位的分析。

3.1.2jiffies变量

一个计数器,用来记录自系统启动以来产生的节拍总数。

因为一秒钟内产生系统时钟中断次数等于宏定义HZ的值,所以变量jiffies的值在一秒内增加HZ。

从The slow
currentTimeMillis()中我们了解到,执行速度缓慢currentTimeMillis()是由两个因素造成的:

3.1.3xtime变量

xtime变量存放当前时间和日期,它是一个timespec类型的数据结构。以便内核对某些对象和事件作时间标记,如记录文件的创建时间、修改时间、上次访问时间,或者供用户进程通过系统调用来使用。

基本每个tick更新一次。

  • JVM使用gettimeofday()而不是clock_gettime()
  • gettimeofday() 如果使用HPET时间源,则速度非常慢。

3.2 单处理器系统上的计时体系结构

考点:tick_handle_periodic函数的功能(Linux的计时体系结构的功能)

永利国际网站 4

tick_init调用clockevents_tegister_notifier注册tick_notifier到clockevents_chain上。

Update_wall_time()完成变量xtime的更新。

time_init_hook()来设置系统时钟中断处理程序。

在时钟中断处理函数中:

会调用tick_init函数,书上很多流程中的函数最终都是被这个函数所调用,流程如下:

永利国际网站 5

但是,HPET现在不是唯一的时间源。最常见的时间源且许多系统使用的是TSC。在我们的项目中,服务器配置了HPET时间源,原因在于:此时间源与NTP客户端完美集成,可以平滑调整时间,而TSC不太稳定(我不知道细节;这是本地Linux大师所说的,我别无选择,只能相信他们)。其他一些开发人员可能会遇到同样的情况。此外,Java开发人员无法知道程序将在何种时间运行。

4 软定时器和延迟函数

软定时器:

  • 动态定时器(内核)
  • 间隔定时器(可以用户)

动态定时器:被动态的创建和撤销,当前活动的动态定时器个数没有限制

定时器是一种软件功能,即允许在将来的某个时刻,函数在给定的时间间隔用完时被调用。每个定时器都包含一个字段,表示定时器将需要多长时间才到期。这个字段的初值就是jiffies的当前值加上合适的节拍数。

注意,对于必须严格遵守定时时间的那些实时应用而言,定时器并不适合,因为定时器的检查总是由可延迟函数进行。

永利国际网站 6

然而,如果我们使用TSC时间源,那么了解结果如何改变仍然很有趣。TSC代表时间戳记计数器,它仅仅是自启动以来计算的CPU周期数(它只有64位宽,因此它将在2.4GHz时钟频率下在243年内回绕)。该值可以使用rdtsc指令读取。传统上,这个值有两个问题:

4.1创建并激活一个动态定时器——init_timer初始化一个time_list对象

  • 创建一个新的timer_list对象
  • 调用init_timer初始化,并设置定时器要处理的函数和参数
  • 设置定时时间
  • 使用add_timer加入到合适的链表中

具体的步骤:

永利国际网站 7

  • 来自不同内核或物理处理器的值可能相互移位,因为处理器可能在不同的时间开始
  • 处理器的时钟频率可能会在执行期间发生变化。

4.2动态定时器的数据结构

用于和系统核心变量jiffies进行比较。

  • 成员变量function:该函数指针变量保存了内核定时器超时后要执行的函数,即定时器超时处理函数。
  • 成员变量data:该无符号长整型变量用作定时器超时处理函数的参数。
  • 成员变量base:该指针变量表明了该内核定时器节点归属于系统中哪一个处理器,在使用函数init_timer()初始化内核定时器节点的过程中,将该指针指向了一个每处理器变量tvec_bases的成员变量t_base。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website