关于linux内核中jiffies和jiffies_64解析

1、前言

linux内核中定义了jiffies变量来记录从系统启动到当前时刻系统时钟所产生的tick数。jiffies变量是一个无符号整型数值,即unsigned long类型。
    它的声明如下(在 include/linux/jiffies.h 中):
    
    假定HZ=1000,那么jiffies只需要约49.7天就会产生回绕(溢出),这是因为jiffies本身是unsigned long 类型,因此在32位系统中支持的最大值为(2^32)-1=4294967295,又因为HZ是1000,即1s内产生1000次
    tick数,也就是说1秒内jiffies的值会增加1000,因此4294967295/3600/24/1000=49.710天。回绕会给内核时间度量带来混乱和其他潜在的问题。因此,在Linux2.6内核中引入一个64位的无符号整型变量jiffies_64,
    内核中计时都是对jiffies_64进行递增。
    /*
  * The 64-bit value is not atomic - you MUST NOT read it
  * without sampling the sequence number in jiffies_lock.
  * get_jiffies_64() will do this for you as appropriate.
  */
    extern u64 __cacheline_aligned_in_smp jiffies_64;
    
    因为u64 是unsigned long long 类型,因此同样HZ=1000的情况下,jiffies_64可以运行几亿年也不会发生回绕问题。

2、分析源码路径

基于kernel4.14源码分析
    /kernel-4.14/kernel/time/jiffies.c
    /kernel-4.14/include/linux/jiffies.h
    /kernel-4.14/arch/arc/kernel/vmlinux.lds.S
    /kernel-4.14/kernel/time/timer.c

3、32位机器上也是使用64位计时,那么为什么不直接把jiffies变量修改为u64类型或者直接用jiffies_64替换jiffies呢?
    根本原因是为了保持兼容性及访问效率!从兼容性方面来看,大量的驱动程序使用jiffies 变量来进行一些与时间相关的操作,所以内核中需要保留该变量,以免影响系统功能;
    从访问效率方面来看,因为在 32 位的系统中访问 64 位的 jiffies_64 变量需要进行两次内存访问,一来访问速度没有直接访问 jiffies 来得快,二来无法保证原子性(在两次
    内存访问中间可能会被中断,从而造成读取数据的不正确)。但是当真的需要访问jiffies_64变量时(一般在驱动程序中很少访问 jiffies_64,通常只有内核核心代码才会访问),
    内核也提供了 get_jiffies_64() 函数来访问,以防止读取数据的不正确。该函数的实现为:

	#if (BITS_PER_LONG < 64)
	 u64 get_jiffies_64(void);
	 #else
	 static inline u64 get_jiffies_64(void)
	 {
		return (u64)jiffies;
	 }
	 #endif

虽然jiffies和jiffies_64是两个变量,但它们最终指向相同的地址,只是jiffies取的是jiffies_64变量的低32位。这种效果通过链接程序实现的。 在linux4.14内核的通过链接器(ld)脚本 vmlinux.lds.S (/kernel-4.14/arch/arc/kernel/vmlinux.lds.S) 可看到:

	 #ifdef CONFIG_CPU_BIG_ENDIAN
		jiffies = jiffies_64 + 4;
	 #else
		jiffies = jiffies_64;
	 #endif

 其中最后一条语句的作用是,让符号jiffies的地址等于符号jiffies_64的地址,即让jiffies变量占用 jiffies_64 的低 32 位。
     注:这里涉及到链接器中的一个重要的概念:
        在目标文件内定义的符号可以在链接器脚本内赋值,此时该符号应被定义为全局的。每个符号都对应一个地址,在链接器中的赋值(=)操作就是更改这个符号对应的地址。

4、jiffies_64的初始值

在jiffies.h中定义了INITIAL_JIFFIES,这个是一个宏,其主要目的是用于给jiffies和jiffies_64变量赋值初始值,这个宏定义如下:

	/*
	* Have the 32 bit jiffies value wrap 5 minutes after boot
	* so jiffies wrap bugs show up earlier.
	*/
	#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))
	用于验证是否存在回绕问题。
	例如kernel4.14中/kernel-4.14/kernel/time/timer.c中使用此宏对jiffies_64进行赋值:
	#define CREATE_TRACE_POINTS
	#include <trace/events/timer.h>

	__visible u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES;

	EXPORT_SYMBOL(jiffies_64);

5、jiffies.h源码解析

关于使用time_after等宏定义的强烈推荐,这是直接给出内核源码中的注释:

/*
  *	These inlines deal with timer wrapping correctly. You are
  *	strongly encouraged to use them
  *	1. Because people otherwise forget
  *	2. Because if the timer wrap changes in future you won't have to
  *	   alter your driver code.
  *
  * time_after(a,b) returns true if the time a is after time b.
  *
  * Do this with "<0" and ">=0" to only test the sign of the result. A
  * good compiler would generate better code (and a really good compiler
  * wouldn't care). Gcc is currently neither.
  */

5.1、常用宏定义说明

time_after(a,b)/time_after64(a,b)---如果时间a的值在时间b的值后面则返回true,否则返回false
time_before(a,b)/time_before64(a,b)---如果时间a的值在时间b的前面则返回true,否则返回false
time_after_eq(a,b)/time_after_eq64(a,b)---如果时间a的值大于或等于时间b的值则返回true,否则返回false
time_before_eq(a,b)/time_before_eq64(a,b)---如果时间a的值小于或等于时间b的值则返回true,否则返回false
time_in_range(a,b,c)/time_in_range64---判断时间a是否位于时间[b,c]之间,如果是返回true,否则返回false
time_in_range_open(a,b,c)---判断时间a是否位于时间[b,c)之间,如果是返回true,否则返回false

5.2、不同时间单位之间的转换接口

/*将jiffies数值转换为毫秒*/
extern unsigned int jiffies_to_msecs(const unsigned long j);
/*将jiffies数值转换为微秒*/
extern unsigned int jiffies_to_usecs(const unsigned long j);
/*将毫秒转为jiffies的值*/
extern unsigned long __msecs_to_jiffies(const unsigned int m);
/*将微秒转为jiffies的值*/
extern unsigned long __usecs_to_jiffies(const unsigned int u);

/*将timespec64表示的时间转为jiffies*/
extern unsigned long timespec64_to_jiffies(const struct timespec64 *value);
/*将jiffies表示的值转为timespec64表示的时间*/
extern void jiffies_to_timespec64(const unsigned long jiffies,struct timespec64 *value);

/*将timeval表示的时间转为jiffies*/
extern unsigned long timeval_to_jiffies(const struct timeval *value);
/*将jiffies表示的值转换为timeval表示的时间*/
extern void jiffies_to_timeval(const unsigned long jiffies,struct timeval *value);

extern clock_t jiffies_to_clock_t(unsigned long x);
extern unsigned long clock_t_to_jiffies(unsigned long x);

6、参考链接

https://www.cnblogs.com/mewmicro/p/6421254.html?utm_source=itdadao&utm_medium=referral

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只特立独行的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值