zoukankan      html  css  js  c++  java
  • Linux时间子系统(十二) periodic tick

    一、tick device概念介绍

    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有关,会在另外的文档中描述,本文主要介绍periodic mode。

    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 device的文章中,我们知道:底层的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)
        if (!cpumask_test_cpu(cpu, newdev->cpumask))        goto out_bc;

        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服务)。另外一种情况是新设备不是CPU的local timer,当然原配也没有那么专一。

    我们先看看第一种情况:如果cpumask_equal返回true,那么说明原配也是local timer,那么没有办法了,谁的rating高就选谁。如果cpumask_equal返回false,那么说明原配不是local timer,那么即便新来的rating低一些也还是优先选择local timer。

    我们再看看第二种情况:这里我绝对逻辑有问题,不知道是代码的问题还是我还没有考虑清楚,先TODO吧。

    (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,具体如下:

    void tick_handle_periodic(struct clock_event_device *dev)
    {
        int cpu = smp_processor_id();
        ktime_t next;

        tick_periodic(cpu); ----周期性tick中要处理的内容,参考下节描述

        if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
            return;
        
        next = ktime_add(dev->next_event, tick_period);----计算下一个周期性tick触发的时间
        for (;;) {
            if (!clockevents_program_event(dev, next, false))---设定下一个clock event触发的时间
                return;
            
            if (timekeeping_valid_for_hres())------在其他文档中描述
                tick_periodic(cpu);
            next = ktime_add(next, tick_period);
        }
    }

     

    如果该tick device所属的clock event device工作在one shot mode,那么还需要为产生周期性tick而进行一些额外处理。

    2、周期性tick中要处理的内容

    代码如下:

    static void tick_periodic(int cpu)
    {
        if (tick_do_timer_cpu == cpu) {----global tick需要进行一些额外处理
            write_seqlock(&jiffies_lock); 
            tick_next_period = ktime_add(tick_next_period, tick_period);

            do_timer(1);-------------更新jiffies,计算平均负载
            write_sequnlock(&jiffies_lock);
            update_wall_time();----------更新wall time
        }

        update_process_times(user_mode(get_irq_regs()));----更新和当前进程相关的内容
        profile_tick(CPU_PROFILING);------和性能剖析相关,不详述了
    }

  • 相关阅读:
    GIT
    curl
    排序算法
    《软件设计师》考点分布
    lua第三方库
    WordPress
    go http
    Unity UI相关总结
    note
    LUA重难点解析
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8448309.html
Copyright © 2011-2022 走看看