mirror of
https://github.com/rdmsr/zag.git
synced 2026-10-31 15:28:21 +00:00
refactor: time -> clock
This commit is contained in:
@@ -4,9 +4,8 @@ const r = @import("root");
|
||||
const ke = r.ke;
|
||||
const ki = ke.private;
|
||||
|
||||
/// TimeCounter structure.
|
||||
/// Represents a time-keeping source upon which the kernel can rely.
|
||||
pub const TimeCounter = struct {
|
||||
/// Represents a clock source upon which the kernel can rely.
|
||||
pub const ClockSource = struct {
|
||||
/// Callback that reads the current count.
|
||||
read_count: *const fn () u64,
|
||||
/// Frequency of the timer in Hz.
|
||||
@@ -14,7 +13,7 @@ pub const TimeCounter = struct {
|
||||
/// Name of the timer.
|
||||
name: []const u8,
|
||||
/// Quality of the timer.
|
||||
/// Used to choose between two TimeCounters.
|
||||
/// Used to choose between two ClockSources.
|
||||
quality: i16,
|
||||
/// Mask to apply to the counter value.
|
||||
mask: u64,
|
||||
@@ -26,95 +25,103 @@ pub const TimeCounter = struct {
|
||||
n: u64,
|
||||
};
|
||||
|
||||
var best_tc: ?*TimeCounter = null;
|
||||
var best_clock: ?*ClockSource = null;
|
||||
|
||||
const TimeState = struct {
|
||||
// Counter value at the time the current TimeCounter was selected.
|
||||
const State = struct {
|
||||
// Counter value at the time the current ClockSource was selected.
|
||||
// Used as the baseline for elapsed time calculation.
|
||||
initial_count: u64,
|
||||
// Accumulated nanosecond offset from previous TimeCounters and overflow events.
|
||||
// Added to the current counter's contribution in `read_time_nano`.
|
||||
// Accumulated nanosecond offset from previous ClockSources and overflow events.
|
||||
// Added to the current counter's contribution in `read_time`.
|
||||
offset: u64,
|
||||
};
|
||||
|
||||
// SeqLock-protected time state.
|
||||
// Avoids a race on `initial_count` and `offset` with `update_overflow`.
|
||||
var state = rtl.SeqLock(TimeState).init(.{
|
||||
var state = rtl.SeqLock(State).init(.{
|
||||
.initial_count = 0,
|
||||
.offset = 0,
|
||||
});
|
||||
|
||||
// Convert a TimeCounter's count to nanoseconds.
|
||||
// Convert a ClockSource's count to nanoseconds.
|
||||
// For 64-bit counters (`mask == maxInt(u64)`), uses a fast multiply-shift path
|
||||
// via precomputed `p` and `n` to avoid 128-bit division at runtime.
|
||||
// Falls back to a division-based conversion for narrower counters.
|
||||
fn ticks_to_ns(tc: *TimeCounter, count: u64) u64 {
|
||||
if (tc.p != 0) {
|
||||
const val: u128 = @as(u128, @intCast(count)) * tc.n;
|
||||
return @intCast(val >> @intCast(tc.p));
|
||||
fn ticks_to_ns(cs: *ClockSource, count: u64) u64 {
|
||||
if (cs.p != 0) {
|
||||
const val: u128 = @as(u128, @intCast(count)) * cs.n;
|
||||
return @intCast(val >> @intCast(cs.p));
|
||||
}
|
||||
|
||||
return (count * std.time.ns_per_s) / tc.frequency;
|
||||
return (count * std.time.ns_per_s) / cs.frequency;
|
||||
}
|
||||
|
||||
/// Register a time-keeping source.
|
||||
/// The TimeCounter will be used if it is the highest quality one available.
|
||||
/// Register a clock source.
|
||||
/// The provided source will be used if it is the highest quality one available.
|
||||
/// This function cannot be safely called when the hardware timer has been started, as data races on `best_tc` are not avoided.
|
||||
pub fn register_source(tc: *TimeCounter) void {
|
||||
pub fn register_source(cs: *ClockSource) void {
|
||||
// Precompute scaling factors for the fast multiply-shift path.
|
||||
// This is meant to avoid 128-bit division and only use multiplication for 64-bit counters.
|
||||
if (tc.mask == std.math.maxInt(u64)) {
|
||||
tc.p = std.math.log2_int_ceil(u64, tc.frequency);
|
||||
tc.n = (@as(u64, std.time.ns_per_s) << @intCast(tc.p)) / tc.frequency;
|
||||
if (cs.mask == std.math.maxInt(u64)) {
|
||||
cs.p = std.math.log2_int_ceil(u64, cs.frequency);
|
||||
cs.n = (@as(u64, std.time.ns_per_s) << @intCast(cs.p)) / cs.frequency;
|
||||
} else {
|
||||
tc.p = 0;
|
||||
tc.n = 0;
|
||||
cs.p = 0;
|
||||
cs.n = 0;
|
||||
}
|
||||
|
||||
// If the best TimeCounter changed, pass the baton:
|
||||
// snapshot the old counter's elapsed time into `offset` and
|
||||
// reset `initial_count` to the new counter's current value.
|
||||
if (best_tc == null or tc.quality > best_tc.?.quality) {
|
||||
const old_best = best_tc;
|
||||
best_tc = tc;
|
||||
if (best_clock == null or cs.quality > best_clock.?.quality) {
|
||||
const old_best = best_clock;
|
||||
best_clock = cs;
|
||||
|
||||
if (old_best) |old| {
|
||||
const old_count = old.read_count() & old.mask;
|
||||
state.raw.offset = ticks_to_ns(old, old_count);
|
||||
}
|
||||
|
||||
state.raw.initial_count = tc.read_count() & tc.mask;
|
||||
state.raw.initial_count = cs.read_count() & cs.mask;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the time elapsed since boot in nanoseconds.
|
||||
pub fn read_time_nano() r.Nanoseconds {
|
||||
const tc = best_tc orelse return 0;
|
||||
pub fn read_time() r.Nanoseconds {
|
||||
const cs = best_clock orelse return 0;
|
||||
|
||||
const s = state.load();
|
||||
const curr_count = tc.read_count() & tc.mask;
|
||||
const elapsed = (curr_count - s.initial_count) & tc.mask;
|
||||
return s.offset + ticks_to_ns(tc, elapsed);
|
||||
const curr_count = cs.read_count() & cs.mask;
|
||||
const elapsed = (curr_count - s.initial_count) & cs.mask;
|
||||
return s.offset + ticks_to_ns(cs, elapsed);
|
||||
}
|
||||
|
||||
/// Return the best TimeCounter.
|
||||
pub fn best() ?*TimeCounter {
|
||||
return best_tc;
|
||||
/// Return the best clock source.
|
||||
pub fn best() ?*ClockSource {
|
||||
return best_clock;
|
||||
}
|
||||
|
||||
/// Do a busy sleep of `ns` nanoseconds using TimeCounter.
|
||||
/// Return whether the current best clock is better than `cs`.
|
||||
pub fn is_better_than(cs: *ClockSource) bool {
|
||||
if (best_clock) |b| {
|
||||
return b.quality > cs.quality;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Do a busy sleep of `ns` nanoseconds.
|
||||
pub fn sleep(ns: r.Nanoseconds) void {
|
||||
const start = read_time_nano();
|
||||
const start = read_time();
|
||||
|
||||
while (true) {
|
||||
var cur = read_time_nano();
|
||||
var cur = read_time();
|
||||
|
||||
if (cur < start) continue;
|
||||
|
||||
if (cur - start >= ns) {
|
||||
cur = read_time_nano();
|
||||
cur = read_time();
|
||||
|
||||
// Check again as we may have overflowed and read a bogus value..
|
||||
// Check again as we may have overflowed and read a bogus value.
|
||||
// This is kinda hacky but oh well.
|
||||
if (cur - start >= ns) {
|
||||
break;
|
||||
@@ -127,17 +134,17 @@ pub fn sleep(ns: r.Nanoseconds) void {
|
||||
|
||||
/// Called periodically to handle counter overflows.
|
||||
pub fn update_overflow() void {
|
||||
const tc = best_tc orelse return;
|
||||
const cs = best_clock orelse return;
|
||||
const s = state.load();
|
||||
const cur_count = tc.read_count() & tc.mask;
|
||||
const cur_count = cs.read_count() & cs.mask;
|
||||
|
||||
if (s.initial_count > cur_count) {
|
||||
// Counter has wrapped, use wrapping arithmetic to figure out by how much,
|
||||
// then fold it into the offset and reset `initial_count`.
|
||||
const overflowed_by = (cur_count -% s.initial_count) & tc.mask;
|
||||
const overflowed_by = (cur_count -% s.initial_count) & cs.mask;
|
||||
state.store(.{
|
||||
.initial_count = cur_count,
|
||||
.offset = s.offset + ticks_to_ns(tc, overflowed_by),
|
||||
.offset = s.offset + ticks_to_ns(cs, overflowed_by),
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -55,7 +55,7 @@ pub fn log(
|
||||
// Reserve space in the ring buffer.
|
||||
var res = ringbuffer.reserve(required_len) catch return;
|
||||
|
||||
res.info.timestamp = ke.time.read_time_nano();
|
||||
res.info.timestamp = ke.clock.read_time();
|
||||
res.info.length = @truncate(required_len);
|
||||
|
||||
// Format the log message into the reserved buffer.
|
||||
|
||||
@@ -20,7 +20,7 @@ pub const log = @import("log.zig");
|
||||
pub const cpu = @import("cpu.zig");
|
||||
pub const thread = @import("thread.zig");
|
||||
pub const dpc = @import("dpc.zig");
|
||||
pub const time = @import("time.zig");
|
||||
pub const clock = @import("clock.zig");
|
||||
pub const sched = @import("sched.zig");
|
||||
pub const wait = @import("wait.zig");
|
||||
pub const timer = @import("timer.zig");
|
||||
|
||||
+7
-6
@@ -10,7 +10,7 @@ pub const Ipl = p.ipl.Ipl;
|
||||
pub const SpinLock = p.spinlock.SpinLock;
|
||||
pub const Thread = p.thread.Thread;
|
||||
pub const Dpc = p.dpc.Dpc;
|
||||
pub const TimeCounter = p.time.TimeCounter;
|
||||
pub const ClockSource = p.clock.ClockSource;
|
||||
pub const Timer = p.timer.Timer;
|
||||
pub const Console = p.log.Console;
|
||||
pub const CpuLocal = p.cpu.CpuLocal;
|
||||
@@ -35,11 +35,12 @@ pub const dpc = struct {
|
||||
pub const enqueue = p.dpc.enqueue;
|
||||
};
|
||||
|
||||
pub const time = struct {
|
||||
pub const register_source = p.time.register_source;
|
||||
pub const read_time_nano = p.time.read_time_nano;
|
||||
pub const best = p.time.best;
|
||||
pub const sleep = p.time.sleep;
|
||||
pub const clock = struct {
|
||||
pub const register_source = p.clock.register_source;
|
||||
pub const read_time = p.clock.read_time;
|
||||
pub const best = p.clock.best;
|
||||
pub const sleep = p.clock.sleep;
|
||||
pub const is_better_than = p.clock.is_better_than;
|
||||
};
|
||||
|
||||
pub const sched = struct {
|
||||
|
||||
+2
-2
@@ -246,7 +246,7 @@ pub fn block() void {
|
||||
td.lock.acquire_no_ipl();
|
||||
|
||||
td.state = .Blocked;
|
||||
td.sleep_start = ke.time.read_time_nano();
|
||||
td.sleep_start = ke.clock.read_time();
|
||||
td.runq = null;
|
||||
|
||||
yield(ke.cpu.current());
|
||||
@@ -259,7 +259,7 @@ pub fn block() void {
|
||||
pub fn unblock(td: *ke.Thread) void {
|
||||
const ipl = td.lock.acquire();
|
||||
|
||||
td.sleep_time = (ke.time.read_time_nano() - td.sleep_start) / std.time.ns_per_ms;
|
||||
td.sleep_time = (ke.clock.read_time() - td.sleep_start) / std.time.ns_per_ms;
|
||||
|
||||
if (td.sleep_time >= config.CONFIG_SCHED_TIMESLICE and td.priority_class == .Batch) {
|
||||
// If we have slept for more than a tick, update interactivity.
|
||||
|
||||
+4
-4
@@ -75,7 +75,7 @@ pub fn set(timer: *Timer, time: r.Nanoseconds, dpc: ?*ke.Dpc) void {
|
||||
defer cpu.lock.release_no_ipl();
|
||||
|
||||
// Initialize the timer.
|
||||
timer.deadline = ke.time.read_time_nano() + time;
|
||||
timer.deadline = ke.clock.read_time() + time;
|
||||
|
||||
timer.cpu = cpu;
|
||||
timer.dpc = dpc;
|
||||
@@ -134,7 +134,7 @@ pub fn clock() void {
|
||||
ke.dpc.enqueue(&percpu.local().dpc, null);
|
||||
// Check for overflows.
|
||||
// This is fine to call a lot, as the function only gets expensive (i.e seqlock store) when an overflow actually happens.
|
||||
ki.time.update_overflow();
|
||||
ki.clock.update_overflow();
|
||||
}
|
||||
|
||||
// Called in a DPC when a timer has expired.
|
||||
@@ -143,7 +143,7 @@ fn handle_expiry(_: ?*anyopaque) void {
|
||||
const cpu = percpu.local();
|
||||
|
||||
while (true) {
|
||||
const curtime = ke.time.read_time_nano();
|
||||
const curtime = ke.clock.read_time();
|
||||
|
||||
cpu.lock.acquire_no_ipl();
|
||||
|
||||
@@ -162,7 +162,7 @@ fn handle_expiry(_: ?*anyopaque) void {
|
||||
cpu.lock.release_no_ipl();
|
||||
|
||||
// Re-check in case the timer expired while we were here.
|
||||
const now = ke.time.read_time_nano();
|
||||
const now = ke.clock.read_time();
|
||||
if (next.deadline <= now or next.deadline - now <= std.time.ns_per_ms) {
|
||||
continue; // expired in the meantime, loop again
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user