linux时间子系统 - 周期性任务

如果内核是有生命的话,那么时间就是内核的心脏,控制着内核的脉搏,但是这颗心脏跳动的方式根据硬件的配置会有不同的跳动方式。内核中有大量的需求需要时间的帮助,比如:定时、进程调度、获得时间等等,在内核中时间子系统就是来实现这部分功能的,根据不同的工作模式(periodic和oneshot)会有不同的工作函数来实现周期性任务,具体分为低精度模式和高精度模式。

1. 低精度模式


1.1 tick_handle_periodic

tick_handle_periodic函数的调用图如下:

linux时间子系统 - 周期性任务_第1张图片

此函数主要是更新jiffies_64、计算负载、更新墙上时间也就是系统时间,由于是工作在periodic模式,所以每次执行完毕,没必要reprogram下一次的event。

下面再介绍一下update_process_times

1.2 update_process_times

linux时间子系统 - 周期性任务_第2张图片

A : 从periodic转换到oneshot模式(动态模式),分为低精度和高精度
B:调度相关操作
C:posix timer相关操作

2. 高精度模式


2.1 高精度周期性任务的注册

在高精度模式下,时钟的轮转都是动态的,所以要执行周期性的任务需要基于动态时钟来模拟周期时钟,高精度时钟是基于hrtimer实现的,所以周期性的任务被初始化为一个hrtimer并被注册。

linux时间子系统 - 周期性任务_第3张图片

A : 更换要调用的函数为hrtimer_interrupt
B : 设置周期性的hrtimer

2.2 hrtimer_interrupt

linux时间子系统 - 周期性任务_第4张图片

hrtimer_interrupt做的主要工作是:
1.得到此时的time_now
2.运行hrtimer队列中的任务
3.设置下一次中断的时间

__hrtimer_run_queues主要从队列中得到合适的函数来执行

(kernel/time/hrtimer.c)

static void __hrtimer_run_queues(struct hrtimer_cpu_base *cpu_base, ktime_t now)
{
    struct hrtimer_clock_base *base = cpu_base->clock_base;
    unsigned int active = cpu_base->active_bases;

    for (; active; base++, active >>= 1) {
        struct timerqueue_node *node;
        ktime_t basenow;

        if (!(active & 0x01))
            continue;

        basenow = ktime_add(now, base->offset);

        while ((node = timerqueue_getnext(&base->active))) {--------------得到下一个hrtimer
            struct hrtimer *timer;

            timer = container_of(node, struct hrtimer, node);

            /*
             * The immediate goal for using the softexpires is
             * minimizing wakeups, not running timers at the
             * earliest interrupt after their soft expiration.
             * This allows us to avoid using a Priority Search
             * Tree, which can answer a stabbing querry for
             * overlapping intervals and instead use the simple
             * BST we already have.
             * We don't add extra wakeups by delaying timers that
             * are right-of a not yet expired timer, because that
             * timer will have to trigger a wakeup anyway.
             */
            if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer))
                break;

            __run_hrtimer(cpu_base, base, timer, &basenow);-------------运行hrtimer
        }
    }
}

不过有一点还没说清楚,hrtimer_interrupt是和tick_handle_periodic一样执行周期任务的,但是从hrtimer_interrupt中只能看到它会调用相关的function,没有看到tick_periodic相关的操作。这是因为在切换到oneshot的时候,已经把相关的hrtimer注册到其中了,具体下节2.2介绍

2.3 tick_setup_sched_timer

在1.2节中对于模式的切换有一个函数没有太多介绍,这个函数就是tick_setup_sched_timer,它的主要工作就是完成执行类似tick_periodic相关操作函数的初始化,把其加入到hrtimer_cpu_base中

( kernel/time/tick-sched.c )

void tick_setup_sched_timer(void)
{
    struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
    ktime_t now = ktime_get();

    /*
     * Emulate tick processing via per-CPU hrtimers:
     */
    hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);--把sched_timer这个hrtimer加入到hrtimer_cpu_base中
    ts->sched_timer.function = tick_sched_timer;------------------------周期性调用的函数(类似tick_periodic)

    /* Get the next period (per cpu) */
    hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());

    /* Offset the tick to avert jiffies_lock contention. */
    if (sched_skew_tick) {
        u64 offset = ktime_to_ns(tick_period) >> 1;
        do_div(offset, num_possible_cpus());
        offset *= smp_processor_id();
        hrtimer_add_expires_ns(&ts->sched_timer, offset);
    }

    hrtimer_forward(&ts->sched_timer, now, tick_period);
    hrtimer_start_expires(&ts->sched_timer, HRTIMER_MODE_ABS_PINNED);
    tick_nohz_activate(ts, NOHZ_MODE_HIGHRES);

2.4 tick_sched_timer

static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer)
{
    struct tick_sched *ts =
        container_of(timer, struct tick_sched, sched_timer);
    struct pt_regs *regs = get_irq_regs();
    ktime_t now = ktime_get();

    tick_sched_do_timer(now);----------------------更新jiffies64

    /*
     * Do not call, when we are not in irq context and have
     * no valid regs pointer
     */
    if (regs)
        tick_sched_handle(ts, regs);

    /* No need to reprogram if we are in idle or full dynticks mode */
    if (unlikely(ts->tick_stopped))
        return HRTIMER_NORESTART;

    hrtimer_forward(timer, now, tick_period);-----

    return HRTIMER_RESTART;
}

change log

date content linux
2016.12.25 (圣诞节还在写博客) linux4.6.3
2017.1.2 分清高精度和低精度周期任务 linux4.6.3

你可能感兴趣的:(Linux内核)