当前位置: 首页 > news >正文

Linux中时间子系统初始化time_init函数

时间子系统初始化time_init

void __init time_init(void)
{
#ifdef CONFIG_HPET_TIMERif (is_hpet_capable()) {/** HPET initialization needs to do memory-mapped io. So, let* us do a late initialization after mem_init().*/late_time_init = hpet_time_init;return;}
#endifxtime.tv_sec = get_cmos_time();xtime.tv_nsec = (INITIAL_JIFFIES % HZ) * (NSEC_PER_SEC / HZ);set_normalized_timespec(&wall_to_monotonic,-xtime.tv_sec, -xtime.tv_nsec);cur_timer = select_timer();printk(KERN_INFO "Using %s for high-res timesource\n",cur_timer->name);time_init_hook();
}

1. 函数功能概述

time_init 函数负责初始化内核的时间系统,包括硬件时钟读取、时间源选择、系统时间基准设置等,为内核提供时间管理的基础设施

2. 代码逐段分析

2.1. 函数定义

void __init time_init(void)
{
  • void:函数没有返回值
  • __init:宏标记,表示该函数只在内核初始化阶段使用,初始化完成后内存会被释放

2.2. HPET 定时器支持(条件编译)

#ifdef CONFIG_HPET_TIMERif (is_hpet_capable()) {/** HPET initialization needs to do memory-mapped io. So, let* us do a late initialization after mem_init().*/late_time_init = hpet_time_init;return;}
#endif
  • #ifdef CONFIG_HPET_TIMER:条件编译,只在支持 HPET 的内核中包含
  • if (is_hpet_capable()):检查系统是否支持 HPET(高精度事件定时器)
  • HPET 初始化需要内存映射IO,所以在 mem_init() 之后进行延迟初始化
  • late_time_init = hpet_time_init:设置延迟初始化函数指针
  • return:如果使用 HPET,直接返回,后续初始化在延迟阶段进行

2.3. 读取 CMOS 时钟时间

        xtime.tv_sec = get_cmos_time();
  • xtime:内核全局变量,存储系统当前时间(从 1970-01-01 开始的秒数)
  • xtime.tv_sec:时间的秒数部分
  • get_cmos_time():函数调用,从 CMOS 实时时钟芯片读取当前时间

2.4. 计算纳秒部分

        xtime.tv_nsec = (INITIAL_JIFFIES % HZ) * (NSEC_PER_SEC / HZ);
  • xtime.tv_nsec:时间的纳秒部分
  • INITIAL_JIFFIES:内核启动时的 jiffies 初始值
  • HZ:系统时钟频率(通常 100, 250, 1000 等)
  • NSEC_PER_SEC:每秒的纳秒数(1,000,000,000)
  • 计算原理(jiffies % HZ) 得到当前秒内已过的时钟滴答数,乘以每个滴答的纳秒数

2.5. 设置单调时间基准

        set_normalized_timespec(&wall_to_monotonic,-xtime.tv_sec, -xtime.tv_nsec);
  • set_normalized_timespec():规范化时间规格函数
  • &wall_to_monotonic:全局变量,存储墙钟时间到单调时间的偏移量
  • -xtime.tv_sec, -xtime.tv_nsec:取负值,表示从墙钟时间减去当前时间得到单调时间起点
  • 作用:建立单调时间基准,确保 CLOCK_MONOTONIC 从系统启动开始计时

2.6. 选择定时器硬件

        cur_timer = select_timer();
  • cur_timer:全局指针,指向当前使用的定时器操作结构
  • select_timer():函数调用,自动检测并选择可用的高精度定时器硬件
  • 选择顺序:通常 HPET > ACPI PMT > PIT > 其他

2.7. 打印定时器信息

        printk(KERN_INFO "Using %s for high-res timesource\n",cur_timer->name);
  • printk(KERN_INFO ...):内核信息打印
  • cur_timer->name:所选定时器的名称字符串
  • 作用:在启动日志中显示使用的高分辨率时间源

2.8. 调用架构特定初始化

        time_init_hook();
}
  • time_init_hook():函数指针,指向架构特定的时间初始化函数
  • 作用:允许不同 CPU 架构进行特定的时间系统设置

读取硬件时间get_cmos_time

unsigned long get_cmos_time(void)
{unsigned long retval;spin_lock(&rtc_lock);if (efi_enabled)retval = efi_get_time();elseretval = mach_get_cmos_time();spin_unlock(&rtc_lock);return retval;
}
inline unsigned long __init efi_get_time(void)
{efi_status_t status;efi_time_t eft;efi_time_cap_t cap;status = phys_efi_get_time(&eft, &cap);if (status != EFI_SUCCESS)printk("Oops: efitime: can't read time status: 0x%lx\n",status);return mktime(eft.year, eft.month, eft.day, eft.hour,eft.minute, eft.second);
}
static inline unsigned long mach_get_cmos_time(void)
{unsigned int year, mon, day, hour, min, sec;int i;/* The Linux interpretation of the CMOS clock register contents:* When the Update-In-Progress (UIP) flag goes from 1 to 0, the* RTC registers show the second which has precisely just started.* Let's hope other operating systems interpret the RTC the same way.*//* read RTC exactly on falling edge of update flag */for (i = 0 ; i < 1000000 ; i++) /* may take up to 1 second... */if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP)break;for (i = 0 ; i < 1000000 ; i++) /* must try at least 2.228 ms */if (!(CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP))break;do { /* Isn't this overkill ? UIP above should guarantee consistency */sec = CMOS_READ(RTC_SECONDS);min = CMOS_READ(RTC_MINUTES);hour = CMOS_READ(RTC_HOURS);day = CMOS_READ(RTC_DAY_OF_MONTH);mon = CMOS_READ(RTC_MONTH);year = CMOS_READ(RTC_YEAR);} while (sec != CMOS_READ(RTC_SECONDS));if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) || RTC_ALWAYS_BCD){BCD_TO_BIN(sec);BCD_TO_BIN(min);BCD_TO_BIN(hour);BCD_TO_BIN(day);BCD_TO_BIN(mon);BCD_TO_BIN(year);}if ((year += 1900) < 1970)year += 100;return mktime(year, mon, day, hour, min, sec);
}

1. 函数功能概述

这些函数负责从硬件时钟(RTC 或 EFI)读取当前时间,并将其转换为从 1970-01-01 开始的秒数,为系统提供初始时间基准

2. get_cmos_time 函数分析

2.1. 函数定义和变量声明

unsigned long get_cmos_time(void)
{unsigned long retval;
  • unsigned long:返回值类型,表示从 1970-01-01 开始的秒数
  • retval:存储返回的时间值

2.2. 获取 RTC 锁

        spin_lock(&rtc_lock);
  • spin_lock(&rtc_lock):获取实时时钟的自旋锁
  • 作用:防止多CPU同时访问RTC硬件,保证读操作的原子性

2.3. EFI 时间获取分支

        if (efi_enabled)retval = efi_get_time();
  • efi_enabled:检查系统是否启用了 EFI(可扩展固件接口)
  • efi_get_time():如果使用 EFI,调用 EFI 时间获取函数

2.4. 传统 CMOS 时间获取分支

        elseretval = mach_get_cmos_time();
  • mach_get_cmos_time():传统 BIOS 系统,调用 CMOS RTC 时间获取函数

2.5. 释放锁并返回

        spin_unlock(&rtc_lock);return retval;
}
  • spin_unlock(&rtc_lock):释放 RTC 锁
  • return retval:返回获取的时间值

3. efi_get_time 函数分析

3.1. 函数定义

inline unsigned long __init efi_get_time(void)
{
  • inline:内联函数,减少函数调用开销
  • __init:只在初始化阶段使用

3.2. 变量声明

        efi_status_t status;efi_time_t eft;efi_time_cap_t cap;
  • efi_status_t:EFI 操作状态类型
  • efi_time_t:EFI 时间结构体
  • efi_time_cap_t:EFI 时间能力结构体

3.3. 调用 EFI 获取时间

        status = phys_efi_get_time(&eft, &cap);
  • phys_efi_get_time():物理 EFI 调用,从固件获取时间
  • &eft:传递时间结构体指针接收时间数据
  • &cap:传递能力结构体指针

3.4. 错误处理

        if (status != EFI_SUCCESS)printk("Oops: efitime: can't read time status: 0x%lx\n",status);
  • 检查 EFI 操作状态
  • 如果失败,打印错误信息但继续执行

3.5. 时间转换

        return mktime(eft.year, eft.month, eft.day, eft.hour,eft.minute, eft.second);
}
  • mktime():将年月日时分秒转换为从 1970-01-01 开始的秒数

4. mach_get_cmos_time 函数分析

4.1. 函数定义和变量声明

static inline unsigned long mach_get_cmos_time(void)
{unsigned int year, mon, day, hour, min, sec;int i;
  • 声明时间分量变量和循环计数器

4.2. 等待 UIP 标志置位

        /* read RTC exactly on falling edge of update flag */for (i = 0 ; i < 1000000 ; i++) /* may take up to 1 second... */if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP)break;
  • 循环等待 UIP(更新进行中)标志置位
  • 最多等待 100 万次,可能长达 1 秒
  • CMOS_READ(RTC_FREQ_SELECT):读取 CMOS 频率选择寄存器
  • RTC_UIP:更新进行中标志位

4.3. 等待 UIP 标志清除

        for (i = 0 ; i < 1000000 ; i++) /* must try at least 2.228 ms */if (!(CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP))break;
  • 等待 UIP 标志清除(从 1 变 0)
  • 必须在 UIP 下降沿后至少尝试 2.228 毫秒
  • 这是读取稳定时间数据的关键点

4.4. 读取时间寄存器(带重试)

        do { /* Isn't this overkill ? UIP above should guarantee consistency */sec = CMOS_READ(RTC_SECONDS);min = CMOS_READ(RTC_MINUTES);hour = CMOS_READ(RTC_HOURS);day = CMOS_READ(RTC_DAY_OF_MONTH);mon = CMOS_READ(RTC_MONTH);year = CMOS_READ(RTC_YEAR);} while (sec != CMOS_READ(RTC_SECONDS));
  • 循环读取所有时间寄存器
  • 条件:直到秒数读取一致(防止在秒变更时读取)

4.5. BCD 到二进制转换

        if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) || RTC_ALWAYS_BCD){BCD_TO_BIN(sec);BCD_TO_BIN(min);BCD_TO_BIN(hour);BCD_TO_BIN(day);BCD_TO_BIN(mon);BCD_TO_BIN(year);}
  • 检查 RTC 控制寄存器是否使用 BCD(二进制编码十进制)格式
  • 如果使用 BCD 或强制 BCD,将所有分量转换为二进制
  • BCD_TO_BIN:宏,将 BCD 码转换为二进制数

4.6. 年份调整

        if ((year += 1900) < 1970)year += 100;
  • 年份加上 1900(RTC 通常存储从 1900 开始的偏移)
  • 如果年份仍小于 1970,再加 100(处理 2000 年问题)
    • 早期的 CMOS RTC 芯片只有 2 位数字存储年份(00-99)
    • 硬件设计时假设是 20 世纪(1900-1999)
    • 当时间进入 2000 年时,年份会从 99 回滚到 00

4.7. 时间转换

        return mktime(year, mon, day, hour, min, sec);
}
  • 调用 mktime 将时间分量转换为 Unix 时间戳

5. 关键技术细节

5.1. RTC 更新机制

RTC 更新周期:
1. UIP 置位: 表示更新开始
2. 更新寄存器: RTC内部更新所有时间寄存器
3. UIP 清除: 更新完成,数据稳定
4. 读取窗口: 在UIP下降沿后读取可获得准确时间

我来详细解释这个 BCD 到二进制转换的宏定义,这是处理硬件时钟数据的核心算法。

6. BCD宏定义解析

#define BCD_TO_BIN(val) ((val)=BCD2BIN(val))
#define BCD2BIN(val)    (((val) & 0x0f) + ((val)>>4)*10)

6.1. BCD2BIN 宏 - 实际转换逻辑

#define BCD2BIN(val)    (((val) & 0x0f) + ((val)>>4)*10)
  • (val) & 0x0f:取低4位(个位数)
  • (val) >> 4:取高4位(十位数)
  • ((val) >> 4) * 10:十位数乘以10
  • 两者相加得到最终的十进制数

6.2. BCD_TO_BIN 宏 - 赋值包装

#define BCD_TO_BIN(val) ((val)=BCD2BIN(val))
  • 调用 BCD2BIN(val) 进行转换
  • 将转换结果赋值回原变量 (val) = ...
  • 直接修改传入的变量值

6.3. BCD(二进制编码十进制)基础

BCD 编码原理

BCD 用4位二进制表示1位十进制数(0-9)十进制 23 的 BCD 表示:
十位数: 2 → 二进制 0010
个位数: 3 → 二进制 0011
组合: 0010 0011 = 0x23

为什么需要 BCD?

  • 硬件设计简单:直接对应数码管显示
  • 避免二进制到十进制的转换开销
  • 早期硬件计算能力有限

6.4. 转换过程详细演示

示例1:BCD 0x23 → 十进制 23

值: 0x23 = 0010 0011 二进制步骤1: (val) & 0x0f 0x23 & 0x0f = 0x03 = 3  ← 个位数步骤2: (val) >> 40x23 >> 4 = 0x02 = 2    ← 十位数步骤3: ((val) >> 4) * 102 * 10 = 20             ← 十位数值步骤4: 相加3 + 20 = 23             ← 最终结果

示例2:BCD 0x59 → 十进制 59

值: 0x59 = 0101 1001 二进制步骤1: 0x59 & 0x0f = 0x09 = 9
步骤2: 0x59 >> 4 = 0x05 = 5  
步骤3: 5 * 10 = 50
步骤4: 9 + 50 = 59
http://www.dtcms.com/a/496319.html

相关文章:

  • 【Algorithm】二分查找算法
  • Python字符串的魔法:拼接、编码与解码的艺术
  • 轻松拖拽:用 Dify 搭建企业级 AI 应用
  • 素材管理姬
  • 贵州黔致酒业推出的简礼酒介绍
  • 自己做网站怎么推广享设计官网
  • 鸿蒙NEXT传感器开发概述:开启智能感知新时代
  • Tailwind CSS 小白快速入门速查手册
  • php网站开发人员网站开发的总结
  • 私人网站建设步骤官网网页制作
  • Sora 后思考:从 AI 工具到 AI 平台,产业 AGI 又近了一步
  • 安全版数据库审计的配置方法
  • GENESIS64 AlarmWorX64助力现代工业监控预警管理
  • 英文模板网站合肥网站设计网址
  • HTTP 请求中断的深度扩展知识
  • 请别人做网站大概要多少钱济南集团网站建设方案
  • 定制网站建设和运营建设管理网站
  • JVM垃圾回收算法有哪些?
  • 衡水精品网站建设author 1 wordpress
  • 软件开发自学步骤视频郑州官网seo推广
  • 成都武侯区建设厅官方网站受欢迎的惠州网站建设
  • 如何建设网站的外接 以及在增加外接的时应当注意什么做公众号app 网站 app
  • 资料分析-基期
  • 云南做网站企业数据哪里找
  • 电商开发平台的核心:API数据接口
  • 介休城乡建设网站wordpress 去除rrs
  • 第二十一章 使用VDMA驱动HDMI显示
  • 架构相关要素Extensibility 和Scalability的翻译区分
  • 【LGR-251-Div.4】洛谷入门赛 #40 解析
  • 网站的建设心得前端培训班推荐