linux全局时间变量及时间读写函数

本文介绍与linux全局时间变量和C库gettimeofdaysettimeofday的底层操作。

软件平台:linux 4.x.x

1.基础原理

1.1 时钟源

晶振(或其它时钟源)是嵌入式系统的“心跳”。其输出可视为一个个脉冲,每个脉冲视为一个节拍(tick),每两个tick间的时间几乎是一定的,其频率称为节拍率(tick rate)

补充:
实际上,晶振起到类似滤波器的效果,仅允许特定频率的信号通过,该频率为晶振的谐振频率。而且,晶振电路输出的信号,还需经过分频或倍频处理,才最终作为CPU工作的心跳。

1.2 时间

所谓时间,便是对该tick的统计结果。犹如沙漏计时一般,只是此刻的时间用电信号的tick衡量。那么,如何获取时间呢?

根据晶振的物理特性,我们可以推断出晶振的tick rate,根据倍频或分频电路后,得到实际输入系统的频率,此时称为系统频率,称为HZ

我们设计一个计数器(很常见的一类电子器件),每检测到一个脉冲,计数值+1。将该计数器与晶振电路相接,便可以实现实时统计脉冲。当计数器时数为jiffies时,此时对应的时间为jiffies/HZ

由此可以看出,时间计数器密不可分,有时候,这两者会混为一谈。(毕竟,它两取值之间成线性关系<因为HZ为常数>)

补充:
当前,时间计算不会那么简单,还有单位换算、计数器溢出、初始相位偏移、频率偏移等问题需要考虑。

2. 内核时间变量

2.1 linux HZ和jiffies

linux系统中,通过全局HZjiffies,表示节拍率和自系统启动以来的节拍总数。两者定义在:

asm/param.h //不绝对啊,有些通过CONFIG_HZ配置,或者其他文件中定义
linux/jiffies.h //应该没跑了,就是这个文件
extern unsigned long volatile __cacheline_aligned_in_smp __jiffy_arch_data jiffies;

想知道当前系统的两个参数取值如何,在命令行查看:

cat /proc/cpuinfo | grep z

如果已知源码,可以在配置文件下查看是否CONFIG_HZ定义:

CONFIG_HZ_250=y
# CONFIG_HZ_300 is not set
# CONFIG_HZ_1000 is not set
CONFIG_HZ=250

此处设置的系统频率为250Hz,(换算成时间为4ms)。

2.2 linux内核时间

在多核CPU及多进程并发和并行的情况下,我们需要关注一下两种时间:
(1)真实时间:UTC,也称为Calendar time
(2)进程时间:进程占用CPU的时间

本文讲述的时真实时间。

2.1.1 时间全局变量

(1)时间存储在哪里?

//kernel/time/timekeeping.c
static struct {
	seqcount_t		seq;
	struct timekeeper	timekeeper;
} tk_core ____cacheline_aligned;

tk_core就是保存内核时间信息的变量。____cacheline_aligned宏定义指示编译器对应于L1缓存行开头的地址处实例化一个结构体或变量(请参考其他文献,本文不深入讨论)。
(2)seqcount 和timekeeper 成员
该变量的两个成员函数类型如下:

//include/linux/seqlock.h
typedef struct seqcount {
	unsigned sequence;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
} seqcount_t;
//include/linux/timekeeper_internal.h
struct timekeeper {
	struct tk_read_base	tkr_mono;
	struct tk_read_base	tkr_raw;
	u64			xtime_sec;
	unsigned long		ktime_sec;
	struct timespec64	wall_to_monotonic;
	ktime_t			offs_real;
	ktime_t			offs_boot;
	ktime_t			offs_tai;
	s32			tai_offset;
	unsigned int		clock_was_set_seq;
	u8			cs_was_changed_seq;
	ktime_t			next_leap_ktime;
	u64			raw_sec;
	u64			cycle_interval;
	u64			xtime_interval;
	s64			xtime_remainder;
	u64			raw_interval;
	u64			ntp_tick;
	s64			ntp_error;
	u32			ntp_error_shift;
	u32			ntp_err_mult;
#ifdef CONFIG_DEBUG_TIMEKEEPING
	long			last_warning;
	int			underflow_seen;
	int			overflow_seen;
#endif
};

3. C库函数

3.1 gettimeofday函数如何获取到时间?

gettimeofday底层调用__getnstimeofday64函数,调用流程如下:
在这里插入图片描述
gettimeofday底层就是读取tk_core全局变量。
注意:以下函数原型有适当省略。
(1)gettimeofday->do_gettimeofday

void do_gettimeofday(struct timeval *tv)
{
	struct timespec64 now;

	getnstimeofday64(&now);//读取时间
	tv->tv_sec = now.tv_sec;
	tv->tv_usec = now.tv_nsec/1000;//完成时间格式转换
}
EXPORT_SYMBOL(do_gettimeofday);

(2)do_gettimeofday->getnstimeofday64

void getnstimeofday64(struct timespec64 *ts)
{
	WARN_ON(__getnstimeofday64(ts));//一个接口
}
EXPORT_SYMBOL(getnstimeofday64);

(3)__getnstimeofday64(核心)

int __getnstimeofday64(struct timespec64 *ts)
{
	struct timekeeper *tk = &tk_core.timekeeper;//全局变量tk_core
	unsigned long seq;
	u64 nsecs;

	do {
		seq = read_seqcount_begin(&tk_core.seq);

		ts->tv_sec = tk->xtime_sec;//读取全局变量tk_core的xtime_sec变量
		nsecs = timekeeping_get_ns(&tk->tkr_mono);//获取ns值

	} while (read_seqcount_retry(&tk_core.seq, seq));

	ts->tv_nsec = 0;
	timespec64_add_ns(ts, nsecs);//将nsec添加入结构体ts中。完成的是ts->tv_nsec=nsecs;

	if (unlikely(timekeeping_suspended))
		return -EAGAIN;
	return 0;
}
EXPORT_SYMBOL(__getnstimeofday64);

3.2 settimeofday如何设置时间?

settimeofday底层调用do_settimeofday64函数,调用流程如下:
在这里插入图片描述

(1)settimeofday->do_settimeofday

static inline int do_settimeofday(const struct timespec *ts)
{
	struct timespec64 ts64;

	ts64 = timespec_to_timespec64(*ts);//时间格式转换
	return do_settimeofday64(&ts64);
}

(2)do_settimeofday->do_settimeofday64

int do_settimeofday64(const struct timespec64 *ts)
{
	struct timekeeper *tk = &tk_core.timekeeper;//指向时钟全局变量tk_core
	struct timespec64 ts_delta, xt;
	unsigned long flags;
	int ret = 0;

	if (!timespec64_valid_strict(ts))
		return -EINVAL;

	raw_spin_lock_irqsave(&timekeeper_lock, flags);
	write_seqcount_begin(&tk_core.seq);

	timekeeping_forward_now(tk);

	xt = tk_xtime(tk);//xt.tv_sec = tk->xtime_sec; xt.tv_nsec = (long)(tk->tkr_mono.xtime_nsec >> tk->tkr_mono.shift);
	ts_delta.tv_sec = ts->tv_sec - xt.tv_sec;//获取要设置的时间ts与当前系统时间tx之间的时间差
	ts_delta.tv_nsec = ts->tv_nsec - xt.tv_nsec;

	if (timespec64_compare(&tk->wall_to_monotonic, &ts_delta) > 0) {
		ret = -EINVAL;
		goto out;
	}

	tk_set_wall_to_mono(tk, timespec64_sub(tk->wall_to_monotonic, ts_delta));

	tk_set_xtime(tk, ts);//	tk->xtime_sec = ts->tv_sec; tk->tkr_mono.xtime_nsec = (u64)ts->tv_nsec << tk->tkr_mono.shift;
out:
	timekeeping_update(tk, TK_CLEAR_NTP | TK_MIRROR | TK_CLOCK_WAS_SET);

	write_seqcount_end(&tk_core.seq);
	raw_spin_unlock_irqrestore(&timekeeper_lock, flags);
	clock_was_set();

	return ret;
}
EXPORT_SYMBOL(do_settimeofday64);

settimeofday就是设置全局变量tk_core

参考资料:
[1] 《linux内核设计与实现》
[2] 《linux/Uinx系统编程》

写在最后的话:
作者水平有限,如有错误,欢迎大家指出。谢谢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值