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

相关文章

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

Linux磁盘分区、格式化和挂载方式

《Linux磁盘分区、格式化和挂载方式》本文详细介绍了Linux系统中磁盘分区、格式化和挂载的基本操作步骤和命令,包括MBR和GPT分区表的区别、fdisk和gdisk命令的使用、常见的文件系统格式以... 目录一、磁盘分区表分类二、fdisk命令创建分区1、交互式的命令2、分区主分区3、创建扩展分区,然后

Linux中chmod权限设置方式

《Linux中chmod权限设置方式》本文介绍了Linux系统中文件和目录权限的设置方法,包括chmod、chown和chgrp命令的使用,以及权限模式和符号模式的详细说明,通过这些命令,用户可以灵活... 目录设置基本权限命令:chmod1、权限介绍2、chmod命令常见用法和示例3、文件权限详解4、ch

Linux内核之内核裁剪详解

《Linux内核之内核裁剪详解》Linux内核裁剪是通过移除不必要的功能和模块,调整配置参数来优化内核,以满足特定需求,裁剪的方法包括使用配置选项、模块化设计和优化配置参数,图形裁剪工具如makeme... 目录简介一、 裁剪的原因二、裁剪的方法三、图形裁剪工具四、操作说明五、make menuconfig

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

Linux使用nohup命令在后台运行脚本

《Linux使用nohup命令在后台运行脚本》在Linux或类Unix系统中,后台运行脚本是一项非常实用的技能,尤其适用于需要长时间运行的任务或服务,本文我们来看看如何使用nohup命令在后台... 目录nohup 命令简介基本用法输出重定向& 符号的作用后台进程的特点注意事项实际应用场景长时间运行的任务服

什么是cron? Linux系统下Cron定时任务使用指南

《什么是cron?Linux系统下Cron定时任务使用指南》在日常的Linux系统管理和维护中,定时执行任务是非常常见的需求,你可能需要每天执行备份任务、清理系统日志或运行特定的脚本,而不想每天... 在管理 linux 服务器的过程中,总有一些任务需要我们定期或重复执行。就比如备份任务,通常会选在服务器资

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

如何用Java结合经纬度位置计算目标点的日出日落时间详解

《如何用Java结合经纬度位置计算目标点的日出日落时间详解》这篇文章主详细讲解了如何基于目标点的经纬度计算日出日落时间,提供了在线API和Java库两种计算方法,并通过实际案例展示了其应用,需要的朋友... 目录前言一、应用示例1、天安门升旗时间2、湖南省日出日落信息二、Java日出日落计算1、在线API2

如何安装HWE内核? Ubuntu安装hwe内核解决硬件太新的问题

《如何安装HWE内核?Ubuntu安装hwe内核解决硬件太新的问题》今天的主角就是hwe内核(hardwareenablementkernel),一般安装的Ubuntu都是初始内核,不能很好地支... 对于追求系统稳定性,又想充分利用最新硬件特性的 Ubuntu 用户来说,HWEXBQgUbdlna(Har