Linux时间子系统5:timekeeper、timecountercyclecounter

1. 前言

    前面我们介绍了用户态获取时间的接口clock_gettime,时钟的种类posix_clocks以及时钟源clocksource。那么我们思考这样一个问题,无论clock_gettime或者posix_clock定义的时间都是相对于某个起始点的时间,即相对于Linux Epoch的秒数,但是我们上一章节介绍的时钟源clocksource,它提供的read接口,我们看一下定义,它返回的是时钟源的cycle值,那么cycle和time是怎么对应的,内核是怎么通过clocksource来实现我们之前说的多种posix clock的,这就是本文要分析的内容timekeeper,同时我们再泛化到所有的硬件计时器,介绍timecounter和cyclecounter的概念。本文同样是在Linux时间子系统之(四):timekeeping文章的基础上,增加了一点个人的笔记。

2 Timekeeping

          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之间更精准的时间信

2.1 Timekeeper数据结构

        struct  timekeeper数据结构如下:

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;
	struct timespec64	monotonic_to_boot;

	/* The following members are for timekeeping internal use */
	u64			cycle_interval;
	u64			xtime_interval;
	s64			xtime_remainder;
	u64			raw_interval;
	/* The ntp_tick_length() value currently being used.
	 * This cached copy ensures we consistently apply the tick
	 * length for an entire tick, as ntp_tick_length may change
	 * mid-tick, and we don't want to apply that new value to
	 * the tick in progress.
	 */
	u64			ntp_tick;
	/* Difference between accumulated time and NTP time in ntp
	 * shifted nano seconds. */
	s64			ntp_error;
	u32			ntp_error_shift;
	u32			ntp_err_mult;
	/* Flag used to avoid updating NTP twice with same second */
	u32			skip_second_overflow;
#ifdef CONFIG_DEBUG_TIMEKEEPING
	long			last_warning;
	/*
	 * These simple flag variables are managed
	 * without locks, which is racy, but they are
	 * ok since we don't really care about being
	 * super precise about how many events were
	 * seen, just that a problem was observed.
	 */
	int			underflow_seen;
	int			overflow_seen;
#endif
};

tkr_mono:记录单调时间的结构体。
tkr_raw:记录原始单调时间的结构体。
xtime_sec:实时时间当前的秒数。
ktime_sec:单调时间当前的秒数。
wall_to_monotonic:实时时间和单调时间之间的差值。
offs_real:单调时间和实时时间之间的差值,offs_real=-wall_to_monotonic。
offs_boot:单调时间和启动时间之间的差值。
offs_tai:单调时间和TAI时间之间的差值,offs_tai=offs_real+tai_offset。
tai_offset:实时时间和TAI时间之间的差值。
clock_was_set_seq:表示时钟被设置的序数。
cs_was_changed_seq:表示时钟源更换的序数。

next_leap_ktime:下一次需要闰秒(跳变秒)的时间。“闰秒”就是1分钟有61秒, “跳秒”都安排在6月30日,或是12月31日的最后一瞬间。地球自转并非十分均匀,准确的说自转是在不断地在变慢的。每当地球自转变化引起的时间误差积累到与原子钟相关接近1秒时,就要人为地把时钟增加或减少1秒,从而使两者重新协调一致。这增加或减少的1秒称为“跳秒”。若是增加的,就是“正跳秒”(拨慢1秒);若是减少的就是“负跳秒”(拨快1秒),不过负跳秒至今还没有发生过。这样,每逢正跳秒那1分钟自然就是61秒了,正因为这1分钟多1秒,所以又叫“闰秒”。
raw_sec:原始单调时间当前的秒数。
monotonic_to_boot:单调时间和启动时间之间的差值。
cycle_interval:表示一次NTP周期包含多少个时钟周期。
xtime_interval:表示一个NTP周期包含多少纳秒,不过这个值是位移过后的,也就是实际的纳秒数向左位移了shift位,而且这个值会根据NTP层的状况做出调整。
xtime_remainder:表示从周期数转换成纳秒数时候的精度损失,后面分析代码的时候会解释。
raw_interval:也表示了一个NTP周期包含多少纳秒,也是位移过后的,不过这个值不会根据NTP的状况做出调整,一旦设置好后就不会变了。在初始状态下,xtime_interval和raw_interval的值是完全一样的。
ntp_tick:记录了NTP周期的纳秒数,这个值也是位移过后的,但其位移的位数不是有时钟源设备决定的,而是一个固定的值。
ntp_error:NTP时间和当前实时时间之间的差值,如果ntp_error大于0,表示当前系统的实时时间慢于NTP时间,相反如果小于0则表示快于。
ntp_error_shift:存放了NTP的shift和时钟源设备shift之间的差值。NTP层也需要对纳秒数做shift的操作,其值由宏NTP_SCALE_SHIFT定义,现在被定义成了32位。但是时钟源设备的shift值是根据条件计算出来的,所以在两层之间虽然都会shift,但位数是不同的。如果需要转换的话,必须记录下来它们之间的差值。
ntp_err_mult:如果ntp_error大于0,则为1,否则都是0。
skip_second_overflow:处理闰秒的时候是否需要跳过这一秒。

其中,tk_read_base的数据结构如下:

struct tk_read_base {
	struct clocksource	*clock;
	u64			mask;
	u64			cycle_last;
	u32			mult;
	u32			shift;
	u64			xtime_nsec;
	ktime_t			base;
	u64			base_real;
};

clock:指向对应底层时钟源设备结构体的指针。
cycle_last:记录了最近一次时钟源的周期计数。
mask、mult和shift:对应底层时钟源设备的mask、mult和shift的值,用于将时钟周期数和纳秒数之间互相转换。
xtime_nsec:实时时间当前的纳秒数,这个值也是移位过后的,也就是实际的纳秒数向左移动了shift位。累积起来会进位。
base:单调时间的基准时间。
base_real:实时时间的基准时间,base_real=base+offs_real。

以上内容,参考Linux时间子系统之时间维护层

2.1 全局变量timekeeper

        timekeeper维护了系统的所有的clock(这句话并不准确,如同posxi clocks那篇文章所说,timekeeper维护了系统中所有的与系统时间相关的clock,这也正是为什么会存在timecounter,我们稍后再讲)。如下:

static struct {
	seqcount_raw_spinlock_t	seq;
	struct timekeeper	timekeeper;
} tk_core ____cacheline_aligned = {
	.seq = SEQCNT_RAW_SPINLOCK_ZERO(tk_core.seq, &timekeeper_lock),
};

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

2.2 初始化

        timekeeping初始化的代码位于timekeeping_init函数中,在系统初始化的时候(start_kernel)会调用该函数进行timekeeping的初始化。

        timekeeping模块中的若干个system clock,数据保存在ram中,一旦断电,数据就丢失了。因此,在系加电启动后,会从persistent clock中中取出当前时间值(例如RTC,RTC有battery供电,因此系统断电也可以保存数据),根据情况初始化各种system clock。如下:
        

void __init timekeeping_init(void)
{
	struct timespec64 wall_time, boot_offset, wall_to_mono;
	struct timekeeper *tk = &tk_core.timekeeper;
	struct clocksource *clock;
	unsigned long flags;

	read_persistent_wall_and_boot_offset(&wall_time, &boot_offset);
	if (timespec64_valid_settod(&wall_time) &&
	    timespec64_to_ns(&wall_time) > 0) {
		persistent_clock_exists = true;
	} else if (timespec64_to_ns(&wall_time) != 0) {
		pr_warn("Persistent clock returned invalid value");
		wall_time = (struct timespec64){0};
	}

	if (timespec64_compare(&wall_time, &boot_offset) < 0)
		boot_offset = (struct timespec64){0};

	/*
	 * We want set wall_to_mono, so the following is true:
	 * wall time + wall_to_mono = boot time
	 */
	wall_to_mono = timespec64_sub(boot_offset, wall_time);

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

	clock = clocksource_default_clock();
	if (clock->enable)
		clock->enable(clock);
	tk_setup_internals(tk, clock);

	tk_set_xtime(tk, &wall_time);
	tk->raw_sec = 0;

	tk_set_wall_to_mono(tk, wall_to_mono);

	timekeeping_update(tk, TK_MIRROR | TK_CLOCK_WAS_SET);

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

read_persistent_wall_and_boot_offset中调用了read_persistent_clock64,这是和architecture相关的函数。接下来的代码都是判断获取到的时间是否合法。只有tegra和omap平台实现了read_persistent_clock函数.其他ARM平台打开CONFIG_RTC_HCTOSYS这个内核配置项,打开该配置后,driver/rtc/hctosys.c将会编译到系统中,由rtc_hctosys函数通过do_settimeofday在系统初始化时完成xtime变量的初始化:

        clocksource_default_clock和tk_setup_internals为timekeeping模块设置默认的clocksource。在timekeeping初始化的时候,很难选择一个最好的clock source,因为很有可能最好的那个还没有初始化呢。因此,这里的策略就是采用一个在timekeeping初始化时一定是ready的clock source,也就是基于jiffies 的那个clocksource。clocksource_default_clock定义在kernel/time/jiffies.c,是一个weak symble,如果你愿意也可以重新定义clocksource_default_clock这个函数。不过,要保证在timekeeping初始化的时候是ready的。

        接下来则是初始化real time clock,monotonic clock和monotonic raw clock

2.3 获取和设定系统时间

获取monotonic clock的时间值:ktime_get和ktime_get_ts64

获取real time clock的时间值:ktime_get_real和ktime_get_real_ts64

获取boot clock的时间值:ktime_get_boottime和ktime_get_boottime_ts64

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

ktime_get_ts的概念和ktime_get是一样的,只不过返回的时间值格式不一样而已。

2.4 更新时钟

timekeeping_update函数用来更新时间维护层的数据。该函数的第二个参数是action动作,目前共定义了下面三个值:

#define TK_CLEAR_NTP		(1 << 0)
#define TK_MIRROR		(1 << 1)
#define TK_CLOCK_WAS_SET	(1 << 2)
  • TK_CLEAR_NTP:是否需要清除NTP层的状态信息。
  • TK_MIRROR:是否需要复制到影子timekeeper结构体中。
  • TK_CLOCK_WAS_SET:是否需要递增clock_was_set_seq变量,该变量在每次设置时钟后都需要加一。

3 cyclecounter和clockcounter

3.1 为什么会有timecounter和cyclecounter

        在内核的driver中,我们可能有这样的需求:获取drive中的A事件和B事件之间的时间值或者一个event stream过程中,各个event的时刻值。这里,driver不关心绝对的时间点,关心的是事件之间的时长。为了应对这个需求,clock source模块提供了timecounter和cyclecounter。

实际上上面的话并不准确,我们这么想,clocksource和timekeeper分别对应了系统时钟的时钟源和时间管理软件,但是对于非系统时钟的其他时钟源,如何获取他们的硬件counter和时间呢?内核用timecounter和cyclecounter就可以统一除系统时钟以外的所有的硬件时钟的需求。

内核中使用struct cyclecounter 来抽象一个free running的counter,从0开始,不断累加。由于counter的bit数目有限,因此,某个时间后,counter会wraparound,从0继续开始。该数据结构定义如下:

struct cyclecounter {
	u64 (*read)(const struct cyclecounter *cc);
	u64 mask;
	u32 mult;
	u32 shift;
};

每个cycle counter的counter value都是针对clock计数的,因此,通过read获取的counter value是基于cycle的,而cycle又是和输入频率有关。不过,对于其他driver而言,cycle数据是没有意义的,最好统一使用纳秒这样的单位,因此在
struct cyclecounter 中就有了mult和shift这两个成员了,这和clocksource的概念是不是基本一致。

实际上,最开始的时候,内核的确是只有clock source模块,它位于timekeeping模块和硬件之间。但是,其他内核模块也有访问free running counter的需要,这时候,内核开发人员创建了cycle counter和timer counter这样的概念,虽然代码有一点重复,但是这样不会触及clock source代码的改动。

timecounter是构架在cycle counter之上,使用纳秒这样的时间单位而不是cycle数目,这样的设计会让用户接口变得更加友好,毕竟大家还是喜欢直观的纳秒值。timecounter的定义如下:

struct timecounter {
	const struct cyclecounter *cc;
	u64 cycle_last;
	u64 nsec;
	u64 mask;
	u64 frac;
};

3.2 如何使用timecounter 

        首先需要初始化,注册timecounter

void timecounter_init(struct timecounter *tc,
		      const struct cyclecounter *cc,
		      u64 start_tstamp)
{
	tc->cc = cc;
	tc->cycle_last = cc->read(cc);
	tc->nsec = start_tstamp;
	tc->mask = (1ULL << cc->shift) - 1;
	tc->frac = 0;
}

读取timecounter:


u64 timecounter_read(struct timecounter *tc)
{
	u64 nsec;

	/* increment time by nanoseconds since last call */
	nsec = timecounter_read_delta(tc);
	nsec += tc->nsec;
	tc->nsec = nsec;

	return nsec;
}

可能很多人认为,除了系统时间,我们还需要读别的什么时间吗?提供这样的接口的意义在哪里呢?在后面PTP时钟同步章节会有分析。不过说到这个,有意思的是硬件厂家的硬件时钟并不总是提供cycle counter的功能,如果硬件厂家提供的接口能读取到的直接是time,而不是cycle,那不是很尴尬吗?实际上我确实遇到过,后面再说。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/720422.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何用Excel随机抽取幸运儿

在举行年会等活动&#xff0c;会在大屏幕互动随机滚动抽取幸运观众&#xff0c;有专门开发的软件或程序&#xff1b; 对于我们日常工作中有时会遇到&#xff0c;如何在群体中随机抽取部分幸运儿的问题&#xff1f; 除了抓阄&#xff0c;当然也可以用Excel解决哦&#xff0c;今…

示例:WPF中应用MarkupExtention自定义IValueConverter

一、目的&#xff1a;应用MarkupExtention定义IValueConverter&#xff0c;使得应用起来更简单和高效 二、实现 public abstract class MarkupValueConverterBase : MarkupExtension, IValueConverter{public abstract object Convert(object value, Type targetType, object …

排序模型的奥秘:如何用AI大模型提升电商、广告和用户增长的效果

摘要 排序模型是数字化营销中最重要的工具之一&#xff0c;它可以帮助我们在海量的信息中筛选出最符合用户需求和偏好的内容&#xff0c;从而提高用户的满意度和转化率。本文从产品经理的视角&#xff0c;介绍了常见的排序模型的原理和应用&#xff0c;包括基于规则的排序、基…

Ubuntu 24.04 SSH Server 修改默认端口重启无效

试用最新的乌班图版本&#xff0c;常规修改ssh端口&#xff0c;修改完毕后重启sshd提示没有找到service&#xff0c;然后尝试去掉d重启ssh后查看状态&#xff0c;端口仍然是默认的22&#xff0c;各种尝试都试了不行&#xff0c;重启服务器后倒是端口修改成功了&#xff0c;心想…

MDPO:Conditional Preference Optimization for Multimodal Large Language Models

MDPO: Conditional Preference Optimization for Multimodal Large Language Models 相关链接&#xff1a;arxiv 关键字&#xff1a;多模态、大型语言模型、偏好优化、条件偏好优化、幻觉减少 摘要 直接偏好优化&#xff08;DPO&#xff09;已被证明是大型语言模型&#xff08…

开源表单流程设计器:做好流程化办公 实现提质增效!

在社会竞争激烈的今天&#xff0c;如何通过各种渠道和方式实现提质增效&#xff1f;低代码技术平台、开源表单流程设计器的出现&#xff0c;正是助力企业实现流程化办公&#xff0c;进入数字化转型的得力助手。想要利用好企业内部数据资源&#xff0c;打破信息化孤岛&#xff0…

排序算法、堆排序、大顶堆、小顶堆、手写快排-215. 数组中的第K个最大元素、2336. 无限集中的最小数字

目录 215. 数组中的第K个最大元素 题目链接及描述 题目分析 堆排序分析 堆排序代码编写 快排分析 快排代码编写 2336、无限集中的最小数字 题目链接及描述 题目分析 代码编写 215. 数组中的第K个最大元素 题目链接及描述 215. 数组中的第K个最大元素 - 力扣&#…

高压防触碰预警装置,工期重要还是命重要?

“说了多少遍了&#xff0c;不要在高压线下赶工期”吊车违规施工碰撞到高压线&#xff0c;导致供电线路跳闸停电事故&#xff0c;现场火花四溅及其危险&#xff0c; 高压线路被外力破坏的情况&#xff0c;违规施工、赶工期、视觉盲区导致线路外破等情况&#xff0c;想必大家也…

银行数仓项目实战(三)--使用Kettle进行增量,全量抽取

文章目录 使用Kettle进行全量抽取使用Kettle进行增量抽取 使用Kettle进行全量抽取 一般只有项目初始化的时候会使用到全量抽取&#xff0c;全量抽取的效率慢&#xff0c;抽取的数据量大。 我们在第一次进行全量抽取的时候&#xff0c;要在表中新建一个字段记录抽取时间&#x…

QPST的使用

QPST&#xff08;Qualcomm Product Support Tool&#xff09;是一个针对高通芯片开发的传输软件。 下载软件 进行安装 安装后使用&#xff0c;QPSTConfig 可以自动抓取dump的log 使用QFile 刷机

uniapp滚动加载

uniapp实现滚动加载&#xff0c;先获取10条数据&#xff0c;滚动到底时&#xff0c;再获取10条数据&#xff0c;以此类推&#xff0c;直至没有数据为止。 使用scroll-view&#xff0c;注意一定要给一个固定高度&#xff0c;隐藏滚动条会更美观 2. 在data中定义 3. 获取数据 …

【PyQt5】一文向您详细介绍 self.setLayout() 的作用

【PyQt5】一文向您详细介绍 self.setLayout() 的作用 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通本硕…

5G工业路由器在智慧交通车路协同应用的深度解析

随着科技的飞速发展&#xff0c;智慧交通已成为现代城市发展的重要方向。在智慧交通的众多技术中&#xff0c;5G工业路由器凭借其高速、稳定、安全等特性&#xff0c;成为车路协同应用中不可或缺的一环。本文将在本文中深度解析5G工业路由器在智慧交通车路协同应用中的重要作用…

文件操作(2)(C语言版)

文件的随机读写&#xff1a; fseek函数&#xff1a; 前面讲解了顺序读写的相关函数&#xff0c;这里介绍一些可以“指哪写哪的函数” 有三个参数&#xff1a; 1、文件的地址 2、相对于第三个参数origin偏移的位置 3、起始位置&#xff08;有三种&#xff09; 第一种&#xff…

【三】【QT开发应用】VSQT和QTCreator项目互相转化的方法,QTCreator项目转化VSQT,VSQT转化为QTCreator

VSQT和QTCreator项目互相转化的方法 QTCreator项目转化VSQT 环境变量配置 将qmake.exe所在的目录添加到系统path里面. 转化命令 qmake -tp vc xxx.pro 生成.vcxproj文件 环境变量配置 将qmake.exe所在的目录路径添加到系统path中. 接着用cmd命令行转换,可能出现的问题 …

基于机器学习和深度学习的C-MAPSS涡扇发动机剩余寿命RUL预测(Python,Jupyter Notebook环境)

涡扇发动机全称为涡轮风扇发动机&#xff0c;是一种先进的空中引擎&#xff0c;由涡轮喷气发动机发展而来。涡扇发动机主要特点是首级压缩机的面积比涡轮喷气发动机大。同时&#xff0c;空气螺旋桨&#xff08;扇&#xff09;将部分吸入的空气从喷射引擎喷射出来&#xff0c;并…

Vue使用vue-esign实现在线签名 加入水印

Vue在线签名 一、目的二、样式三、代码1、依赖2、代码2.1 在线签名组件2.1.1 基础的2.1.2 携带时间水印的 2.2父组件 一、目的 又来了一个问题&#xff0c;直接让我在线签名&#xff08;还不能存储base64&#xff09;&#xff0c;并且还得上传&#xff0c;我直接***违禁词。 好…

基于Python的垃圾分类检测识别系统(Yolo4网络)【W8】

简介&#xff1a; 垃圾分类检测识别系统旨在利用深度学习和计算机视觉技术&#xff0c;实现对不同类别垃圾的自动识别和分类。应用环境包括Python编程语言、主流深度学习框架如TensorFlow或PyTorch&#xff0c;以及图像处理库OpenCV等&#xff0c;通过这些工具集成和优化模型&a…

M41T00串行实时时钟-国产兼容RS4C1339

RS4C1340是一种实时时钟&#xff08;RTC&#xff09;/日历&#xff0c;与ST M41T00引脚兼容&#xff0c;功能等效&#xff0c;包括软件时钟校准。该器件还提供VBAT引脚上的涓流充电能力、较低的计时电压和振荡器STOP标志。寄存器映射的块访问与ST设备相同。涓流充电器和标志需要…

MATLAB 二维平面绘图

x 0:0.01:2pi: 大家还记得这个是什么意思吧 就是0到2π 每次所取的数 是相差0.01进行选取的 ysin&#xff08;x&#xff09;: figure (这个意思就是建立一个幕布) plot&#xff08;x&#xff0c;y&#xff09; 这个主要是绘制当前的二维平面的图 但是大家会发现这张图里没有标…