<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="http://gentoo-zh.org/extern.php?action=feed&amp;tid=835&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo中文社区 / Gentoo 之 time subsystem 概述]]></title>
		<link>http://www.gentoo-zh.org/viewtopic.php?id=835</link>
		<description><![CDATA[Gentoo 之 time subsystem 概述 最近发表的帖子。]]></description>
		<lastBuildDate>Sun, 17 Mar 2024 13:29:59 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[Gentoo 之 time subsystem 概述]]></title>
			<link>http://www.gentoo-zh.org/viewtopic.php?pid=953#p953</link>
			<description><![CDATA[<p>Linux的计时系统(Timekeeping)在ULK中有专门一章来讲，但ULK第三版是基于2.6.10的，TimeKeeping部分已经有了很大的变化，如NOHZ mode和hrtimer，在ULK中都没有涉及，本文希望能对现在的kernel的Timekeeping部分做较完整的讲述。</p><p>从2.6.10到2.6.38，Timekeeping部分的变动可以参考&lt;Hrtimers and Beyond: Transforming the Linux Time Subsystems&gt;这篇文章，里面比较详细的讲述了2.6.16之前版本的Timekeeping的不足，以及日后的改进，到2.6.38，文章里提到的改进大多都已经merge到kernel里。</p><p>如ULK所说，Timekeeping主要完成以下工作：<br />*更新自系统启动以来所经过的时间<br />*更新系统的时间和日期<br />*衡量进程的运行时间以及为进程分配时间片<br />*更新资源使用统计数<br />*检查软定时器是否到时</p><p>2.6.10 kernel的Timekeeping的结构图如下： <br /><span class="postimg"><img src="http://blog.chinaunix.net/attachment/201203/7/25871104_1331136119238G.png" alt="FluxBB bbcode 测试" /></span> </p><br /><p> 虚线右侧是硬件部分，不同Arch提供不一样的硬件，左侧是kernel。<br />为了完成Timekeeping的功能，硬件需要提供至少一种Clock source和Clock event source。Clock source只是一个简单的Counter，使能之后以固定的频率递增，kernel通过主动读取Counter可以判断时间的增加值，Clock event source是一个硬件Timer，设定后，可以以固定的频率产生中断，kernel可以在中断服务程序里完成上面所说的工作。理论上来说，只有Timer也可以维持系统的运行，但所有与时间相关的精度就仅限于Timer的产生频率。而Timer的中断频率一般不会很高，桌面系统常用100HZ~1000HZ，所以现在的系统Timer和Count都是必不可少的。</p><p>x86提供了多种Clock source和Clock event source。<br />Clock source有tsc, hpet, acpi_pm，clock event source有pit, hpet, local apic timer, acpi_pm timer等。这些硬件在ULK上均有描述。假如系统中存在多种Clock source和clock event source，kernel会以一定的规则来选择。<br />对于clock source，稳定且分辨率更高的会被选用，当然通过/sys/devices/system/clocksource/clocksource0/available_clocksouce可以手动选择clock source；<br />对于clock event source，稍微复杂一些，在uniprocessor系统中，优先选择支持One-shot模式且分辨率更高的，在SMP系统中，存在global event device和local event device。这两者的区别在ULK中有讲，global event device主要负责soft timer的处理，local event device负责与本CPU相关的工作，如CPU上进程执行时间的检查以及后面要说的hrtimer等。local event device除了优先选择oneshot模式和分辨率更高的之外，还要求device支持设置中断亲和力，因为local event device产生的中断，其他CPU是不能响应的。因此 lapic timer是local event device的最佳人选；local event device一般选择hpet或者pit(没有hpet的情况下)。<br />在我的机器上，global device是hpet，local device是lapic timer。</p><p>除了上面说的硬件之外，还有一个RTC，主要是为了保存实时时钟，但他也提供了timer的功能，只不过kernel很少用，定时开机会用到。</p><p>上述硬件的lapic产生的中断的中断号为0xef(LOC)，RTC的中断号为0x8，global event device的中断号为0，即我们常说的timer中断。</p><p>Timer的中断在kernel即对应与jiffies，每次Timer中断，jiffies就增加1，为防止jiffies在短时间溢出，还有一个64位的jiffies_64，二者共用低32位。<br />kernel每收到一次Timer中断，就会将jiffies加1，并读取Clock source的值，通过HZ与s/ms/ns之间的关系计算系统的当前时间，写入到xtime和wall_to_monotonic中；检查进程的时间片是否用完，并为用完时间片的进程分配新的时间片；处理统计工作(Profiling)；并在退出中断的时候检查soft timer是否到时。</p><p>soft timer采用的是时间轮模型，从上面的图可以知道，它是基于jiffies的，换句话说，他将jiffies作为其时钟源，当jiffies发生变化时，soft timer就会被处理，也就是global event device产生中断后。但对soft timer的处理并不是在timer 中断中进行的，而是在退出timer中断是的exit_irq()函数，该函数会检查softirq，soft timer就是以softirq的形式实现的。run_timer_softirq()函数执行具体的处理过程。</p><p>应该说，2.6.16之前的timekeeping是比较简单的，但弊端也较多。主要体现在一下几个方面：<br />1，缺乏必要的抽象层。代码与硬件结合紧密，不同架构下往往需要实现很多重复的代码，比如对clock source和clock event的管理<br />2，周期性的timer中断导致CPU不能进入sleep mode<br />3，soft timer的精度不够，其基于jiffies，最多只能提供jiffies的精度，在这样的timer模型下，要提高精度，只能提高HZ数，但这会给系统带来额外的负担</p><p>因此，2.6.16之后的kerel加入很多修改，主要包括：<br />*clock source和clock event source抽象层<br />*NOHZ mode<br />*hrtimer</p><p>clock source和clock event source抽象层将clock source device和clock event device的硬件进行抽象并统一管理，Arch相关的代码只需要负责注册这两类device即可；<br />NOHZ mode则抛弃了以前periodic的方式(周期性的产生Timer中断)，使用硬件timer的oneshot模式，即配置一次，产生一次中断，这样就可以在系统空闲的时候，将硬件timer的下一次到期时间设置得较长，让CPU能够较长时间的进入sleep mode，从而达到省电和降温的目的；<br />hrtimer在传统的time wheel的模式外，新建立了一套timer机制，叫做hrtimers，即高精度定时器。hrtimer不再基于jiffies(Tick)，最高可以达到ns的精度(需要硬件的支持)<br />新的timekeeping架构如下图： </p><p><span class="postimg"><img src="http://blog.chinaunix.net/attachment/201203/7/25871104_13311361393UtE.png" alt="FluxBB bbcode 测试" /></span> </p><p> 在新的架构之下，硬件被抽象成为clock source device和clock event device，时钟中断被抽象称为clock event，event还以分发，不同的event device在收到event之后，会去执行其所属的event_handler，而event_handler是回调函数，由tick模块根据不同的mode进行填充。</p><p>tick模块中继续对event device进行包装，产生tick device的概念，由于NOHZ和hrtimer的引入，event device可以有两种工作模式：periodic，oneshot。具有工作模式属性的event device即为tick device。事实上，tick device仅包含event device和mode两个成员。<br />对应于event device的两种模式，tick模块实现了三种模式的tick，PERIODIC_MODE, NOHZ_MODE_LOWRES, NOHZ_MODE_HIGHRES。<br />在kernel的config中，有几个选项来控制这些模式的选择：<br />CONFIG_NO_HZ, CONFIG_HIGH_RES_TIMERS<br />当CONFIG_NO_HZ=yes，CONFIG_HIGH_RES_TIMERS=no，使用NOHZ_MODE_LOWRES模式<br />当CONFIG_HIGH_RES_TIMERS=yes，使用NOHZ_MODE_HIGHRES模式<br />当CONFIG_NO_HZ=no, CONFIG_HIGH_RES_TIMERS=no，使用PERIODIC_MODE</p><p>PERIODIC_MODE下的tick周期性的产生，频率为HZ的值，硬件timer也配置为周期性的产生中断；<br />NOHZ_MODE_LOWRES模式下，tick的产生不是周期性的，而根据当前系统的负载来决定下一次tick的产生时间，对于硬件来说就是使用ONESHOT mode，在每次timer产生时决定下一次中断产生的时间，再将计算得到的值写入到硬件timer的寄存器中；<br />NOHZ_MODE_HIGHRES模式跟NOHZ_MODE_LOWRES模式类似，但是NOHZ_MODE_LOWRES模式决定下一次tick产生的时间还是以tick_period为单位的，其精度并没有提升，而NOHZ_MODE_HIGHRES模式则通过判断最近到时的hrtimer的到期值来决定下一次tick产生的时间，因此精度可以达到ns。<br />因此高精度的hrtimer也只能在NOHZ_MODE_HIGHRES模式下使用。</p><p>从实现上来说，这三个模式的主要差别就是event device的event_handler是不同的，PERIODIC_MODE的event_handler是tick_handle_periodic()，NOHZ_MODE_LOWRES的event_handler是tick_nohz_handler()，NOHZ_MODE_HIGHRES的event_handler是hrtimer_interrupt()。</p><p>kernel在初始化时会首先进入PERIODIC_MODE，然后在event_handler中根据kernel的CONFIG和硬件是否支持决定是否进入NOHZ_MODE_LOWRES和NOHZ_MODE_HIGHRES。</p><p>前面说过基于时间轮的soft timer精度只能到达tick的精度，而hrtimer的精度则可以达到ns，其原因就在于hrtimer只能在NOHZ_MODE_HIGHRES模式下工作，而此模式下，下一次tick的产生是根据本地CPU中最早到期的hrtimer的到期时间决定的，因此下一次tick的产生精度也就能达到ns，而在下一次tick的event_handler中，hrtimer就会被处理。<br />时间轮的timer是在softirq中处理的，而hrtimer是在timer的中断处理函数中处理的。</p>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Sun, 17 Mar 2024 13:29:59 +0000</pubDate>
			<guid>http://www.gentoo-zh.org/viewtopic.php?pid=953#p953</guid>
		</item>
	</channel>
</rss>
