☰
Linux 内核定时器与时钟管理全景解析:linux-insides-zh 定时器章节源码级导读
2026/9/28 2:41:25 网站建设 项目流程

【免费下载链接】linux-insides-zh

Linux 内核揭秘

项目地址:https://gitcode.com/hust-open-atom-club/linux-insides-zh
点击查看免费下载

Linux 内核的定时器与时间管理子系统是驱动 TCP 超时、任务调度、异步函数触发、下一事件中断调度等核心功能的基石。本文以 Timers 章节为主线,从内核启动路径中时间管理相关函数的调用顺序出发,系统讲解jiffies、clocksource、tick broadcast、dyntick、动态定时器、clockevents以及 x86_64 时钟源和时钟系统调用的实现原理,帮助你建立从"硬件计数器"到"用户态时间"的完整知识链路。

章节导读:Timers/README 覆盖的七大主题

本章节由 7 篇连载文章组成,每篇对应内核时间管理的一个核心抽象:

  1. 简介——从setup_arch起步,介绍jiffies与clocksource两个基础概念及其初始化;
  2. 时钟源框架简介——深入clocksource框架的注册、选择与 sysfs 接口;
  3. tick broadcast 框架与 dyntick——讲解处理器进入深度睡眠后由谁唤醒、以及无 tick(NO_HZ)模式;
  4. 定时器介绍——动态定时器(软件定时器)的数据结构与运行机制;
  5. Clockevents 框架简介——与 clocksource 互为"概念反转"的时钟事件设备管理框架;
  6. x86 相关的时钟源——逐一分析 HPET、ACPI PM、TSC 三个 x86_64 时钟源;
  7. 时钟相关的系统调用——从用户态gettimeofday、clock_gettime、nanosleep一路追踪到内核实现。

阅读本文后,你将能够:理解jiffies与HZ的关系并熟练使用超时表达式;看懂dmesg中时钟源注册与切换日志;掌握通过 sysfs 查看/切换时钟源的方法;理解动态定时器与 clockevents 框架的 API 用法;最终具备独立阅读kernel/time/与arch/x86/kernel/下时间管理代码的能力。

jiffies 与 clocksource:时间管理的两个起点

内核启动路径中的时间管理函数

内核解压完成后,架构无关代码从 init/main.c 附近的start_kernel开始执行。在setup_arch函数中可以看到第一个与时间管理相关的调用:

x86_init.timers.wallclock_init();

x86_init结构体(struct x86_init_ops)为不同平台(如 Intel MID、Intel CE4100)保存了一组默认 setup 函数指针,其中timers字段类型为x86_init_timers,包含四个函数指针:

  • setup_percpu_clockev——为 boot CPU 建立 per-CPU 时钟事件设备;
  • tsc_pre_init——TSC 初始化之前调用的平台函数;
  • timer_init——初始化平台定时器;
  • wallclock_init——初始化墙钟(wallclock)设备。

标准 PC 硬件上wallclock_init指向空操作函数x86_init_noop,仅 Intel MID 平台会将其替换为intel_mid_rtc_init(该函数解析 Simple Firmware Interface 的 M-RTC 表并设置x86_platform.get_wallclock/set_wallclock)。紧接着setup_arch中还有第二个关键调用:

register_refined_jiffies(CLOCK_TICK_RATE);

jiffies:系统启动以来的 tick 计数

jiffies是 Linux 内核中的全局变量,保存系统启动以来发生的时钟中断(tick)次数,初始化时置零,每次定时器中断递增。内核同时定义了两个相关变量:

extern unsigned long volatile __jiffy_data jiffies; extern u64 jiffies_64;

实际生效的变量取决于处理器类型:x86_64 使用u64,x86_32 使用unsigned long(即jiffies_64的低 32 位),这一映射由链接脚本 arch/x86/kernel/vmlinux.lds.S 完成。jiffies的读出必须通过read_seqbegin/read_seqretry保护的 seqlock 机制(见kernel/time/jiffies.c中get_jiffies_64的实现),原因是有一些机器无法原子地读取完整的 64 位值。

clocksource:硬件计数器的抽象层

不同硬件提供的时钟源能力差异巨大:x86 片上的 TSC 频率可等于处理器频率;HPET 是至少 10 MHz 的 64 位计数器。为统一管理这些硬件,内核引入clocksource概念——"硬件抽象的自由运行计数器",为内核提供时间值。以 jiffies 为例,其 clocksource 定义为:

static struct clocksource clocksource_jiffies = { .name = "jiffies", .rating = 1, .read = jiffies_read, .mask = 0xffffffff, .mult = NSEC_PER_JIFFY << JIFFIES_SHIFT, .shift = JIFFIES_SHIFT, .max_cycles = 10, };

关键字段说明:

  • rating:时钟源管理代码据此选择最佳时钟源,取值范围与含义如下:
    • 1-99——仅用于启动和测试;
    • 100-199——可用于实际使用,但不理想;
    • 200-299——正确可用的时钟源(如 HPET 为 250、acpi_pm 为 200);
    • 300-399——相当快且准确的时钟源(如 TSC 为 300);
    • 400-499——理想时钟源,可用时必选。
  • mask:非 64 位计数器的减法无需特殊溢出逻辑。jiffies 的 mask 是 32 位0xffffffff,意味着约 42 秒后回绕到零。
  • mult / shift:将计数器原始值转换为纳秒,转换公式为((u64) cycles * mult) >> shift。jiffies 的mult = NSEC_PER_JIFFY << JIFFIES_SHIFT,其中NSEC_PER_JIFFY = (NSEC_PER_SEC + HZ/2) / HZ;JIFFIES_SHIFT依 HZ 取值:HZ < 34时为 6,HZ < 67时为 7,否则为 8。
  • max_cycles:可安全相乘而不溢出(溢出可能)的最大周期值。

HZ代表系统定时器频率,由CONFIG_HZ配置项决定。在 x86 上定义为#define HZ CONFIG_HZ,可选值见内核配置菜单:

x86 默认CONFIG_HZ_1000=y(对应HZ=1000),此时定时器中断每秒发生 1000 次;若选择 250 HZ,则每 4ms 一次中断。

register_refined_jiffies 的注册流程

register_refined_jiffies(CLOCK_TICK_RATE)的核心目标是注册refined_jiffies时钟源。它与标准clocksource_jiffies的区别在于:标准 jiffies 的分辨率受限于定时器中断频率(HZ),精度可能不足;refined_jiffies则以CLOCK_TICK_RATE为基准获得更高精度。其中CLOCK_TICK_RATE展开为PIT_TICK_RATE,即 Intel 8253 可编程间隔定时器的频率1193182 Hz。

实现要点(kernel/time/jiffies.c):

refined_jiffies = clocksource_jiffies; refined_jiffies.name = "refined-jiffies"; refined_jiffies.rating++; /* 由 1 提升到 2,成为时钟源管理代码的最佳候选 */

随后计算每个 tick 的周期数、通过移位获得额外精度,最终调用__clocksource_register(&refined_jiffies)完成注册。

使用 jiffies 的实战表达式

jiffies / HZ可得到系统运行秒数(uptime)。内核中大量使用jiffies + n*HZ设定未来超时点:

/* 从现在起 30 秒 */ jiffies + 30*HZ /* 从现在起 2 分钟 */ jiffies + 120*HZ /* 从现在起 1 毫秒 */ jiffies + HZ / 1000

配合time_before/time_after_eq等宏即可实现超时轮询,例如arch/x86/kernel/smpboot.c的do_boot_cpu中等待应用处理器 10 秒响应,或sound/isa/sscape.c中obp_startup_ack等待声卡启动确认。这是内核中最常见的超时编程模式。

clocksource 框架深入:注册、选择与 sysfs

clocksource 结构全字段解析

clocksource结构定义于include/linux/clocksource.h,除前面见过的字段外还包括:

  • read——返回计数器周期值(cycle_t,即u64)的函数指针;
  • max_idle_ns——允许的最大空闲时间(纳秒),服务于启用CONFIG_NO_HZ的内核:动态 tick 允许内核休眠超过单个 tick 甚至无限长,该字段即休眠上限;
  • maxadj——mult的最大调整值,用于校正((u64) cycles * mult) >> shift的舍入误差,并防止调整后mult溢出;
  • enable/disable/suspend/resume——启用、禁用、挂起、恢复回调;
  • owner——所属内核模块的引用。

受配置选项影响的字段还有两类:

  • CONFIG_ARCH_CLOCKSOURCE_DATA:x86/IA64 专属的arch_clocksource_data,其中vclock_mode标识 vDSO 时钟模式(VCLOCK_NONE=0、VCLOCK_TSC=1、VCLOCK_HPET=2、VCLOCK_PVCLOCK=3);
  • CONFIG_CLOCKSOURCE_WATCHDOG:wd_list、cs_last、wd_last三个看门狗相关字段,仅 x86 架构启用该选项——因为 TSC 需要对照其他时钟源验证。

注册新时钟源的 API 与调用链

注册 API 共有三个等价入口:

static inline int __clocksource_register(struct clocksource *cs) { return __clocksource_register_scale(cs, 1, 0); } static inline int clocksource_register_hz(struct clocksource *cs, u32 hz) { return __clocksource_register_scale(cs, 1, hz); } static inline int clocksource_register_khz(struct clocksource *cs, u32 khz) { return __clocksource_register_scale(cs, 1000, khz); }

三者最终都调用kernel/time/clocksource.c中的__clocksource_register_scale(cs, scale, freq)。该函数先调用__clocksource_update_freq_scale计算mult/shift(若频率非零),再在clocksource_mutex保护下依次执行:

clocksource_enqueue(cs); /* 按 rating 排序插入 clocksource_list */ clocksource_enqueue_watchdog(cs); /* 按标志插入看门狗链表 */ clocksource_select(); /* 选择最佳时钟源 */

clocksource_enqueue遍历已注册列表,按rating降序找到插入点;clocksource_select(内部为__clocksource_select(false))从链表头取出 rating 最高的时钟源并切换:

if (curr_clocksource != best && !timekeeping_notify(best)) { pr_info("Switched to clocksource %s\n", best->name); curr_clocksource = best; }

dmesg中可以观察到上述过程的完整轨迹:

$ dmesg | grep "clocksource:" [ 0.000000] clocksource: refined-jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1910969940391419 ns [ 0.000000] clocksource: hpet: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 133484882848 ns [ 0.094084] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1911260446275000 ns [ 0.205302] clocksource: acpi_pm: mask: 0xffffff max_cycles: 0xffffff, max_idle_ns: 2085701024 ns [ 1.452979] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x7350b459580, max_idle_ns: 881591204237 ns

sysfs 接口:查看与切换时钟源

init_clocksource_sysfs通过device_initcall注册clocksource子系统,创建/sys/devices/system/clocksource/目录及三个属性文件:current_clocksource、unbind_clocksource、available_clocksource。实战用法:

$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource tsc hpet acpi_pm $ cat /sys/devices/system/clocksource/clocksource0/current_clocksource tsc

注意jiffies与refined_jiffies不会出现在可用列表中——该文件只映射带CLOCK_SOURCE_VALID_FOR_HRES标志的高分辨率时钟源。

tick broadcast 框架与 dyntick:让空闲处理器"睡得更深"

tick_init 与 idle 进程

start_kernel中setup_arch之后第一个时间管理相关调用是tick_init()(kernel/time/tick-common.c):

void __init tick_init(void) { tick_broadcast_init(); tick_nohz_init(); }

tick_broadcast_init初始化 tick broadcast 框架,tick_nohz_init初始化无 tick(tickless)模式数据结构。背景问题在于:当处理器运行cpu_idle_loop空闲循环时,系统定时器中断对功耗管理几乎无意义——空闲处理器没必要被周期性中断打扰。

两种省电模式

Linux 内核提供两种省略调度时钟中断的方式:

  • CONFIG_NO_HZ_IDLE(dyntick-idle 模式):空闲处理器不再接收周期性定时器中断,改为按需中断。进入空闲时调用tick_nohz_idle_enter,退出时调用tick_nohz_idle_exit(均在kernel/time/tick-sched.c);
  • CONFIG_NO_HZ_FULL(full dynticks):空闲处理器以及只有一个可运行任务的"忙碌"处理器都省略调度时钟中断,大幅减少定时器中断次数。

tick broadcast:谁唤醒沉睡的处理器

当处理器进入深度睡眠(cpuidle 框架的 C-states)时,其本地定时器可能停止工作,此时需要一个不受 C-states 影响的定时器负责唤醒。tick broadcast 框架正是为此而生。

tick_broadcast_init通过zalloc_cpumask_var分配多个 cpumask:

  • tick_broadcast_mask——处于睡眠模式的处理器位图;
  • tick_broadcast_on——处于周期性广播状态的处理器位图;
  • tmpmask——临时位图;
  • tick_broadcast_oneshot_mask/tick_broadcast_pending_mask/tick_broadcast_force_mask——仅在CONFIG_TICK_ONESHOT下分配,对应 oneshot 模式下的通知、挂起与强制广播。

时钟事件设备可处于两种模式:CLOCK_EVT_FEAT_PERIODIC(周期性事件)与CLOCK_EVT_FEAT_ONESHOT(一次性事件)。

新时钟事件设备注册时,tick_check_new_device会调用tick_install_broadcast_device判断其能否充当广播设备:先通过tick_check_broadcast_device检查features与rating,再用clockevents_exchange_device替换旧设备,最后在tick_broadcast_mask非空时以tick_broadcast_start_periodic启动周期模式。中断到来时,HPET 的hpet_interrupt_handler调用hevt->event_handler(hevt),最终经tick_do_broadcast发送 IPI 唤醒目标处理器集合。

NO_HZ 数据结构的初始化

tick_nohz_init检查tick_nohz_full_running状态,通过tick_nohz_init_all(CONFIG_NO_HZ_FULL_ALL下)分配tick_nohz_full_mask并置全部位;随后分配housekeeping_mask(至少需要一个不做 NO_HZ 的处理器负责 timekeeping),并用arch_irq_work_has_interrupt(x86 上检查cpu_has_apic)确认架构支持 IPI 自唤醒。最后将当前处理器从tick_nohz_full_mask中清除(它用于 timekeeping),并对剩余处理器调用context_tracking_cpu_set开启上下文跟踪。

动态定时器:内核的软件定时器

定时器概念与典型用法

Linux 内核提供软件定时器(software timer / dynamic timer)概念,允许内核函数在未来的某个时刻被调用,广泛用于垃圾回收、协议超时等场景。例如net/netfilter/ipset/ip_set_list_set.c中list_set结构体的gc字段:

struct list_set { ... struct timer_list gc; ... };

其初始化中设置回调与到期时间:

map->gc.function = gc; map->gc.expires = jiffies + IPSET_GC_PERIOD(set->timeout) * HZ;

init_timers:定时器子系统的启动

init_timers()(kernel/time/timer.c)在tick_init之后被调用,包含四个步骤:

void __init init_timers(void) { init_timer_cpus(); init_timer_stats(); timer_register_cpu_notifier(); open_softirq(TIMER_SOFTIRQ, run_timer_softirq); }
  • init_timer_cpus为每个 possible CPU 初始化tvec_base;
  • init_timer_stats初始化 per-CPU 的tstats_lookup_lock自旋锁,保护/proc/timer_stats统计数据;
  • timer_register_cpu_notifier在CONFIG_HOTPLUG_CPU下注册 CPU 热插拔通知,处理器下线时经timer_cpu_notify调用migrate_timers迁移定时器;
  • open_softirq(TIMER_SOFTIRQ, run_timer_softirq)注册软中断处理函数——动态定时器不在硬件中断上下文直接处理(耗时操作),而是推迟到软中断执行。

tvec_base:分层定时器轮(timer wheel)

tvec_base是每个处理器动态定时器的主数据结构:

struct tvec_base { spinlock_t lock; struct timer_list *running_timer; unsigned long timer_jiffies; unsigned long next_timer; unsigned long active_timers; unsigned long all_timers; int cpu; bool migration_enabled; bool nohz_active; struct tvec_root tv1; struct tvec tv2; struct tvec tv3; struct tvec tv4; struct tvec tv5; } ____cacheline_aligned;

字段含义:timer_jiffies是最早到期时间,用于定位已过期定时器;next_timer在 NO_HZ 模式下为下一次中断保存下一个待处理定时器;active_timers统计不可延迟定时器,all_timers统计全部定时器;migration_enabled与nohz_active分别表示定时器迁移能力与 NO_HZ 状态。

最后五个字段构成经典的分层定时器轮。tv1的元素个数由TVR_SIZE决定,而TVR_BITS依赖CONFIG_BASE_SMALL:

#define TVR_SIZE (1 << TVR_BITS) #define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8)

CONFIG_BASE_SMALL若启用可缩小内核数据结构(默认关闭时tv1有 256 个槽位)。tv1存放将在接下来 255 次中断内到期的定时器;tv2/tv3/tv4/tv5分别存放2^14-1、2^20-1、2^26及更长到期周期的定时器。

定时器运行机制:run_timer_softirq

run_timer_softirq比较当前jiffies与base->timer_jiffies,若当前时间已到则调用__run_timers:在持锁状态下循环执行已到期定时器。核心是计算tv1索引index = base->timer_jiffies & TVR_MASK;若索引为 0,则通过cascade函数将上层(tv2~tv5)的定时器逐级级联下来;随后递增timer_jiffies,用hlist_move_list移出该槽位链表并逐个调用call_timer_fn执行回调fn(data)。

动态定时器 API 速查

使用动态定时器需定义timer_list变量,两种初始化方式:

#define init_timer(timer) \ __init_timer((timer), 0) #define TIMER_INITIALIZER(_function, _expires, _data) \ __TIMER_INITIALIZER((_function), (_expires), (_data), 0)

启动与停止:

void add_timer(struct timer_list *timer); int del_timer(struct timer_list *timer);

clockevents 框架:时钟源的"概念反转"

框架定位

正如文档所述:"Clock events are the conceptual reverse of clock sources"。clocksource框架负责提供"时间线"(把计数器转换为纳秒等单位),而clockevents框架负责管理能够在未来某个确定时刻触发事件(中断)的时钟事件设备。

clock_event_device 结构与状态机

核心结构为include/linux/clockchips.h中的clock_event_device,包含name、event_handler、set_next_event、features等字段。通用 features:

#define CLOCK_EVT_FEAT_PERIODIC 0x000001 /* 可编程为周期性事件 */ #define CLOCK_EVT_FEAT_ONESHOT 0x000002 /* 仅触发一次事件 */

x86_64 还有CLOCK_EVT_FEAT_C3STOP (0x000008)——设备在 C3 状态会停止工作。

时钟事件设备状态机(enum clock_event_state):

  • CLOCK_EVT_STATE_DETACHED——未被框架使用(所有设备注册时的初始状态);
  • CLOCK_EVT_STATE_SHUTDOWN——已断电;
  • CLOCK_EVT_STATE_PERIODIC——可编程为周期性事件;
  • CLOCK_EVT_STATE_ONESHOT——可编程为一次性事件;
  • CLOCK_EVT_STATE_ONESHOT_STOPPED——曾编程为一次性事件、当前临时停止。

设备注册流程

以 at91sam926x 周期间隔定时器(PIT)为例,初始化clock_event_device后调用clockevents_register_device(&data->clkevt)。注册函数(kernel/time/clockevents.c)流程如下:

  1. clockevent_set_state(dev, CLOCK_EVT_STATE_DETACHED)设置初始状态;
  2. 校验dev->cpumask,为空则取smp_processor_id()对应处理器;
  3. 在raw_spin_lock_irqsave(&clockevents_lock, flags)保护下:
    • list_add(&dev->list, &clockevent_devices)加入全局设备链表;
    • tick_check_new_device(dev)检查新设备是否优于当前 tick 设备(tick_check_preferred中 oneshot 特性优先,其次比较 rating 与 cpumask),若更优则clockevents_exchange_device交换并tick_setup_device按模式调用tick_setup_periodic/tick_setup_oneshot;
    • clockevents_notify_released()清理已释放设备。

其中tick_setup_periodic调用clockevents_switch_state(dev, CLOCK_EVT_STATE_PERIODIC)与clockevents_program_event,__clockevents_switch_state依据目标状态分发到设备回调,例如 PIT 的pit_clkevt_set_periodic会向AT91_PIT_MR寄存器写入(cycle - 1) | AT91_PIT_PITEN | AT91_PIT_PITIEN(置位 24、25 位以启用周期中断)。

x86_64 的三大时钟源:HPET、ACPI PM 与 TSC

通过 sysfs 与 dmesg 可观察 x86_64 平台上按初始化顺序注册的三个高分辨率时钟源:

$ dmesg | grep clocksource [ 0.000000] clocksource: hpet: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 133484882848 ns [ 0.186498] clocksource: Switched to clocksource hpet [ 0.196827] clocksource: acpi_pm: mask: 0xffffff max_cycles: 0xffffff, max_idle_ns: 2085701024 ns [ 1.413685] tsc: Refined TSC clocksource calibration: 3999.981 MHz [ 2.413748] clocksource: Switched to clocksource tsc

HPET(rating 250)

x86 的 HPET 实现位于arch/x86/kernel/hpet.c。start_kernel中late_time_init(x86 上为x86_late_time_init,依次调用x86_init.timers.timer_init()与tsc_init())触发初始化。hpet_time_init若hpet_enable()失败则回退到setup_pit_timer(),最后setup_default_timer_irq()配置 IRQ0。hpet_enable检查内核命令行未传hpet=disable、确认 ACPI HPET 表提供的hpet_address后,用ioremap_nocache(hpet_address, HPET_MMAP_SIZE)映射 1024 字节寄存器空间。其 clocksource 定义为:

static struct clocksource clocksource_hpet = { .name = "hpet", .rating = 250, .read = read_hpet, .mask = HPET_MASK, .flags = CLOCK_SOURCE_IS_CONTINUOUS, .resume = hpet_resume_counter, .archdata = { .vclock_mode = VCLOCK_HPET }, };

read_hpet直接读取 Main Counter Register 返回原子计数器。

ACPI PM timer(rating 200)

实现位于drivers/clocksource/acpi_pm.c,经 fs initcall 进入init_acpi_pm_clocksource。它依赖pmtmr_ioport(由arch/x86/kernel/acpi/boot.c的acpi_parse_fadt从 FADT 表的X_PM_TMR_BLK字段解析得到)。寄存器为 32 位运行计数,read_pmtmr()通过inl(pmtmr_ioport) & ACPI_PM_MASK读取低 24 位。其 clocksource 定义 rating 为 200:

static struct clocksource clocksource_acpi_pm = { .name = "acpi_pm", .rating = 200, .read = acpi_pm_read, .mask = (cycle_t)ACPI_PM_MASK, .flags = CLOCK_SOURCE_IS_CONTINUOUS, };

TSC(rating 300,最终胜出)

实现位于arch/x86/kernel/tsc.c。tsc_init先检查cpu_has_tsc(展开为boot_cpu_has(X86_FEATURE_TSC)),随后calibrate_tsc通过 MSR、PIT 等方式校准频率,并为所有可能处理器初始化cyc2ns_init与set_cyc2ns_scale。实际注册推迟到 device initcall 阶段的init_tsc_clocksource,以确保 TSC 在 HPET 之后注册(TSC 需先经 HPET 校验)。定义:

static struct clocksource clocksource_tsc = { .name = "tsc", .rating = 300, .read = read_tsc, .mask = CLOCKSOURCE_MASK(64), .flags = CLOCK_SOURCE_IS_CONTINUOUS | CLOCK_SOURCE_MUST_VERIFY, .archdata = { .vclock_mode = VCLOCK_TSC }, };

由于 rating 最高(300),TSC 最终被选为当前时钟源。TSC 频率与处理器状态无关(恒定速率递增),可在/proc/cpuinfo中查看model name对应的标称频率。

时钟相关的系统调用:从用户态到内核

用户程序同样需要时间。本章最后一部分以gettimeofday、clock_gettime、nanosleep三个系统调用为例,追踪从 glibc 标准库到内核实现的完整路径(仅讨论 x86_64)。

以最简单的gettimeofday为例:

#include <time.h> #include <sys/time.h> #include <stdio.h> int main(int argc, char **argv) { char buffer[40]; struct timeval time; gettimeofday(&time, NULL); strftime(buffer, 40, "Current date/time: %m-%d-%Y/%T", localtime(&time.tv_sec)); printf("%s\n", buffer); return 0; }

struct timeval包含tv_sec(秒)与tv_usec(微秒)。关键点在于gettimeofday并非普通系统调用——glibc 的实现(sysdeps/unix/sysv/linux/x86/gettimeofday.c)优先通过_dl_vdso_vsym解析 vDSO 中的__vdso_gettimeofday符号,失败才回退到常规系统调用路径:

return (_dl_vdso_vsym ("__vdso_gettimeofday", &linux26) ?: (void*) (&__gettimeofday_syscall));

vDSO 入口定义于arch/x86/entry/vdso/vclock_gettime.c,其中gettimeofday是__vdso_gettimeofday的弱别名,后者在timeval非空时调用do_realtime完成当前时间读取。借助 vDSO 机制,用户态可无陷入(trap)地读取时间,这正是时钟源archdata.vclock_mode(VCLOCK_TSC / VCLOCK_HPET)与 vDSO 协同的价值所在。

总结与继续阅读指引

至此,时间管理子系统的完整脉络已清晰可见:

  • jiffies提供 tick 计数基础,clocksource统一抽象各类硬件计数器并支持按 rating 择优切换;
  • tick broadcast与dyntick/NO_HZ让空闲处理器摆脱周期性中断、兼顾功耗与唤醒;
  • 动态定时器基于分层定时器轮(timer wheel)在软中断中批量执行到期回调;
  • clockevents框架管理能在未来时刻触发中断的时钟事件设备;
  • x86_64的 HPET、ACPI PM、TSC 三个时钟源按初始化顺序注册并逐级切换;
  • 用户态通过系统调用或 vDSO 获取时间,与上述内核抽象环环相扣。

继续深入研究可依次阅读 linux-timers-1.md 至 linux-timers-7.md 七篇连载,其中每一篇都附有对应的内核源码文件路径;也可对照 翻译状态说明 了解各篇翻译进度。本章内容与 初始化章节(setup_arch、start_kernel调用顺序)、中断章节(软中断与 IRQ 处理)以及 CPU 概念章节(cpumask、per-CPU 变量)相互印证,推荐交叉阅读以建立全局视角。

【免费下载链接】linux-insides-zh

Linux 内核揭秘

项目地址:https://gitcode.com/hust-open-atom-club/linux-insides-zh
点击查看免费下载

相关推荐

上一篇:Zoom 集成故障排查指南:利用 knowledge-work-plugins 的 /debug-zoom 技能分层定位并修复问题
下一篇:fp-ts与GraphQL:使用io-ts验证GraphQL查询结果

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询