概述
时间的表示
在内核当中有几个不同的结构用于表示时间。
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内核时间管理子系统——timekeeping的全部内容,希望文章能够帮你解决Linux内核时间管理子系统——timekeeping所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复