static unsigned long long riscv_clocksource_rdtime(struct clocksource *cs) { return get_cycles64(); }
clocksource
clocksource 提供了对不同软硬件时钟的抽象。可以理解为时间源,为 kernel 提供当前时间。
struct clocksource { cycle_t (*read)(struct clocksource *cs); // 指向读取时钟的函数 cycle_t mask; // 能够表示的 cycle 上限,通常是 32/64 位的全 f,做与操作可以避免对 overflow 进行专门处理 u32 mult; // 将时间源的计数单位 (cycle_t) 转换为 ns u32 shift; // 换算公式为 (cycles * mult) >> shift u64 max_idle_ns; // 允许的最大空闲时间,单位 ns。当设置 CONFIG_NO_HZ 时,使用动态 tick,不限制 kernel 的睡眠时间,需要进行限制 u32 maxadj; // 允许的最大调整值,避免转换时 overflow #ifdef CONFIG_ARCH_CLOCKSOURCE_DATA struct arch_clocksource_data archdata; // 架构专有(目前只有 x86 和 ia64)。 #endif u64 max_cycles; // 设置了 cycle 上限,避免换算时溢出 const char *name; // 时间源名称 struct list_head list; // 注册了该时间源? int rating; // 优先级 int (*enable)(struct clocksource *cs); // 启用时间源函数 void (*disable)(struct clocksource *cs); // 停用时间源函数 unsigned long flags; void (*suspend)(struct clocksource *cs); // 暂停时间源函数 void (*resume)(struct clocksource *cs); // 恢复时间源函数 /* private: */ #ifdef CONFIG_CLOCKSOURCE_WATCHDOG // 用于监控时间源,校验时间是否准确 /* Watchdog related data, used by the framework */ struct list_head wd_list; cycle_t cs_last; cycle_t wd_last; #endif struct module *owner; // 指向拥有该时间源的内核模块 };
其中 rating 表示了时间源的准确度,它将作为 Linux 选择时钟源时的优先级:
- 1-99 - Only available for bootup and testing purposes;
- 100-199 - Functional for real use, but not desired.
- 200-299 - A correct and usable clocksource.
- 300-399 - A reasonably fast and accurate clocksource.
- 400-499 - The ideal clocksource. A must-use where available;
只有 rating 最高的时间源会被选用
riscv_clocksource
static struct clocksource riscv_clocksource = { .name = "riscv_clocksource", .rating = 300, .mask = CLOCKSOURCE_MASK(64), .flags = CLOCK_SOURCE_IS_CONTINUOUS, .read = riscv_clocksource_rdtime, };
riscv_timer_init_dt注册 riscv clocksource
riscv_timer_init_dt
clocksource_register_hz(&riscv_clocksource, riscv_timebase);
sched_clock_register(riscv_sched_clock, 64, riscv_timebase);
jiffies clocksource
在 Linux 中作为软件维护的时钟。表示一小段短暂而不确定的时间。
属于低精度时间源,因为没有 CLOCK_SOURCE_VALID_FOR_HRES 的 flag,所以不会出现在 available_clocksource 中。
static struct clocksource clocksource_jiffies = { | |
.name = "jiffies", | |
.rating = 1, /* lowest valid rating*/ // 优先级最低 | |
.read = jiffies_read, // 读时返回 jiffies | |
.mask = CLOCKSOURCE_MASK(32), | |
.mult = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */ | |
.shift = JIFFIES_SHIFT, // NSEC_PER_JIFFY 和 JIFFIES_SHIFT 由 CONFIG_HZ 决定 | |
.max_cycles = 10, | |
}; |
Linux 用全局变量 jiffies / jiffies_64 来存放系统启动后经过的 jiffy 数目:
extern u64 __jiffy_data jiffies_64; // x86_64 下使用,非原子,读时需要加锁,如 get_jiffies_64 用了 seqlock | |
extern unsigned long volatile __jiffy_data jiffies; // x86_32 下使用 |
根据 arch/x86/kernel/vmlinux.lds.S,在 32 位下,jiffies 指向 jiffies_64 的低 32 位。
每当收到 clock_event_device (后详)发出的中断时,会调用其 handler ,即 tick_handle_periodic ,于是有
tick_handle_periodic => tick_periodic => do_timer => jiffies_64 += ticks
由于 do_timer 的参数为 1,因此 jiffies_64 += 1。而根据 tick_setup_device 中 tick_period = ktime_set(0, NSEC_PER_SEC / HZ)
,表示 tick_device 每个 tick 间隔为 1 / HZ 秒。于是每个 jiffies 代表的时间为 1/ HZ 秒,系统启动至今所经过的秒数 = jiffies_64 / HZ。HZ 由 CONFIG_HZ 决定,而 CONFIG_HZ 由 CONFIG_HZ_* 决定,有 100/250/300/1000 可选,一般为 1000。所以每隔 1 毫秒,jiffies "时钟" 就会加一,1 jiffy 等于 1 毫秒。
从 jiffies 我们可以发现,时钟源的不仅能够通过读取硬件时钟源来实现,还能通过 tick_device,更本质上来说是定时器来实现