在早期的linux内核版本的时间概念都是由周期时钟提供的。虽然比较有效,但是,对于关注能耗电量的系统上,就不能满足长时间休眠的需求,因为周期系统要求必须在一定的频率下,周期性的处于活动状态。因此,linux提出了tickless system,即无时钟系统。其关键就是判定系统当前是否无事可做,若是则禁用时钟系统。判定系统当前无事可做的依据是:如果运行队列时没有活动进程,内核将选择idle进程来运行,而此时动态时钟发挥作用。
一、动态时钟使用的数据结构tick_sched
  1.  struct tick_sched {
    struct hrtimer sched_timer;//用于实现时钟的定时器
    unsigned long check_clocks;
    enum tick_nohz_mode nohz_mode;
    ktime_t idle_tick;//禁用周期时钟之前,上一个时钟信号到期时间。
    int inidle;
    int tick_stopped;//周期时钟是否已经停用,若停用,则置为1
    unsigned long idle_jiffies;//存储周期时钟禁用时的jiffy值
    unsigned long idle_calls;//内核试图停用周期时钟次数。
    unsigned long idle_sleeps;//成功停用周期时钟次数。
    int idle_active;
    ktime_t idle_entrytime;
    ktime_t idle_waketime;
    ktime_t idle_exittime;
    ktime_t idle_sleeptime;//周期时钟上一次禁用的准确时间
    ktime_t idle_lastupdate;
    ktime_t sleep_length;//周期时钟禁用的时间长度
    unsigned long last_jiffies;
    unsigned long next_jiffies;//下一个定时器到期的jiffy值
    ktime_t idle_expires;//下一个将到期的经典定时器到期时间的jiffy值
    int do_timer_last;
    }

二、低分辨率下的动态时钟

每个定时软中断中会判断是否启用动态时钟,具体调用序列如下:
run_timer_softirq--》hrtimer_run_pending--》tick_check_oneshot_change--》tick_nohz_switch_to_nohz
而其tick_nohz_switch_to_nohz具体实现如下:
  1.  static void tick_nohz_switch_to_nohz(void)
    {
    struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
    ktime_t next;
    if (!tick_nohz_enabled)//若没有启动动态时钟直接返回
    return;
    local_irq_disable();
    if (tick_switch_to_oneshot(tick_nohz_handler)) {//切换时钟设备的处理函数为tick_nohz_switch_to_nohz
    local_irq_enable();
    return;
    }
    ts->nohz_mode = NOHZ_MODE_LOWRES;//设置为低分辨率
    /*
    * Recycle the hrtimer in ts, so we can share the
    * hrtimer_forward with the highres code.
    */
    //更新jiffy值
    hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
    /* Get the next period */
    next = tick_init_jiffy_update();
    //设置下一个时钟事件
    for (;;) {
    hrtimer_set_expires(&ts->sched_timer, next);
    if (!tick_program_event(next, ))
    break;
    next = ktime_add(next, tick_period);
    }
    local_irq_enable();
    printk(KERN_INFO "Switched to NOHz mode on CPU #%d\n",
    smp_processor_id());
    }

动态时钟处理程序tick_nohz_handler的实现如下:

 
  1.  static void tick_nohz_handler(struct clock_event_device *dev)
    {
    struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
    struct pt_regs *regs = get_irq_regs();
    int cpu = smp_processor_id();
    ktime_t now = ktime_get();
    dev->next_event.tv64 = KTIME_MAX;
    //设置当前cpu负责全局时钟设备
    if (unlikely(tick_do_timer_cpu == TICK_DO_TIMER_NONE))
    tick_do_timer_cpu = cpu;
    //若是全局时钟设备,则更新jiffy值
    if (tick_do_timer_cpu == cpu)
    tick_do_update_jiffies64(now);
    //若是启动禁用全局时钟,则更新watchdog的时间戳
    if (ts->tick_stopped) {
    touch_softlockup_watchdog();
    ts->idle_jiffies++;
    }
    update_process_times(user_mode(regs));
    profile_tick(CPU_PROFILING);
    //定时下一个时钟周期,并且更新jiffy
    while (tick_nohz_reprogram(ts, now)) {
    now = ktime_get();
    tick_do_update_jiffies64(now);
    }
    }
更新jiffy值tick_do_update_jiffies64得具体实现如下:
 
  1.  static void tick_do_update_jiffies64(ktime_t now)
    {
    ......
    //更新经过的jiffy时间,判断是否在一个周期内,若是则直接返回
    delta = ktime_sub(now, last_jiffies_update);
    if (delta.tv64 < tick_period.tv64)
    return;
    //计算经过的jiffy差值,判断是否大于一个周期,若是大于,则先更新一个周期的jiffy值
    delta = ktime_sub(now, last_jiffies_update);
    if (delta.tv64 >= tick_period.tv64) {
    delta = ktime_sub(delta, tick_period);
    last_jiffies_update = ktime_add(last_jiffies_update,
    tick_period);
    //若是大于一个周期,则再计算差值,再加上这个差值的jiffy值
    /* Slow path for long timeouts */
    if (unlikely(delta.tv64 >= tick_period.tv64)) {
    s64 incr = ktime_to_ns(tick_period);
    ticks = ktime_divns(delta, incr);
    last_jiffies_update = ktime_add_ns(last_jiffies_update,
    incr * ticks);
    }
    do_timer(++ticks);
    //更新下一个周期的jiffy值
    /* Keep the tick_next_period variable up to date */
    tick_next_period = ktime_add(last_jiffies_update, tick_period);
    }
    write_sequnlock(&xtime_lock);
    }
三、高分辨率下的动态时钟
高分辨率下的动态时钟实现比较容易,具体涉及的地方如下:
1、确定全局时钟的责任,在tick_sched_timer中,如下:
  1.  #ifdef CONFIG_NO_HZ
    /*
    * Check if the do_timer duty was dropped. We don't care about
    * concurrency: This happens only when the cpu in charge went
    * into a long sleep. If two cpus happen to assign themself to
    * this duty, then the jiffies update is still serialized by
    * xtime_lock.
    */
    if (unlikely(tick_do_timer_cpu == TICK_DO_TIMER_NONE))
    tick_do_timer_cpu = cpu;
    #endif
2、宣布高精度定时器下启用动态时钟,在tick_setup_sched_timer函数中,如下:
 

#ifdef CONFIG_NO_HZ

if (tick_nohz_enabled)

ts->nohz_mode = NOHZ_MODE_HIGHRES;

#endif

三、前面介绍的是在高低分辨率情况下,对动态时钟的支持,也即如何做一些更新jiffy类的操作。那么,何时启用和禁止时钟呢?前面已经说过,就是在idle任务中完成这样的操作,因为各个架构内容不同,但原理类似。我们举x86下面的实现片段,如下:
 
  1.  void cpu_idle(void)
    {
    ......
    /* endless idle loop with no priority at all */
    while () {
    tick_nohz_stop_sched_tick();//停止时钟
    while (!need_resched()) {
    ......
    //pm_idle的实现依赖具体架构而定,x86支持的一种实现是mwait实现,这种实现真正的是haunt住cpu,cpu切实不运转了,可以实现节能的目的,而ARM上可以使用wfi指令,cpu也haunt住,通过中断可以唤醒。
    /* Don't trace irqs off for idle */
    stop_critical_timings();
    pm_idle();//节能的关键部分
    start_critical_timings();
    }
    tick_nohz_restart_sched_tick();//启动时钟
    preempt_enable_no_resched();
    schedule();//调度切换
    preempt_disable();
    }
    }
而停止时钟tick_nohz_stop_sched_tick的本质实现:检查下一个定时器事件是否在一个时钟之后,若是,则重新编程时钟设备,忽略下一个时钟周期信号,直至有必要时候才恢复,而且在tick_sched中更新统计信息。重启时钟的原理也很简单,在此不再累述。
四、介绍一下广播模式
在某些省电模式启用时,时候总时间设备进入睡眠,而系统中不只有一个时钟事件设备,仍然可用另一个可工作的设备替换停止设备。在这种情况下,APIC是不工作的,但广播设备仍然可以工作,tick_handle_periodic_broadcast用作事件处理程序,该程序在每个tick_period之后。下面介绍tick_handle_periodic_broadcast的具体实现,如下:
  1.  static void tick_handle_periodic_broadcast(struct clock_event_device *dev)
    {
    ktime_t next;
    tick_do_periodic_broadcast();
    /*
    * The device is in periodic mode. No reprogramming necessary:
    */
    if (dev->mode == CLOCK_EVT_MODE_PERIODIC)//必须为oneshot模式
    return;
    /*
    * Setup the next period for devices, which do not have
    * periodic mode. We read dev->next_event first and add to it
    * when the event alrady expired. clockevents_program_event()
    * sets dev->next_event only when the event is really
    * programmed to the device.
    */
    for (next = dev->next_event; ;) {
    next = ktime_add(next, tick_period);
    //重新编程下一个事件
    if (!clockevents_program_event(dev, next, ktime_get()))
    return;
    tick_do_periodic_broadcast();//处理本cpu事件和向其他cpu发送ipi中断,从而调用其他cpu的事件处理程序
    }
    }

tick_do_periodic_broadcast --》tick_do_broadcast ,而tick_do_broadcast是关键,其实现如下:

  1.  static void tick_do_broadcast(struct cpumask *mask)
    {
    int cpu = smp_processor_id();
    struct tick_device *td;
    //调用本cpu的事件处理程序
    if (cpumask_test_cpu(cpu, mask)) {
    cpumask_clear_cpu(cpu, mask);
    td = &per_cpu(tick_cpu_device, cpu);
    td->evtdev->event_handler(td->evtdev);
    }
    if (!cpumask_empty(mask)) {
    //向其他cpu发送ipi中断
    td = &per_cpu(tick_cpu_device, cpumask_first(mask));
    td->evtdev->broadcast(mask);
    }
    }
向其他cpu发送ipi中断实现如下:
  1.  static void lapic_timer_broadcast(const struct cpumask *mask)
    {
    #ifdef CONFIG_SMP
    apic->send_IPI_mask(mask, LOCAL_TIMER_VECTOR);
    #endif
    }

最终ipi中断导致其他cpu调用本cpu的时钟事件设备的处理函数。

linux动态时钟探索的更多相关文章

  1. Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)

    在前面章节的讨论中,我们一直基于一个假设:Linux中的时钟事件都是由一个周期时钟提供,不管系统中的clock_event_device是工作于周期触发模式,还是工作于单触发模式,也不管定时器系统是工 ...

  2. Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)【转】

    转自:http://blog.csdn.net/droidphone/article/details/8112948 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] 数据结 ...

  3. Linux动态频率调节系统CPUFreq之二:核心(core)架构与API

    上一节中,我们大致地讲解了一下CPUFreq在用户空间的sysfs接口和它的几个重要的数据结构,同时也提到,CPUFreq子系统把一些公共的代码逻辑组织在一起,构成了CPUFreq的核心部分,这些公共 ...

  4. js 动态时钟

    js 动态时钟 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www ...

  5. 使用Timer类的两个实例 动态时钟

    package chapter16; import javax.swing.*; import chapter15.StillClock; import java.awt.event.*; publi ...

  6. linux 实时时钟(RTC)驱动【转】

    转自:http://blog.csdn.net/yaozhenguo2006/article/details/6820218 这个是linux内核文档关于rtc实时时钟部分的说明,此文档主要描述了rt ...

  7. Linux 系统时钟(date) 硬件时钟(hwclock)

    /********************************************************************* * Linux 系统时钟(date) 硬件时钟(hwclo ...

  8. 24小时学通Linux内核--内核探索工具类

    寒假闲下来了,可以尽情的做自己喜欢的事情,专心待在实验室里燥起来了,因为大二的时候接触过Linux,只是关于内核方面确实是不好懂,所以十天的时间里还是希望能够补充一下Linux内核相关知识,接下来继续 ...

  9. linux动态库默认搜索路径设置的三种方法

    众所周知, Linux 动态库的默认搜索路径是 /lib 和 /usr/lib .动态库被创建后,一般都复制到这两个目录中.当程序执行时需要某动态库, 并且该动态库还未加载到内存中,则系统会自动到这两 ...

随机推荐

  1. ORACLE中的支持正则表达式的函数

    ORACLE中的支持正则表达式的函数主要有下面四个:1,REGEXP_LIKE :与LIKE的功能相似2,REGEXP_INSTR :与INSTR的功能相似3,REGEXP_SUBSTR :与SUBS ...

  2. HDU 3966 Aragorn's Story 树链剖分+树状数组 或 树链剖分+线段树

    HDU 3966 Aragorn's Story 先把树剖成链,然后用树状数组维护: 讲真,研究了好久,还是没明白 树状数组这样实现"区间更新+单点查询"的原理... 神奇... ...

  3. asp.netajax开发应用心得-accordation控件的事件处理

    今天,再次运行以前的项目时,发现按钮的单击事件不起作用了,加了断点之后发现根本没有触发该事件.... 按照网上找到的答案,有的说把控件删掉重新拖拽一个进去,虽然以前也遇到过控件失效,重新拖拽有效的时候 ...

  4. hibernate入门实例

    1. 环境配置 1.1 hiberante环境配置 hibernate可实现面向对象的数据存储.hibernate的官网:http://hibernate.org/ 官网上选择hibernate OR ...

  5. PHP数学函数

    Abs: 取得绝对值. Acos: 取得反余弦值. Asin: 取得反正弦值. Atan: 取得反正切值. Atan2: 计算二数的反正切值. base_convert: 转换数字的进位方式. Bin ...

  6. PHP语言基础简单整理

    1.开始结束标记<? ... ?> 2.定义变量:$变量名 例: $str="锦清笋";不需要指明数据类型 3.输出语句:(1)echo "hello wor ...

  7. 通过Navicat for MySQL远程连接的时候报错mysql 1130

    1130 重装数据库 解决这个问题

  8. 【译】Android 6.0 Changes (机翻加轻微人工校对)

    Android 6.0 Changes In this document Runtime Permissions Doze and App Standby Apache HTTP Client Rem ...

  9. DotNetBar for Windows Forms 11.8.0.8冰河之刃重打包版

    关于 DotNetBar for Windows Forms 11.8.0.8_冰河之刃重打包版 基于 官方原版的安装包 + http://www.cnblogs.com/tracky 提供的补丁DL ...

  10. C++软件添加dump调试打印日志

    #include <DbgHelp.h> #pragma comment(lib, "dbghelp.lib") LONG WINAPI TopLevelExcepti ...