linux 时间子系统之基本概念(一)

time相关的用户空间api:

clock_t clock(void);
int stime(const time_t *t);
The clock() function returns an approximation of processor time used by the program.The  value  returned  is the CPU time used so far as a clock_t; to get the number of seconds used.

time_t time(time_t *tloc);
time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).
linux kernel用系统调用sys_time和sys_stime来支持这两个函数。实际上,在引入更高精度的时间相关的系统调用之后(例如:sys_gettimeofday),
上面这两个系统调用可以用新的系统调在用户空间实现time和stime函数。在kernel中,只有定义了__ARCH_WANT_SYS_TIME这个宏,
系统才会提供上面这两个系统调用。当然,提供这样的系统调用多半是为了兼容旧的应用软件
配合上面的接口函数还有一系列将当前时间点到linux epoch的秒数转换成适合人类阅读的接口函数,例如asctime, ctime, gmtime, localtime, mktime, asctime_r, ctime_r, gmtime_r, localtime_r ,这些函数主要用来将time_t类型的时间转换成break-down time或者字符形式。

 int gettimeofday(struct timeval *tv, struct timezone *tz);
 The  functions  gettimeofday()  and  settimeofday()  can  get  and  set  the  time as well as a timezone. The use of the timezone structure is obsolete; the tz argument should normally be specified as NULL.
微秒级别的时间函数,gettimeofday函数可以获取从linux epoch到当前时间点的秒数以及微秒数(在内核态,这个时间值仍然是通过timekeeper模块获得的,具体接口是getnstimeofday64,该接口的时间精度是纳秒级别的,不过没有关系,除以1000就获得微秒级别的精度了),settimeofday则是设定从linux epoch到当前时间点的秒数以及微秒数。同样的,设定时间的进程必须拥有CAP_SYS_TIME的权利,否则会失败。tz参数是由于历史原因而存在,实际上内核并没有对timezone进行支持。

显然,sys_gettimeofday和sys_settimeofday这两个系统调用是用来支持上面两个函数功能的,值得一提的是:这些系统调用在新的POSIX标准中 gettimeofday和settimeofday接口函数被标注为obsolescent,取而代之的是clock_gettime和clock_settime接口函数,因为它可以提供纳秒级的时间准确度.

int clock_getres(clockid_t clk_id, struct timespec *res);
int clock_gettime(clockid_t clk_id, struct timespec *tp);
The  function  clock_getres() finds the resolution (precision) of the specified clock clk_id, and, if res is non-NULL, stores it in the struct timespec pointed
       to by res.  The resolution of clocks depends on the implementation and cannot be configured by a particular process.  If the time value pointed to by the argu‐
       ment tp of clock_settime() is not a multiple of res, then it is truncated to a multiple of res.

Sufficiently recent versions of glibc and the Linux kernel support the following clocks:

       CLOCK_REALTIME
              System-wide clock that measures real (i.e., wall-clock) time.  Setting this clock requires appropriate privileges.  This clock is affected by discontin‐
              uous jumps in the system time (e.g., if the system administrator manually changes the clock), and by the incremental adjustments performed by adjtime(3)
              and NTP.

       CLOCK_REALTIME_COARSE (since Linux 2.6.32; Linux-specific)
              A faster but less precise version of CLOCK_REALTIME.  Use when you need very fast, but not fine-grained timestamps.

       CLOCK_MONOTONIC
              Clock  that cannot be set and represents monotonic time since some unspecified starting point.  This clock is not affected by discontinuous jumps in the
              system time (e.g., if the system administrator manually changes the clock), but is affected by the incremental adjustments performed by  adjtime(3)  and
              NTP.

       CLOCK_MONOTONIC_COARSE (since Linux 2.6.32; Linux-specific)
              A faster but less precise version of CLOCK_MONOTONIC.  Use when you need very fast, but not fine-grained timestamps.

       CLOCK_MONOTONIC_RAW (since Linux 2.6.28; Linux-specific)
              Similar  to CLOCK_MONOTONIC, but provides access to a raw hardware-based time that is not subject to NTP adjustments or the incremental adjustments per‐
              formed by adjtime(3).

       CLOCK_BOOTTIME (since Linux 2.6.39; Linux-specific)
              Identical to CLOCK_MONOTONIC, except it also includes any time that the system is suspended.  This allows applications to get a suspend-aware  monotonic
              clock without having to deal with the complications of CLOCK_REALTIME, which may have discontinuities if the time is changed using settimeofday(2).

       CLOCK_PROCESS_CPUTIME_ID (since Linux 2.6.12)
              Per-process CPU-time clock (measures CPU time consumed by all threads in the process).

       CLOCK_THREAD_CPUTIME_ID (since Linux 2.6.12)
              Thread-specific CPU-time clock.

根据应用的需求,内核维护了几个不同系统时钟。大家最熟悉的当然就是CLOCK_REALTIME这个系统时钟,因为它表示了真实世界的墙上时钟(前面两节的接口函数没有指定CLOCK ID,实际上获取的就是CLOCK_REALTIME的时间值)。
CLOCK_REALTIME这个系统时钟允许用户对其进行设定(当然要有CAP_SYS_TIME权限),这也就表示在用户空间可以对该系统时钟进行修改,产生不连续的时间间断点。除此之外,也可以通过NTP对该时钟进行调整(不会有间断点,NTP调整的是local oscillator和上游服务器频率误差而已)。

clock()计算的是从程序运行开始被程序进程使用的CPU时间tick,而不是真正现实世界流逝的时间。很显然这个时间肯定要比现实时间要慢的多,除非CPU只有你这么一个进程在跑,占用了全部CPU。所以如果你要benchmark一段代码,看看运行这段代码花费了多少时间,用clock()很合适。那么要比较精确地计算现实流逝地时间,怎么办呢?用clock_gettime()

extern int clock_gettime(clockid_t, struct timespec*);

struct timespec {
 __kernel_time_t tv_sec;
 long tv_nsec;
};

第一个参数clockid_t类型常见的有四种:

CLOCK_REALTIME:系统实时时间。
CLOCK_MONOTONIC:从系统启动时开始计时,不受系统时间被用户改变的影响。
CLOCK_PROCESS_CPUTIME_ID:本进程到当前代码系统CPU花费的时间,包含该进程下的所有线程。
CLOCK_THREAD_CPUTIME_ID:本线程到当前代码系统CPU花费的时间。

可以看到,如果用CLOCK_PROCESS_CPUTIME_ID,就和直接用clock()是一样的。而要计算现实时间,就不能用后两种类型,必须用CLOCK_REALTIME或者CLOCK_MONOTONIC。要计算时间差,秒对秒一减,纳秒对纳秒一减,统一到同一个单位相加即可。

除了上面四种clockid_t类型,还有几种。

CLOCK_REALTIME_COARSE,CLOCK_MONOTONIC_COARSE:带COARSE后缀的,精度没有不带后缀的高,但是速度快。

CLOCK_MONOTONIC_RAW:和CLOCK_MONOTONIC类似,但是它是访问的硬件时间,不受NTP时间服务器的调整和adjtime()的影响。

CLOCK_BOOTTIME:和CLOCK_MONOTONIC一样,但是包含系统挂起的时间。

timekeeping模块是一个提供时间服务的基础模块。Linux内核提供各种time line,real time clock,monotonic clock、monotonic raw clock等,timekeeping模块就是负责跟踪、维护这些timeline的,并且向其他模块(timer相关模块、用户空间的时间服务等)提供服务,而timekeeping模块维护timeline的基础是基于clocksource模块和tick模块。通过tick模块的tick事件,可以周期性的更新time line,通过clocksource模块、可以获取tick之间更精准的时间信息。

为了完成Timekeeping的功能,硬件需要提供至少一种Clock source和Clock event source。Clock source只是一个简单的Counter,使能之后以固定的频率递增,kernel通过主动读取Counter可以判断时间的增加值,Clock event source是一个硬件Timer,设定后,可以以固定的频率产生中断,kernel可以在中断服务程序里完成上面所说的工作。理论上来说,只有Timer也可以维持系统的运行,但所有与时间相关的精度就仅限于Timer的产生频率。而Timer的中断频率一般不会很高,桌面系统常用100HZ~1000HZ,所以现在的系统Timer和Count都是必不可少的。

Timer的中断在kernel即对应与jiffies,每次Timer中断,jiffies就增加1,为防止jiffies在短时间溢出,还有一个64位的jiffies_64,二者共用低32位。
kernel每收到一次Timer中断,就会将jiffies加1,并读取Clock source的值,通过HZ与s/ms/ns之间的关系计算系统的当前时间,写入到xtime和wall_to_monotonic中;检查进程的时间片是否用完,并为用完时间片的进程分配新的时间片;处理统计工作(Profiling);并在退出中断的时候检查soft timer是否到时。

ktime_get调用很有可能发生在两次tick之间,这时候,仅仅依靠当前系统时钟的值精度就不甚理想了,毕竟那个时间值是per tick更新的。因此,为了获得高精度,ns值的获取是通过timekeeping_get_ns完成的,该函数获取了real time clock的当前时刻的纳秒值,而这是通过上一次的tick时候的real time clock的时间值(xtime_nsec)加上当前时刻到上一次tick之间的delta时间值计算得到的。

如果是global tick,需要调用update_wall_time来更新系统时间。timekeeping模块是按照自己的节奏来更新系统时间的,更新一般是发生在周期性tick到来的时候。如果HZ=100的话,那么每10ms就会有一个tick事件(clockevent事件),跟的太紧,会浪费CPU,跟的太松会损失一些精度。timekeeper中的cycle_interval成员就是周期性tick的cycle interval,如果距离上次的更新还不到一个tick的时间,那么就不再更新系统时间,直接退出。

下面是老的内核架构time相关模块之间的层次关系:
在这里插入图片描述
首先,在每个architecture相关的代码中要有实现clock event和clock source模块。这里听起来名字是高大上,实际上,这里仅仅是借用了新时间子系统的名词,对于旧的内核,clock event就是通过timer硬件的中断处理函数完成的,在此基础上可以构建tick模块,tick模块维护了系统的tick,例如系统存在10ms的tick,每次tick到来的时候,timekeeping模块就增加系统时间,如果timekeeping完全是tick驱动,那么它精度只能是10ms,为了更高精度,clock source模块就是一个提供tick之间的offset时间信息的接口函数。

如果说clock source是一个time line,那么clock event是在timeline上指定的点产生clock event的设备,之所以能产生异步事件,当然是基于中断子系统了,clock source chip driver会申请中断并调用通用clock event模块的callback函数来通知这样的异步事件。

hrtimer 是靠硬件的timer 的one-shot模式实现的,找到最近要超期的那个timer,设定到HW timer中,hrtimer的主要左右是提供程序精确的延时。

hrtimer本身是不依赖于tick的,因此它的逻辑很简单,hrtimer到期后执行callback,然后就是从红黑树中摘下最近的那个hrtimer,设定下一次触发的时间点,如此周而复始。因此,hw timer的中断触发时间是和红黑树中的hrtimer有关,如果红黑树是空的,那么hw timer就静默了。

Here are several ways you can identify if your system supports high resolution timers.
Examine kernel startup messages
Watch the kernel boot messages, or use dmesg. If the kernel successfully turns on the high resolution timer feature, it will print the message “Switched to high resolution mode on CPU0” (or something similar) during startup.

Examine /proc/timer_list
You can also examine the timer_list, and see whether specific clocks are listed as supporting high resolution. Here is a dump of /proc/timer_list on an OSK (ARM-based) development board, showing the clocks configured for high resolution.

cat /proc/timer_list

参考资料:
https://www.landley.net/kdocs/ols/2006/ols2006v1-pages-333-346.pdf

发布了85 篇原创文章 · 获赞 26 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/whuzm08/article/details/103960180