Linux时间子系统之三:时间的维护者:timekeeper

本文详细探讨了Linux内核中的时间子系统,介绍了多种时间概念如RTC时间、墙上时间等,以及它们之间的关联方式。同时深入讲解了timekeeper结构的作用和初始化过程,包括时间更新与获取的具体实现。

专题文档汇总目录

Notes:

原文地址:Linux时间子系统之三:时间的维护者:timekeeper

 

本系列文章的前两节讨论了用于计时的时钟源:clocksource,以及内核内部时间的一些表示方法,但是对于真实的用户来说,我们感知的是真实世界的真实时间,也就是所谓的墙上时间,clocksource只能提供一个按给定频率不停递增的周期计数,如何把它和真实的墙上时间相关联?本节的内容正是要讨论这一点。

1.  时间的种类

内核管理着多种时间,它们分别是:

  • RTC时间
  • wall time:墙上时间
  • monotonic time
  • raw monotonic time
  • boot time:总启动时间

RTC时间  在PC中,RTC时间又叫CMOS时间,它通常由一个专门的计时硬件来实现,软件可以读取该硬件来获得年月日、时分秒等时间信息,而在嵌入式系统中,有使用专门的RTC芯片,也有直接把RTC集成到Soc芯片中,读取Soc中的某个寄存器即可获取当前时间信息。一般来说,RTC是一种可持续计时的,也就是说,不管系统是否上电,RTC中的时间信息都不会丢失,计时会一直持续进行,硬件上通常使用一个后备电池对RTC硬件进行单独的供电。因为RTC硬件的多样性,开发者需要为每种RTC时钟硬件提供相应的驱动程序,内核和用户空间通过驱动程序访问RTC硬件来获取或设置时间信息。

xtime  xtime和RTC时间一样,都是人们日常所使用的墙上时间,只是RTC时间的精度通常比较低,大多数情况下只能达到毫秒级别的精度,如果是使用外部的RTC芯片,访问速度也比较慢,为此,内核维护了另外一个wall time时间:xtime,取决于用于对xtime计时的clocksource,它的精度甚至可以达到纳秒级别,因为xtime实际上是一个内存中的变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。xtime记录的是自1970年1月1日24时到当前时刻所经历的纳秒数。

monotonic time  该时间自系统开机后就一直单调地增加,它不像xtime可以因用户的调整时间而产生跳变,不过该时间不计算系统休眠的时间,也就是说,系统休眠时,monotoic时间不会递增。

raw monotonic time  该时间与monotonic时间类似,也是单调递增的时间,唯一的不同是:raw monotonic time“更纯净”,他不会受到NTP时间调整的影响,它代表着系统独立时钟硬件对时间的统计。

boot time  与monotonic时间相同,不过会累加上系统休眠的时间,它代表着系统上电后的总时间

时间种类精度(统计单位)访问速度累计休眠时间受NTP调整的影响
RTCYesYes
xtimeYesYes
monotonicNoYes
raw monotonicNoNo
boot timeYesYes
2.  struct timekeeper

内核用timekeeper结构来组织与时间相关的数据,它的定义如下:

struct timekeeper {
    /* Current clocksource used for timekeeping. */
    struct clocksource *clock;
    /* NTP adjusted clock multiplier */
    u32    mult;
    /* The shift value of the current clocksource. */
    int    shift;

    /* Number of clock cycles in one NTP interval. */
    cycle_t cycle_interval;
    /* Number of clock shifted nano seconds in one NTP interval. */
    u64    xtime_interval;
    /* shifted nano seconds left over when rounding cycle_interval */
    s64    xtime_remainder;
    /* Raw nano seconds accumulated per NTP interval. */
    u32    raw_interval;

    /* Clock shifted nano seconds remainder not stored in xtime.tv_nsec. */
    u64    xtime_nsec;
    /* Difference between accumulated time and NTP time in ntp
     * shifted nano seconds. */
    s64    ntp_error;
    /* Shift conversion between clock shifted nano seconds and
     * ntp shifted nano seconds. */
    int    ntp_error_shift;

    /* The current time */
    struct timespec xtime;
    /*
     * wall_to_monotonic is what we need to add to xtime (or xtime corrected
     * for sub jiffie times) to get to monotonic time.  Monotonic is pegged
     * at zero at system boot time, so wall_to_monotonic will be negative,
     * however, we will ALWAYS keep the tv_nsec part positive so we can use
     * the usual normalization.
     *
     * wall_to_monotonic is moved after resume from suspend for the
     * monotonic time not to jump. We need to add total_sleep_time to
     * wall_to_monotonic to get the real boot based time offset.
     *
     * - wall_to_monotonic is no longer the boot time, getboottime must be
     * used instead.
     */
    struct timespec wall_to_monotonic;
    /* time spent in suspend */
    struct timespec total_sleep_time;
    /* The raw monotonic time for the CLOCK_MONOTONIC_RAW posix clock. */
    struct timespec raw_time;

    /* Offset clock monotonic -> clock realtime */
    ktime_t offs_real;----------------------------------------------------------------------monotonic-realtime的差值,一般未负数。

    /* Offset clock monotonic -> clock boottime */
    ktime_t offs_boot;----------------------------------------------------------------------boottime-monotonic的差值。

    /* Seqlock for all timekeeper values */
    seqlock_t lock;
}

 

 

其中的xtime字段就是上面所说的墙上时间,它是一个timespec结构的变量,它记录了自1970年1月1日以来所经过的时间,因为是timespec结构,所以它的精度可以达到纳秒级,当然那要取决于系统的硬件是否支持这一精度。

内核除了用xtime表示墙上的真实时间外,还维护了另外一个时间:monotonic time,可以把它理解为自系统启动以来所经过的时间,该时间只能单调递增,可以理解为xtime虽然正常情况下也是递增的,但是毕竟用户可以主动向前或向后调整墙上时间,从而修改xtime值。但是monotonic时间不可以往后退,系统启动后只能不断递增。奇怪的是,内核并没有直接定义一个这样的变量来记录monotonic时间,而是定义了一个变量wall_to_monotonic,记录了墙上时间和monotonic时间之间的偏移量,当需要获得monotonic时间时,把xtime和wall_to_monotonic相加即可,因为默认启动时monotonic时间为0,所以实际上wall_to_monotonic的值是一个负数,它和xtime同一时间被初始化,请参考timekeeping_init函数。

计算monotonic时间要去除系统休眠期间花费的时间,内核用total_sleep_time记录休眠的时间,每次休眠醒来后重新累加该时间,并调整wall_to_monotonic的值,使其在系统休眠醒来后,monotonic时间不会发生跳变。因为wall_to_monotonic值被调整。所以如果想获取boot time,需要加入该变量的值:

void get_monotonic_boottime(struct timespec *ts)
{
    struct timespec tomono, sleep;
    unsigned int seq;
    s64 nsecs;

    WARN_ON(timekeeping_suspended);

    do {
        seq = read_seqbegin(&timekeeper.lock);
        *ts = timekeeper.xtime;--------------------------------walltime
        tomono = timekeeper.wall_to_monotonic;-----------------monotonic time
        sleep = timekeeper.total_sleep_time;-------------------sleep time
        nsecs = timekeeping_get_ns();

    } while (read_seqretry(&timekeeper.lock, seq));
    
    set_normalized_timespec(ts, ts->tv_sec + tomono.tv_sec + sleep.tv_sec,
        (s64)ts->tv_nsec + tomono.tv_nsec + sleep.tv_nsec + nsecs);
}

 

raw_time字段用来表示真正的硬件时间,也就是上面所说的raw monotonic time,它不受时间调整的影响,monotonic时间虽然也不受settimeofday的影响,但会受到ntp调整的影响,但是raw_time不受ntp的影响,他真的就是开完机后就单调地递增。xtime、monotonic-time和raw_time可以通过用户空间的clock_gettime函数获得,对应的ID参数分别是 CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_MONOTONIC_RAW。

clock字段则指向了目前timekeeper所使用的时钟源,xtime,monotonic time和raw time都是基于该时钟源进行计时操作,当有新的精度更高的时钟源被注册时,通过timekeeping_notify函数,change_clocksource函数将会被调用,timekeeper.clock字段将会被更新,指向新的clocksource。

早期的内核版本中,xtime、wall_to_monotonic、raw_time其实是定义为全局静态变量,到我目前的版本(V3.4.10),这几个变量被移入到了timekeeper结构中,现在只需维护一个timekeeper全局静态变量即可:

static struct timekeeper timekeeper;

 

3.  timekeeper的初始化

timekeeper的初始化由timekeeping_init完成,该函数在start_kernel的初始化序列中被调用,timekeeping_init首先从RTC中获取当前时间:

void __init timekeeping_init(void)
{
    struct clocksource *clock;
    unsigned long flags;
    struct timespec now, boot;

    read_persistent_clock(&now);
    if (!timespec_valid_strict(&now)) {
        pr_warn("WARNING: Persistent clock returned invalid value!\n"
            "         Check your CMOS/BIOS settings.\n");
        now.tv_sec = 0;
        now.tv_nsec = 0;
    }

    read_boot_clock(&boot);
    if (!timespec_valid_strict(&boot)) {
        pr_warn("WARNING: Boot clock returned invalid value!\n"
            "         Check your CMOS/BIOS settings.\n");
        boot.tv_sec = 0;
        boot.tv_nsec = 0;
    }

    seqlock_init(&timekeeper.lock);

    ntp_init();--------------------------------------------对锁和ntp进行必要的初始化

    write_seqlock_irqsave(&timekeeper.lock, flags);
    clock = clocksource_default_clock();-------------------获取默认的clocksource,如果平台没有重新实现clocksource_default_clock函数,默认的clocksource就是基于jiffies的clocksource_jiffies,然后通过timekeeper_setup_inernals内部函数把timekeeper和clocksource进行关联
    if (clock->enable)
        clock->enable(clock);
    timekeeper_setup_internals(clock);

    timekeeper.xtime.tv_sec = now.tv_sec;----------------利用RTC的当前时间,初始化xtime,raw_time,wall_to_monotonic等字段
    timekeeper.xtime.tv_nsec = now.tv_nsec;
    timekeeper.raw_time.tv_sec = 0;
    timekeeper.raw_time.tv_nsec = 0;
    if (boot.tv_sec == 0 && boot.tv_nsec == 0) {
        boot.tv_sec = timekeeper.xtime.tv_sec;
        boot.tv_nsec = timekeeper.xtime.tv_nsec;
    }
    set_normalized_timespec(&timekeeper.wall_to_monotonic,
                -boot.tv_sec, -boot.tv_nsec);
    update_rt_offset();--------------------------------初始化代表实时时间和monotonic时间之间偏移量的offs_real字段,total_sleep_time字段初始化为0
    timekeeper.total_sleep_time.tv_sec = 0;
    timekeeper.total_sleep_time.tv_nsec = 0;
    write_sequnlock_irqrestore(&timekeeper.lock, flags);
}

 

xtime字段因为是保存在内存中,系统掉电后无法保存时间信息,所以每次启动时都要通过timekeeping_init从RTC中同步正确的时间信息。其中,read_persistent_clock和read_boot_clock是平台级的函数,分别用于获取RTC硬件时间和启动时的时间,不过值得注意到是,到目前为止(我的代码树基于3.4版本),ARM体系中,只有tegra和omap平台实现了read_persistent_clock函数。如果平台没有实现该函数,内核提供了一个默认的实现:

void __attribute__((weak)) read_persistent_clock(struct timespec *ts)
{
    ts->tv_sec = 0;
    ts->tv_nsec = 0;
}

void __attribute__((weak)) read_boot_clock(struct timespec *ts)
{
    ts->tv_sec = 0;
    ts->tv_nsec = 0;
}

 

 

 

那么,其他ARM平台是如何初始化xtime的?答案就是CONFIG_RTC_HCTOSYS这个内核配置项,打开该配置后,driver/rtc/hctosys.c将会编译到系统中,由rtc_hctosys函数通过do_settimeofday在系统初始化时完成xtime变量的初始化:

static int __init rtc_hctosys(void)
{
    int err = -ENODEV;
    struct rtc_time tm;
    struct timespec tv = {
        .tv_nsec = NSEC_PER_SEC >> 1,
    };
    struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);--------找到rtc设备

    if (rtc == NULL) {
        pr_err("%s: unable to open rtc device (%s)\n",
            __FILE__, CONFIG_RTC_HCTOSYS_DEVICE);
        goto err_open;
    }

    err = rtc_read_time(rtc, &tm);---------------------------------------------读取rtc时间到tm
    if (err) {
        dev_err(rtc->dev.parent,
            "hctosys: unable to read the hardware clock\n");
        goto err_read;

    }

    err = rtc_valid_tm(&tm);
    if (err) {
        dev_err(rtc->dev.parent,
            "hctosys: invalid date/time\n");
        goto err_invalid;
    }

    rtc_tm_to_time(&tm, &tv.tv_sec);---------------------------------------rtc时间转换成timespec时间

    do_settimeofday(&tv);--------------------------------------------------设置walltime

    dev_info(rtc->dev.parent,
        "setting system clock to "
        "%d-%02d-%02d %02d:%02d:%02d UTC (%u)\n",
        tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
        tm.tm_hour, tm.tm_min, tm.tm_sec,
        (unsigned int) tv.tv_sec);

err_invalid:
err_read:
    rtc_class_close(rtc);

err_open:
    rtc_hctosys_ret = err;

    return err;
}

 

 

4.  时间的更新

xtime一旦初始化完成后,timekeeper就开始独立于RTC,利用自身关联的clocksource进行时间的更新操作,根据内核的配置项的不同,更新时间的操作发生的频度也不尽相同,如果没有配置NO_HZ选项,通常每个tick的定时中断周期,do_timer会被调用一次,相反,如果配置了NO_HZ选项,可能会在好几个tick后,do_timer才会被调用一次,当然传入的参数是本次更新离上一次更新时相隔了多少个tick周期,系统会保证在clocksource的max_idle_ns时间内调用do_timer,以防止clocksource的溢出:

void do_timer(unsigned long ticks)
{
    jiffies_64 += ticks;
    update_wall_time();
    calc_global_load(ticks);
}

 

在do_timer中,jiffies_64变量被相应地累加,然后在update_wall_time中完成xtime等时间的更新操作,更新时间的核心操作就是读取关联clocksource的计数值,累加到xtime等字段中,其中还设计ntp时间的调整等代码,详细的代码就不贴了。

5.  获取时间

timekeeper提供了一系列的接口用于获取各种时间信息。

  • void getboottime(struct timespec *ts);    获取系统启动时刻的实时时间
  • void get_monotonic_boottime(struct timespec *ts);     获取系统启动以来所经过的时间,包含休眠时间
  • ktime_t ktime_get_boottime(void);   获取系统启动以来所经过的c时间,包含休眠时间,返回ktime类型
  • ktime_t ktime_get(void);    获取系统启动以来所经过的c时间,不包含休眠时间,返回ktime类型
  • void ktime_get_ts(struct timespec *ts) ;   获取系统启动以来所经过的c时间,不包含休眠时间,返回timespec结构
  • unsigned long get_seconds(void);    返回xtime中的秒计数值
  • struct timespec current_kernel_time(void);    返回内核最后一次更新的xtime时间,不累计最后一次更新至今clocksource的计数值
  • void getnstimeofday(struct timespec *ts);    获取当前时间,返回timespec结构
  • void do_gettimeofday(struct timeval *tv);    获取当前时间,返回timeval结构

 

关于timekeeping的补充

1. 更新walltime

更新walltime有几个通道,用户空间通过stime/settimeofday;内核do_timer()更新jiffies的时候通过update_wall_time()。

因为timekeeper.wall_to_monotonic依赖于timekeeper.xtime,所以每次更新xtime的时候都要考虑wall_to_monotonic。

int do_settimeofday(const struct timespec *tv)
{
...
    timekeeping_forward_now();---------------------------------------更新时间

    ts_delta.tv_sec = tv->tv_sec - timekeeper.xtime.tv_sec;
    ts_delta.tv_nsec = tv->tv_nsec - timekeeper.xtime.tv_nsec;-------计算调整的xtime差值
    timekeeper.wall_to_monotonic =
            timespec_sub(timekeeper.wall_to_monotonic, ts_delta);----将settimeofday的修改差值反映到wall_to_monotonic以达到保证monotonic递增的目的。
...
}

 

NTP调整walltime,do_adjtimex-->timekeeping_inject_offset。

int timekeeping_inject_offset(struct timespec *ts)
{
...
    timekeeping_forward_now();

    tmp = timespec_add(timekeeper.xtime,  *ts);
    if (!timespec_valid_strict(&tmp)) {
        ret = -EINVAL;
        goto error;
    }

    timekeeper.xtime = timespec_add(timekeeper.xtime, *ts);
    timekeeper.wall_to_monotonic =
                timespec_sub(timekeeper.wall_to_monotonic, *ts);
...
}

 

 

2. 更新total_sleep_time

维护total_sleep_time的地方有两处:一是通过RTC,在rtc_resume的时候通过timekeeping_inject_sleeptime();另一是通过timekeeping功能维护。

2.1 RTC维护sleeptime

RTC用于维护系统suspend时间通过rtc_suspend/rtc_resume。

static int __init rtc_init(void)
{
...
    rtc_class->suspend = rtc_suspend;
    rtc_class->resume = rtc_resume;
...
}

 

在rtc_suspend中保存old_rtc和old_system,然后在rec_resume中计算sleep_time。

static int rtc_resume(struct device *dev)
{
...
    /* snapshot the current rtc and system time at resume */
    getnstimeofday(&new_system);
    rtc_read_time(rtc, &tm);
    if (rtc_valid_tm(&tm) != 0) {
        pr_debug("%s:  bogus resume time\n", dev_name(&rtc->dev));
        return 0;
    }
    rtc_tm_to_time(&tm, &new_rtc.tv_sec);
    new_rtc.tv_nsec = 0;

    if (new_rtc.tv_sec < old_rtc.tv_sec) {
        pr_debug("%s:  time travel!\n", dev_name(&rtc->dev));
        return 0;
    }

    /* calculate the RTC time delta (sleep time)*/
    sleep_time = timespec_sub(new_rtc, old_rtc);

    /*
     * Since these RTC suspend/resume handlers are not called
     * at the very end of suspend or the start of resume,
     * some run-time may pass on either sides of the sleep time
     * so subtract kernel run-time between rtc_suspend to rtc_resume
     * to keep things accurate.
     */
    sleep_time = timespec_sub(sleep_time,
            timespec_sub(new_system, old_system));

    if (sleep_time.tv_sec >= 0)
        timekeeping_inject_sleeptime(&sleep_time);
    return 0;
}

 

 

2.2 timekeeping维护sleeptime

timekeeping的suspend/resume维护了sleeptime:

/**
 * timekeeping_resume - Resumes the generic timekeeping subsystem.
 *
 * This is for the generic clocksource timekeeping.
 * xtime/wall_to_monotonic/jiffies/etc are
 * still managed by arch specific suspend/resume code.
 */
static void timekeeping_resume(void)
{
    unsigned long flags;
    struct timespec ts;

    read_persistent_clock(&ts);---------------------------------------------------在resume再次读取persistent时间

    clocksource_resume();---------------------------------------------------------resume clocksource_list上的设备

    write_seqlock_irqsave(&timekeeper.lock, flags);

    if (timespec_compare(&ts, &timekeeping_suspend_time) > 0) {
        ts = timespec_sub(ts, timekeeping_suspend_time);
        __timekeeping_inject_sleeptime(&ts);--------------------------------------计算suspend前后的时间差值,作为sleeptime,并更新到timekeeper.total_sleep_time。
    }
    /* re-base the last cycle value */
    timekeeper.clock->cycle_last = timekeeper.clock->read(timekeeper.clock);
    timekeeper.ntp_error = 0;
    timekeeping_suspended = 0;
    timekeeping_update(false);
    write_sequnlock_irqrestore(&timekeeper.lock, flags);

    touch_softlockup_watchdog();

    clockevents_notify(CLOCK_EVT_NOTIFY_RESUME, NULL);--------------------------resume clockevents设备

    /* Resume hrtimers */
    hrtimers_resume();-----------------------------------------------------------打开hrtimers。
}

static int timekeeping_suspend(void)
{
    unsigned long flags;
    struct timespec        delta, delta_delta;
    static struct timespec    old_delta;-----------------------------------------注意此变量为static,在timekeeping_suspend被执行过程中会保持上一次调用值。

    read_persistent_clock(&timekeeping_suspend_time);----------------------------读取当前persistent时钟计数到timekeeping_suspend_time中。

    write_seqlock_irqsave(&timekeeper.lock, flags);
    timekeeping_forward_now();
    timekeeping_suspended = 1;

    /*
     * To avoid drift caused by repeated suspend/resumes,
     * which each can add ~1 second drift error,
     * try to compensate so the difference in system time
     * and persistent_clock time stays close to constant.
     */
    delta = timespec_sub(timekeeper.xtime, timekeeping_suspend_time);
    delta_delta = timespec_sub(delta, old_delta);
    if (abs(delta_delta.tv_sec)  >= 2) {
        /*
         * if delta_delta is too large, assume time correction
         * has occured and set old_delta to the current delta.
         */
        old_delta = delta;
    } else {
        /* Otherwise try to adjust old_system to compensate */
        timekeeping_suspend_time =
            timespec_add(timekeeping_suspend_time, delta_delta);
    }
    write_sequnlock_irqrestore(&timekeeper.lock, flags);

    clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL);----------------------suspend clockevent设备
    clocksource_suspend();---------------------------------------------------将相关clocksource拉入suspend状态

    return 0;
}

/* sysfs resume/suspend bits for timekeeping */
static struct syscore_ops timekeeping_syscore_ops = {
    .resume        = timekeeping_resume,
    .suspend    = timekeeping_suspend,
};

 

 

 

2.3 如何更新total_sleep_time

timekeeping_inject_sleeptime-->__timekeeping_inject_sleeptime更新timekeeper.total_sleep_time。

/**
 * __timekeeping_inject_sleeptime - Internal function to add sleep interval
 * @delta: pointer to a timespec delta value
 *
 * Takes a timespec offset measuring a suspend interval and properly
 * adds the sleep offset to the timekeeping variables.
 */
static void __timekeeping_inject_sleeptime(struct timespec *delta)
{
    if (!timespec_valid_strict(delta)) {
        printk(KERN_WARNING "__timekeeping_inject_sleeptime: Invalid "
                    "sleep delta value!\n");
        return;
    }

    timekeeper.xtime = timespec_add(timekeeper.xtime, *delta);-------------------------xtime需要加上睡眠时间
    timekeeper.wall_to_monotonic =
            timespec_sub(timekeeper.wall_to_monotonic, *delta);------------------------由于xtime加上了睡眠时间,但是monotonic不包括睡眠时间,所以wall_to_monotonic需要减去睡眠时间。
    update_sleep_time(timespec_add(timekeeper.total_sleep_time, *delta));--------------累积睡眠时间,更新到timekeeper.total_sleep_time,
}

 

 

 

3. cpu suspend对时间的影响

static void timekeeping_resume(void)
{
    unsigned long flags;
    struct timespec ts;

    read_persistent_clock(&ts);

    clocksource_resume();

    write_seqlock_irqsave(&timekeeper.lock, flags);

    if (timespec_compare(&ts, &timekeeping_suspend_time) > 0) {
        ts = timespec_sub(ts, timekeeping_suspend_time);
        __timekeeping_inject_sleeptime(&ts);
    }
    /* re-base the last cycle value */
    timekeeper.clock->cycle_last = timekeeper.clock->read(timekeeper.clock);
    timekeeper.ntp_error = 0;
    timekeeping_suspended = 0;
    timekeeping_update(false);
    write_sequnlock_irqrestore(&timekeeper.lock, flags);

    touch_softlockup_watchdog();

    clockevents_notify(CLOCK_EVT_NOTIFY_RESUME, NULL);
    printk("arnoldlu %s timekeeping_suspend_time=%ld.%09ld\n", __func__, timekeeping_suspend_time.tv_sec, timekeeping_suspend_time.tv_nsec);

    /* Resume hrtimers */
    hrtimers_resume();
}

static int timekeeping_suspend(void)
{
    unsigned long flags;
    struct timespec        delta, delta_delta;
    static struct timespec    old_delta;

    read_persistent_clock(&timekeeping_suspend_time);

    write_seqlock_irqsave(&timekeeper.lock, flags);
    timekeeping_forward_now();
    timekeeping_suspended = 1;

    /*
     * To avoid drift caused by repeated suspend/resumes,
     * which each can add ~1 second drift error,
     * try to compensate so the difference in system time
     * and persistent_clock time stays close to constant.
     */
    delta = timespec_sub(timekeeper.xtime, timekeeping_suspend_time);
    delta_delta = timespec_sub(delta, old_delta);
    if (abs(delta_delta.tv_sec)  >= 2) {
        /*
         * if delta_delta is too large, assume time correction
         * has occured and set old_delta to the current delta.
         */
        old_delta = delta;
    } else {
        /* Otherwise try to adjust old_system to compensate */
        timekeeping_suspend_time =
            timespec_add(timekeeping_suspend_time, delta_delta);
    }
    write_sequnlock_irqrestore(&timekeeper.lock, flags);
    printk("arnoldlu %s timekeeping_suspend_time=%ld.%09ld\n", __func__, timekeeping_suspend_time.tv_sec, timekeeping_suspend_time.tv_nsec);
    clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL);
    clocksource_suspend();

    return 0;
}

 

 

persistent时间读取-->关闭tick-->打开tick-->persistent时间读取

 

转载于:https://www.cnblogs.com/arnoldlu/p/7078250.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值