Linux之时间子系统(四): tick 层模块(periodic 和dynamic )
一、时间子系统的软件架构

二、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最大)
(2)global 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。
(3)broadcast 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 deviceif (!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返回true,CPU和该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,进行下一轮的判断;
(4)OK,经过复杂的检查,我们终于决定要用这个新注册的clock event device来替代current了(当然,也有可能current根本不存在)。在进行替换之前,我们还有检查一下current是否是broadcast tick device,如果是的话,还不能将其退回clockevents layer,仅仅是设定其状态为shutdown。curdev = NULL这一句很重要,在clockevents_exchange_device函数中,如果curdev == NULL的话,old device将不会从全局链表中摘下,挂入clockevents_released链表。
(5)setup 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。
(5)tick_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_periodicif ((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;
#endifif (!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 we're in oneshot mode,
* before we call tick_periodic() in a loop, we need
* to be sure we're 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事件,可是,没有那个固定的tick,OS 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,在一个HZ=1000的系统上,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的timer,timer到期后,调度器选择另外一个进程,然后再次设定timer。当然,如果没有进程优先级的概念(或者说优先级仅仅体现在分配的时间片比较多的情况下),并且系统中处于runnable状态的进程较少,整体的运作还是OK的。如果有优先级概念怎么办?如果进程执行过程中被中断打断,切换到另外的进程怎么办?如果系统内的进程数目很多如何保证调度器的性能?算了,太复杂了,还是有tick比较好,因此实际中,linux kernel在有任务执行的时候还是会启动周期性的tick。当然,世界上没有绝对正确的设计,任何优雅的设计都是适用于一定的应用场景的。其实自然界的规律不也是这样吗?牛顿的定律也不是绝对的正确,仅仅适用于低速的场景,当物体运动的速度接近光速的时候,牛顿的经典力学定律都失效了。
2、内核中的tickless
本节我们主要来看看内核中的tickless的情况。传统的unix和旧的linux(2000年初之前的)都是有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 timer(tick 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 shot(Note:这时候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如何运作?
相关文章:
Linux之时间子系统(四): tick 层模块(periodic 和dynamic )
一、时间子系统的软件架构 二、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文…...
Docker Command
小试牛刀 # 查看docker版本 docker -v docker --version # 查看帮助 docker --help # 永远的Hello World docker run hello-world镜像操作 查看本地已有的镜像 docker images -a :列出本地所有的镜像(含中间映像层) -q :只显示镜像ID --digests :显示…...
Linux系统部署Paperless-Ngx文档管理系统结合内网穿透实现公网访问
文章目录 1. 部署Paperless-ngx2. 本地访问Paperless-ngx3. Linux安装Cpolar4. 配置公网地址5. 远程访问6. 固定Cpolar公网地址7. 固定地址访问 Paperless-ngx是一个开源的文档管理系统,可以将物理文档转换成可搜索的在线档案,从而减少纸张的使用。它内置…...
6.shell case控制语句
case控制语句 1.什么是case case条件语句相当于多分支的if/elif/else条件语句,主要还是用来做条件判断的,常被应用于实现系统服务启动脚本。 case语句中,会将case获取的变量值与表达式部分的值1、值2、值3等逐个进行比较,如果变量值和某个表…...
如何判断HDMI接口版本是1.4还是2.0呢?
如何判断HDMI接口版本是1.4还是2.0呢? HDMI是一种用于传输高质量音频和视频信号的接口标准。随着技术的不断发展,HDMI接口也经历了多次升级和改进。在市场上,常见的HDMI接口版本包括1.4和2.0。判断HDMI接口版本主要通过以下几种方法ÿ…...
【开发环境搭建篇】NodeJS的安装和配置
作者介绍:本人笔名姑苏老陈,从事JAVA开发工作十多年了,带过大学刚毕业的实习生,也带过技术团队。最近有个朋友的表弟,马上要大学毕业了,想从事JAVA开发工作,但不知道从何处入手。于是࿰…...
【Docker】docker和docker-compose一键安装脚本(linux)
一、准备和运行脚本 当前脚本下载的docker和docker-compose兼容系统架构为x64,可以根据自己实际系统版本更改下载链接 1. 在控制台使用vim新建: vim install-docker.sh2. 复制内容并粘贴: #!/usr/bin/env bash # 设置脚本在遇到错误时终止执行 set -…...
在 Windows 中安装配置并启动运行 Jenkins【图文详细教程】
安装 Jenkins 的系统要求: 最少 256MB 可用内存最少 1GB 可用磁盘空间JDK 8 / 11 /17(Jenkins 是用 Java 写的,打包成 war 包) 查看 JDK 的版本 Java JDK 在 Windows 中安装可以参考:https://www.yuque.com/u27599042/…...
C# 读取txt文本所有行
引用:System.IO; Path.Combine(); //将字符串组合成一个路径 Path.GetFullPath(); //返回指定路径的绝对路径 File.ReadAllLines(); //读取文本框返回一个数组 File.ReadAllText(); //读取文本框返回一个字符串 File.ReadAllBytes(); //读取文本框返回字节 …...
STM32使用常见错误合集(正在更新版)
本文章记录一些学习STM32的一些错误问题,师承江科大哈哈哈 一、编译、烧录类问题 1、烧录不成功,Keil提示RDDI-DAP Error【场景:PWM驱动直流电机】 解决方案:将电机断开再进行烧录,断开后就可以美美烧录不报错啦~ …...
Java Random类
一、Random类 在项目开发中,经常需要使用随机数值,例如,网站登录中的验证码,或者需要以一定概率实现的某种效果(如游戏程序中的物品掉落等),就需要Java提供的Random类,该类用于生成…...
【Spring Cloud】微服务通信概述
SueWakeup 个人主页:SueWakeup 系列专栏:学习技术栈 个性签名:人生乏味啊,我欲令之光怪陆离 本文封面由 凯楠📷 友情赞助播出 目录 前言 1. Dubbo(Spring Cloud Alibaba)和 Spring Cloud 的适…...
MySQL的概述与安装
一、数据库的基本概念: 1.1 数据: 1) 描述事物的符号记录称为数据(Data)。数字、文字、图形、图像、声音、档案记录等 都是数据。 2)数据是以“记录”的形式按照统一的格式进行存储的,而不是…...
《被讨厌的勇气》书摘2
发生什么不重要,怎样看待才重要 生活的真谛就是活在当下 来自他人和社会的评价,造成了我们的骄傲和自卑。人们经常借“爱”之名,行控制之实 学会课题分离 当我们发现自己的价值的时候,才具备了让自己真正自由和自主的勇气 人…...
基于SpringBoot的会员制医疗预约服务管理信息系统
开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea 系统展示 系统功能模块 会员制医疗预约服务管…...
【二十三】【算法分析与设计】三柱汉诺塔详解,计算子移动次数,正常递归计算,观察数据得出数学规律,递归图得出数学规律,将递归函数转化为递推式
目录 汉诺塔递归 汉诺塔子移动次数的计算 牛牛的汉诺塔 选择正常的递归模拟计算子移动次数 根据具体数据得出数学规律 根据递归图得出数学规律 将递归函数转化为递推式 结尾 汉诺塔递归 汉诺塔是一个经典问题,相传在古印度圣庙中,有一种被称为汉…...
C# WPF编程-XAML
C# WPF编程-XAML XAML简述XAML子集XAML编译XAML基础XAML名称空间XAML中的属性和事件属性事件使用其他名称空间中的类型 加载和编译XAML XAML简述 XAML(Extensible Application Markup Language的简写,发音"zammel")是用于实例化.NET对象的标记语言。XAML…...
java 高级面试题(借鉴)(下)
雪花算法原理 第1位符号位固定为0,41位时间戳,10位workId,12位序列号,位数可以有不同实现。 优点:每个毫秒值包含的ID值很多,不够可以变动位数来增加,性能佳(依赖workId的实现…...
C++测试代码
C测试代码 目录 基于C实现的AOP功能 基于C实现的AOP功能 #include <iostream> #include <string>struct LogHeader {std::string prefix;std::string aspect; };template <typename T> void before(const std::string& msg, const LogHeader& heade…...
Flask python 开发篇:蓝图的使用
蓝图 引言一、为什么使用蓝图?二、蓝图的概念三、创建蓝图四、注册蓝图五、分享我的creat_app方法六、写在最后 引言 falsk是个轻量级的框架,核心实现简单,但同事可以让开发人员自由的扩展功能。开发中,使用模块导入的方式。 一…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
