专题文档汇总目录

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

Linux时间子系统之(十二):periodic tick的更多相关文章

  1. Linux时间子系统之(二):软件架构

    专题文档汇总目录 Notes:从框架上讲解了时间子系统,从底向上包括CPU Local TImer.Global Counter.Clock Souce/Clock Events模块管理.Tick D ...

  2. Linux时间子系统(十二) periodic tick

    一.tick device概念介绍 1.数据结构 在内核中,使用struct tick_device来抽象系统中的tick设备,如下: struct tick_device {     struct ...

  3. Linux时间子系统专题汇总

    关于Linux时间子系统有两个系列文章讲的非常好,分别是WowoTech和DroidPhone. 还有两本书分别是介绍: Linux用户空间时间子系统<Linux/UNIX系统编程手册>的 ...

  4. Linux时间子系统之(十四):tick broadcast framework

    专题文档汇总目录 Notes:BroadcastTick作为cpuidle的waker,硬件基础.BroadcastTick嵌入在当前系统Tick框架中.BroadcastTick设备初始化:周期性T ...

  5. Linux时间子系统之(十六):clockevent

    专题文档汇总目录 Notes:介绍struct clocke_event_device及其功能feature.模式:触发event接口clockevents_program_event:clockev ...

  6. Linux时间子系统之(十三):Tick Device layer综述

    专题文档汇总目录 Notes:从概念层次描述了tick-comm.oneshot tick.broadcast tick:重点介绍了tick和tickless概念及其区别,两种tick device: ...

  7. Linux时间子系统之(十五):clocksource

    专题文档汇总目录 Notes:clocksource基本概念,struct clocksource详解:注册和注销clocksource:内核如何选取clocksource:clocksource相关 ...

  8. Linux时间子系统(十五) clocksource

    一.前言 和洋葱一样,软件也是有层次的,内核往往需要对形形色色的某类型的驱动进行抽象,屏蔽掉其具体的特质,获取该类驱动共同的逻辑,而又根据这些逻辑撰写该类驱动的抽象层.嵌入式系统总是会提供timer的 ...

  9. Linux时间子系统之(四):timekeeping

    专题文档汇总目录 Notes:timekeeping模块的狠心数据结构是timekeeper,它维护了系统不同类型时钟的时间值,并且介绍了获取不同类型时钟时间的函数. clocksource切换通过c ...

随机推荐

  1. anndroid 模糊引导界面

    先上两张图,后面补上代码 我们以前的写法是在需要显示模糊引导的地方,写一个布局,然后第一次使用的时候显示出来.但是这样做代码结构不清晰,所以我们有必要将这些View独立出来,写成一个自定义的View ...

  2. Linux - grep的一些进阶选项

    [root@www ~]# grep [-A] [-B] [--color=auto] '搜寻字串' filename 选项与参数: -A :后面可加数字,为 after 的意思,除了列出该行外,后续 ...

  3. 智能合约最佳实践 之 Solidity 编码规范

    每一门语言都有其相应的编码规范, Solidity 也一样, 下面官方推荐的规范及我的总结,供大家参考,希望可以帮助大家写出更好规范的智能合约. 命名规范 避免使用 小写的l,大写的I,大写的O 应该 ...

  4. 别跟我谈EF抵抗并发,敢问你到底会不会用EntityFramework

    前言 一直以来写的博文都是比较温婉型的博文,今天这篇博文算是一篇批判性博文,有问题欢迎探讨,如标题,你到底会不会用EntityFramework啊. 你到底会不会用EntityFramework啊 面 ...

  5. 用python开发调试器——起始篇

    首先,你得准备一套python开发环境,正常情况下,一般是在windows下开发的,因为win系统应用广泛,再则就是要有个IDE,这里我选择我熟悉的Eclipse.环境搭建,网上都有,比如:http: ...

  6. java并发包分析之———concurrentHashMap

    一.Map体系 Hashtable是JDK 5之前Map唯一线程安全的内置实现(Collections.synchronizedMap不算).Hashtable继承的是Dictionary(Hasht ...

  7. Space Golf~物理题目

    Description You surely have never heard of this new planet surface exploration scheme, as it is bein ...

  8. mysql distinct field1,field2,field3, .... from table

    mysql distinct field1,field2,field3, .... from table 我们知道 这样的sql可以去掉重复项 (field1的重复项); select distinc ...

  9. WebApi PUT、DELETE请求时出现405 - 不允许用于访问此页的 HTTP 谓词。

    开发时,新建WebApi项目需要用到Restful规范,此时请求有POST\PUT\DELETE\GET等请求 此时需要在web.config中加入 <system.webServer> ...

  10. Grunt的配置和使用

    Grunt和Grunt插件是通过NodeJs的包管理工具npm安装并进行管理的. Grunt 0.4.x必须配合NodeJs=>0.8.0版本使用(奇数版本的NodeJs不是稳定的开发版本)   ...