公司加强网站建设,沙漠风网站开发怎样,公司宣传网页,签名字体在线生成器一、时间子系统的软件架构 二、tick 层模块的文件
tick-common.c tick-oneshot.c tick-sched.c
tick-broadcast.c tick-broadcast-hrtimer.c
这三个文件属于tick device layer。 tick-common.c文件是periodic tick模块#xff0c;用于管理周期性tick事件。 tick-oneshot.c文…一、时间子系统的软件架构 二、tick 层模块的文件
tick-common.c tick-oneshot.c tick-sched.c
tick-broadcast.c tick-broadcast-hrtimer.c
这三个文件属于tick device layer。 tick-common.c文件是periodic tick模块用于管理周期性tick事件。 tick-oneshot.c文件是for高精度timer的用于管理高精度tick时间。 tick-sched.c是用于dynamic tick的。
tick-broadcast.c 和 tick-broadcast-hrtimer.c 是broadcast tick模块。
如果说每个.c文件是一个模块的话我们可以首先简单描述tick device layer的各个模块。tick-common.c描述了tick device的一些通用操作此外该文件还包括了周期性tick的代码。想要让系统工作在tickless mode更准确应该是Dynamic tick模块也就是说根据系统的当前运行状况动态的启停周期性tick需要两个模块的支持分别是tick-oneshot.c和tick-sched.c。tick-oneshot.c主要是提供和tick device的one shot mode相关的操作接口函数。从字面上看tick-sched.c是和tick的调度相关所谓tick的调度包括两个方面一方面是在系统正常运行过程中如何产生周期性的tick event另一方面是在系统没有任务执行进入idle状态的时候如何停止周期性的tick以及恢复的时候如何更新系统状态例如jiffies等。tick-broadcast.c和tick-broadcast-hrtimer.c是和tick broadcast相关本文不会涉及这部分的内容会有专门的文档描述它
1、什么是tick
想要理解什么是tick device什么是tickless kernel首先当然要理解什么是tick要理解什么是tick首先要理解OS kernel是如何运作的。系统中有很多日常性的事情需要处理例如
更新系统时间
处理低精度timer
处理正在运行进程的时间片信息
系统在处理这些事情的时候使用了轮询的方式也就是说按照固定的频率去做这些操作。这时候就需要HW的协助一般而言硬件会有HW timer称之system timer可以周期性的trigger interrupt让系统去处理上述的日常性事务。每次timer中断到来的时候内核的各个模块就知道一个固定的时间片已经过去。对于日常生活tick这个概念是和钟表关联的钟表会发出周期性的滴答的声音这个声音被称为tick。CPU和OS kernel扩展了这个概念周期性产生的timer中断事件被称为tick而能够产生tick的设备就称为tick device。
如何选择tick的周期是需要在power comsuption、时间精度以及系统响应时间上进行平衡。我们考虑系统中基于tick的低精度timer模块选择较高的tick频率会提高时间精度例如对于10ms的tick周期意味着低精度timer的时间精度就是10ms设定3ms的低精度timer没有任何意义。为了提高时间精度我们可以提高tick的频率例如可以提升到1ms的tick但是这时更多的CPU的时间被花费在timer的中断处理实际上当系统不繁忙的时候并不是每一个tick都是那么有意义实际上大部分的tick到来的时候OS kernel往往只是空转实际上并有什么事情做这对系统的power consumption是有害的。对于嵌入式设备周期性的tick对power consumption危害更大因为对于嵌入式设备待机时间是一个很重要的指标而周期性tick则意味着系统不可能真正的进入idle状态而是会周期性的被wakeup这些动作会吃掉电池的电量。同理对于调度器而言亦然。如果设定10ms的tick分配每个进程的时间片精度只是10ms调度器计算每个进程占用CPU的时间也只能是以10ms为单位。为了提高进程时间片精度我们可以提高tick的频率例如可以提升到1ms的tick但是这时更多的CPU的时间被花费在进程上下文的切换上但是对应的好处是系统的响应时间会更短。 三、periodic tick 模块
1、数据结构
在内核中使用struct tick_device来抽象系统中的tick设备如下 struct tick_device { struct clock_event_device *evtdev; enum tick_device_mode mode; }; 从上面的定义就可以看出所谓tick device其实就是工作在某种模式下的clock event设备。工作模式体现在tick device的mode成员evtdev指向了和该tick device关联的clock event设备。
tick device的工作模式定义如下 enum tick_device_mode { TICKDEV_MODE_PERIODIC, TICKDEV_MODE_ONESHOT, }; tick device可以工作在两种模式下一种是周期性tick模式另外一种是one shot模式。one shot模式主要和tickless系统以及高精度timer有关。
2、tick device的分类以及和CPU的关系
1 local tick device。在单核系统中传统的unix都是在tick驱动下进行任务调度、低精度timer触发等在多核架构下系统为每一个cpu建立了一个tick device如下 DEFINE_PER_CPU(struct tick_device, tick_cpu_device); local tick device的clock event device应该具备下面的特点
a该clock event device对应的HW timer必须是和该CPU core是有关联的的也就是说该hw timer的中断是可以送达到该CPU core的。struct clock_event_device 有一个cpumask成员它可以指示该clock event device为哪一个或者哪几个CPU core工作。如果采用ARM generic timer的硬件其HW timer总是为一个CPU core服务的我们称之为per cpu timer。
b该clock event device支持one shot模式并且精度最高rating最大
2global tick device。具体定义如下 int tick_do_timer_cpu __read_mostly TICK_DO_TIMER_BOOT; 有些任务不适合在local tick device中处理例如更新jiffies更新系统的wall time更新系统的平均负载不是单一CPU core的负载这些都是系统级别的任务只需要在local tick device中选择一个作为global tick device就OK了。tick_do_timer_cpu指明哪一个cpu上的local tick作为global tick。
3broadcast tick device定义如下 static struct tick_device tick_broadcast_device; 我们会单独一份文档描述它这里就不再描述了。
四、初始化tick device
1、注册一个新的clock event device的时候tick device layer要做什么
在clock_event的文章中我们知道底层的timer硬件驱动在初始化的时候会注册clock event device在注册过程中就会调用tick_check_new_device函数来看看是否需要进行tick device的初始化如果已经已经初始化OK的tick device是否有更换更高精度clock event device的需求。代码如下 void tick_check_new_device(struct clock_event_device *newdev) { struct clock_event_device *curdev; struct tick_device *td; int cpu; cpu smp_processor_id();1 td per_cpu(tick_cpu_device, cpu);获取当前cpu的tick device curdev td-evtdev; 目前tick device正在使用的clock event device if (!tick_check_percpu(curdev, newdev, cpu))2 goto out_bc; if (!tick_check_preferred(curdev, newdev))3 goto out_bc; if (!try_module_get(newdev-owner)) 增加新设备的reference count return; if (tick_is_broadcast_device(curdev)) { 4 clockevents_shutdown(curdev); curdev NULL; } clockevents_exchange_device(curdev, newdev); 通知clockevent layer tick_setup_device(td, newdev, cpu, cpumask_of(cpu)); 5 if (newdev-features CLOCK_EVT_FEAT_ONESHOT) tick_oneshot_notify(); return; out_bc: tick_install_broadcast_device(newdev); 其他文档中描述 } 1是否是为本CPU服务的clock event device如果不是那么不需要考虑per cpu tick device的初始化或者更换该cpu tick device的clock event device。当然这是还是可以考虑用在broadcast tick device的。
2第二个关卡是per cpu的检查。如果检查不通过那么说明这个新注册的clock event device和该CPU不来电不能用于该cpu的local tick。如果注册的hw timer都是cpu local的仅仅属于一个cpu这时候该clock event device的cpumask只有一个bit被set那么事情会比较简单。然而事情往往没有那么简单一个hw timer可以服务多个cpu。我们这里说HW timer服务于某个cpu其实最重要的是irq是否可以分发到指定的cpu上。我们可以看看tick_check_percpu的实现 static bool tick_check_percpu(struct clock_event_device *curdev, struct clock_event_device *newdev, int cpu) { if (!cpumask_test_cpu(cpu, newdev-cpumask))a return false; if (cpumask_equal(newdev-cpumask, cpumask_of(cpu)))b return true; if (newdev-irq 0 !irq_can_set_affinity(newdev-irq))c return false; if (curdev cpumask_equal(curdev-cpumask, cpumask_of(cpu)))d return false; return true; } a判断这个新注册的clock event device是否可以服务该CPU如果它根本不鸟这个cpu那么不用浪费时间了。
b判断这个新注册的clock event device是否只服务该CPU。如果这个clock event device就是服务该cpu的那么别想三想四了这个clock event device就是你这个CPU的人了。
c如果能走到这里说明该clock event device可以服务多个CPU指定的cpu作为参数传递进来只是其中之一而已这时候可以通过设定irq affinity将该clock event device的irq定向到该cpu。当前前提是可以进行irq affinity的设定这里就是进行这样的检查。
d走到这里说明该新注册的clock event device是可以进行irq affinity设定的。我们可以通过修改irq affinity让该hw timer服务于这个指定的CPU。恩听起来有些麻烦的确如此如果当前CPU的tick device正在使用的clock event device就是special for当前CPU的根本不鸟其他CPU有如此专情的clock event device夫复何求果断拒绝新注册的设备。
3程序来到这里说明tick_check_percpu返回trueCPU和该clock event device之间的已经是眉目传情了不过是否可以入主就看该cpu的原配是否有足够强大的能力精度和特性。tick_check_preferred代码如下 static bool tick_check_preferred(struct clock_event_device *curdev, struct clock_event_device *newdev) { if (!(newdev-features CLOCK_EVT_FEAT_ONESHOT)) {a if (curdev (curdev-features CLOCK_EVT_FEAT_ONESHOT)) return false; if (tick_oneshot_mode_active()) return false; } return !curdev || newdev-rating curdev-rating || !cpumask_equal(curdev-cpumask, newdev-cpumask);b } a首先进行one shot能力比拼。如果新的clock event device没有one shot能力而原配有新欢失败。如果都没有one shot的能力那么要看看当前系统是否启用了高精度timer或者tickless。本质上如果clock event device没有oneshot功能那么高精度timer或者tickless都是处于委曲求全的状态如果这样还是维持原配的委曲求全的状态新欢失败
b如果current是NULL的话事情变得非常简单当然是新来的这个clock event device胜出了这时候后面的比较都没有意义了。如果原配存在的话那么可以看rating如果新来的精度高那也选择新来的clock event device。是否精度低就一定不选新的呢也不是新设备还是有机会力挽狂澜的如果新来的是local timer而原配是非local timer这时候也可以考虑选择新的毕竟新来的clock event device是local timer精度低一些也没有关系。
当tick_check_percpu返回true的时候有两种情况一种是不管current是什么状态新设备是CPU的local timer只为这个cpu服务即cpumask 和当前cpu相等。另外一种情况是新设备不是CPU的local timer当然原配也没有那么专一。
我们先看看第一种情况如果cpumask_equal返回true那么说明原配也是local timer那么没有办法了谁的rating高就选谁。如果cpumask_equal返回false那么说明原配不是local timer那么即便新来的rating低一些也还是优先选择local timer。
我们再看看第二种情况新clock_event_device 虽然是个海王但是可以设置中断亲和性即可以给cpu 发生中断同时本地的clock_event_device 不存在 或者 本地的clock_event_device 也是个海王就返回true进行下一轮的判断
4OK经过复杂的检查我们终于决定要用这个新注册的clock event device来替代current了当然也有可能current根本不存在。在进行替换之前我们还有检查一下current是否是broadcast tick device如果是的话还不能将其退回clockevents layer仅仅是设定其状态为shutdown。curdev NULL这一句很重要在clockevents_exchange_device函数中如果curdev NULL的话old device将不会从全局链表中摘下挂入clockevents_released链表。
5setup tick device参考下一节描述。
2、如何Setup 一个 tick device
所谓setup一个tick device就是对tick device心仪的clock event设备进行设置并将该tick device的evtdev指向新注册的这个clock event device具体代码如下 static void tick_setup_device(struct tick_device *td, struct clock_event_device *newdev, int cpu, const struct cpumask *cpumask) { ktime_t next_event; void (*handler)(struct clock_event_device *) NULL; if (!td-evtdev) { if (tick_do_timer_cpu TICK_DO_TIMER_BOOT) {1 …… } td-mode TICKDEV_MODE_PERIODIC;2 } else { handler td-evtdev-event_handler; next_event td-evtdev-next_event; td-evtdev-event_handler clockevents_handle_noop; 3 } td-evtdev newdev; 终于修成正果了呵呵 if (!cpumask_equal(newdev-cpumask, cpumask)) 4 irq_set_affinity(newdev-irq, cpumask); if (tick_device_uses_broadcast(newdev, cpu)) 留给broadcast tick文档吧 return; if (td-mode TICKDEV_MODE_PERIODIC) tick_setup_periodic(newdev, 0); 5 else tick_setup_oneshot(newdev, handler, next_event); 其他文档描述 } 1在multi core的环境下每一个CPU core都自己的tick device可以称之local tick device这些tick device中有一个被选择做global tick device负责维护整个系统的jiffies。如果该tick device的是第一次设定并且目前系统中没有global tick设备那么可以考虑选择该tick设备作为global设备进行系统时间和jiffies的更新。更细节的内容请参考timekeeping文档。
2在最初设定tick device的时候缺省被设定为周期性的tick。当然这仅仅是初始设定实际上在满足一定的条件下在适当的时间tick device是可以切换到其他模式的下面会具体描述。
3旧的clockevent设备就要退居二线了将其handler修改为clockevents_handle_noop。
4如果不是local timer那么还需要调用irq_set_affinity函数将该clockevent的中断定向到本CPU。
5tick_setup_periodic的代码如下注下面的代码分析中暂不考虑broadcast tick的情况 void tick_setup_periodic(struct clock_event_device *dev, int broadcast) { tick_set_periodic_handler(dev, broadcast); 设定event handler为tick_handle_periodic if ((dev-features CLOCK_EVT_FEAT_PERIODIC) !tick_broadcast_oneshot_active()) { clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);a } else { unsigned long seq; ktime_t next; do { seq read_seqbegin(jiffies_lock); next tick_next_period; 获取下一个周期性tick触发的时间 } while (read_seqretry(jiffies_lock, seq)); clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT); 模式设定 for (;;) { if (!clockevents_program_event(dev, next, false)) program next clock event return; next ktime_add(next, tick_period); 计算下一个周期性tick触发的时间 } } } a如果底层的clock event device支持periodic模式那么直接调用clockevents_set_mode设定模式就OK了
b如果底层的clock event device不支持periodic模式而tick device目前是周期性tick mode那么要稍微复杂一些需要用clock event device的one shot模式来实现周期性tick。 五、周期性tick的运作
1、从中断到clock event handler
一般而言底层的clock event chip driver会注册中断我们用ARM generic timer驱动为例注册的代码如下 … err request_percpu_irq(ppi, arch_timer_handler_phys, arch_timer, arch_timer_evt); …… 具体的timer的中断handler如下 static irqreturn_t arch_timer_handler_phys_mem(int irq, void *dev_id) { …… evt-event_handler(evt); …… } 也就是说在timer interrupt handler中会调用clock event device的event handler而在周期性tick的场景下这个event handler被设定为tick_handle_periodic。
2、周期性tick的clock event handler的执行分析
由于每个cpu都有自己的tick device因此在每个cpu上每个tick到了的时候都会调用tick_handle_periodic函数进行周期性tick中要处理的task具体如下 /* * Event handler for periodic ticks */ void tick_handle_periodic(struct clock_event_device *dev) { int cpu smp_processor_id(); ktime_t next dev-next_event; tick_periodic(cpu); //周期性tick中要处理的内容 #if defined(CONFIG_HIGH_RES_TIMERS) || defined(CONFIG_NO_HZ_COMMON) /* * The cpu might have transitioned to HIGHRES or NOHZ mode via * update_process_times() - run_local_timers() - * hrtimer_run_queues(). */ if (dev-event_handler ! tick_handle_periodic) return; #endif if (!clockevent_state_oneshot(dev)) return; for (;;) { /* * Setup the next period for devices, which do not have * periodic mode: */ next ktime_add(next, tick_period); //计算下一个周期性tick触发的时间 if (!clockevents_program_event(dev, next, false))//设定下一个clock event触发的时间 return; /* * Have to be careful here. If were in oneshot mode, * before we call tick_periodic() in a loop, we need * to be sure were using a real hardware clocksource. * Otherwise we could get trapped in an infinite * loop, as the tick_periodic() increments jiffies, * which then will increment time, possibly causing * the loop to trigger again and again. */ if (timekeeping_valid_for_hres()) tick_periodic(cpu); } } 如果该tick device所属的clock event device工作在one shot mode那么还需要为产生周期性tick而进行一些额外处理。
2、周期性tick中要处理的内容
代码如下 /* * Periodic tick */ static void tick_periodic(int cpu) { if (tick_do_timer_cpu cpu) { //global tick需要进行一些额外处理 raw_spin_lock(jiffies_lock); write_seqcount_begin(jiffies_seq); /* Keep track of the next tick event */ tick_next_period ktime_add(tick_next_period, tick_period); do_timer(1); //更新jiffies计算平均负载 write_seqcount_end(jiffies_seq); raw_spin_unlock(jiffies_lock); update_wall_time(); //更新wall time } update_process_times(user_mode(get_irq_regs())); //更新和当前进程相关的内容 profile_tick(CPU_PROFILING);// 和性能剖析相关 } 六、dynamic tick 模块
1、什么是tickless
tickless本质上上是去掉那个烦恼的滴答声音。对于OS kernel而言tickless也就是意味着没有那个固定周期的timer interrupt事件可是没有那个固定的tickOS kernel如何运转呢
①首先看看如何处理timer。各种驱动和内核模块例如网络子系统的TCP模块都有timer的需求因此时间子系统需要管理所有注册到系统的timer。对于有tick的系统在每个tick中scan所有的timer是一个顺理成章的想法如果检查到timer超期或者即将超期系统会调用该timer的callback函数。当然由于要在每个tick到来的时候检查timer因此效率非常重要内核有一些有意思的设计有兴趣的读者可以看看低精度timer的的scan过程。
没有tick怎么办这时候需要找到所有timer中最近要超期的timer将其时间值设定到实际的HW timer中就OK了当然这时候需要底层的HW timer支持one shot也就是说该timer的中断就来一次在该timer的的中断处理中除了处理超期函数之外还需要scan所有timer找到最近要超期的timer将其时间值设定到实际的HW timer中就OK了然后不断的重复上面的过程就OK了。假设系统中注册了1200ns, 1870ns, 2980ns, 4500ns, 5000ns和6250ns的timer在一个HZ1000的系统上timer的超期都是在规则的tick时间点上对于tickless的系统timer的中断不是均匀的具体如下图所示 ②我们再来看看更新系统时间。对于有tick的系统非常简单在每个tick到来的时候调用update_wall_time来更新系统时间当然由于是周期性tick这时候每次都是累加相同的时间。对于tickless的系统我们可以选择在每个timer超期的中断中调用update_wall_time来更新系统时间不过这种设计不好一方面如果系统中的timer数目太多那么update_wall_time调用太频繁而实际上是否系统需要这么高精度的时间值呢更重要的是timer中断到来是不确定的和系统中的timer设定相关有的时间段timer中断比较频繁获取的系统时间精度比较高有的时间段timer中断比较稀疏那么获取的系统时间精度比较低。
有一个问题目前最新内核中对于tickless 时是如何更新wall_time ?
③看调度器怎么适应tickless。我们知道除非你是一个完全基于优先级的调度器否则系统都会给进程分配一个时间片time slice当占用CPU的时间片配额使用完了该进程会挂入队列等待调度器分配下一个时间片并调度运行。有tick当然比较简单在该tick的timer中断中减去当前进程的时间片。没有tick总是比较麻烦我能想到的方法是假设我们给进程分配40ms的时间片那么在调度该进程的时候需要设定一个40ms的timertimer到期后调度器选择另外一个进程然后再次设定timer。当然如果没有进程优先级的概念或者说优先级仅仅体现在分配的时间片比较多的情况下并且系统中处于runnable状态的进程较少整体的运作还是OK的。如果有优先级概念怎么办如果进程执行过程中被中断打断切换到另外的进程怎么办如果系统内的进程数目很多如何保证调度器的性能算了太复杂了还是有tick比较好因此实际中linux kernel在有任务执行的时候还是会启动周期性的tick。当然世界上没有绝对正确的设计任何优雅的设计都是适用于一定的应用场景的。其实自然界的规律不也是这样吗牛顿的定律也不是绝对的正确仅仅适用于低速的场景当物体运动的速度接近光速的时候牛顿的经典力学定律都失效了。
2、内核中的tickless
本节我们主要来看看内核中的tickless的情况。传统的unix和旧的linux2000年初之前的都是有tick的对于新的内核配置CONFIG_HZ_PERIODIC的情况下也是有tick的新的linux kernel中增加了tickless的选项
CONFIG_NO_HZ_IDLE
CONFIG_NO_HZ_FULL
CONFIG_NO_HZ_IDLE是说在系统dile的时候是没有tick的当然在系统运行的时候还是有tick的因此我们也称之dynamic tick或者NO HZ mode。3.10版本之后引入一个full tickless mode听起来好象任何情况下都是没有tick的不过实际上也没有那么强除了CPU处于idle的时候可以停下tick当CPU上有且只有一个进程运行的时候也可以停下周期性tick其他的情况下例如有多个进程等待调度执行都还是有tick的。这个配置实际上只是对High-performance computing (HPC)有意义的因此不是本文的重点。
3、tick device概述
Tick device是能够提供连续的tick event的设备。目前linux kernel中有periodic tick和one-shot tick两种tick device。periodic tick可以按照固定的时间间隔产生tick event。one-shot tick则是设定后只能产生一次tick event如果要连续产生tick event那么需要每次都进行设定。
每一个cpu都有属于自己的tick device。定义为tick_cpu_device。每一个tick device都有自己的类型periodic或者one-shot每一个tick device其实就是一个clock event device增加了表示mode的member不同类型的tick device有不同的event handler。对于periodic类型的tick设备其clock event device的event handler是tick_handle_periodic没有配置高精度timer或者hrtimer_interrupt配置了高精度timer。对于one-shot类型的tick设备其clock event device的event handler是hrtimer_interrupt配置了高精度timer或者tick_nohz_handler没有配置高精度timer。
Tick Device模块负责管理系统中的所有的tick设备在SMP环境下每一个CPU都自己的tick device这些tick device中有一个被选择做global tick device该device负责维护整个系统的jiffies以及更新哪些基于jiffies进行的全系统统计信息。
4、kernel如何初始化tick device layer以及周期性tick的运作
如果把tick device的逻辑当作一个故事那么故事的开始来自clockevent device layer。每当底层有新的clockevent device加入到系统中的时候会调用clockevents_register_device或者clockevents_config_and_register向通用clockevent layer注册一个新的clockevent设备这时候会调用tick_check_new_device通知tick device layer有新货到来。如果tick device和clockevent device你情我愿那么就会调用tick_setup_device函数setup这个tick device了。一般而言刚系统初始化的时候所有cpu的tick device都没有匹配clock event device因此该cpu的local tick device也就是global tick device了。而且如果tick device是新婚匹配之前tick device的clock event device等于NULL那么tick device的模式将被设定为TICKDEV_MODE_PERIODIC即便clock event有one shot能力即便系统配置了NO HZ。好吧反正无论如何都需要从周期性tick开始那么看看如何进行周期性tick的初始化的。
tick_setup_periodic函数用来设定一个periodic tick device。当然最重要的设定event handler对于周期性tick device其clock event device的handler被设定为tick_handle_periodic。光有handler也不行还得kick off底层的硬件让其周期性的产生clock event这样才能推动系统的运作这是通过调用clockevent device layer的接口函数完成的。
最后我们思考一个问题系统启动过程中什么时候开始有tick多核系统BSP首先启动在其初始化过程中会调用time_init这里会启动clocksource的初始化过程。这时候周期性的tick就会开始了。在某个阶段其他的processor会启动然后会注册其自己的local timer这样各个cpu上的tick就都启动了
七、设置了高精度timer的情况下dynamic tick如何运作
1、软件层次
下面的这幅图是以tick device为核心描述了该模块和其他时间子系统模块的交互过程配置高精度timer和dynamic tick的情况 上图中红色边框的模块是per cpu的模块所谓per cpu就是说每个cpu都会维护属于一个自己的对象。例如对于tick device每个CPU都会维护自己的tick device不过为了不让图片变得太大上图只画了一个CPU的情况其他CPU的动作是类似。为何clock event没有被涂上红色的边框呢实际上clock event device并不是per cpu的有些per cpu的local timer也有global timer如果硬件设计人员愿意的话一个CPU可以有多个local timer系统中所有的timer硬件被抽象成一个个的clock event device进行系统级别的管理每个CPU并不会特别维护一个属于自己的clock event device。弱水三千只取一瓢。每个CPU只会在众多clock event device中选取那个最适合自己的clock event device构建CPU local tick device。
tick device系统的驱动力来自中断子系统当HW timertick device使用的那个超期会触发中断因此会调用hrtimer_interrupt来驱动高精度timer的运转执行超期timer的call back函数。而在hrtimer_interrupt中会扫描保存高精度timer的红黑树找到下一个超期需要设定的时间调用tick_program_event来设定下一次的超期事件你知道的这是我们的tick device工作在one shot mode需要不断的set next expire time才能驱动整个系统才会不断的向前。
传统的低精度timer是周期性tick驱动的但是目前tick 处于one shot mode怎么办只能是模拟了Tick device layer需要设定一个周期性触发的高精度timer在这个timer的超期函数中tick_sched_timer执行进行原来周期性tick的工作例如触发TIMER_SOFTIRQ以便推动系统低精度timer的运作更新timekeeping模块中的real clock。
2、如何切换到tickless
我们知道开始tick device总是工作在周期性tick的mode一切就像过去一样无论何时系统总是有那个周期性的tick到来。这个周期性的tick是由于硬件timer的中断推动该HW Timer的中断会注册soft irq因此HW timer总会周期性的触发soft irq的执行也就是run_timer_softirq函数。在该函数中会根据情况将hrtimer切换到高精度模式hrtimer也有两种mode一种高精度mode一种是低精度mode系统总是从低精度mode开始。在系统切换到高精度timer mode的时候hrtimer_switch_to_hres由于高精度timer必须需要底层的tick device运行在one shot mode因此这时会调用tick_switch_to_oneshot函数将该CPU上的tick device的mode切换置one shotNote这时候event handler设定为hrtimer_interrupt。同样的底层的clock event device也会被设定为one shot mode。一旦进入one shot mode那个周期性到来的timer中断就会消失了从此系统只会根据系统中的hrtimer的设定情况来一次性的设定底层HW timer的触发。
3、如何产生周期性tick
虽然tick device以及底层的HW timer都工作在one shot mode看起来系统的HW timer中断都是按需产生多么美妙。但是由于各种原因此处省略3000字在系统运行过程中那个周期性的tick还需要保持因此在切换到one shot mode的同时也会调用tick_setup_sched_timer函数创建一个sched timer一个普通的hrtimer而已该timer的特点就是每次超期后还会调用hrtimer_forward不断的将自己挂回hrtimer的红黑树于是乎tick_sched_do_timer接口按照tick的周期不断的被调用从而模拟了周期性的tick。
4、在idle的时候如何停掉tick
我们知道各个cpu上的swapper进程0号进程又叫idle进程最后都是会执行cpu_idle_loop函数该函数在真正执行cpu idle指令之前会调用tick_nohz_idle_enter在该函数中sched timer会被停掉因此周期性的HW timer不会再来这时候将cpu从idle中唤醒的只能是和实际上系统中的hrtimer中的那个最近的超期时间有关。
5、如何恢复tick
概念同上当从idle中醒来tick_nohz_idle_exit函数被调用重建sched timer一切恢复了原状。
6、没有设置高精度timer的情况下dynamic tick如何运作