Linux内核时间管理子系统——timekeeping

时间的表示

在内核当中有几个不同的结构用于表示时间。

timespec

该数据结构来自于POSIX.1b规范,用于在用户态和内核态之间传递时间信息。POSIX还定义了许多API供用户态调用,例如clock_gettime(),clock_settime()等,其中的表示时间的结构都是timespec。在内核中该结构定义在include/uapi/linux/time.h。
struct timespec {
        __kernel_time_t tv_sec;                 /* seconds */
        long            tv_nsec;                /* nanoseconds */
};

timeval

该结构也用于用户态和内核态间的时间信息传递。不同于timespec的是它的两个成员分别是秒和微妙,精度要比timespec差。另外一个区别是相关API也不同,timeval相关的API是gettimeofday()等。
struct timeval {
        __kernel_time_t         tv_sec;         /* seconds */
        __kernel_suseconds_t    tv_usec;        /* microseconds */
};

ktime

不同于timespec,ktime定义于include/linux/ktime.h,且仅用于内核当中。对于64位系统而言,该结构就是一个64位有符号数。
union ktime {
        s64     tv64;
#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
        struct {
# ifdef __BIG_ENDIAN
        s32     sec, nsec;
# else
        s32     nsec, sec;
# endif
        } tv;
#endif
};
内核提供了多种helper API用于不同时间结构之间的转化。
static inline ktime_t timespec_to_ktime(struct timespec ts)
#define ktime_to_timespec(kt)           ns_to_timespec((kt).tv64)

static inline ktime_t timeval_to_ktime(struct timeval tv)
#define ktime_to_timeval(kt)            ns_to_timeval((kt).tv64)
内核提供了多种API用于将ktime转化为更常见的毫秒、微妙、纳秒等时间单位。
static inline s64 ktime_to_us(const ktime_t kt)
static inline s64 ktime_to_ms(const ktime_t kt)
#define ktime_to_ns(kt)                 ((kt).tv64)
另外内核还提供了用于对ktime之间进行加减等运算,对ktime和us、ns、ms之间进行加减等运算的API,详见include/linux/ktime.h。

时间的类型

下面这些时间类型均来自POSIX,详见clock_gettime()的manpage。

CLOCK_REALTIME

即wall clock(墙上时间),真实世界的时间(某年某月某日)。该时间可以被人为跳跃的修改,如date命令,或渐进的修改,如使用adjtime()函数或NTP。

CLOCK_MONOTONIC

是一种单调增长的时间。改时间不能被人为跳跃修改,但是可以被渐进的修改。

CLOCK_BOOTTIME

系统的运行时间。类似于CLOCK_MONOTONIC,区别是包含睡眠时间。

CLOCK_MONOTONIC_RAW

与CLOCK_MONOTONIC类似,但是是一种完全基于硬件的时间,甚至不能被渐进的修改(adjtime()或NTP)。

时间的记录

以上提到的各种时间均被内核使用timekeeper结构体记录。该结构定义于include/linux/timekeeper_internal.h。
/* Structure holding internal timekeeping values. */
struct timekeeper {
        /* Current clocksource used for timekeeping. */
        struct clocksource      *clock;
        /* NTP adjusted clock multiplier */
        u32                     mult;
        /* The shift value of the current clocksource. */
        u32                     shift;
        /* Number of clock cycles in one NTP interval. */
        cycle_t                 cycle_interval;
        /* Last cycle value (also stored in clock->cycle_last) */
        cycle_t                 cycle_last;
        /* 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;

        /* Current CLOCK_REALTIME time in seconds */
        u64                     xtime_sec;
        /* Clock shifted nano seconds */
        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. */
        u32                     ntp_error_shift;

        /*
         * 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;
        /* Offset clock monotonic -> clock realtime */
        ktime_t                 offs_real;
        /* time spent in suspend */
        struct timespec         total_sleep_time;
        /* Offset clock monotonic -> clock boottime */
        ktime_t                 offs_boot;
        /* The raw monotonic time for the CLOCK_MONOTONIC_RAW posix clock. */
        struct timespec         raw_time;
        /* The current UTC to TAI offset in seconds */
        s32                     tai_offset;
        /* Offset clock monotonic -> clock tai */
        ktime_t                 offs_tai;

};

clocksource

指向当前使用的时钟源。

xtime_sec, xtime_nsec

墙上时间(wall clock)。xtime_sec代表墙上时间的秒部分,xtime_nsec是墙上时间的纳秒部分左移timekeeper->shift位(注意是左移后的值)。

wall_to_monotonic, offs_real

墙上时间加上wall_to_monotonic就得到了monotonic时间。相反地monotonic时间加上offs_real就得到了墙上时间。

raw_time

代表monotonic raw时间。

total_sleep_time, offs_boot

系统在休眠状态的总时间。将monotonic时间加上total_sleep_time就得到了boottime。相反地boottime加上offs_boot就得到了墙上时间。

获取时间的API

获取墙上时间
ktime_t ktime_get_real(void)
void getnstimeofday(struct timespec *ts)
获取monotonic时间
ktime_t ktime_get(void)
void ktime_get_ts(struct timespec *ts)
获取monotonic raw时间
void getrawmonotonic(struct timespec *ts)
获取boottime时间
void getboottime(struct timespec *ts)

jiffie

jiffie可以看作是对于时钟中断数目的一种计数。有两种jiffie变量,分别是jiffies和jiffies_64,声明在include/linux/jiffies.h。
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
在32位体系结构中,内核通过loader脚本的技巧,将jiffies实现为jiffies_64的低32位。对于64位系统这两个变量其实完全等价。
在32位系统中,jiffies_64由于无法进行原子操作,因此对他的读取需要通过下面的API完成。
static inline u64 get_jiffies_64(void)
jiffie每经过一个时钟中断会被加一。时钟中断的频率由宏定义HZ决定。而HZ则由配置内核使得选项CONFIG_HZ决定。一般是几百到一千,即几百赫兹到一千赫兹。

时间的更新

对于jiffie和系统当中各种类型时间的更新在函数do_timer()中完成。参数ticks代表经过了多少个时钟中断。在函数实现中首先更新jiffie,再调用update_wall_time()更新系统时间。
/*
 * Must hold jiffies_lock
 */
void do_timer(unsigned long ticks)
{
        jiffies_64 += ticks;
        update_wall_time();
        calc_global_load(ticks);
}

update_wall_time

时间更新的最小粒度是一个NTP interval,即一个时钟中断间隔内的纳秒数。只有当时间流逝超过一个NTP interval时,timekeeper内的时间变量才会被更新。
#define NTP_INTERVAL_FREQ  (HZ)
#define NTP_INTERVAL_LENGTH (NSEC_PER_SEC/NTP_INTERVAL_FREQ)
下面是timekeeper中与时间更新相关的几个域的定义。cycle_interval代表一个NTP interval中的时钟源周期数。由于可能无法整除,因此它是一个取整后的结果。xtime_interval代表cycle_interval个周期对应的纳秒数的左移shift位。将xtime_interval右移shift位就可以得到cycle_interval个周期的纳秒数。将xtime_remainder右移shift位可以得到cycle_interval个周期的纳秒数与NTP interval的差。这三个量的关系如下:
xtime_interval >> shift + xtime_remainder >> shift == NTP_INTERVAL

cycle_interval = [ NTP_INTERVAL * 2 ^ clock->shift  / clock->mult ]
xtime_interval = interval * clock->mult = [ NTP_INTERVAL / 2 ^ clock->shift  / clock->mult ] * clock->mult
xtime_remainder = NTP_INTERVAL * 2 ^ clock->shift - xtime_interval

每经过N个cycle_interval周期,内核将对xtime增加N * (xtime_interval >> shift)个纳秒数,完成时间更新。由于在计算过程中考虑到了对NTP的处理,真实的处理过程要更加复杂。











你可能感兴趣的:(Linux)