Linux时间子系统5:timekeeper、timecountercyclecounter

news2024/11/25 14:25:52

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.coloradmin.cn/o/1827643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Centos7系统下Docker的安装与配置

文章目录 前言下载Docker安装yum库安装Docker启动和校验配置Docker镜像加速卸载Docker 前言 此博客的内容的为自己的学习笔记&#xff0c;如果需要更具体的内容&#xff0c;可查看Docker官网文档内容 注意&#xff1a;以下命令在root管理员用户下运行&#xff0c;如果在普通用…

2024年了,苹果可以通话录音了

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 6月11日凌晨&#xff0c;苹果在WWDC24大会上&#xff0c;密集输出了酝酿多时的AI应用更新。苹果对通话、对话、图…

使用Ventoy 替代Win_To_Go更好的随身系统

Ventoy支持在物理机上直接启动安装了 Linux/Windows 系统的 磁盘映像文件 。 系统是在真实物理机上运行&#xff0c;并不是在虚拟机里运行&#xff0c;没有性能损失。支持 Legacy BIOS 和 UEFI 模式。支持从任意磁盘启动磁盘映像。 Windows 支持固定大小以及动态扩展类型的 VH…

【C语言】二维数组(详解)

目录 1. 二维数组的创建 1.1 二维数组的概念 1.2 二维数组的创建 2. 二维数组的初始化 2.1 不完全初始化 2.2 完全初始化 2.3 按照行初始化 2.4 初始化时能省略行&#xff0c;但不能省略列 3. 二维数组的使用 3.1 二维数组下标 3.2 二维数组…

docker下载ridis

1、执行 docker pull redis:4.0.1 命令&#xff0c;下载 redis 镜像 &#xff08;需确保装有并启动bocker&#xff09; 通过docker启动redis 分配端口和端口映射 密码等 rootiZf8z985hmyc9bkejcfmqrZ:~# docker run --rm -d --name redis6379 -p 6379:6379 redis:4.0.1 --req…

外观模式(大话设计模式)C/C++版本

外观模式 C #include <iostream> using namespace std;class stock1 { public:void Sell(){cout << "股票1卖出" << endl;}void Buy(){cout << "股票1买入" << endl;} };class stock2 { public:void Sell(){cout << …

动态IP掉线该怎么解决?

动态IP可以说是做爬虫、采集数据、搜集热门商品信息中必备的代理工具&#xff0c;但在爬虫的使用中&#xff0c;总是会遇到动态IP掉线的情况&#xff0c;从而影响使用效率&#xff0c;本文将探讨动态IP代理掉线的几种常见原因&#xff0c;并提供解决方法&#xff0c;以帮助大家…

MSPM0L1306快速创建可移动工程(一)

设置成文本文件 宏定义 __MSPM0L1306__

CSS概述

CSS是一种样式表语言&#xff0c;用于为HTML文档控制外观&#xff0c;定义布局。例如&#xff0c; CSS涉及字体、颜色、边距、高度、宽度、背景图像、高级定位等方面 。 ● 可将页面的内容与表现形式分离&#xff0c;页面内容存放在HTML文档中&#xff0c;而用 于定义表现形式…

力扣136. 只出现一次的数字

Problem: 136. 只出现一次的数字 文章目录 题目描述思路复杂度Code 题目描述 思路 由于题目要求使用线性时间复杂度和常量级的空间复杂度&#xff0c;再加上找重复元素这个特性&#xff0c;我们可以想到使用位运算来求解&#xff1a; 1.任何数与其本身异或得0&#xff0c;任何…

仅1.6万元,100%源码交付企业级开源 AIoT 物联网平台,可视化Web组态大屏,二次开发项目...

100%源码交付&#xff0c;文末联系小编 01 AIoT 物联网平台 千丝物芯是一个简单易用的适合中小企业和个人使用的物联网平台&#xff0c;系统运行采用前后端分离&#xff0c;分别运行java后端和vue前端&#xff0c;项目依赖Mysql、Redis和Emqx消息服务器。 兼容多种传感器 支持百…

[大模型]XVERSE-MoE-A4.2B Transformers 部署调用

XVERSE-MoE-A4.2B介绍 XVERSE-MoE-A4.2B 是由深圳元象科技自主研发的支持多语言的大语言模型&#xff08;Large Language Model&#xff09;&#xff0c;使用混合专家模型&#xff08;MoE&#xff0c;Mixture-of-experts&#xff09;架构&#xff0c;模型的总参数规模为 258 亿…

时机:产品成功的关键因子

在商业世界里&#xff0c;产品成功与否往往与许多因素有关&#xff1a;优秀的创意、强大的团队、充足的资金等。然而&#xff0c;在这些因素之外&#xff0c;一个常被忽视但至关重要的因素就是“时机”。正如古语所言&#xff1a;“人为可做&#xff0c;天时难造”&#xff0c;…

【idea】解决springboot项目中遇到的问题

一、Maven报错Could not find artifact com.mysql:mysql-connector-j:pom:unknown in aliyunmaven解决及分析 报错 创建springboot项目&#xff0c;勾选数据库驱动&#xff0c;springboot版本为3&#xff0c;现在改成了2.7.2&#xff0c;Maven就发生了报错Could not find art…

计算机组成原理历年考研真题对应知识点(计算机系统层次结构)

目录 1.2计算机系统层次结构 1.2.2计算机硬件 【命题追踪——冯诺依曼计算机的特点(2019)】 【命题追踪——MAR 和 MDR 位数的概念和计算(2010、2011)】 1.2.3计算机软件 【命题追踪——三种机器语言的特点(2015)】 【命题追踪——各种翻译程序的概念(2016)】 1.2.5计算…

文章MSM_metagenomics(五):共现分析

欢迎大家关注全网生信学习者系列&#xff1a; WX公zhong号&#xff1a;生信学习者Xiao hong书&#xff1a;生信学习者知hu&#xff1a;生信学习者CDSN&#xff1a;生信学习者2 介绍 本教程是使用一个Python脚本来分析多种微生物&#xff08;即strains, species, genus等&…

享元和代理模式

文章目录 享元模式1.引出享元模式1.展示网站项目需求2.传统方案解决3.问题分析 2.享元模式1.基本介绍2.原理类图3.外部状态和内部状态4.类图5.代码实现1.AbsWebSite.java 抽象的网站2.ConcreteWebSite.java 具体的网站&#xff0c;type属性是内部状态3.WebSiteFactory.java 网站…

Tensorflow-GPU工具包了解和详细安装方法

目录 基础知识信息了解 显卡算力 CUDA兼容 Tensorflow gpu安装 CUDA/cuDNN匹配和下载 查看Conda driver的版本 下载CUDA工具包 查看对应cuDNN版本 下载cuDNN加速库 CUDA/cuDNN安装 CUDA安装方法 cuDNN加速库安装 配置CUDA/cuDNN环境变量 配置环境变量 核验是否安…

后端开发中缓存的作用以及基于Spring框架演示实现缓存

缓存的作用及演示 现在我们使用的程序都是通过去数据库里拿数据然后展示的 长期对数据库进行数据访问 这样数据库的压力会越来越大 数据库扛不住了 创建了一个新的区域 程序访问去缓存 缓存区数据库 缓存里放数据 有效降低数据访问的压力 我们首先进行一个演示 为了演示…

Python 全栈系列253 再梳理flask-celery的搭建

说明 最近做了几个实验&#xff0c;将结论梳理一下&#xff0c;方便以后翻看。 1 flask-celery 主要用于数据流的同步任务&#xff0c;其执行由flask-aps发起&#xff0c;基于IO并发的方法&#xff0c;达到资源的高效利用&#xff0c;满足业务上的需求。2 目前部署环境有算网…