在早期的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. WebAPI返回数据类型解惑

    本文来自:http://www.cnblogs.com/lzrabbit/archive/2013/03/19/2948522.html 最近开始使用WebAPI,上手很容易,然后有些疑惑 1.Web ...

  2. C++11新特性学习

    http://www.cprogramming.com/c++11/c++11-lambda-closures.html

  3. 错误 java.lang.ClassCastException: com.ylpw.sms.YZZYSenderUtil cannot be cast to ResourceBundle

    出现错误: java.lang.ClassCastException: com.ylpw.sms.YZZYSenderUtil cannot be cast to ResourceBundle 百度搜 ...

  4. POJ 3278 Catch That Cow

    注:本人英语很渣,题目大意大多来自百度~=0= 题目大意 农民约翰需要抓住他的牛,他和他的牛在一条直线上(估计是一维生物),约翰在N (0 ≤ N ≤ 100,000)处,他的牛在 K (0 ≤ K ...

  5. solr索引服务器的配置和solrj集成开发总结

    一.环境:solr6.2 + jdk1.8 + tomcat8   (solr不同版本需要最低的环境不同) solr6 需要至少jdk1.8   .对应的solr5+jdk1.7+tomcat7 实测 ...

  6. ubuntu命令

    chmod 777.755 root权限 rm 删除文件 rm -rf 删除文件夹 ctrl+o 保存 ctrl+x 退出 :wq 保存并退出 查看一台服务器上面哪些服务及端口 netstat  -l ...

  7. repcache实现memcached主从

    1.repcached介绍 repcached是日本人开发的实现memcached复制功能,它是一个单 master单 slave的方案,但它的 master/slave都是可读写的,而且可以相互同步 ...

  8. Ubuntu上安装MySql过程,以及遇到的一些问题

    今天在Ubuntu服务器上安装MySql的时候遇到了一些问题,记录下来,以防以后忘记. 安装环境:Ubuntu14.04 安装命令: //安装Mysal服务端//会提示输入root密码 sudo ap ...

  9. 前端:IE兼容性的相关方法

    有一段时间做前端的时候,IE下的就兼容性是比较令人头痛的问题,我在这一过程中也是看了很多的资料,然后把一些自己觉得比较普遍的问题进行一下相关的总结. 1.在IE6下,格式为png的图片在IE6上的透明 ...

  10. 场景7 Data Guard

    场景7  Data Guard 官方文档 :Oracle Data Guard Concepts and Administration 用于数据容灾,通过主备库同步(主库将redo日志传送到备库,一个 ...