linux内核 时间同步机理分析笔记

2024-08-21 23:52

本文主要是介绍linux内核 时间同步机理分析笔记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 内核时间管理的相关组件

1.1 clocksource 和 clock_event_device

1.1.1 简介

外部时钟设备的主要作用是提供精确的计时功能和定期产生中断的功能,内部把提供精确计时的功能抽象为clocksource对象,把定期产生中断的功能抽象为clock_event_device对象。                
                                《Linux内核精析》12.2.1 clocksource概述

                                《深⼊ LINUX 内核架构》P716

1.1.2 常见的外部时钟设备

TSC,HPET,ACPI PMT
                                《精通Linux内核开发》10.1 时间表⽰
                                《深⼊理解linux内核》P229

1.1.3 调试

/sys/devices/system/clocksource/
/sys/devices/system/clockevents/

1.2 timekeeping模块

1.2.1 简介

struct timekeeper定义在include/linux/timekeeper_internal.h中,保存了各种计时值。它是维护并操纵不同时间线的计时数据的主要数据结构,比如单调时间和原始时间
                                《Linux内核精析》12.3.2 timeval和timespec

1.2.2 数据结构

tk_core 

//kernel/time/timekeeping.c
/** The most important data for readout fits into a single 64 byte* cache line.*/
static struct {seqcount_t      seq; struct timekeeper   timekeeper;
} tk_core ____cacheline_aligned = {.seq = SEQCNT_ZERO(tk_core.seq),
};

 struct timekeeper;

//include/linux/timekeeper_internal.h
/*** struct timekeeper - Structure holding internal timekeeping values.* @tkr_mono:       The readout base structure for CLOCK_MONOTONIC* @tkr_raw:        The readout base structure for CLOCK_MONOTONIC_RAW* @xtime_sec:      Current CLOCK_REALTIME time in seconds* ......*/
struct timekeeper {struct tk_read_base tkr_mono;struct tk_read_base tkr_raw;u64         xtime_sec;unsigned long       ktime_sec;......
};

struct tk_read_base;

//include/linux/timekeeper_internal.h
/*** struct tk_read_base - base structure for timekeeping readout* @clock:  Current clocksource used for timekeeping.* @mask:   Bitmask for two's complement subtraction of non 64bit clocks* @cycle_last: @clock cycle value at last update* @mult:   (NTP adjusted) multiplier for scaled math conversion* @shift:  Shift value for scaled math conversion* @xtime_nsec: Shifted (fractional) nano seconds offset for readout* @base:   ktime_t (nanoseconds) base time for readout* @base_real:  Nanoseconds base value for clock REALTIME readout* ......*/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;
};

1.2.3 struct timekeeper中时间变量的更新流程

tick_sched_timer();-> tick_sched_do_timer();-> tick_do_update_jiffies64();                                      -> update_wall_time();                                          -> timekeeping_advance();                                   -> accumulate_nsecs_to_secs();                          -> k->xtime_sec++;                                  -> timekeeping_update();                                -> tk_update_ktime_data(tk);                        -> tk->ktime_sec = seconds;                     -> tk->tkr_mono.base = ns_to_ktime(seconds * NSEC_PER_SEC + nsec);

2 计算时间的流逝

时钟源硬件会产生固定周期的物理信号送给外部时钟设备,时钟设备硬件可以记录收到了多少个周期的时钟信号。
内核代码读取时钟设备硬件记录的周期数,然后将其转换成时间,周期数转换成时间的算法如下:

static inline s64 clocksource_cyc2ns(u64 cycles, u32 mult, u32 shift)
{return ((u64) cycles * mult) >> shift;
}

时钟源硬件并不总是精确的,它们的频率可能不⼀样。这个时钟变化会导 致时间漂移。在这种情况下,可以调整mult变量来弥补这个时间漂移。
                                《精通Linux内核开发》10.2 硬件抽象

3 内核时间同步的关键变量:mult

应用层的时间同步程序如何修改内核的mult变量

应用层的时间同步程序(chronyd, phc2sys等)最终都会调用内核的do_adjtimex()来进行时间调整,这个流程会修改mult变量,如下:

do_adjtimex();-> __do_adjtimex();-> ntp_update_frequency();-> tick_length     += new_base - tick_length_base;-> timekeeping_advance();-> timekeeping_adjust();    //Adjust the multiplier to correct NTP error-> tk->ntp_tick = ntp_tick_length();-> mult = div64_u64((tk->ntp_tick >> tk->ntp_error_shift) -tk->xtime_remainder, tk->cycle_interval);-> timekeeping_apply_adjustment(tk, offset, mult - tk->tkr_mono.mult);-> tk->tkr_mono.mult += mult_adj;

4 抓取实际的内核数据进行验证

4.1 查看当前clocksource的频率

当前系统的clocksource是TSC,如下:

# cat /sys/devices/system/clocksource/clocksource0/current_clocksource 
tsc

TSC时钟源的频率是 2419.200 MHz,信息如下:

# dmesg | grep -i TSC
[    0.000000] tsc: Detected 2400.000 MHz processor
[    0.000000] tsc: Detected 2419.200 MHz TSC
[    0.044651] TSC deadline timer available
[    0.159823] clocksource: tsc-early: mask: 0xffffffffffffffff max_cycles: 0x22df1149949, max_idle_ns: 440795312789 ns
[    0.778100] clocksource: Switched to clocksource tsc-early
[    0.805072] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x22df1149949, max_idle_ns: 440795312789 ns
[    0.805086] clocksource: Switched to clocksource tsc

4.2 使用kprobe模块抓取内核的mult和shift变量

4.2.1 查看tk_core结构体对象的地址

数据结构关系如下,要想抓取mult和shift变量,我们需要首先获取tk_core。

通过/proc/kallsyms文件中查看到的tk_core地址为0xffffffffae4a0100,信息如下:

# cat /proc/kallsyms | grep tk_core
ffffffffae4a0100 b tk_core

4.2.2 实现kprobe模块

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>#include <linux/timekeeper_internal.h>
#include "your_kernel_src/kernel/time/tick-internal.h"
#include "your_kernel_src/kernel/time/ntp_internal.h"
#include "your_kernel_src/kernel/time/timekeeping_internal.h"#define MAX_SYMBOL_LEN  64
static char symbol[MAX_SYMBOL_LEN] = "do_adjtimex";
module_param_string(symbol, symbol, sizeof(symbol), 0644);struct test_tk_core {seqcount_t      seq;struct timekeeper   timekeeper;
}; struct test_tk_core * tk_core = 0xffffffffae4a0100;   /* /proc/kallsyms中查看到的tk_core地址 *//* For each probe you need to allocate a kprobe structure */
static struct kprobe kp = {.symbol_name    = symbol,
};/* kprobe pre_handler: called just before the probed instruction is executed */
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{printk("--------------- clocksource name:%s, mult=%u, shift=%u \n", tk_core->timekeeper.tkr_mono.clock->name,tk_core->timekeeper.tkr_mono.mult,tk_core->timekeeper.tkr_mono.shift);/* A dump_stack() here will give a stack backtrace */return 0;
}/* kprobe post_handler: called after the probed instruction is executed */
static void handler_post(struct kprobe *p, struct pt_regs *regs,unsigned long flags)
{printk("--------------- clocksource name:%s, mult=%u, shift=%u \n", tk_core->timekeeper.tkr_mono.clock->name,tk_core->timekeeper.tkr_mono.mult,tk_core->timekeeper.tkr_mono.shift);
}static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
{pr_info("fault_handler: p->addr = 0x%p, trap #%dn", p->addr, trapnr);/* Return 0 because we don't handle the fault. */return 0;
}static int __init kprobe_init(void)
{int ret;kp.pre_handler = handler_pre;kp.post_handler = handler_post;//kp.fault_handler = handler_fault;ret = register_kprobe(&kp);if (ret < 0) {pr_err("register_kprobe failed, returned %d\n", ret);return ret;}pr_info("Planted kprobe at %p\n", kp.addr);return 0;
}static void __exit kprobe_exit(void)
{unregister_kprobe(&kp);pr_info("kprobe at %p unregistered\n", kp.addr);
}module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");

将上面代码编译成内核模块(.ko),然后insmod安装即可。

4.3 对kprobe模块抓取到的数据进行分析

4.3.1 本机时间准确时

本机时间准确时,使用dmesg看到kprobe模块抓取的信息如下:

[21821.544394] --------------- clocksource name:tsc, mult=6935128, shift=24 
[21821.544395] --------------- clocksource name:tsc, mult=6935128, shift=24

时钟频率是2419.200 MHz

2419200000 * 6935128 >> 24 = 1000 014 642ns

4.3.2 将本机时间调慢7分钟

将本机时间比标准时间调慢7分钟,使用dmesg看到kprobe模块抓取的信息如下:

[20967.796255] --------------- clocksource name:tsc, mult=7628528, shift=24 
[20967.796257] --------------- clocksource name:tsc, mult=7628528, shift=24

2419200000 * 7628528 >> 24 = 1099 999 841ns

4.3.3 将本机时间调快6分钟

将本机时间比标准时间调快6分钟,使用dmesg看到kprobe模块抓取的信息如下:

[21149.432284] --------------- clocksource name:tsc, mult=6241523, shift=24 
[21149.432288] --------------- clocksource name:tsc, mult=6241523, shift=24

2419200000 * 6241523 >> 24 = 899 999 883ns

5 总结

当linux内核记录的时间比标准时间慢时,时间同步程序会修改内核的mult变量,让内核时间走的快一些;
当linux内核记录的时间比标准时间快时,时间同步程序会修改内核的mult变量,让内核时间走的慢一些。

扩展,时间同步的时间源

PTP, PPS: Linux时间同步(PPS、PTP、chrony)分析笔记_linux pps 授时-CSDN博客

这篇关于linux内核 时间同步机理分析笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1094648

相关文章

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

Python与MySQL实现数据库实时同步的详细步骤

《Python与MySQL实现数据库实时同步的详细步骤》在日常开发中,数据同步是一项常见的需求,本篇文章将使用Python和MySQL来实现数据库实时同步,我们将围绕数据变更捕获、数据处理和数据写入这... 目录前言摘要概述:数据同步方案1. 基本思路2. mysql Binlog 简介实现步骤与代码示例1

C# LiteDB处理时间序列数据的高性能解决方案

《C#LiteDB处理时间序列数据的高性能解决方案》LiteDB作为.NET生态下的轻量级嵌入式NoSQL数据库,一直是时间序列处理的优选方案,本文将为大家大家简单介绍一下LiteDB处理时间序列数... 目录为什么选择LiteDB处理时间序列数据第一章:LiteDB时间序列数据模型设计1.1 核心设计原则

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4: