pub struct Clock { /* private fields */ }
Expand description
A hardware clock definition.
A Clock
consists of a function that returns the hardware clock’s current
timestamp in Ticks
(now()
), and a Duration
that defines the amount
of time represented by a single tick of the clock.
Using Clock
s
A Clock
must be provided when constructing a Timer
.
The Clock
provided to Timer::new
is used to
determine the time to advance to when turning the timer wheel, and to
determine the start time when adding a Sleep
future to the
timer wheel.
In addition, once a global timer is set using the
set_global_timer
function, the
Instant::now()
function may be used to produce Instant
s representing
the current timestamp according to that timer’s Clock
. The Instant
type is analogous to the std::time::Instant
type in the Rust standard
library, and may be used to compare the time elapsed between two events, as
a timestamp for logs and other diagnostics, and similar purposes.
Implementing now()
Constructing a new Clock
definition takes a function, called
now()
, that returns the current hardware timestamp in a 64-bit number of,
ticks. The period of time represented by a tick is indicated by the
tick_duration
argument to Clock::new
. In order to define a Clock
representing a particular hardware time source, a now()
function must be
implemented using that time source.
Monotonicity
Implementations of now()
MUST ensure that timestamps returned by
now()
MUST be monontonically non-decreasing. This means that
a call to now()
MUST NOT ever return a value less than the value returned
by a previous call tonow()
.
Note that this means that timestamps returned by now()
are expected
not to overflow. Of course, all integers will overflow eventually, so
this requirement can reasonably be weakened to expecting that timestamps
returned by now()
will not overflow unless the system has been running
for a duration substantially longer than the system is expected to run
for. For example, if a system is expected to run for as long as a year
without being restarted, it’s not unreasonable for timestamps returned
by now()
to overflow after, say, 100 years. Ideally, a general-purpose
Clock
implementation would not overflow for, say, 1,000 years.
The implication of this is that if the timestamp counters provided by
the hardware platform are less than 64 bits wide (e.g., 16- or 32-bit
timestamps), the Clock
implementation is responsible for ensuring that
they are extended to 64 bits, such as by counting overflows in the
Clock
implementation.
Examples
The simplest possible Clock
implementation is one for a
timestamp-counter-style hardware clock, where the timestamp counter is
incremented on a fixed interval by the hardware. For such a counter, the
Clock
definition might look something like this:
use maitake::time::{Clock, Duration};
// The fixed interval at which the timestamp counter is incremented.
//
// This is a made-up value; for real hardware, this value would be
// determined from information provided by the hardware manufacturer,
// and may need to be calculated based on the system's clock frequency.
const TICK_DURATION: Duration = {
let dur_ns = 1_000_000_000 / arch::TIMESTAMP_COUNTER_FREQUENCY_HZ;
Duration::from_nanos(dur_ns)
};
// Define a `Clock` implementation for the timestamp counter.
let clock = Clock::new(TICK_DURATION, || {
// A (pretend) function that reads the value of the timestamp
// counter. In real life, this might be a specific instruction,
// or a read from a timestamp counter register.
arch::read_timestamp_counter()
})
// Adding a name to the clock definition allows it to be i
// identified in fmt::Debug output.
.named("timestamp-counter");
On some platforms, the frequency with which a timestamp counter is
incremented may be configured by setting a divisor that divides the base
frequency of the clock. On such a platform, it is possible to select the
tick duration when constructing a new Clock
. We could then provide a
function that returns a clock with a requested tick duration:
use maitake::time::{Clock, Duration};
fn new_clock(tick_duration: Duration) -> Clock {
// Determine the divisor necessary to achieve the requested tick
// duration, based on the hardware clock's base frequency.
let divisor = {
let duration_ns = tick_duration.as_nanos();
assert!(
duration_ns as u32 >= arch::CLOCK_BASE_DURATION_NS,
"tick duration too short"
);
let div_u128 = duration_ns / arch::CLOCK_BASE_DURATION_NS as u128;
u32::try_from(div_u128).expect("tick duration too long")
};
// Set the divisor to the hardware clock. On real hardware, this
// might be a write to a register or memory-mapped IO location
// that controls the clock's divisor.
arch::set_clock_divisor(divisor as u32);
// Define a `Clock` implementation for the timestamp counter.
Clock::new(tick_duration, arch::read_timestamp_counter)
.named("timestamp-counter")
}
In addition to timestamp-counter-based hardware clocks, a Clock
definition
can be provided for an interrupt-based hardware clock. In this case, we
would provide an interrupt handler for the hardware timer interrupt that
increments an AtomicU64
counter, and a
now()
function that reads the counter. Essentially, we are reimplementing
a hardware timestamp counter in software, using the hardware timer interrupt
to increment the counter. For example:
use maitake::time::{Clock, Duration};
use core::sync::atomic::{AtomicU64, Ordering};
// A counter that is incremented by the hardware timer interrupt.
static CLOCK_TICKS: AtomicU64 = AtomicU64::new(0);
// The hardware timer interrupt handler.
fn timer_interrupt_handler() {
// Increment the counter.
CLOCK_TICKS.fetch_add(1, Ordering::Relaxed);
}
// Returns the current timestamp by reading the counter.
fn now() -> u64 {
CLOCK_TICKS.load(Ordering::Relaxed)
}
fn new_clock(tick_duration: Duration) -> Clock {
// Set the hardware timer to generate periodic interrupts.
arch::set_timer_period_ns(tick_duration.as_nanos() as u64);
// Set the interrupt handler for the hardware timer interrupt,
// and start the timer.
arch::set_interrupt_handler(arch::Interrupt::Timer, timer_interrupt_handler);
arch::start_periodic_timer();
// Define a `Clock` implementation for the interrupt-based timer.
Clock::new(tick_duration, now)
.named("periodic-timer")
}
Implementations§
source§impl Clock
impl Clock
sourcepub const fn new(tick_duration: Duration, now: fn() -> Ticks) -> Self
pub const fn new(tick_duration: Duration, now: fn() -> Ticks) -> Self
Returns a new Clock
with the provided tick Duration
and now()
function.
See the type-level documentation for Clock
for details on implementing the now()
function.
sourcepub const fn named(self, name: &'static str) -> Self
pub const fn named(self, name: &'static str) -> Self
Add an arbitrary user-defined name to this Clock
.
This is generally used to describe the hardware time source used by the
now()
function for this Clock
.
sourcepub fn tick_duration(&self) -> Duration
pub fn tick_duration(&self) -> Duration
Returns the Duration
of one tick of this clock.
sourcepub fn max_duration(&self) -> Duration
pub fn max_duration(&self) -> Duration
Returns the maximum duration of this clock.
sourcepub fn name(&self) -> &'static str
pub fn name(&self) -> &'static str
Returns this Clock
’s name, if it was given one using the Clock::named
method.