Linux tick

【periodic tick】

【数据结构】

enum tick_device_mode {

    TICKDEV_MODE_PERIODIC,

    TICKDEV_MODE_ONESHOT,

};

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可以工作在两种模式下,一种是周期性tick模式,另外一种是one shot模式。one shot模式主要和tickless系统以及高精度timer有关。

【tick device的分类以及和CPU的关系】

1local tick device

DEFINE_PER_CPU(struct tick_device, tick_cpu_device);

在多核系统中,每个cpu拥有一个本地tick设备,改设备具备一下特点:

  • 该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。
  • 该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

广播tick设备,参见下面介绍。

【tick device的设置】

1)设置时机

注册一个新的clock event device的时候,系统会调用tick_check_new_device函数来检查是否需要进行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();

    td = &per_cpu(tick_cpu_device, cpu);

    curdev = td->evtdev;

    /* cpu local device ? */

    if (!tick_check_percpu(curdev, newdev, cpu))

        goto out_bc;

    /* Preference decision */

    if (!tick_check_preferred(curdev, newdev))

        goto out_bc;

    if (!try_module_get(newdev->owner))

        return;

    /*

     * Replace the eventually existing device by the new

     * device. If the current device is the broadcast device, do

     * not give it back to the clockevents layer !

     */

    if (tick_is_broadcast_device(curdev)) {

        clockevents_shutdown(curdev);

        curdev = NULL;

    }

    clockevents_exchange_device(curdev, newdev);

    tick_setup_device(td, newdev, cpu, cpumask_of(cpu));

    if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)

        tick_oneshot_notify();

    return;

out_bc:

    /*

     * Can the new device be used as a broadcast device ?

     */

    tick_install_broadcast_device(newdev);

}

  • 首先要检查该tick device是否只为当前cpu服务,如果不是,则需要考虑下broadcast场景
  • 如果是percpu的,那么还要检查该tick device的精度和特性(主要是oneshot特性是否支持)是否比已有的tick device更好
  • 如果前面的检查都通过了,还要进行broadcast的检查,如果满足的话,还不能将其退回clockevents layer,仅仅是设定其状态为shutdown。curdev = NULL这一句很重要,在clockevents_exchange_device函数中,如果curdev == NULL的话,old device将不会从全局链表中摘下,挂入clockevents_released链表。
  • tick_setup_device函数用于具体的设置新tick device的动作。

2)设置过程

所谓setup一个tick device就是对tick device心仪的clock event设备进行设置,并将该tick device的evtdev指向新注册的这个clock event device。

【tick device的运作】

一般而言,底层的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。tick_handle_periodic函数的核心工作在tick_periodic函数中实现:
static void tick_periodic(int cpu)

{

    if (tick_do_timer_cpu == cpu) {  //global tick需要额外进行的系统级处理

        write_seqlock(&jiffies_lock);

        /* Keep track of the next tick event */

        tick_next_period = ktime_add(tick_next_period, tick_period);

        do_timer(1);  //更新jiffies,计算系统级平均负载

        write_sequnlock(&jiffies_lock);

        update_wall_time(); //更新walltime

    }

    update_process_times(user_mode(get_irq_regs()));  //更新当前进程相关时间

    profile_tick(CPU_PROFILING);

}

【dynamic tick】

【什么是dynamic tick】

dynamic tick又叫tickless mode,也就是说根据系统的当前运行状况,动态的启停“周期性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等)。

【tickless遇到的挑战】

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。我们知道,除非你是一个完全基于优先级的调度器,否则系统都会给进程分配一个时间片(time slice),当占用CPU的时间片配额使用完了,该进程会挂入队列,等待调度器分配下一个时间片,并调度运行。有tick当然比较简单,在该tick的timer中断中减去当前进程的时间片。没有tick总是比较麻烦,我能想到的方法是:假设我们给进程分配40ms的时间片,那么在调度该进程的时候需要设定一个40ms的timer,timer到期后,调度器选择另外一个进程,然后再次设定timer。当然,如果没有进程优先级的概念(或者说优先级仅仅体现在分配的时间片比较多的情况下),并且系统中处于runnable状态的进程较少,整体的运作还是OK的。如果有优先级概念怎么办?如果进程执行过程中被中断打断,切换到另外的进程怎么办?如果系统内的进程数目很多如何保证调度器的性能?算了,太复杂了,还是有tick比较好,因此实际中,linux kernel在有任务执行的时候还是会启动周期性的tick。当然,世界上没有绝对正确的设计,任何优雅的设计都是适用于一定的应用场景的。其实自然界的规律不也是这样吗?牛顿的定律也不是绝对的正确,仅仅适用于低速的场景,当物体运动的速度接近光速的时候,牛顿的经典力学定律都失效了。

【内核中的tickless】

传统的unix和旧的linux(2000年初之前的)都是有tick的(对于新的内核,配置CONFIG_HZ_PERIODIC的情况下也是有tick的),新的linux kernel中增加了tickless的选项:

---CONFIG_NO_HZ_IDLE

---CONFIG_NO_HZ_FULL

CONFIG_NO_HZ_IDLE是说在系统idle的时候是没有tick,当然,在系统运行的时候还是有tick的,因此,我们也称之dynamic tick或者NO HZ mode。3.10版本之后,引入一个full tickless mode,听起来好象任何情况下都是没有tick的,不过实际上也没有那么强,除了CPU处于idle的时候可以停下tick,CPU上有且只有一个进程运行的时候,也可以停下周期性tick,其他的情况下,例如有多个进程等待调度执行,都还是有tick的。这个配置实际上只是对High-performance computing (HPC)有意义的。

注:在系统初始化的时候,tick device的模式将被设定为TICKDEV_MODE_PERIODIC,即便clock event有one shot能力,即便系统配置了NO HZ。反正无论如何都需要从周期性tick开始。

【如何切换到tickless】

我们知道,开始tick device总是工作在周期性tickmode,一切就像过去一样,无论何时,系统总是有那个周期性的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 devicemode切换置one shot (Note:这时候event handler设定为hrtimer_interrupt)。同样的,底层的clock event device也会被设定为one shot mode。一旦进入one shot mode,那个周期性到来的timer中断就会消失了,从此系统只会根据系统中的hrtimer的设定情况来一次性的设定底层HW timer的触发。

【如何产生周期性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

【在idle的时候如何停掉tick】

我们知道,各个cpu上的swapper进程(0号进程,又叫idle进程)最后都是会执行cpu_idle_loop函数,该函数在真正执行cpu idle指令之前会调用tick_nohz_idle_enter,在该函数中,sched timer会被停掉,因此,周期性的HW timer不会再来,这时候将cpu从idle中唤醒的只能是和实际上系统中的hrtimer中的那个最近的超期时间有关。

【如何恢复tick】

概念同上,当从idle中醒来,tick_nohz_idle_exit函数被调用,重建sched timer,一切恢复了原状。

【broadcast tick】

在内核中,有cpuidle framework可以控制cpu的节电:当没有进程调度到该cpu上执行的时候,swapper进程粉墨登场,将该cpu会被推入到idle状态。当然CPU的idle状态有深有浅,CPU睡的比较深入的时候,有可能会关闭本地的timer硬件。这样就会引入一个很有意思的问题:local timer将无法唤醒CPU,该cpu上的所有的software timer将无法唤醒cpu。tick broadcast framework就是用来解决这个问题(C3STOP问题)的。

为了维持software timer的功能,我们可以考虑下面的方法:

(1)使用一个系统级别的HW timer(和cpuidle事件独立,不受其影响,always-on的,不属于任何一个CPU,如果使用GIC的话,那么该SOC timer的中断会使用SPI类型的中断,local timer都是使用PPI中断)来通知cpu醒来,处理software timer。

(2)如果没有系统级别的HW timer,那么可以考虑选择某个CPU CORElocal timer作为唤醒源(通过IPI唤醒其他处于idle状态的CPU core。当然,这样选择会导致该cpu无法进入idle mode,从而影响了power saving。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值