1. 概览
你的百米记录是多少?你的千米赛跑记录是多少?你的爱车到达百公里每小时的时间是多少?在前一天晚上你就设置好了电饭煲,让其在第二天早上你醒来前就煮好粥。你的上下班点又是多少?你们又是如何和你的异性定好约会时间的?可见在人类社会中时间的概念是相当的重要。在linux的内核中也是如此,有时需要等待硬件一段时间以让其初始化完成。有时你需要在确定的几秒后来访问硬件,此时你的程序需要对比当前的时间点和开始等待的时间点间隔是否达到了要求值。
2. 每秒系统滴答次数–HZ
HZ代表kernel的系统时钟每秒的产生的中断次数,例如HZ为250时,那么每秒系统时钟产生中断的间隔则是1/250s即4ms。其定义如下
//file:kernel/include/asm-generic/param.h
# define HZ CONFIG_HZ /* Internal kernel timer frequency */
可见HZ由CONFIG_HZ定义,但是CONFIG_HZ则是编译时自动生成的,可以从.config中找到具体的值
//file:kernel/.config
CONFIG_HZ=250
在代码中也可以直接打印出HZ的值,其代码如下
time_test_drv_init
TIME_TEST_INFO("[HZ]%d[jiffies]%lu", HZ, jiffies);
执行结果如下
[492257.266132] timeTest:time_test_drv_init,47:[HZ]250[jiffies]4417956623
3. 系统滴答记录–jiffies
jiffies字面意思为时间量,其在内核中用于记录系统启动后系统滴答发生的次数,其定义如下
//file:kernel/arch/arm64/kernel/vmlinux.lds.S
jiffies = jiffies_64;
//kernel/include/linux/jiffies.h
extern unsigned long volatile __cacheline_aligned_in_smp __jiffy_arch_data jiffies;
4. jiffies时间对比API
4.1 time_before&&time_before_eq
time_before用于对比两个时刻的jiffies值,其定义如下
//file:kernel/include/linux/jiffies.h
#define time_before(a,b) time_after(b,a)
作用也很简单,如果a时间点在b之前那么就返回true,否则返回false。在c中true也就是非零值,false则为零。对于 time_before_eq和time_before就是多了一层为真的条件,在a和b相等时也返回真值。示例代码如下
time_test_drv_init
unsigned long delayTime = jiffies + HZ;//delay 1s
time_before(jiffies, delayTime);
4.2 time_after&&time_after_eq
time_after用于判断两个时刻jiffies值,其定义如下
//kernel/include/linux/jiffies.h
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)((b) - (a)) < 0))
如果a的值在b之后,就返回true,否则返回false。time_after_eq也是多了一层为真的条件,在a和b相等时也会返回true。
下面的代码判断展示以上的接口使用
time_test_drv_init
TIME_TEST_INFO("time_after:[delayTime]%lu, [jiffies]%lu, [timer_after]%d, [time_before]%d",
delayTime,
jiffies,
time_after(jiffies, delayTime),
time_before(jiffies, delayTime));
执行结果如下
[492257.266142] timeTest:time_test_drv_init,56:time_after:[delayTime]4417956873, [jiffies]4417956623, [timer_after]0, [time_before]1
5.内核时间的获取
在我们的日常生活中所说的时间一般指的就是墙时间,但对于设备驱动来说墙时间显得不必要并且表示也更复杂,设备驱动中更在意的启动时间以及过去了多久。
5.1 ktime_get
ktime_get 实际上获取的就是CLOCK_MONOTONIC时间,其在内核中的描述如下
Useful for reliable timestamps and measuring short time intervals accurately.
Starts at system boot time but stops during suspend.
通过ktime_get获取的时间是不统计设备休眠时间的,其返回值类型为ktime_t,单位为纳秒,并且这个时间统计的起始点则是设备启动后。其ktime_get和ktime_t的定义如下
//kernel/include/linux/ktime.h
/* Nanosecond scalar representation for kernel time values */
typedef s64 ktime_t;
//kernel/kernel/time/timekeeping.c
ktime_t ktime_get(void)
{
...
return ktime_add_ns(base, nsecs);
}
下面是接口使用的代码片段
time_test_drv_init
ktime_t curTime = 0;
curTime = ktime_get();
TIME_TEST_INFO("ktime_get:%lld ns", curTime);
下面是上面代码执行的结果
[492257.266149] timeTest:time_test_drv_init,63:ktime_get:492257307974640 ns
5.2 ktime_get_ts64
ktime_get_ts64和ktime_get的功能是完全一样的,区别在于对时间的表示数据类型由ktime_t变成了timespec64,timespec64的定义如下
//kernel/include/linux/time64.h
struct timespec64 {
time64_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
timespec64中包含了秒和纳秒,相对ktime_t纳秒这个时间表示更加适合人类查看,下面是接口使用的代码片段
static void show_time_ts64(const char* caller, const int line, const struct timespec64 *curTimeTs)
{
pr_info("%s,%d:%lld s %ld ns\n", caller, __LINE__, curTimeTs->tv_sec, curTimeTs->tv_nsec);
}
time_test_drv_init
struct timespec64 curTimeTs;
ktime_get_boottime_ts64(&curTimeTs);
show_time_ts64(__func__, __LINE__, &curTimeTs);
下面是上面代码执行的结果
[492257.266159] timeTest:time_test_drv_init,36:492257 s 307981986 ns
5.3 ktime_get_boottime
通过ktime_get_boottime获取的时间和ktime_get最大的不同是其包含了设备进入休眠的时间,其返回值类型为ktime_t,单位为纳秒,这个时间统计的起始点也是设备启动后。其定义如下
/**
* ktime_get_boottime - Returns monotonic time since boot in ktime_t format
*
* This is similar to CLOCK_MONTONIC/ktime_get, but also includes the
* time spent in suspend.
*/
static inline ktime_t ktime_get_boottime(void)
{
return ktime_get_with_offset(TK_OFFS_BOOT);
}
使用代码如下
time_test_drv_init
ktime_t curTime = 0;
curTime = ktime_get_boottime();
TIME_TEST_INFO("ktime_get_boottime:%lld ns", curTime);
执行结果如下
[492257.266168] timeTest:time_test_drv_init,73:ktime_get_boottime:581660801601637 ns
5.4 ktime_get_boottime_ts
ktime_get_boottime_ts相对于ktime_get_boottime的功能是完全一样的,区别在于对时间的表示数据类型由ktime_t变成了timespec64,timespec64的解释见5.2。
5.5 ktime_get_real
ktime_get_real获取的时间的起点不是设备的启动时间点了,而是相对UTC的。内核中的说明如下
Returns the time in relative to the UNIX epoch starting in 1970 using the
Coordinated Universal Time (UTC), same as gettimeofday() user space. This
is used for all timestamps that need to persist across a reboot, like inode times,
but should be avoided for internal uses, since it can jump backwards due to a leap
second update, NTP adjustment settimeofday() operation from user space.
下面是该几口的使用示例
time_test_drv_init
ktime_t curTime = 0;
curTime = ktime_get_real();
TIME_TEST_INFO("ktime_get_real:%lld ns", curTime);
执行结果如下
[492257.266278] timeTest:time_test_drv_init,83:ktime_get_real:1646007269096922216 ns
可见1970+(1646007269096922216ns) ~= 2022。
5.6 ktime_get_real_ts
示例代码如下
time_test_drv_init
struct timespec64 curTimeTs;
ktime_get_real_ts64(&curTimeTs);
执行结果如下
[492257.266285] timeTest:time_test_drv_init,36:1646007269 s 96929639 ns
6.完整源码如下
https://gitee.com/solo-king/linux-kernel-base-usage/blob/master/flagstaff/delayTest.c