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_TIMEKEEPINGlong			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.mzph.cn/diannao/29526.shtml

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

相关文章

pytorch安装----CPU版本

在安装之前&#xff0c;需要先配置GPU环境&#xff08;安装CUDA和CudaNN) 命令行输入nvidia-smi&#xff0c;查看驱动信息 安装相应的CUDA 和CUDANN 验证&#xff1a;输入nvcc --version 或者nvcc -V 进行检查 在anaconda里创建环境 conda create -n py39gpu python3.9激活环…

丰臣秀吉-读书笔记二

“啊&#xff01;平凡的一生&#xff0c;想来也够长了。不过也短。究竟长还是短&#xff1f;或许只有这一刹那是永恒的吧&#xff0c;死亡的一刹那。生命若是永恒&#xff0c;也就取决于那一刹那。” 如果我死了&#xff0c;我的愿望只有这个&#xff1a; “无论多么黑暗&#…

【STC8A8K64D4开发板】第3-1讲:温度传感器DS18B20

第3-1讲&#xff1a;温度传感器DS18B20 学习目的了解DS18B20数字温度传感器的基本原理及其数据格式。掌握STC8A8K64D4与DS18B20单总线通信的程序设计&#xff0c;通信步骤&#xff0c;数据校验等。 硬件电路设计 DS18B20简介 DS18B20主要特性 DS18B20是Dallas 半导体公司推出的…

KernelFuzzer部署、使用与原理分析

文章目录 前言1、概述1.1、整体架构1.2、工作流程1.2.1、环境配置流程1.2.2、计划任务执行流程1.2.3、Fuzz测试流程1.2.3.1、整体资源调度1.2.3.2、选取Fuzz测试目标1.2.3.3、生成Fuzz测试参数1.2.3.4、进行Fuzz测试 2、安装与使用2.1、源码安装2.1.1、部署系统依赖组件2.1.1.1…

硫碳复合材料可用作固态电池正极材料 锂硫电池是重要下游

硫碳复合材料可用作固态电池正极材料 锂硫电池是重要下游 硫碳复合材料&#xff0c;是半固态电池、固态电池的正极材料&#xff0c;主要用于金属硫电池制造领域&#xff0c;在锂硫电池应用中研究热度最高。 锂硫电池&#xff0c;一种二次电池&#xff0c;以硫元素为正极&#x…

HarmonyOS 页面路由(Router)

1. HarmonyOS页面路由(Router) 页面路由指在应用程序中实现不同页面之间的跳转和数据传递。HarmonyOS提供了Router模块&#xff0c;通过不同的url地址&#xff0c;可以方便地进行页面路由&#xff0c;轻松地访问不同的页面。本文将从页面跳转、页面返回和页面返回前增加一个询问…

VBA学习(9):按指定名单一键删除工作表

今天继续给大家聊VBA编程中工作表对象的常用操作&#xff0c;主要内容是如何批量删除工作表&#xff1b;也就是删除单个工作表、删除全部工作表和删除指定名单内的工作表。 1.删除单个工作表 删除工作表需要使用到工作表对象的delete方法&#xff0c;语法格式如下&#xff1a…

聚类分析 #数据挖掘 #Python

聚类分析&#xff08;Cluster Analysis&#xff09;是一种无监督机器学习方法&#xff0c;主要用于数据挖掘和数据分析中&#xff0c;它的目标是将一组对象或观测值根据它们之间的相似性或相关性自动分组&#xff0c;形成不同的簇或类别。聚类分析并不预先知道每个观测值的具体…

可燃气体报警器:户外工地安全预警先锋,定期检定保障安全无忧

在现代化的建设进程中&#xff0c;户外工地作为城市发展的重要推动力&#xff0c;其安全问题一直备受关注。 工地现场往往涉及多种易燃易爆气体&#xff0c;一旦发生泄漏&#xff0c;后果不堪设想。因此&#xff0c;如何有效预警并防范可燃气体泄露&#xff0c;成为户外工地安…

新手小白从Windows转Linux,或许manjaro更适合你!

网管小贾 / sysadm.cc 野生动物园里有一块并不怎么大的水塘&#xff0c;一群火烈鸟就生活在这里。 它们在水塘里悠闲地漫步&#xff0c;饿了就找些小鱼小虾&#xff0c;困了就伸个懒腰、打个盹。 就这样日复一日&#xff0c;过着百无聊赖的日子&#xff0c;直到有一天…… 这…

easyexcel和poi版本冲突报错深入解析v2

easyexcel报错解决 问题 项目由poi改用easyexcel&#xff0c;报错如下&#xff1a; java.lang.NoSuchMethodError: ‘org.apache.poi.ss.usermodel.CellType org.apache.poi.ss.usermodel.Cell.getCellType()’ 原因 easyexcel中的poi和项目原本的poi版本冲突问题。 由于之前做…

数据结构---排序算法

个人介绍 hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的…

数据中心机房建设标准

数据中心机房是专门设计用于存放、管理和维护计算机服务器、网络设备、存储设备以及其他IT基础设施的物理空间。它们是信息化社会的基石&#xff0c;为各种在线服务提供硬件和网络支持。数据中心机房的主要功能包括数据存储、处理、传输以及提供计算资源。 数据中心机房建设涉及…

用python绘制三维条形图

用python绘制三维条形图 三维条形图特点与用途 效果代码 三维条形图 三维条形图是一种在三维空间中表示数据的方法&#xff0c;它通过垂直或水平的条形长度来显示类别之间的差异。与传统的二维条形图相比&#xff0c;三维条形图增加了深度或高度的维度&#xff0c;使得数据可视…

数理化解题研究杂志社数理化解题研究编辑部2024年第12期目录

教学设计与教学策略研究 聚焦数学思想 贯彻核心素养——以“函数的奇偶性”的教学设计为例 宋方宁;李硕; 2-4 高中数学课堂案例研究——探讨“教-学-评”一体化模式的应用 赖琰媛;曹小燕; 5-7 漫谈体验式教学在高中数学教学中的运用策略 林素珍; 15-17《数理化解题…

洁盟超声波清洗机怎么样?2024爆款机型声波清洗机测评、一篇看懂

随着现在近视率的逐年上升&#xff0c;戴眼镜的人群越来越多&#xff01;当然他们也在面临着一个眼镜清洗的问题&#xff01;因为长期佩戴眼镜&#xff0c;镜框还有镜片上面都是会积累灰尘、油污、污垢以及细菌&#xff0c;脏脏的不仅令眼镜不美观&#xff0c;同时在长期的佩戴…

【嵌入式】SD NAND:SD卡的集成与优化

嵌入式SD卡&#xff0c;也称为SD NAND或贴片式SD卡&#xff0c;是一种专为空间受限的设备设计的存储解决方案。这种存储卡与传统的SD卡不同&#xff0c;它采用贴片式封装&#xff0c;可以直接焊接到设备的PCB上&#xff0c;从而为电子设备提供内置存储功能。以下是嵌入式SD卡的…

简单介绍vim

文章目录 前言一、Vim的特点二、安装Vim三、设置Vim配置文件的位置&#xff1a;编辑配置文件&#xff1a;添加配置选项&#xff1a;保存并退出编辑器&#xff1a;快速配置验证设置&#xff1a; 总结 前言 Vim是一款强大的文本编辑器&#xff0c;被广泛用于各种编程和文本编辑任…

canvas实现画布拖拽效果 适配Uniapp和Vue (开箱即用)

需求:我司是做AIGC项目最近和地铁项目有关需要实现海报效果图&#xff0c;并且需要使用画布拖拽和修改上传删除等等功能 当时连续加班花了10个工作日搓出来 实现挺简单的但是Canvas数据处理还是挺麻烦的 大概功能如图下 首先我们需要引入Fabric.js 这个库封装好了原生的Canva…