<ins id='dd7te'></ins>

      <acronym id='dd7te'><em id='dd7te'></em><td id='dd7te'><div id='dd7te'></div></td></acronym><address id='dd7te'><big id='dd7te'><big id='dd7te'></big><legend id='dd7te'></legend></big></address>

      1. <tr id='dd7te'><strong id='dd7te'></strong><small id='dd7te'></small><button id='dd7te'></button><li id='dd7te'><noscript id='dd7te'><big id='dd7te'></big><dt id='dd7te'></dt></noscript></li></tr><ol id='dd7te'><table id='dd7te'><blockquote id='dd7te'><tbody id='dd7te'></tbody></blockquote></table></ol><u id='dd7te'></u><kbd id='dd7te'><kbd id='dd7te'></kbd></kbd>
          <i id='dd7te'><div id='dd7te'><ins id='dd7te'></ins></div></i>
          <span id='dd7te'></span>
          <i id='dd7te'></i>
          <fieldset id='dd7te'></fieldset>

          <code id='dd7te'><strong id='dd7te'></strong></code>
          <dl id='dd7te'></dl>

        1. Linux 时钟管理

          • 时间:
          • 浏览:8
          • 来源:124软件资讯网
            Linux 中的准时器

              在 Linux 内核中主要有两种类型的准时器  。一类称为 timeout 类型  ,另一类称为 timer 类型  。timeout 类型的准时器通常用于检测种种错误条件 ,例如用于检测网卡收发数据包是否会超时的准时器 ,IO 装备的读写是否会超时的准时器等等  。通常情形下这些错误很少发生  ,因此  ,使用 timeout 类型的准时器一样平常在超时之前就会被移除 ,从而很少发生真正的函数挪用和系统开销  。总的来说  ,使用 timeout 类型的准时器发生的系统开销很小  ,它是下文提及的 timer wheel 通常使用的情况  。此外  ,在使用 timeout 类型准时器的地方往往并不体贴超时处置惩罚  ,因此超时准确与否  ,早 0.01 秒或者晚 0.01 秒并不十分主要 ,这在下文叙述 deferrable timers 时会进一步先容 。timer 类型的准时器与 timeout 类型的准时器正相反 ,使用 timer 类型的准时器往往要求在准确的时钟条件下完成特定的事务 ,通常是周期性的而且依赖超时机制举行处置惩罚 。例如装备驱动通常会准时读写装备来举行数据交互  。怎样高效的治理 timer 类型的准时器对提高系统的处置惩罚效率十分主要  ,下文在先容 hrtimer 时会有越发详细的叙述  。

              内核需要举行时钟治理 ,离不开底层的硬件支持 。在早期是通过 8253 芯片提供的 PIT(Programmable Interval Timer)来提供时钟  ,可是 PIT 的频率很低  ,只能提供最高 1ms 的时钟精度  ,由于 PIT 触发的中止速率太慢  ,会导致很大的时延  ,对于像音视频这类对时间精度要求更高的应用并不足够  ,会极大的影响用户体验  。随着硬件平台的不停生长转变  ,陆续泛起了 TSC(Time Stamp Counter) ,HPET(High Precision Event Timer)  ,ACPI PM Timer(ACPI Power Management Timer)  ,CPU Local APIC Timer 等精度更高的时钟 。这些时钟陆续被 Linux 的时钟子系统所采取 ,从而不停的提高 Linux 时钟子系统的性能和天真性  。这些差别的时钟会在下文差别的章节中划分举行先容 。

              Timer wheel

              在 Linux 2.6.16 之前  ,内核一直使用一种称为 timer wheel 的机制来治理时钟  。这就是熟知的 kernel 一直接纳的基于 HZ 的 timer 机制  。Timer wheel 的焦点数据结构如清单 1 所示:

            清单 1. Timer wheel 的焦点数据结构

             #define TVN_BITS (CONFIG_BASE_SMALL ? 4 : 6) 
             #define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8) 
             #define TVN_SIZE (1 << TVN_BITS) 
             #define TVR_SIZE (1 << TVR_BITS) 
             #define TVN_MASK (TVN_SIZE - 1) 
             #define TVR_MASK (TVR_SIZE - 1) 
             
             struct tvec { 
                struct list_head vec[TVN_SIZE]; 
             }; 
             
             struct tvec_root { 
                struct list_head vec[TVR_SIZE]; 
             }; 
             
             struct tvec_base { 
                spinlock_t lock; 
                struct timer_list *running_timer; 
                unsigned long timer_jiffies; 
                unsigned long next_timer; 
                struct tvec_root tv1; 
                struct tvec tv2; 
                struct tvec tv3; 
                struct tvec tv4; 
                struct tvec tv5; 
             } ____cacheline_aligned; 

              以 CONFIG_BASE_SMALL 界说为 0 为例  ,TVR_SIZE = 256 ,TVN_SIZE = 64  ,这样

              可以获得如图 1 所示的 timer wheel 的结构 。

            图 1. Timer wheel 的逻辑结构
            Linux 时钟管理

              list_head的作用
            list_head 是 Linux 内核使用的一个双向循环链表表头  。任何一个需要使用链表的数据结构可以通过内嵌 list_head 的方式 ,将其链接在一起从而形成一个双向链表 。参见 list_head 在 include/Linux/list.h 中的界说和实现  。

              在 timer wheel 的框架下  ,所有系统正在使用的 timer 并不是顺序存放在一个平展的链表中  ,由于这样做会使得查找 ,插入  ,删除等操作效率低下  。Timer wheel 提供了 5 个 timer 数组  ,数组之间存在着类似时分秒的进位关系  。TV1 为第一个 timer 数组  ,其中存放着从 timer_jiffies(当前到期的 jiffies)到 timer_jiffies + 255 共 256 个 tick 对应的 timer list  。由于在一个 tick 上可能同时有多个 timer 等候超时处置惩罚  ,timer wheel 使用 list_head 将所有 timer 串成一个链表 ,以便在超时时顺序处置惩罚  。TV2 有 64 个单元  ,每个单元都对应着 256 个 tick  ,因此 TV2 所表现的超时时间规模从 timer_jiffies + 256 到 timer_jiffies + 256 * 64 – 1 。依次类推 TV3  ,TV4  ,TV5  。以 HZ=1000 为例  ,每 1ms 发生一次中止  ,TV1 就会被会见一次  ,可是 TV2 要每 256ms 才会被会见一次 ,TV3 要 16s ,TV4 要 17 分钟  ,TV5 甚至要 19 小时才有时机检查一次  。最终  ,timer wheel 可以治理的最大超时值为 2^32  。一共使用了 512 个 list_head(256+64+64+64+64)  。若是 CONFIG_BASE_SMALL 界说为 1  ,则最终使用的 list_head 个数为 128 个(64+16+16+16+16)  ,占用的内存更少  ,更适合嵌入式系统使用  。Timer wheel 的处置惩罚逻辑如清单 2 所示:

            清单 2. timer wheel 的焦点处置惩罚函数

             static inline void __run_timers(struct tvec_base *base) 
             { 
              struct timer_list *timer; 
             
              spin_lock_irq(&base->lock); 
              while (time_after_eq(jiffies, base->timer_jiffies)) { 
                struct list_head work_list; 
                struct list_head *head = &work_list; 
                int index = base->timer_jiffies & TVR_MASK; 
             
                 
                if (!index && 
                  (!cascade(base, &base->tv2, INDEX(0))) && 
                    (!cascade(base, &base->tv3, INDEX(1))) && 
                      !cascade(base, &base->tv4, INDEX(2))) 
                  cascade(base, &base->tv5, INDEX(3)); 
                ++base->timer_jiffies; 
                list_replace_init(base->tv1.vec + index, &work_list); 
                while (!list_empty(head)) { 
                  void (*fn)(unsigned long); 
                  unsigned long data; 
             
                  timer = list_first_entry(head, struct timer_list,entry); 
                  fn = timer->function; 
                  data = timer->data; 
                  . . . . 
                  fn(data); 
              . . . . 
             } 

              base->timer_jiffies 用来记载在 TV1 中最靠近超时的 tick 的位置  。index 是用来遍历 TV1 的索引 。每一次循环 index 会定位一个当前待处置惩罚的 tick  ,并处置惩罚这个 tick 下所有超时的 timer  。base->timer_jiffies 会在每次循环后增添一个 jiffy ,index 也会随之向前移动  。当 index 变为 0 时表现 TV1 完成了一次完整的遍历  ,此时所有在 TV1 中的 timer 都被处置惩罚了  ,因此需要通过 cascade 将后面 TV2  ,TV3 等 timer list 中的 timer 向前移动 ,类似于进位  。这种层叠的 timer list 实现机制可以大大降低每次检查超时 timer 的时间  ,每次中止只需要针对 TV1 举行检查  ,只有须要时才举行 cascade  。即便云云  ,timer wheel 的实现机制仍然存在很大毛病 。一个毛病就是 cascade 开销过大  。在极端的条件下  ,同时会有多个 TV 需要举行 cascade 处置惩罚  ,会发生很大的时延 。这也是为什么说 timeout 类型的准时器是 timer wheel 的主要应用情况  ,或者说 timer wheel 是为 timeout 类型的准时器优化的  。由于 timeout 类型的准时器的应用场景多是错误条件的检测  ,这类错误发生的机率很小  ,通常不到超时就被删除了  ,因此不会发生 cascade 的开销  。另一方面  ,由于 timer wheel 是建设在 HZ 的基础上的  ,因此其计时精度无法进一步提高  。究竟一味的通过提高 HZ 值来提高计时精度并无意义  ,效果只能是发生大量的准时中止  ,增添分外的系统开销  。因此  ,有须要将高精度的 timer 与低精度的 timer 离开  ,这样既可以确保低精度的 timeout 类型的准时器应用  ,也便于高精度的 timer 类型准时器的应用 。另有一个主要的因素是 timer wheel 的实现与 jiffies 的耦合性太强  ,很是未便于扩展  。因此  ,自从 2.6.16 最先  ,一个新的 timer 子系统 hrtimer 被加入到内核中  。

              hrtimer (High-resolution Timer)

              hrtimer 首先要实现的功效就是要战胜 timer wheel 的弱点:低精度以及与内核其他模块的高耦合性  。在正式先容 hrtimer 之前 ,有须要先先容几个常用的基本观点:

              时钟源装备(clock-source device)

              系统中可以提供一定精度的计时装备都可以作为时钟源装备  。如 TSC  ,HPET  ,ACPI PM-Timer  ,PIT 等 。可是差别的时钟源提供的时钟精度是纷歧样的  。像 TSC ,HPET 等时钟源既支持高精度模式(high-resolution mode)也支持低精度模式(low-resolution mode)  ,而 PIT 只能支持低精度模式  。此外 ,时钟源的计时都是单调递增的(monotonically)  ,若是时钟源的计时泛起翻转(即返回到 0 值)  ,很容易造成计时错误  , 内核的一个 patch(commit id: ff69f2)就是处置惩罚这类问题的一个很好示例  。时钟源作为系统时钟的提供者  ,在可靠而且可用的条件下精度越高越好  。在 Linux 中差别的时钟源有差别的 rating  ,具有更高 rating 的时钟源会优先被系统使用  。如图 2 所示:

            表 1. 时钟源中 rating 的界说

            1 ~ 99 100 ~ 199 200 ~ 299 300 ~ 399 400 ~ 499
            很是差的时钟源  ,只能作为最后的选择 。如 jiffies 基本可以使用但并非理想的时钟源 。如 PIT 准确可用的时钟源  。如 ACPI PM Timer  ,HPET 快速而且准确的时钟源  。如 TSC 理想时钟源  。如 kvm_clock  ,xen_clock

              时钟事务装备(clock-event device)

              系统中可以触发 one-shot(单次)或者周期性中止的装备都可以作为时钟事务装备 。如 HPET  ,CPU Local APIC Timer 等 。HPET 比力特殊 ,它既可以做时钟源装备也可以做时钟事务装备 。时钟事务装备的类型分为全局和 per-CPU 两种类型 。全局的时钟事务装备虽然隶属于某一个特定的 CPU 上  ,可是完成的是系统相关的事情  ,例如完成系统的 tick 更新;per-CPU 的时钟事务装备主要完成 Local CPU 上的一些功效  ,例如对在当前 CPU 上运行历程的时间统计  ,profile  ,设置 Local CPU 上的下一次事务中止等  。和时钟源装备的实现类似  ,时钟事务装备也通过 rating 来区分优先级关系  。

              tick device

              Tick device 用来处置惩罚周期性的 tick event 。Tick device 实在是时钟事务装备的一个 wrapper  ,因此 tick device 也有 one-shot 和周期性这两种中止触发模式  。每注册一个时钟事务装备  ,这个装备会自动被注册为一个 tick device  。全局的 tick device 用来更新诸如 jiffies 这样的全局信息  ,per-CPU 的 tick device 则用来更新每个 CPU 相关的特定信息  。

              broadcast

              CPU 的 C-STATE
            CPU 在空闲时会凭据空闲时间的是非选择进入差别的睡眠级别  ,称为 C-STATE  。C0 为正常运行状态  ,C1 到 C7 为睡眠状态 ,数值越大  ,睡眠水平越深 ,也就越省电 。CPU 空闲越久  ,进入睡眠的级别越高 ,可是叫醒所需的时间也越长  。叫醒也是需要消耗能源的  ,因此  ,只有选择合适的睡眠级别才气确保节能的最大化  。

              Broadcast 的泛起是为了应对这样一种情形:假定 CPU 使用 Local APIC Timer 作为 per-CPU 的 tick device ,可是某些特定的 CPU(如 Intel 的 Westmere 之前的 CPU)在进入 C3+ 的状态时 Local APIC Timer 也会同时制止事情  ,进入睡眠状态 。在这种情形下 broadcast 可以替换 Local APIC Timer 继续完成统计历程的执行时间等有关操作  。本质上 broadcast 是发送一个 IPI(Inter-processor interrupt)中止给其他所有的 CPU  ,当目的 CPU 收到这个 IPI 中止后就会挪用原先 Local APIC Timer 正常事情时的中止处置惩罚函数 ,从而实现了同样的功效  。现在主要在 x86 以及 MIPS 下会用到 broadcast 功效  。

              Timekeeping & GTOD (Generic Time-of-Day)

              Timekeeping(可以明白为时间丈量或者计时)是内核时间治理的一个焦点组成部门 。没有 Timekeeping ,就无法更新系统时间 ,维持系统“心跳” 。GTOD 是一个通用的框架  ,用来实现诸如设置系统时间 gettimeofday 或者修改系统时间 settimeofday 等事情  。为了实现以上功效 ,Linux 实现了多种与时间相关但用于差别目的的数据结构  。

             struct timespec { 
              __kernel_time_t  tv_sec;         
              long        tv_nsec;         
             }; 

              timespec 精度是纳秒  。它用来生存从 00:00:00 GMT, 1 January 1970 最先经由的时间  。内核使用全局变量 xtime 来记载这一信息  ,这就是通常所说的“Wall Time”或者“Real Time”  。与此对应的是“System Time”  。System Time 是一个单调递增的时间  ,每次系统启动时从 0 最先计时  。

             struct timeval { 
              __kernel_time_t     tv_sec;      
              __kernel_suseconds_t  tv_usec;      
             }; 

              timeval 精度是微秒 。timeval 主要用来指定一段时间距离  。

             union ktime { 
                s64   tv64; 
             #if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR) 
                struct { 
             # ifdef __BIG_ENDIAN 
                s32   sec, nsec; 
             # else 
                s32   nsec, sec; 
             # endif 
                } tv; 
             #endif 
             }; 

              ktime_t 是 hrtimer 主要使用的时间结构  。无论使用哪种系统结构 ,ktime_t 始终保持 64bit 的精度 ,而且思量了巨细端的影响  。

             typedef u64 cycle_t; 

              cycle_t 是从时钟源装备中读取的时钟类型  。

              为了治理这些差别的时间结构  ,Linux 实现了一系列辅助函数来完成相互间的转换  。

              ktime_to_timespec  ,ktime_to_timeval  ,ktime_to_ns/ktime_to_us ,反过来有诸如 ns_to_ktime 等类似的函数  。

              timeval_to_ns ,timespec_to_ns ,反过来有诸如 ns_to_timeval 等类似的函数  。

              timeval_to_jiffies ,timespec_to_jiffies ,msecs_to_jiffies, usecs_to_jiffies, clock_t_to_jiffies 反过来有诸如 ns_to_timeval 等类似的函数  。

              clocksource_cyc2ns / cyclecounter_cyc2ns

              有了以上的先容 ,通过图 3 可以越发清晰的看到这几者之间的关联 。

            图 2. 内核时钟子系统的结构关系
            Linux 时钟管理

              时钟源装备和时钟事务装备的引入 ,将原本放在各个系统结构中重复实现的冗余代码封装到各自的抽象层中  ,这样做不光消除了原来 timer wheel 与内核其他模块的紧耦合性  ,更主要的是系统可以在运行状态动态替换时钟源装备和时钟事务装备而不影响系统正常使用  ,譬如当 CPU 由于睡眠要关闭当前使用的时钟源装备或者时钟事务装备时系统可以平滑的切换到其他仍处于事情状态的装备上  。Timekeeping/GTOD 在使用时钟源装备的基础上也接纳类似的封装实现了系统结构的无关性和通用性  。hrtimer 则可以通过 timekeeping 提供的接口完成准时器的更新  ,通过时钟事务装备提供的事务机制  ,完成对 timer 的治理  。在图 3 中另有一个主要的模块就是 tick device 的抽象 ,尤其是 dynamic tick  。Dynamic tick 的泛起是为了能在系统空闲时通过制止 tick 的运行以到达降低 CPU 功耗的目的  。使用 dynamic tick 的系统  ,只有在有现实事情时才会发生 tick  ,否则 tick 是处于制止状态  。下文会有专门的章节举行叙述  。

              hrtimer 的实现机制

              hrtimer 是建设在 per-CPU 时钟事务装备上的  ,对于一个 SMP 系统 ,若是只有全局的时钟事务装备  ,hrtimer 无法事情 。由于若是没有 per-CPU 时钟事务装备  ,时钟中止发生时系统必须发生须要的 IPI 中止来通知其他 CPU 完成响应的事情  ,而过多的 IPI 中止会带来很大的系统开销 ,这样会令使用 hrtimer 的价格太大  ,不如不用  。为了支持 hrtimer  ,内核需要设置 CONFIG_HIGH_RES_TIMERS=y  。hrtimer 有两种事情模式:低精度模式(low-resolution mode)与高精度模式(high-resolution mode) 。虽然 hrtimer 子系统是为高精度的 timer 准备的 ,可是系统可能在运行历程中动态切换到差别精度的时钟源装备 ,因此  ,hrtimer 必须能够在低精度模式与高精度模式下自由切换  。由于低精度模式是建设在高精度模式之上的 ,因此即便系统只支持低精度模式  ,部门支持高精度模式的代码仍然会编译到内核当中  。

              在低精度模式下  ,hrtimer 的焦点处置惩罚函数是 hrtimer_run_queues  ,每一次 tick 中止都要执行一次  。如清单 3 所示 。这个函数的挪用流程为:

             update_process_times 
              run_local_timers 
                hrtimer_run_queues 
                raise_softirq(TIMER_SOFTIRQ) 

            清单 3. 低精度模式下 hrtimer 的焦点处置惩罚函数

             void hrtimer_run_queues(void) 
             { 
              struct rb_node *node; 
              struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases); 
              struct hrtimer_clock_base *base; 
              int index, gettime = 1; 
             
              if (hrtimer_hres_active()) 
                return; 
             
              for (index = 0; index < HRTIMER_MAX_CLOCK_BASES; index++) { 
                base = &cpu_base->clock_base[index]; 
             
                if (!base->first) 
                  continue; 
             
                if (gettime) { 
                  hrtimer_get_softirq_time(cpu_base); 
                  gettime = 0; 
                } 
             
                raw_spin_lock(&cpu_base->lock); 
             
                while ((node = base->first)) { 
                  struct hrtimer *timer; 
             
                  timer = rb_entry(node, struct hrtimer, node); 
                  if (base->softirq_time.tv64 <= 
                      hrtimer_get_expires_tv64(timer)) 
                    break; 
             
                  __run_hrtimer(timer, &base->softirq_time); 
                } 
                raw_spin_unlock(&cpu_base->lock); 
              } 
             } 

              hrtimer_bases 是实现 hrtimer 的焦点数据结构 ,通过 hrtimer_bases  ,hrtimer 可以治理挂在每一个 CPU 上的所有 timer  。每个 CPU 上的 timer list 不再使用 timer wheel 中多级链表的实现方式  ,而是接纳了红黑树(Red-Black Tree)来举行治理 。hrtimer_bases 的界说如清单 4 所示:

            清单 4. hrtimer_bases 的界说

             DEFINE_PER_CPU(struct hrtimer_cpu_base, hrtimer_bases) = 
             { 
             
                .clock_base = 
                { 
                    { 
                        .index = CLOCK_REALTIME, 
                        .get_time = &ktime_get_real, 
                        .resolution = KTIME_LOW_RES, 
                    }, 
                    { 
                        .index = CLOCK_MONOTONIC, 
                        .get_time = &ktime_get, 
                        .resolution = KTIME_LOW_RES, 
                    }, 
                } 
             }; 

              图 4 展示了 hrtimer 怎样通过 hrtimer_bases 来治理 timer  。

            图 3. hrtimer 的时钟治理
            Linux 时钟管理

              每个 hrtimer_bases 都包罗两个 clock_base  ,一个是 CLOCK_REALTIME 类型的 ,另一个是 CLOCK_MONOTONIC 类型的  。hrtimer 可以选择其中之一来设置 timer 的 expire time, 可以是现实的时间 , 也可以是相对系统运行的时间  。

              在 hrtimer_run_queues 的处置惩罚中  ,首先要通过 hrtimer_bases 找到正在执行当前中止的 CPU 相关联的 clock_base ,然后逐个检查每个 clock_base 上挂的 timer 是否超时 。由于 timer 在添加到 clock_base 上时使用了红黑树 ,最早超时的 timer 被放到树的最左侧 ,因此寻找超时 timer 的历程很是迅速  ,找到的所有超时 timer 会被逐一处置惩罚 。超时的 timer 凭据其类型分为 softIRQ / per-CPU / unlocked 几种  。若是一个 timer 是 softIRQ 类型的  ,这个超时的 timer 需要被转移到 hrtimer_bases 的 cb_pending 的 list 上  ,待 IRQ0 的软中止被激活后 ,通过 run_hrtimer_pending 执行  ,另外两类则必须在 hardIRQ 中通过 __run_hrtimer 直接执行  。不外在较新的 kernel(> 2.6.29)中  ,cb_pending 被作废  ,这样所有的超时 timers 都必须在 hardIRQ 的 context 中执行  。这样修改的目的 ,一是为了简化代码逻辑  ,二是为了淘汰 2 次 context 的切换:一次从 hardIRQ 到 softIRQ ,另一次从 softIRQ 到被超时 timer 叫醒的历程  。

              在 update_process_times 中  ,除了处置惩罚处于低精度模式的 hrtimer 外  ,还要叫醒 IRQ0 的 softIRQ(TIMER_SOFTIRQ(run_timer_softirq))以便执行 timer wheel 的代码  。由于 hrtimer 子系统的加入 ,在 IRQ0 的 softIRQ 中  ,还需要通过 hrtimer_run_pending 检查是否可以将 hrtimer 切换到高精度模式 ,如清单 5 所示:

            清单 5. hrtimer 举行精度切换的处置惩罚函数

             void hrtimer_run_pending(void) 
             { 
              if (hrtimer_hres_active()) 
                return; 
             
               
              if (tick_check_oneshot_change(!hrtimer_is_hres_enabled())) 
                hrtimer_switch_to_hres(); 
             } 

              正如这段代码的作者注释中所提到的  ,每一次触发 IRQ0 的 softIRQ 都需要检查一次是否可以将 hrtimer 切换到高精度  ,显然是十分低效的 ,希望未来有更好的要领不用每次都举行检查 。

              若是可以将 hrtimer 切换到高精度模式  ,则挪用 hrtimer_switch_to_hres 函数举行切换  。如清单 6 所示:

            清单 6. hrtimer 切换到高精度模式的焦点函数

              
             static int hrtimer_switch_to_hres(void) 
             { 
              int cpu = smp_processor_id(); 
              struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu); 
              unsigned long flags; 
             
              if (base->hres_active) 
                return 1; 
             
              local_irq_save(flags); 
             
              if (tick_init_highres()) { 
                local_irq_restore(flags); 
                printk(KERN_WARNING "Could not switch to high resolution " 
                     "mode on CPU %d\n", cpu); 
                return 0; 
              } 
              base->hres_active = 1; 
              base->clock_base[CLOCK_REALTIME].resolution = KTIME_HIGH_RES; 
              base->clock_base[CLOCK_MONOTONIC].resolution = KTIME_HIGH_RES; 
             
              tick_setup_sched_timer(); 
             
               
              retrigger_next_event(NULL); 
              local_irq_restore(flags); 
              return 1; 
             } 

              hrtimer_interrupt的使用情况
            hrtimer_interrupt 有 2 种常见的使用方式 。一是作为 tick 的推动器在发生 tick 中止时被挪用;另一种情形就是通过软中止 HRTIMER_SOFTIRQ(run_hrtimer_softirq)被挪用 ,通常是被驱动法式或者间接的使用这些驱动法式的用户法式所挪用

              在这个函数中 ,首先使用 tick_init_highres 更新与原来的 tick device 绑定的时钟事务装备的 event handler  ,例如将在低精度模式下的事情函数 tick_handle_periodic / tick_handle_periodic_broadcast 换成 hrtimer_interrupt(它是 hrtimer 在高精度模式下的 timer 中止处置惩罚函数) ,同时将 tick device 的触发模式变为 one-shot ,即单次触发模式  ,这是使用 dynamic tick 或者 hrtimer 时 tick device 的事情模式  。由于 dynamic tick 可以随时制止和最先  ,以不纪律的速率发生 tick  ,因此支持 one-shot 模式的时钟事务装备是必须的;对于 hrtimer  ,由于 hrtimer 接纳事务机制驱动 timer 前进  ,因此使用 one-shot 的触发模式也是顺理成章的  。不外这样一来  ,原本 tick device 每次执行中止时需要完成的周期性使命如更新 jiffies / wall time (do_timer) 以及更新 process 的使用时间(update_process_times)等事情在切换到高精度模式之后就没有了  ,因此在执行完 tick_init_highres 之后紧接着会挪用 tick_setup_sched_timer 函数来完成这部门设置事情  ,如清单 7 所示:

            清单 7. hrtimer 高精度模式下模拟周期运行的 tick device 的简化实现

             void tick_setup_sched_timer(void) 
             { 
              struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched); 
              ktime_t now = ktime_get(); 
              u64 offset; 
             
               
              hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); 
              ts->sched_timer.function = tick_sched_timer; 
             
              . . . . 
               
              for (;;) { 
                hrtimer_forward(&ts->sched_timer, now, tick_period); 
                hrtimer_start_expires(&ts->sched_timer, 
                      HRTIMER_MODE_ABS_PINNED); 
                 
                if (hrtimer_active(&ts->sched_timer)) 
                  break; 
                now = ktime_get(); 
              } 
              . . . . 
             } 

              这个函数使用 tick_cpu_sched 这个 per-CPU 变量来模拟原来 tick device 的功效  。tick_cpu_sched 自己绑定了一个 hrtimer  ,这个 hrtimer 的超时值为下一个 tick  ,回调函数为 tick_sched_timer 。因此 ,每过一个 tick  ,tick_sched_timer 就会被挪用一次  ,在这个回调函数中首先完成原来 tick device 的事情  ,然后设置下一次的超时值为再下一个 tick  ,从而到达了模拟周期运行的 tick device 的功效  。若是所有的 CPU 在统一时间点被叫醒  ,并发执行 tick 时可能会泛起 lock 竞争以及 cache-line 冲突  ,为此 Linux 内核做了特殊处置惩罚:若是假设 CPU 的个数为 N  ,则所有的 CPU 都在 tick_period 前 1/2 的时间内执行 tick 事情  ,而且每个 CPU 的执行距离是 tick_period / (2N) ,见清单 8 所示:

            清单 8. hrtimer 在高精度模式下 tick 执行周期的设置

             void tick_setup_sched_timer(void) 
             { 
              . . . . 
               
              hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update()); 
              offset = ktime_to_ns(tick_period) >> 1; 
              do_div(offset, num_possible_cpus()); 
              offset *= smp_processor_id(); 
              hrtimer_add_expires_ns(&ts->sched_timer, offset); 
              . . . . 
             } 

              回到 hrtimer_switch_to_hres 函数中 ,在一切准备停当后  ,挪用 retrigger_next_event 激活下一次的 timer 就可以最先正常的运作了  。

              随着 hrtimer 子系统的生长  ,一些问题也逐渐袒露了出来  。一个比力典型的问题就是 CPU 的功耗问题  。现代 CPU 都实现了节能的特征  ,在没有事情时 CPU 会自动降低频率  ,关闭 CPU 内部一些非要害模块以到达节能的目的  。由于 hrtimer 的精度很高  ,触发中止的频率也会很高  ,频仍的中止会极大的影响 CPU 的节能 。在这方面 hrtimer 一直在不停的举行调整 。以下几个例子都是针对这一问题所做的革新 。

            schedule_hrtimeout 函数

              
             int __sched schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode) 

              schedule_hrtimeout 用来发生一个高精度的调理超时  ,以 ns 为单元 。这样可以越发细粒度的使用内核的调理器  。在 Arjan van de Ven 的最初实现中  ,这个函数有一个很大的问题:由于其粒度很细  ,以是可能会越发频仍的叫醒内核  ,导致消耗更多的能源 。为了实现既能节约能源  ,又能确保准确的调理超时 ,Arjan van de Ven 的措施是将一个超时点酿成一个超时规模 。设置 hrtimer A 的超时值有一个上限  ,称为 hard expire  ,在 hard expire 这个时间点上设置 hrtimer A 的超时中止;同时设置 hrtimer A 的超时值有一个下限  ,称为 soft expire  。在 soft expire 到 hard expire 之间若是有一个 hrtimer B 的中止被触发  ,在 hrtimer B 的中止处置惩罚函数中  ,内核会检查是否有其他 hrtimer 的 soft expire 超时了 ,譬如 hrtimer A 的 soft expire 超时了  ,纵然 hrtimer A 的 hard expire 没有到  ,也可以顺带被处置惩罚 。换言之  ,将原来必须在 hard expire 超时才气执行的一个点酿成一个规模后  ,可以只管把 hrtimer 中止放在一起处置惩罚  ,这样 CPU 被重复叫醒的几率会变小 ,从而到达节能的效果  ,同时这个 hrtimer 也可以保证其执行精度 。

              Deferrable timers & round jiffies

              在内核中使用的某些 legacy timer 对于准确的超时值并不敏感 ,早一点或者晚一点执行并不会发生多大的影响  ,因此  ,若是可以把这些对时间不敏感同时超时时间又比力靠近的 timer 网络在一起执行 ,可以进一步淘汰 CPU 被叫醒的次数  ,从而到达节能的目地 。这正是引入 Deferrable timers 的目地  。若是一个 timer 可以被短暂延时  ,那么可以通过挪用 init_timer_deferrable 设置 defer 标志  ,从而在执行时天真选择处置惩罚方式 。不外  ,若是这些 timers 都被延时到统一个时间点上也不是最优的选择 ,这样同样会发生 lock 竞争以及 cache-line 的问题  。因此  ,即便将 defer timers 网络到一起  ,相互之间也必须稍稍错开一些以防止上述问题  。这正是引入 round_jiffies 函数的缘故原由 。虽然这样做会使得 CPU 被叫醒的次数稍多一些 ,可是由于距离短  ,CPU 并不会进入很深的睡眠  ,这个价格照旧可以接受的  。由于 round_jiffies 需要在每次更新 timer 的超时值(mod_timer)时被挪用 ,显得有些繁琐 ,因此又泛起了更为便捷的 round jiffies 机制  ,称为 timer slack  。Timer slack 修改了 timer_list 的结构界说  ,将需要偏移的 jiffies 值生存在 timer_list 内部  ,通过 apply_slack 在每次更新 timer 的历程中自动更新超时值  。apply_slack 的实现如清单 9 所示:

            清单 9. apply_slack 的实现

              
             static inline 
             unsigned long apply_slack(struct timer_list *timer, unsigned long expires) 
             { 
              unsigned long expires_limit, mask; 
              int bit; 
             
              expires_limit = expires; 
             
              if (timer->slack >= 0) { 
                expires_limit = expires + timer->slack; 
              } else { 
                unsigned long now = jiffies;  
             
                 
                if (time_after(expires, now)) 
                  expires_limit = expires + (expires - now)/256; 
              } 
              mask = expires ^ expires_limit; 
              if (mask == 0) 
                return expires; 
             
              bit = find_last_bit(&mask, BITS_PER_LONG); 
             
              mask = (1 << bit) - 1; 
             
              expires_limit = expires_limit & ~(mask); 
             
              return expires_limit; 
             } 

              随着现代盘算机系统的生长 ,对节能的需求越来越高 ,尤其是在使用条记本  ,手持装备等移动情况是对节能要求更高  。Linux 固然也会越发关注这方面的需求  。hrtimer 子系统的优化只管确保在使用高精度的时钟的同时节约能源 ,若是系统在空闲时也可以只管的节能 ,则 Linux 系统的节能优势可以进一步放大  。这也是引入 dynamic tick 的基础缘故原由  。

              Dynamic tick & tickless

              在 dynamic tick 引入之前  ,内核一直使用周期性的基于 HZ 的 tick  。传统的 tick 机制在系统进入空闲状态时仍然会发生周期性的中止  ,这种频仍的中止迫使 CPU 无法进入更深的睡眠  。若是铺开这个限制 ,在系统进入空闲时制止 tick  ,有事情时恢复 tick  ,实现完全自由的  ,凭据需要发生 tick 的机制  ,可以使 CPU 获得更多的睡眠时机以及更深的睡眠  ,从而进一步节能  。dynamic tick 的泛起  ,就是为彻底替换掉周期性的 tick 机制而发生的  。周期性运行的 tick 机制需要完成诸如历程时间片的盘算  ,更新 profile  ,协助 CPU 举行负载平衡等诸多事情  ,这些事情 dynamic tick 都提供了响应的模拟机制来完成 。由于 dynamic tick 的实现需要内核的许多模块的配合 ,包罗了许多实现细节  ,这里只先容 dynamic tick 的焦点事情机制  ,以及怎样启动和制止 dynamic tick 。

              Dynamic tick 的焦点处置惩罚流程

              从上文中可知内核时钟子系统支持低精度和高精度两种模式 ,因此 dynamic tick 也必须有两套对应的处置惩罚机制  。从清单 5 中可以得知  ,若是系统支持 hrtimer 的高精度模式  ,hrtimer 可以在此从低精度模式切换到高精度模式  。实在清单 5 另有另外一个主要功效:它也是低精度模式下从周期性 tick 到 dynamic tick 的切换点  。若是当前系统不支持高精度模式  ,系统会实验切换到 NOHZ 模式 ,也就是使用 dynamic tick 的模式  ,固然条件是内核使能了 NOHZ 模式  。其焦点处置惩罚函数如清单 10 所示 。这个函数的挪用流程如下:

             tick_check_oneshot_change 
             tick_nohz_switch_to_nohz 
              tick_switch_to_oneshot(tick_nohz_handler) 

            清单 10. 低精度模式下 dynamic tick 的焦点处置惩罚函数

             static void tick_nohz_handler(struct clock_event_device *dev) 
             { 
              struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched); 
              struct pt_regs *regs = get_irq_regs(); 
              int cpu = smp_processor_id(); 
              ktime_t now = ktime_get(); 
             
              dev->next_event.tv64 = KTIME_MAX; 
             
              if (unlikely(tick_do_timer_cpu == TICK_DO_TIMER_NONE)) 
                tick_do_timer_cpu = cpu; 
             
               
              if (tick_do_timer_cpu == cpu) 
                tick_do_update_jiffies64(now); 
               
              if (ts->tick_stopped) { 
                touch_softlockup_watchdog(); 
                ts->idle_jiffies++; 
              } 
             
              update_process_times(user_mode(regs)); 
              profile_tick(CPU_PROFILING); 
             
              while (tick_nohz_reprogram(ts, now)) { 
                now = ktime_get(); 
                tick_do_update_jiffies64(now); 
              } 
             } 

              在这个函数中  ,首先模拟周期性 tick device 完成类似的事情:若是当前 CPU 卖力全局 tick device 的事情  ,则更新 jiffies  ,同时完成对当地 CPU 的历程时间统计等事情  。若是当前 tick device 在此之前已经处于制止状态 ,为了防止 tick 制止时间过长造成 watchdog 超时 ,从而引发 soft-lockdep 的错误  ,需要通过挪用 touch_softlockup_watchdog 复位软件看门狗防止其溢出  。正如代码中注释所形貌的  ,这种情形有可能泛起在启动完毕 ,完全空闲等候登录的 SMP 系统上 。最后需要设置下一次 tick 的超时时间 。若是 tick_nohz_reprogram 执行时间凌驾了一个 jiffy ,会导致设置的下一次超时时间已经由期  ,因此需要重新设置  ,响应的也需要再次更新 jiffies  。这里虽然设置了下一次的超时势件  ,可是由于系统空闲时会制止 tick  ,因此下一次的超时势件可能发生  ,也可能不发生  。这也正是 dynamic tick 基础特征 。

              从清单 7 中可以看到  ,在高精度模式下 tick_sched_timer 用来模拟周期性 tick device 的功效 。dynamic tick 的实现也使用了这个函数  。这是由于 hrtimer 在高精度模式时必须使用 one-shot 模式的 tick device  ,这也同时切合 dynamic tick 的要求  。虽然使用同样的函数  ,外貌上都市触发周期性的 tick 中止 ,可是使用 dynamic tick 的系统在空闲时会制止 tick 事情  ,因此 tick 中止不会是周期发生的  。

              Dynamic tick 的最先和制止

              当 CPU 进入空闲时是最好的时机  。此时可以启动 dynamic tick 机制  ,制止 tick;反之在 CPU 从空闲中恢复到事情状态时  ,则可以制止 dynamic tick 。见清单 11 所示:

            清单 11. CPU 在 idle 时 dynamic tick 的启动 / 制止设置

             void cpu_idle(void) 
             { 
             . . . . 
                while (1) { 
                    tick_nohz_stop_sched_tick(1); 
                    while (!need_resched()) { 
                          . . . . 
                    } 
             
                    tick_nohz_restart_sched_tick(); 
                } 
             . . . . 
             } 

              timer 子系统的初始化历程

              在划分相识了内核时钟子系统各个模块后  ,现在可以系统的先容内核时钟子系统的初始化历程  。系统刚上电时  ,需要注册 IRQ0 时钟中止  ,完成时钟源装备 ,时钟事务装备  ,tick device 等初始化操作并选择合适的事情模式 。由于刚启动时没有特殊主要的使命要做 ,因此默认是进入低精度 + 周期 tick 的事情模式 ,之后会凭据硬件的设置(如硬件上是否支持 HPET 等高精度 timer)和软件的设置(如是否通过下令行参数或者内核设置使能了高精度 timer 等特征)举行切换  。在一个支持 hrtimer 高精度模式并使能了 dynamic tick 的系统中  ,第一次发生 IRQ0 的软中止时 hrtimer 就会举行从低精度到高精度的切换 ,然后再进一步切换到 NOHZ 模式  。IRQ0 为系统的时钟中止  ,使用全局的时钟事务装备(global_clock_event)来处置惩罚的 ,其界说如下:

             static struct irqaction irq0 = { 
                .handler = timer_interrupt, 
                .flags = IRQF_DISABLED | IRQF_NOBALANCING | IRQF_IRQPOLL | IRQF_TIMER, 
                .name = "timer" 
             }; 

              它的中止处置惩罚函数 timer_interrupt 的简化实现如清单 12 所示:

            清单 12. IRQ0 中止处置惩罚函数的简化实现

             static irqreturn_t timer_interrupt(int irq, void *dev_id) 
             { 
             . . . . 
             
                global_clock_event->event_handler(global_clock_event); 
             . . . . 
                return IRQ_HANDLED; 
             } 

              在 global_clock_event->event_handler 的处置惩罚中  ,除了更新 local CPU 上运行历程时间的统计 ,profile 等事情  ,更主要的是要完成更新 jiffies 等全局操作 。这个全局的时钟事务装备的 event_handler 凭据使用情况的差别  ,在低精度模式下可能是 tick_handle_periodic / tick_handle_periodic_broadcast ,在高精度模式下是 hrtimer_interrupt  。现在只有 HPET 或者 PIT 可以作为 global_clock_event 使用  。其初始化流程清单 13 所示:

            清单 13. timer 子系统的初始化流程

             void __init time_init(void) 
             { 
                late_time_init = x86_late_time_init; 
             } 
             
             static __init void x86_late_time_init(void) 
             { 
                x86_init.timers.timer_init(); 
                tsc_init(); 
             } 
             
              
             void __init hpet_time_init(void) 
             { 
                if (!hpet_enable()) 
                    setup_pit_timer(); 
                setup_default_timer_irq(); 
             } 

              由清单 13 可以看到 ,系统优先使用 HPET 作为 global_clock_event ,只有在 HPET 没有使能时  ,PIT 才有时机成为 global_clock_event 。在使能 HPET 的历程中  ,HPET 会同时被注册为时钟源装备和时钟事务装备 。

             hpet_enable 
              hpet_clocksource_register 
             hpet_legacy_clockevent_register 
              clockevents_register_device(&hpet_clockevent); 

              clockevent_register_device 会触发 CLOCK_EVT_NOTIFY_ADD 事务  ,即建立对应的 tick device 。然后在 tick_notify 这个事务处置惩罚函数中会添加新的 tick device  。

             clockevent_register_device trigger event CLOCK_EVT_NOTIFY_ADD 
             tick_notify receives event CLOCK_EVT_NOTIFY_ADD 
              tick_check_new_device 
                tick_setup_device 

              在 tick device 的设置历程中  ,会凭据新加入的时钟事务装备是否使用 broadcast 来划分设置 event_handler  。对于 tick device 的处置惩罚函数 ,可见图 5 所示:

            表 2. tick device 在差别模式下的处置惩罚函数

             low resolution mode High resolution mode
            periodic tick tick_handle_periodic hrtimer_interrupt
            dynamic tick tick_nohz_handler hrtimer_interrupt

              另外  ,在系统运行的历程中  ,可以通过检察 /proc/timer_list 来显示系统当前设置的所有时钟的详细情形  ,譬如当前系统运动的时钟源装备 ,时钟事务装备  ,tick device 等 。也可以通过检察 /proc/timer_stats 来检察当前系统中所有正在使用的 timer 的统计信息  。包罗所有正在使用 timer 的历程 ,启动 / 制止 timer 的函数 ,timer 使用的频率等信息  。内核需要设置 CONFIG_TIMER_STATS=y  ,而且在系统启动时这个功效是关闭的 ,需要通过如下下令激活"echo 1 >/proc/timer_stats" 。/proc/timer_stats 的显示花样如下所示:

              , ()

              总结

              随着应用情况的改变  ,使用需求的多样化 ,Linux 的时钟子系统也在不停的衍变  。为了更好的支持音视频等对时间精度高的应用 ,Linux 提出了 hrtimer 这一高精度的时钟子系统  ,为了节约能源  ,Linux 改变了恒久以来一直使用的基于 HZ 的 tick 机制 ,接纳了 tickless 系统  。纵然是在对硬件平台的支持上 ,也是在不停革新  。举例来说  ,由于 TSC 精度高 ,是首选的时钟源装备 。可是现代 CPU 会在系统空闲时降低频率以节约能源 ,从而导致 TSC 的频率也会追随发生改变  。这样会导致 TSC 无法作为稳固的时钟源装备使用  。随着新的 CPU 的泛起  ,纵然 CPU 的频率发生转变  ,TSC 也可以一直维持在牢固频率上  ,从而确保其稳固性  。在 Intel 的 Westmere 之前的 CPU 中 ,TSC 和 Local APIC Timer 类似  ,都市在 C3+ 状态时进入睡眠  ,从而导致系统需要切换到其他较低精度的时钟源装备上  ,可是在 Intel Westmere 之后的 CPU 中  ,TSC 可以一直保持运行状态  ,纵然 CPU 进入了 C3+ 的睡眠状态  ,从而制止了时钟源装备的切换  。在 SMP 的情况下 ,尤其是 16-COREs  ,32-COREs 这样的多 CPU 系统中 ,每个 CPU 之间的 TSC 很难保持同步  ,很容易泛起“Out-of-Sync” 。若是在这种情况下使用 TSC ,会造成 CPU 之间的计时误差  ,然而在 Intel 最新的 Nehalem-EX CPU 中  ,已经可以确保 TSC 在多个 CPU 之间保持同步  ,从而可以使用 TSC 作为首选的时钟源装备  。由此可见  ,无论是现在照旧未来  ,只要有需要  ,内核的时钟子系统就会一直向前生长  。