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

    专题文档汇总目录

     Notes:TickDevice模式,以及clocckevent设备。TickDevice设备的初始化,TickDevice是如何加入到系统中的。周期性Tick的产生。

    原文地址: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设备。

    Notes:clock_event_device是对能触发clock时间设备的属性、能力的一个描述。属性包括features、irq、cpumask、rating等,能力包括设置next_event、event_handler、broadcast等。两种模式是周期性和一次触发模式。周期性tick只需要设置一次tick周期;而one shot每次到期后需要再次设置cycles,主要用户NOTICK和hrtimer。

    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的关系

    Notes:

    local tick device DEFINE_PER_CPU(struct tick_device, tick_cpu_device) 必须能将中断送达绑定CPU
    global tick device int tick_do_timer_cpu __read_mostly = TICK_DO_TIMER_BOOT;

    从local tick device中选一个而作为global tick device

    broadcast tick device static struct tick_device tick_broadcast_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); ----其他文档中描述
    }

    Notes:从一大堆goto out_bc可知,对于替换当前tick device还是很谨慎的。不满足条件的clock event device作为broadcast device备选。交给tick_install_broadcast_device裁决,tick_check_broadcast_device裁决通过后,替换当前的tick_broadcast_device.evtdev。

    (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);------和性能剖析相关,不详述了
    }

    原创文章,转发请注明出处。蜗窝科技

    http://www.wowotech.net/timer_subsystem/periodic-tick.html

  • 相关阅读:
    多重背包POJ1276不要求恰好装满 poj1014多重背包恰好装满
    哈理工1053完全背包
    求最小公倍数与最大公约数的函数
    Bus Pass ZOJ 2913 BFS 最大中取最小的
    POJ 3624 charm bracelet 01背包 不要求装满
    HavelHakimi定理(判断一个序列是否可图)
    z0j1008Gnome Tetravex
    ZOJ 1136 Multiple BFS 取模 POJ 1465
    01背包 擎天柱 恰好装满 zjut(浙江工业大学OJ) 1355
    zoj 2412 水田灌溉,求连通分支个数
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/7078199.html
Copyright © 2011-2022 走看看