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 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返回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_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 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的中断不是均匀的,具体如下图所示:

timerline

②我们再来看看更新系统时间。对于有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的情况):

dtick

上图中,红色边框的模块是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如何运作?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/769066.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu22.04物理机双系统手动分区

ubuntu22.04物理机双系统手动分区 文章目录 ubuntu22.04物理机双系统手动分区1. EFI系统分区2. 交换分区3. /根分区4. /home分区分区后的信息 手动分区顺序:EFI系统分区(/boot/efi)、交换分区(/swap)、/根分区、/home分区。 具体参数设置: 1. EFI系统分…

OpenHarmony使用智能指针管理动态分配内存对象

概述 智能指针是行为类似指针的类,在模拟指针功能的同时提供增强特性,如针对具有动态分配内存对象的自动内存管理等。 自动内存管理主要是指对超出生命周期的对象正确并自动地释放其内存空间,以避免出现内存泄漏等相关内存问题。智能指针对…

Vue复习

1. MVVM 模型 ● Model(模型):表示应用程序中的数据模型。它代表着应用程序中的业务逻辑和状态。 ● View(视图):表示应用程序的用户界面。它是用户与应用程序交互的方式。 ● ViewModel(视图模…

Docker 安装 Nginx 容器,反向代理

Docker官方镜像https://hub.docker.com/ 寻找Nginx镜像 下载Nginx镜像 docker pull nginx #下载最新版Nginx镜像 (其实此命令就等同于 : docker pull nginx:latest ) docker pull nginx:xxx #下载指定版本的Nginx镜像 (xxx指具体版本号)检查当前所有Docker下载的镜像 docker…

关于使用TCP-S7协议读写西门子PLC字符串的问题

我们可以使用TCP-S7协议读写西门子PLC, 比如PLC中定义一个String[50] 的地址DB300.20 地址DB300.20 DB块编号为300,偏移量【地址】是30 S7协议是西门子PLC自定义的协议,默认端口102,本质仍然是TCP协议的一种具体实现&#xff…

HMI界面之:医疗设备界面

一、什么是医疗HMI界面 医疗HMI界面是指医疗设备或系统中的人机界面(Human-Machine Interface),用于与医疗设备进行交互和操作的界面。它是医疗设备中的重要组成部分,通过图形化、直观化的界面,使医护人员能够方便地控…

短剧APP系统开发:探索短剧的发展机遇,提高收益

近年来,短剧在各大社交平台上快速发展,市场规模大幅度上升,成为了大众闲暇时光的娱乐的首选方式之一,深受大众的喜爱。 与传统的影视相比,短剧时间短、节奏快、剧情爽,让给观众更加容易“上头”。对于创业…

举4例说明Python如何使用正则表达式分割字符串

在Python中,你可以使用re模块的split()函数来根据正则表达式分割字符串。这个函数的工作原理类似于Python内置的str.split()方法,但它允许你使用正则表达式作为分隔符。 示例 1: 使用单个字符作为分隔符 假设你有一个由逗号分隔的字符串,你可…

力扣:205. 同构字符串

前言:剑指offer刷题系列 问题: 给定两个字符串 s 和 t ,判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符,同时不改变字符…

【Lazy ORM 框架学习】

Gitee 点赞关注不迷路 项目地址 快速入门 模块所属层级描述快照版本正式版本wu-database-lazy-lambdalambda针对不同数据源wu-database-lazy-orm-coreorm 核心orm核心处理wu-database-lazy-sqlsql核心处理成处理sql解析、sql执行、sql映射wu-elasticsearch-starterESESwu-hb…

时间的守护者:无硫手指套的神奇传说

在钟表制造的世界里,有一个神奇的工具被誉为“精工制表良器”——那就是无硫手指套。这并不是一个普通的故事,而是一段讲述质量、技术和关怀的传奇。 很久以前,在一个钟表制造工坊里,技师们为了追求完美,不断地探索着提…

服务器被挖矿了怎么办,实战清退

当我们发现服务器资源大量被占用的时候,疑似中招了怎么办 第一时间重启服务是不行的,这些挖矿木马一定是会伴随着你的重启而自动重启,一定时间内重新霸占你的服务器资源 第一步检查高占用进程 top -c ps -ef 要注意这里%CPU,如果…

Linux操作系统及进程(三)进程优先级及特性

目录 一、优先级概念 二、查看系统进程 三、进程切换 一、优先级概念 1.cpu资源分配的先后顺序,就是指进程的优先权(priority)。 2.优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。…

pinia的异步以及getter

getter定义 action异步 使用

PyQt:实现菜单栏的点击拖动效果

一、整体步骤 1.设计UI文件 2.调用显示 3.效果展示 二、设计UI文件 1.添加 Scroll Area控件,作为菜单栏的布置区域 2.设置 Scroll Area控件的属性 3.Scroll Area控件内放置 按钮控件 组成菜单栏 此处,放置了需要了6个按钮,并设置按钮的固…

跨境电商测评自养号需要解决哪些问题?

现在做测评工作室这块的,真正有技术的每天单都做不过来,同样也滋生出很多找别人买个设备和账号就以为自己懂了,直接开始教学来割韭菜,很多人没接触过这行业,不知道里面的水很深,花了钱,却没有掌…

xilinx linux AXI GPIO 驱动学习

vivado工程 vivado 配置一个 AXI GPIO&#xff0c; 全输出&#xff0c;宽度为1 设备树解读 生成的对应pl.dtsi设备树文件如下 axi_gpio: gpio40020000 {#gpio-cells <2>;clock-names "s_axi_aclk";clocks <&clkc 15>;compatible "xlnx,…

巧用cpl文件维权和免杀(上)

cpl文件 CPL文件&#xff0c;是Windows控制面板扩展项&#xff0c;CPL全拼为Control Panel Item在system32目录下有一系列的cpl文件,分别对应着各种控制面板的子选项 列入我们winR输入main.cpl 将会打开控制面板中的鼠标属性 cpl文件本质是属于PE文件 但cpl并不像exe,更像是dl…

SQL107 将两个 SELECT 语句结合起来(二)(不用union,在where里用or)

select prod_id,quantity from OrderItems where quantity 100 or prod_id like BNBG% order by prod_id;在where子句里使用or

金蝶云星空和管易云单据接口对接

金蝶云星空和管易云单据接口对接 对接系统&#xff1a;管易云 管易云是金蝶旗下专注提供电商企业管理软件服务的子品牌&#xff0c;先后开发了C-ERP、EC-OMS、EC-WMS、E店管家、BBC、B2B、B2C商城网站建设等产品和服务&#xff0c;涵盖电商业务全流程。 写入目标:金蝶云星空 金…