文章目录
1. 简介
内核版本:linux-6.1
时间子系统的作用:
- 时间读取功能
- 定时器功能,有传统的定时器timer、高精度定时器hrtimer
- 提供用于调度的tick,有周期tick和动态tick
2. Linux的各种时间
Linux的系统时钟ID定义如下,选其中常用的简单介绍一下。
相关的文档可以查看内核文档Documentation/core-api/timekeeping.rst
或ktime accessors — The Linux Kernel documentation。
用户态的clock_gettime相关接口也有一些介绍,Linux系统上,可以用man 2 clock_gettime
查看,或者查看网页版clock_getres(2) - Linux manual page。
wowotech介绍:Linux的时钟。
/*
* The IDs of the various system clocks (for POSIX.1b interval timers):
*/
#define CLOCK_REALTIME 0
#define CLOCK_MONOTONIC 1
#define CLOCK_PROCESS_CPUTIME_ID 2
#define CLOCK_THREAD_CPUTIME_ID 3
#define CLOCK_MONOTONIC_RAW 4
#define CLOCK_REALTIME_COARSE 5
#define CLOCK_MONOTONIC_COARSE 6
#define CLOCK_BOOTTIME 7
#define CLOCK_REALTIME_ALARM 8
#define CLOCK_BOOTTIME_ALARM 9
2.1. 墙上时间(REALTIME)
真实时间的时间,也就是协调世界时(UTC) ,起始于1970年1月1日0时0分0秒。
在内核中,REALTIME用在需要在重启期间保持的时间戳,如文件inode时间。这个时间会受到系统时间不连续跳跃的影响,比如闰秒更新、NTP调整settimeofday操作,应尽量避免用于内部使用。
2.2. 单调时间(MONOTONIC)
不可设置的全系统时钟,表示单调时间,正如POSIX所描述的那样——“过去某个未指定的点”。在Linux上,该点对应于系统自启动以来运行的秒数。CLOCK_MONOTONIC时钟不受系统时间不连续跳跃的影响(例如,如果系统管理员手动更改时钟),但受adjtime和NTP执行的增量调整的影响。此时钟不计算系统暂停的时间。在内核中用于可靠的时间戳和精确测量短时间间隔。用户态所有CLOCK_MONOTONIC变体都保证连续调用返回的时间不会倒退,但连续调用可能会根据体系结构返回相同(而不是增加)的时间值。
2.3. 单调时间(MONOTONIC_RAW)
与MONOTONIC类似,是基于硬件的原始时间,不受adjtime或NTP(Network Time Protocol,网络时间协议)调整的影响。在内核中也很少用到。
2.4. 系统启动时间(BOOTTIME)
不可设置的全系统时钟,与MONOTONIC类似,但它会统计系统挂起的时间。会受到settimeofday获取其他相关系统调用影响。
内核中可以用在需要系统挂起之后与其他机器保持时间同步的场景,比如密钥过期时间。
2.5. 国际原子时(TAI)
TAI是International Atomic Time的反向简称。一种不可设置的全系统时钟,源自墙上时间,忽略闰秒问题,不会有REALTIME由NTP插入闰秒引起的中断和向后跳转。内核中很少用到。
3. 时间子系统的初始化
时间子系统初始化中调用关系如下,其中timekeeping_init和time_init是比较重要的函数。后边会在做介绍。
3.1. tick_init
broadcast和nohz初始化,这里都是针对子系统软件上的初始化,没有实际的硬件操作。
broadcast用来实现CPU suspend后的唤醒等,nohz则与动态时钟调度相关。
tick_broadcast_init主要是一些CPU mask标志的初始化。
tick_nohz_init也依赖tick_nohz_full_running,如果tick_nohz_full_running为false,就直接返回了。而tick_nohz_full_running只有在housekeeping_setup -> tick_nohz_full_setup才会被设为true,需要使能CONFIG_CPU_ISOLATION才有可能被调到。
/**
* tick_init - initialize the tick control
*/
void __init tick_init(void)
{
tick_broadcast_init();
tick_nohz_init();
}
3.2. init_timers
init_timers是针对传统定时器的初始化,主要完成两个工作:
-
percpu变量timer_bases。
-
打开TIMER_SOFTIRQ软中断
void __init init_timers(void)
{
init_timer_cpus();
posix_cputimers_init_work();
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}
3.3. hrtimers_init
hrtimer顾名思义就是高精度定时器,hrtimers_init主要完成两个工作:
-
初始struct hrtimer_cpu_base,percpu变量hrtimer_bases。
-
打开HRTIMER_SOFTIRQ软中断
void __init hrtimers_init(void)
{
hrtimers_prepare_cpu(smp_processor_id());
open_softirq(HRTIMER_SOFTIRQ, hrtimer_run_softirq);
}
3.4. timekeeping_init
timekeeping_init是时间子系统非常重要的一个初始化,实现了各种时间基准的初始化,tk_core的timekeeper是timekeeping核心,并由tk_core衍生出了tk_fast_mono和tk_fast_raw,ktime_get相关的接口都是基于tk_core.timekeeper或tk_fast_mono、tk_fast_raw来获取时间的。
3.5. time_init
time_init是一个体系结构相关的函数,arm64的代码位于arch/arm64/kernel/time.c,主要流程如下。
- of_clk_init初始化时钟
- timer_probe查找系统定时器并初始化
- tick_setup_hrtimer_broadcast,在使能高精度timer和broadcast的情况下有用,创建一个名为bctimer的hrtimer,并注册ce_broadcast_hrtimer作为clock_event_device。
- arch_timer_get_rate,获取arch timer的速率,如果为0,直接panic。正常情况下,在timer_probe初始化定时器后,会返回系统定时器速率。
- 直接用获取的arch_timer速率计算lpj_fine,
lpj_fine = arch_timer_rate / HZ
。作为loops_per_jiffy的一种快速获取方法,从而节省启动时间。
3.5.1. timer_probe
这里比较重要的是timer_probe,arm和arm64都要求必须要有arch_timer用来做为clocksource,提供tick来驱动系统调度。这些timer用TIMER_OF_DECLARE来声明,TIMER_OF_DECLARE用于声明一个struct of_device_id,并放到__timer_of_table
段,其中struct of_device_id的data成员指向初始化换函数。比如drivers/clockcource/arm_arch_timer.c
。
TIMER_OF_DECLARE(armv7_arch_timer, "arm,armv7-timer", arch_timer_of_init);
TIMER_OF_DECLARE(armv8_arch_timer, "arm,armv8-timer", arch_timer_of_init);
在内核启动过程中,timer_probe遍历__timer_of_table
段,扫描设备树中可以匹配的定时器节点,并调用对应的初始化函数。