diff --git a/Cargo.lock b/Cargo.lock index 4c8c0c495..a53f9f8e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -227,6 +227,7 @@ dependencies = [ "libflate", "log", "lru", + "paste", "pod", "rand", "ringbuf", diff --git a/framework/aster-frame/src/arch/x86/kernel/tsc.rs b/framework/aster-frame/src/arch/x86/kernel/tsc.rs index 357f8a63b..f07d5617d 100644 --- a/framework/aster-frame/src/arch/x86/kernel/tsc.rs +++ b/framework/aster-frame/src/arch/x86/kernel/tsc.rs @@ -12,7 +12,7 @@ use x86::cpuid::cpuid; use crate::{ arch::timer::{ pit::{self, OperatingMode}, - TIMER_FREQ, TIMER_IRQ_NUM, + TIMER_FREQ, }, trap::IrqLine, }; @@ -71,7 +71,7 @@ pub fn determine_tsc_freq_via_cpuid() -> Option { /// the PIT to calculate this frequency. pub fn determine_tsc_freq_via_pit() -> u64 { // Allocate IRQ - let mut irq = IrqLine::alloc_specific(TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap(); + let mut irq = IrqLine::alloc().unwrap(); irq.on_active(pit_callback); // Enable PIT diff --git a/framework/aster-frame/src/arch/x86/mod.rs b/framework/aster-frame/src/arch/x86/mod.rs index 460e0f62f..8344bfc01 100644 --- a/framework/aster-frame/src/arch/x86/mod.rs +++ b/framework/aster-frame/src/arch/x86/mod.rs @@ -12,7 +12,7 @@ pub(crate) mod pci; pub mod qemu; #[cfg(feature = "intel_tdx")] pub(crate) mod tdx_guest; -pub(crate) mod timer; +pub mod timer; use core::{arch::x86_64::_rdtsc, sync::atomic::Ordering}; diff --git a/framework/aster-frame/src/arch/x86/timer/apic.rs b/framework/aster-frame/src/arch/x86/timer/apic.rs index 48c3e3079..ec57fba43 100644 --- a/framework/aster-frame/src/arch/x86/timer/apic.rs +++ b/framework/aster-frame/src/arch/x86/timer/apic.rs @@ -27,14 +27,16 @@ use crate::{ trap::IrqLine, }; -pub fn init() { +/// Init APIC with tsc deadline mode or periodic mode. +/// Return the corresponding `IrqLine` for the System Timer. +pub(super) fn init() -> IrqLine { init_tsc_freq(); if is_tsc_deadline_mode_supported() { info!("[Timer]: Enable APIC TSC deadline mode."); - init_tsc_mode(); + init_tsc_mode() } else { info!("[Timer]: Enable APIC periodic mode."); - init_periodic_mode(); + init_periodic_mode() } } @@ -47,10 +49,11 @@ fn is_tsc_deadline_mode_supported() -> bool { (cpuid.ecx & TSC_DEADLINE_MODE_SUPPORT) > 0 } -fn init_tsc_mode() { +fn init_tsc_mode() -> IrqLine { + let timer_irq = IrqLine::alloc().unwrap(); let mut apic_lock = APIC_INSTANCE.get().unwrap().lock_irq_disabled(); // Enable tsc deadline mode - apic_lock.set_lvt_timer(super::TIMER_IRQ_NUM.load(Ordering::Relaxed) as u64 | (1 << 18)); + apic_lock.set_lvt_timer(timer_irq.num() as u64 | (1 << 18)); drop(apic_lock); let tsc_step = TSC_FREQ.load(Ordering::Relaxed) / TIMER_FREQ; @@ -62,11 +65,13 @@ fn init_tsc_mode() { callback.call(()); APIC_TIMER_CALLBACK.call_once(|| Arc::new(callback)); + + timer_irq } -fn init_periodic_mode() { +fn init_periodic_mode() -> IrqLine { // Allocate IRQ - let mut irq = IrqLine::alloc_specific(super::TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap(); + let mut irq = IrqLine::alloc().unwrap(); irq.on_active(pit_callback); // Enable PIT @@ -80,6 +85,8 @@ fn init_periodic_mode() { drop(apic_lock); static IS_FINISH: AtomicBool = AtomicBool::new(false); + static INIT_COUNT: AtomicU64 = AtomicU64::new(0); + x86_64::instructions::interrupts::enable(); while !IS_FINISH.load(Ordering::Acquire) { x86_64::instructions::hlt(); @@ -87,6 +94,16 @@ fn init_periodic_mode() { x86_64::instructions::interrupts::disable(); drop(irq); + // Init APIC Timer + let timer_irq = IrqLine::alloc().unwrap(); + + let mut apic_lock = APIC_INSTANCE.get().unwrap().lock_irq_disabled(); + apic_lock.set_timer_init_count(INIT_COUNT.load(Ordering::Relaxed)); + apic_lock.set_lvt_timer(timer_irq.num() as u64 | (1 << 17)); + apic_lock.set_timer_div_config(DivideConfig::Divide64); + + return timer_irq; + fn pit_callback(trap_frame: &TrapFrame) { static IN_TIME: AtomicU64 = AtomicU64::new(0); static APIC_FIRST_COUNT: AtomicU64 = AtomicU64::new(0); @@ -108,17 +125,13 @@ fn init_periodic_mode() { let mut apic_lock = APIC_INSTANCE.get().unwrap().lock_irq_disabled(); let remain_ticks = apic_lock.timer_current_count(); apic_lock.set_timer_init_count(0); - - // Init APIC Timer let ticks = (0xFFFF_FFFF - remain_ticks - APIC_FIRST_COUNT.load(Ordering::Relaxed)) / CALLBACK_TIMES; - apic_lock.set_timer_init_count(ticks); - apic_lock.set_lvt_timer(super::TIMER_IRQ_NUM.load(Ordering::Relaxed) as u64 | (1 << 17)); - apic_lock.set_timer_div_config(DivideConfig::Divide64); info!( "APIC Timer ticks count:{:x}, remain ticks: {:x},Timer Freq:{} Hz", ticks, remain_ticks, TIMER_FREQ ); + INIT_COUNT.store(ticks, Ordering::Release); IS_FINISH.store(true, Ordering::Release); } } diff --git a/framework/aster-frame/src/arch/x86/timer/jiffies.rs b/framework/aster-frame/src/arch/x86/timer/jiffies.rs new file mode 100644 index 000000000..8a7e66df8 --- /dev/null +++ b/framework/aster-frame/src/arch/x86/timer/jiffies.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::{ + sync::atomic::{AtomicU64, Ordering}, + time::Duration, +}; + +use super::TIMER_FREQ; + +/// Jiffies is a term used to denote the units of time measurement by the kernel. +/// +/// A jiffy represents one tick of the system timer interrupt, +/// whose frequency is equal to [`TIMER_FREQ`] Hz. +#[derive(Copy, Clone, Debug)] +pub struct Jiffies(u64); + +pub(super) static ELAPSED: AtomicU64 = AtomicU64::new(0); + +impl Jiffies { + /// Creates a new instance. + pub fn new(value: u64) -> Self { + Self(value) + } + + /// Returns the elapsed time since the system boots up. + pub fn elapsed() -> Self { + Self::new(ELAPSED.load(Ordering::Relaxed)) + } + + /// Gets the number of jiffies. + pub fn as_u64(self) -> u64 { + self.0 + } + + /// Gets the `Duration` calculated from the jiffies counts. + pub fn as_duration(self) -> Duration { + Duration::from_millis(self.0 * 1000 / TIMER_FREQ) + } +} + +impl From for Duration { + fn from(value: Jiffies) -> Self { + value.as_duration() + } +} diff --git a/framework/aster-frame/src/arch/x86/timer/mod.rs b/framework/aster-frame/src/arch/x86/timer/mod.rs index 6da465d82..592a14570 100644 --- a/framework/aster-frame/src/arch/x86/timer/mod.rs +++ b/framework/aster-frame/src/arch/x86/timer/mod.rs @@ -1,20 +1,19 @@ // SPDX-License-Identifier: MPL-2.0 -pub mod apic; -pub mod hpet; -pub mod pit; +mod apic; +mod hpet; +mod jiffies; +pub(crate) mod pit; -use alloc::{boxed::Box, collections::BinaryHeap, sync::Arc, vec::Vec}; -use core::{ - any::Any, - sync::atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering}, -}; +use alloc::{boxed::Box, vec::Vec}; +use core::{cell::RefCell, sync::atomic::Ordering}; +pub use jiffies::Jiffies; use spin::Once; use trapframe::TrapFrame; use self::apic::APIC_TIMER_CALLBACK; -use crate::{arch::x86::kernel, sync::SpinLock, trap::IrqLine}; +use crate::{arch::x86::kernel, cpu_local, trap::IrqLine, CpuLocal}; /// The timer frequency (Hz). Here we choose 1000Hz since 1000Hz is easier for unit conversion and /// convenient for timer. What's more, the frequency cannot be set too high or too low, 1000Hz is @@ -27,147 +26,50 @@ use crate::{arch::x86::kernel, sync::SpinLock, trap::IrqLine}; /// frequencies lower than 19Hz = 1193182 / 65536 (Timer rate / Divider) pub const TIMER_FREQ: u64 = 1000; -pub static TIMER_IRQ_NUM: AtomicU8 = AtomicU8::new(32); -pub static TICK: AtomicU64 = AtomicU64::new(0); - static TIMER_IRQ: Once = Once::new(); -pub fn init() { - if kernel::apic::APIC_INSTANCE.is_completed() { - // Get the free irq number first. Use `allocate_target_irq` to get the Irq handle after dropping it. - // Because the function inside `apic::init` will allocate this irq. - let irq = IrqLine::alloc().unwrap(); - TIMER_IRQ_NUM.store(irq.num(), Ordering::Relaxed); - drop(irq); - apic::init(); +pub(super) fn init() { + /// In PIT mode, channel 0 is connected directly to IRQ0, which is + /// the `IrqLine` with the `irq_num` 32 (0-31 `IrqLine`s are reserved). + /// + /// Ref: https://wiki.osdev.org/Programmable_Interval_Timer#Outputs. + const PIT_MODE_TIMER_IRQ_NUM: u8 = 32; + + let mut timer_irq = if kernel::apic::APIC_INSTANCE.is_completed() { + apic::init() } else { pit::init(pit::OperatingMode::SquareWaveGenerator); + IrqLine::alloc_specific(PIT_MODE_TIMER_IRQ_NUM).unwrap() }; - TIMEOUT_LIST.call_once(|| SpinLock::new(BinaryHeap::new())); - let mut timer_irq = IrqLine::alloc_specific(TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap(); + timer_irq.on_active(timer_callback); TIMER_IRQ.call_once(|| timer_irq); } -fn timer_callback(trap_frame: &TrapFrame) { - let current_ticks = TICK.fetch_add(1, Ordering::SeqCst); +cpu_local! { + static INTERRUPT_CALLBACKS: RefCell>> = RefCell::new(Vec::new()); +} - let callbacks = { - let mut callbacks = Vec::new(); - let mut timeout_list = TIMEOUT_LIST.get().unwrap().lock_irq_disabled(); +/// Register a function that will be executed during the system timer interruption. +pub fn register_callback(func: F) +where + F: Fn() + Sync + Send + 'static, +{ + CpuLocal::borrow_with(&INTERRUPT_CALLBACKS, |callbacks| { + callbacks.borrow_mut().push(Box::new(func)); + }); +} - while let Some(t) = timeout_list.peek() { - if t.is_cancelled() { - // Just ignore the cancelled callback - timeout_list.pop(); - } else if t.expire_ticks <= current_ticks { - callbacks.push(timeout_list.pop().unwrap()); - } else { - break; - } +fn timer_callback(_: &TrapFrame) { + jiffies::ELAPSED.fetch_add(1, Ordering::SeqCst); + + CpuLocal::borrow_with(&INTERRUPT_CALLBACKS, |callbacks| { + for callback in callbacks.borrow().iter() { + (callback)(); } - callbacks - }; - - for callback in callbacks { - (callback.callback)(&callback); - } + }); if APIC_TIMER_CALLBACK.is_completed() { APIC_TIMER_CALLBACK.get().unwrap().call(()); } } - -static TIMEOUT_LIST: Once>>> = Once::new(); - -pub struct TimerCallback { - expire_ticks: u64, - data: Arc, - callback: Box, - is_cancelled: AtomicBool, -} - -impl TimerCallback { - fn new( - timeout_ticks: u64, - data: Arc, - callback: Box, - ) -> Self { - Self { - expire_ticks: timeout_ticks, - data, - callback, - is_cancelled: AtomicBool::new(false), - } - } - - pub fn data(&self) -> &Arc { - &self.data - } - - /// Whether the set timeout is reached - pub fn is_expired(&self) -> bool { - let current_tick = TICK.load(Ordering::Acquire); - self.expire_ticks <= current_tick - } - - /// Cancel a timer callback. If the callback function has not been called, - /// it will never be called again. - pub fn cancel(&self) { - self.is_cancelled.store(true, Ordering::Release); - } - - // Whether the timer callback is cancelled. - fn is_cancelled(&self) -> bool { - self.is_cancelled.load(Ordering::Acquire) - } -} - -impl PartialEq for TimerCallback { - fn eq(&self, other: &Self) -> bool { - self.expire_ticks == other.expire_ticks - } -} - -impl Eq for TimerCallback {} - -impl PartialOrd for TimerCallback { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for TimerCallback { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.expire_ticks.cmp(&other.expire_ticks).reverse() - } -} - -/// add timeout task into timeout list, the frequency see const TIMER_FREQ -/// -/// user should ensure that the callback function cannot take too much time -/// -pub fn add_timeout_list(timeout: u64, data: T, callback: F) -> Arc -where - F: Fn(&TimerCallback) + Send + Sync + 'static, - T: Any + Send + Sync, -{ - let timer_callback = TimerCallback::new( - TICK.load(Ordering::Acquire) + timeout, - Arc::new(data), - Box::new(callback), - ); - let arc = Arc::new(timer_callback); - TIMEOUT_LIST - .get() - .unwrap() - .lock_irq_disabled() - .push(arc.clone()); - arc -} - -/// The time since the system boots up. -/// The currently returned results are in milliseconds. -pub fn read_monotonic_milli_seconds() -> u64 { - TICK.load(Ordering::Acquire) * (1000 / TIMER_FREQ) -} diff --git a/framework/aster-frame/src/trap/irq.rs b/framework/aster-frame/src/trap/irq.rs index 4cfa302bb..2614a4226 100644 --- a/framework/aster-frame/src/trap/irq.rs +++ b/framework/aster-frame/src/trap/irq.rs @@ -16,7 +16,7 @@ pub type IrqCallbackFunction = dyn Fn(&TrapFrame) + Sync + Send + 'static; /// An Interrupt ReQuest(IRQ) line. User can use `alloc` or `alloc_specific` to get specific IRQ line. /// /// The IRQ number is guaranteed to be external IRQ number and user can register callback functions to this IRQ resource. -/// When this resrouce is dropped, all the callback in this will be unregistered automatically. +/// When this resource is dropped, all the callback in this will be unregistered automatically. #[derive(Debug)] #[must_use] pub struct IrqLine { diff --git a/kernel/aster-nix/Cargo.toml b/kernel/aster-nix/Cargo.toml index c8f52bd72..679a39350 100644 --- a/kernel/aster-nix/Cargo.toml +++ b/kernel/aster-nix/Cargo.toml @@ -26,6 +26,7 @@ int-to-c-enum = { path = "../libs/int-to-c-enum" } cpio-decoder = { path = "../libs/cpio-decoder" } ascii = { version = "1.1", default-features = false, features = ["alloc"] } intrusive-collections = "0.9.5" +paste = "1.0" time = { version = "0.3", default-features = false, features = ["alloc"] } smoltcp = { version = "0.9.1", default-features = false, features = [ "alloc", diff --git a/kernel/aster-nix/src/fs/exfat/utils.rs b/kernel/aster-nix/src/fs/exfat/utils.rs index 6606fd6d7..3d21f518a 100644 --- a/kernel/aster-nix/src/fs/exfat/utils.rs +++ b/kernel/aster-nix/src/fs/exfat/utils.rs @@ -5,7 +5,7 @@ use core::{ops::Range, time::Duration}; use time::{OffsetDateTime, PrimitiveDateTime, Time}; use super::fat::ClusterID; -use crate::prelude::*; +use crate::{prelude::*, time::clocks::RealTimeClock}; pub fn make_hash_index(cluster: ClusterID, offset: u32) -> usize { (cluster as usize) << 32usize | (offset as usize & 0xffffffffusize) @@ -59,8 +59,7 @@ impl DosTimestamp { pub fn now() -> Result { #[cfg(not(ktest))] { - use crate::time::now_as_duration; - DosTimestamp::from_duration(now_as_duration(&crate::time::ClockID::CLOCK_REALTIME)?) + DosTimestamp::from_duration(RealTimeClock::get().read_time()) } // When ktesting, the time module has not been initialized yet, return a fake value instead. diff --git a/kernel/aster-nix/src/net/iface/time.rs b/kernel/aster-nix/src/net/iface/time.rs index fe6b08e16..1c275e9c4 100644 --- a/kernel/aster-nix/src/net/iface/time.rs +++ b/kernel/aster-nix/src/net/iface/time.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MPL-2.0 -use aster_frame::timer::read_monotonic_milli_seconds; +use aster_frame::arch::timer::Jiffies; pub(super) fn get_network_timestamp() -> smoltcp::time::Instant { - let millis = read_monotonic_milli_seconds(); + let millis = Jiffies::elapsed().as_duration().as_millis(); smoltcp::time::Instant::from_millis(millis as i64) } diff --git a/kernel/aster-nix/src/prelude.rs b/kernel/aster-nix/src/prelude.rs index 0206faddb..aac2be6ef 100644 --- a/kernel/aster-nix/src/prelude.rs +++ b/kernel/aster-nix/src/prelude.rs @@ -44,6 +44,7 @@ pub(crate) use crate::{ current, current_thread, error::{Errno, Error}, print, println, + time::Clock, }; pub(crate) type Result = core::result::Result; pub(crate) use crate::{return_errno, return_errno_with_message}; diff --git a/kernel/aster-nix/src/syscall/clock_gettime.rs b/kernel/aster-nix/src/syscall/clock_gettime.rs index 99936e979..94d8816c3 100644 --- a/kernel/aster-nix/src/syscall/clock_gettime.rs +++ b/kernel/aster-nix/src/syscall/clock_gettime.rs @@ -1,9 +1,21 @@ // SPDX-License-Identifier: MPL-2.0 +#![allow(non_camel_case_types)] +use core::time::Duration; + +use int_to_c_enum::TryFromInt; + use super::SyscallReturn; use crate::{ prelude::*, - time::{clockid_t, now_as_duration, timespec_t, ClockID}, + time::{ + clockid_t, + clocks::{ + BootTimeClock, MonotonicClock, MonotonicCoarseClock, MonotonicRawClock, RealTimeClock, + RealTimeCoarseClock, + }, + timespec_t, Clock, + }, util::write_val_to_user, }; @@ -11,10 +23,40 @@ pub fn sys_clock_gettime(clockid: clockid_t, timespec_addr: Vaddr) -> Result Result { + match clock_id { + ClockID::CLOCK_REALTIME => Ok(RealTimeClock::get().read_time()), + ClockID::CLOCK_MONOTONIC => Ok(MonotonicClock::get().read_time()), + ClockID::CLOCK_MONOTONIC_RAW => Ok(MonotonicRawClock::get().read_time()), + ClockID::CLOCK_REALTIME_COARSE => Ok(RealTimeCoarseClock::get().read_time()), + ClockID::CLOCK_MONOTONIC_COARSE => Ok(MonotonicCoarseClock::get().read_time()), + ClockID::CLOCK_BOOTTIME => Ok(BootTimeClock::get().read_time()), + _ => { + return_errno_with_message!(Errno::EINVAL, "unsupported clock_id"); + } + } +} diff --git a/kernel/aster-nix/src/syscall/mod.rs b/kernel/aster-nix/src/syscall/mod.rs index 801622f47..5f3ac0e1f 100644 --- a/kernel/aster-nix/src/syscall/mod.rs +++ b/kernel/aster-nix/src/syscall/mod.rs @@ -3,6 +3,7 @@ //! Read the Cpu context content then dispatch syscall to corrsponding handler //! The each sub module contains functions that handle real syscall logic. use aster_frame::cpu::UserContext; +pub use clock_gettime::ClockID; use crate::prelude::*; diff --git a/kernel/aster-nix/src/syscall/nanosleep.rs b/kernel/aster-nix/src/syscall/nanosleep.rs index 21eaf16aa..91def9a41 100644 --- a/kernel/aster-nix/src/syscall/nanosleep.rs +++ b/kernel/aster-nix/src/syscall/nanosleep.rs @@ -2,11 +2,11 @@ use core::time::Duration; -use super::SyscallReturn; +use super::{clock_gettime::read_clock, ClockID, SyscallReturn}; use crate::{ prelude::*, process::signal::Pauser, - time::{clockid_t, now_as_duration, timespec_t, ClockID, TIMER_ABSTIME}, + time::{clockid_t, timespec_t, TIMER_ABSTIME}, util::{read_val_from_user, write_val_to_user}, }; @@ -58,7 +58,7 @@ fn do_clock_nanosleep( clock_id, is_abs_time, request_time, remain_timespec_addr ); - let start_time = now_as_duration(&clock_id)?; + let start_time = read_clock(&clock_id)?; let timeout = if is_abs_time { if request_time < start_time { return Ok(SyscallReturn::Return(0)); @@ -77,7 +77,7 @@ fn do_clock_nanosleep( match res { Err(e) if e.error() == Errno::ETIME => Ok(SyscallReturn::Return(0)), Err(e) if e.error() == Errno::EINTR => { - let end_time = now_as_duration(&clock_id)?; + let end_time = read_clock(&clock_id)?; if end_time >= start_time + timeout { return Ok(SyscallReturn::Return(0)); diff --git a/kernel/aster-nix/src/time/clocks/mod.rs b/kernel/aster-nix/src/time/clocks/mod.rs new file mode 100644 index 000000000..93665606c --- /dev/null +++ b/kernel/aster-nix/src/time/clocks/mod.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MPL-2.0 + +pub use system_wide::*; + +mod system_wide; + +pub(super) fn init() { + system_wide::init(); +} diff --git a/kernel/aster-nix/src/time/clocks/system_wide.rs b/kernel/aster-nix/src/time/clocks/system_wide.rs new file mode 100644 index 000000000..4224b4cc6 --- /dev/null +++ b/kernel/aster-nix/src/time/clocks/system_wide.rs @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::sync::Arc; +use core::time::Duration; + +use aster_frame::{ + arch::timer::{self, Jiffies}, + cpu_local, + sync::SpinLock, + CpuLocal, +}; +use aster_time::read_monotonic_time; +use paste::paste; +use spin::Once; + +use crate::time::{system_time::START_TIME_AS_DURATION, timer::TimerManager, Clock, SystemTime}; + +/// The Clock that reads the jiffies, and turn the counter into `Duration`. +pub struct JiffiesClock { + _private: (), +} + +/// `RealTimeClock` represents a clock that provides the current real time. +pub struct RealTimeClock { + _private: (), +} + +impl RealTimeClock { + /// Get the singleton of this clock. + pub fn get() -> &'static Arc { + CLOCK_REALTIME_INSTANCE.get().unwrap() + } + + /// Get the cpu-local system-wide `TimerManager` singleton of this clock. + pub fn timer_manager() -> &'static Arc { + CLOCK_REALTIME_MANAGER.get().unwrap() + } +} + +/// `MonotonicClock` represents a clock that measures time in a way that is +/// monotonically increasing since the system was booted. +pub struct MonotonicClock { + _private: (), +} + +impl MonotonicClock { + /// Get the singleton of this clock. + pub fn get() -> &'static Arc { + CLOCK_MONOTONIC_INSTANCE.get().unwrap() + } + + /// Get the cpu-local system-wide `TimerManager` singleton of this clock. + pub fn timer_manager() -> &'static Arc { + CLOCK_MONOTONIC_MANAGER.get().unwrap() + } +} + +/// `RealTimeCoarseClock` is a coarse-grained version of a real-time clock. +/// +/// This clock will maintain a record to `RealTimeClock`. This record +/// will be updated during each system timer interruption. Reading this clock +/// will directly reads the value of the record instead of calculating the time +/// based on the clocksource. Hence it is faster but less accurate. +/// +/// Usually it will not be used to create a timer. +pub struct RealTimeCoarseClock { + _private: (), +} + +impl RealTimeCoarseClock { + /// A reference to the current value of this clock. + fn current_ref() -> &'static Once> { + static CURRENT: Once> = Once::new(); + + &CURRENT + } + + /// Get the singleton of this clock. + pub fn get() -> &'static Arc { + CLOCK_REALTIME_COARSE_INSTANCE.get().unwrap() + } +} + +/// `MonotonicCoarseClock` is a coarse-grained version of the monotonic clock. +/// +/// This clock is based on [`RealTimeCoarseClock`]. +/// +/// Usually it will not be used to create a timer. +pub struct MonotonicCoarseClock { + _private: (), +} + +impl MonotonicCoarseClock { + /// Get the singleton of this clock. + pub fn get() -> &'static Arc { + CLOCK_MONOTONIC_COARSE_INSTANCE.get().unwrap() + } +} + +/// `MonotonicRawClock` provides raw monotonic time that is not influenced by +/// NTP corrections. +/// +/// Note: Currently we have not implement NTP corrections so we treat this clock +/// as the [`MonotonicClock`]. +pub struct MonotonicRawClock { + _private: (), +} + +impl MonotonicRawClock { + /// Get the singleton of this clock. + pub fn get() -> &'static Arc { + CLOCK_MONOTONIC_RAW_INSTANCE.get().unwrap() + } +} + +/// `BootTimeClock` measures the time elapsed since the system was booted, +/// including time when the system was suspended. +/// +/// Note: currently the system will not be suspended so we treat this clock +/// as the [`MonotonicClock`]. +pub struct BootTimeClock { + _private: (), +} + +impl BootTimeClock { + /// Get the singleton of this clock. + pub fn get() -> &'static Arc { + CLOCK_BOOTTIME_INSTANCE.get().unwrap() + } +} + +impl Clock for JiffiesClock { + fn read_time(&self) -> Duration { + Jiffies::elapsed().as_duration() + } +} + +impl Clock for RealTimeClock { + fn read_time(&self) -> Duration { + SystemTime::now() + .duration_since(&SystemTime::UNIX_EPOCH) + .unwrap() + } +} + +impl Clock for MonotonicClock { + fn read_time(&self) -> Duration { + read_monotonic_time() + } +} + +impl Clock for RealTimeCoarseClock { + fn read_time(&self) -> Duration { + *Self::current_ref().get().unwrap().lock_irq_disabled() + } +} + +impl Clock for MonotonicCoarseClock { + fn read_time(&self) -> Duration { + RealTimeCoarseClock::get().read_time() - *START_TIME_AS_DURATION.get().unwrap() + } +} + +impl Clock for MonotonicRawClock { + fn read_time(&self) -> Duration { + read_monotonic_time() + } +} + +impl Clock for BootTimeClock { + fn read_time(&self) -> Duration { + read_monotonic_time() + } +} + +/// Define the system-wide clocks. +macro_rules! define_system_clocks { + ($($clock_id:ident => $clock_type:ident,)*) => { + $( + paste! { + pub static [<$clock_id _INSTANCE>]: Once> = Once::new(); + } + )* + + fn _init_system_wide_clocks() { + $( + let clock = Arc::new( + $clock_type { + _private: (), + } + ); + paste! { + [<$clock_id _INSTANCE>].call_once(|| clock.clone()); + } + )* + } + } +} + +/// Define the timer managers of some system-wide clocks. +macro_rules! define_timer_managers { + ($($clock_id:ident,)*) => { + $( + paste! { + cpu_local! { + pub static [<$clock_id _MANAGER>]: Once> = Once::new(); + } + } + )* + + fn _init_system_wide_timer_managers() { + $( + let clock = paste! {[<$clock_id _INSTANCE>].get().unwrap().clone()}; + let clock_manager = TimerManager::new(clock); + paste! { + CpuLocal::borrow_with(&[<$clock_id _MANAGER>], |manager| { + manager.call_once(|| clock_manager.clone()); + }); + } + let callback = move || { + clock_manager.process_expired_timers(); + }; + timer::register_callback(callback); + )* + } + } +} + +define_system_clocks! { + CLOCK_REALTIME => RealTimeClock, + CLOCK_REALTIME_COARSE => RealTimeCoarseClock, + CLOCK_MONOTONIC => MonotonicClock, + CLOCK_MONOTONIC_COARSE => MonotonicCoarseClock, + CLOCK_MONOTONIC_RAW => MonotonicRawClock, + CLOCK_BOOTTIME => BootTimeClock, +} + +define_timer_managers![CLOCK_REALTIME, CLOCK_MONOTONIC,]; + +/// Init the system-wide clocks. +fn init_system_wide_clocks() { + _init_system_wide_clocks(); +} + +/// Init the system-wide cpu-local [`TimerManager`]s. +fn init_system_wide_timer_managers() { + _init_system_wide_timer_managers(); +} + +/// The system-wide [`TimerManager`] for the [`JiffiesClock`]. +pub static JIFFIES_TIMER_MANAGER: Once> = Once::new(); + +fn init_jiffies_clock_manager() { + let jiffies_clock = JiffiesClock { _private: () }; + let jiffies_timer_manager = TimerManager::new(Arc::new(jiffies_clock)); + JIFFIES_TIMER_MANAGER.call_once(|| jiffies_timer_manager.clone()); + + let callback = move || { + jiffies_timer_manager.process_expired_timers(); + }; + timer::register_callback(callback); +} + +fn update_coarse_clock() { + let real_time = RealTimeClock::get().read_time(); + let current = RealTimeCoarseClock::current_ref().get().unwrap(); + *current.lock_irq_disabled() = real_time; +} + +fn init_coarse_clock() { + let real_time = RealTimeClock::get().read_time(); + RealTimeCoarseClock::current_ref().call_once(|| SpinLock::new(real_time)); + timer::register_callback(update_coarse_clock); +} + +pub(super) fn init() { + init_system_wide_clocks(); + init_system_wide_timer_managers(); + init_jiffies_clock_manager(); + init_coarse_clock(); +} + +#[cfg(ktest)] +/// Init `CLOCK_REALTIME_MANAGER` for process-related ktests. +/// +/// TODO: `ktest` may require a feature that allows the registration of initialization functions +/// to avoid functions like this one. +pub fn init_for_ktest() { + // If `spin::Once` has initialized, this closure will not be executed. + CLOCK_REALTIME_MANAGER.call_once(|| { + let clock = RealTimeClock { _private: () }; + TimerManager::new(Arc::new(clock)) + }); +} diff --git a/kernel/aster-nix/src/time/core/mod.rs b/kernel/aster-nix/src/time/core/mod.rs new file mode 100644 index 000000000..3a9ca30c3 --- /dev/null +++ b/kernel/aster-nix/src/time/core/mod.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::time::Duration; + +use aster_frame::arch::timer::TIMER_FREQ; +use aster_time::NANOS_PER_SECOND; + +pub mod timer; + +type Nanos = u64; + +/// A trait that can abstract clocks which have the ability to read time, +/// and has a fixed resolution. +pub trait Clock: Send + Sync { + /// Read the current time of this clock. + fn read_time(&self) -> Duration; + + /// Return the resolution of this clock. + /// Set to the resolution of system time interrupt by default. + fn resolution(&self) -> Nanos + where + Self: Sized, + { + NANOS_PER_SECOND as u64 / TIMER_FREQ + } +} diff --git a/kernel/aster-nix/src/time/core/timer.rs b/kernel/aster-nix/src/time/core/timer.rs new file mode 100644 index 000000000..5334cf4f1 --- /dev/null +++ b/kernel/aster-nix/src/time/core/timer.rs @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::{ + boxed::Box, + collections::BinaryHeap, + sync::{Arc, Weak}, + vec::Vec, +}; +use core::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; + +use aster_frame::sync::SpinLock; + +use super::Clock; + +/// A timer with periodic functionality. +/// +/// Setting the timer will trigger a callback function upon expiration of +/// the set time. To enable its periodic functionality, users should set +/// its `interval` field with [`Timer::set_interval`]. By doing this, +/// the timer will use the interval time to configure a new timing after expiration. +pub struct Timer { + interval: SpinLock, + timer_manager: Arc, + registered_callback: Box, + timer_callback: SpinLock>, +} + +impl Timer { + /// Create a `Timer` instance from a [`TimerManager`]. + /// This timer will be managed by the `TimerManager`. + /// + /// Note that if the callback instructions involves sleep, users should put these instructions + /// into something like `WorkQueue` to avoid sleeping during system timer interruptions. + fn new(registered_callback: F, timer_manager: Arc) -> Arc + where + F: Fn() + Send + Sync + 'static, + { + Arc::new(Self { + interval: SpinLock::new(Duration::ZERO), + timer_manager, + registered_callback: Box::new(registered_callback), + timer_callback: SpinLock::new(Weak::default()), + }) + } + + /// Set the interval time for this timer. + /// The timer will be reset with the interval time upon expiration. + pub fn set_interval(&self, interval: Duration) { + *self.interval.lock_irq_disabled() = interval; + } + + /// Cancel the current timer's set timeout callback. + pub fn cancel(&self) { + let timer_callback = self.timer_callback.lock_irq_disabled(); + if let Some(timer_callback) = timer_callback.upgrade() { + timer_callback.cancel(); + } + } + + /// Set the timer with a timeout. + /// + /// The registered callback function of this timer will be invoked + /// when reaching timeout. If the timer has a valid interval, this timer + /// will be set again with the interval when reaching timeout. + pub fn set_timeout(self: &Arc, timeout: Duration) { + let now = self.timer_manager.clock.read_time(); + let expired_time = now + timeout; + let timer_weak = Arc::downgrade(self); + let new_timer_callback = Arc::new(TimerCallback::new( + expired_time, + Box::new(move || interval_timer_callback(&timer_weak)), + )); + + let mut timer_callback = self.timer_callback.lock_irq_disabled(); + if let Some(timer_callback) = timer_callback.upgrade() { + timer_callback.cancel(); + } + *timer_callback = Arc::downgrade(&new_timer_callback); + self.timer_manager.insert(new_timer_callback); + } + + /// Return the current expired time of this timer. + pub fn expired_time(&self) -> Duration { + let timer_callback = self.timer_callback.lock_irq_disabled().upgrade(); + timer_callback.map_or(Duration::ZERO, |timer_callback| timer_callback.expired_time) + } + + /// Return the remain time to expiration of this timer. + /// + /// If the timer has not been set, this method + /// will return `Duration::ZERO`. + pub fn remain(&self) -> Duration { + let now = self.timer_manager.clock.read_time(); + let expired_time = self.expired_time(); + if expired_time > now { + expired_time - now + } else { + Duration::ZERO + } + } + + /// Return a reference to the [`TimerManager`] which manages + /// the current timer. + pub fn timer_manager(&self) -> &Arc { + &self.timer_manager + } +} + +fn interval_timer_callback(timer: &Weak) { + let Some(timer) = timer.upgrade() else { + return; + }; + + (timer.registered_callback)(); + let interval = timer.interval.lock_irq_disabled(); + if *interval != Duration::ZERO { + timer.set_timeout(*interval); + } +} + +/// `TimerManager` is used to create timers and manage their expiries. It holds a clock and can +/// create [`Timer`]s based on this clock. +/// +/// These created `Timer`s will hold an `Arc` pointer to this manager, hence this manager +/// will be actually dropped after all the created timers have been dropped. +pub struct TimerManager { + clock: Arc, + timer_callbacks: SpinLock>>, +} + +impl TimerManager { + /// Create a `TimerManager` instance from a clock. + pub fn new(clock: Arc) -> Arc { + Arc::new(Self { + clock, + timer_callbacks: SpinLock::new(BinaryHeap::new()), + }) + } + + fn insert(&self, timer_callback: Arc) { + self.timer_callbacks + .lock_irq_disabled() + .push(timer_callback); + } + + /// Check the managed timers, and if any have timed out, + /// call the corresponding callback functions. + pub fn process_expired_timers(&self) { + let callbacks = { + let mut timeout_list = self.timer_callbacks.lock_irq_disabled(); + if timeout_list.len() == 0 { + return; + } + + let mut callbacks = Vec::new(); + let current_time = self.clock.read_time(); + while let Some(t) = timeout_list.peek() { + if t.is_cancelled() { + // Just ignore the cancelled callback + timeout_list.pop(); + } else if t.expired_time <= current_time { + callbacks.push(timeout_list.pop().unwrap()); + } else { + break; + } + } + callbacks + }; + + for callback in callbacks { + (callback.callback)(); + } + } + + /// Create an [`Timer`], which will be managed by this `TimerManager`. + pub fn create_timer(self: &Arc, function: F) -> Arc + where + F: Fn() + Send + Sync + 'static, + { + Timer::new(function, self.clone()) + } +} + +/// A `TimerCallback` can be used to execute a timer callback function. +struct TimerCallback { + expired_time: Duration, + callback: Box, + is_cancelled: AtomicBool, +} + +impl TimerCallback { + /// Create an instance of `TimerCallback`. + fn new(timeout: Duration, callback: Box) -> Self { + Self { + expired_time: timeout, + callback, + is_cancelled: AtomicBool::new(false), + } + } + + /// Cancel a `TimerCallback`. If the callback function has not been called, + /// it will never be called again. + fn cancel(&self) { + self.is_cancelled.store(true, Ordering::Release); + } + + // Return whether the `TimerCallback` is cancelled. + fn is_cancelled(&self) -> bool { + self.is_cancelled.load(Ordering::Acquire) + } +} + +impl PartialEq for TimerCallback { + fn eq(&self, other: &Self) -> bool { + self.expired_time == other.expired_time + } +} + +impl Eq for TimerCallback {} + +impl PartialOrd for TimerCallback { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for TimerCallback { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + // We want `TimerCallback`s to be processed in ascending order of `expired_time`, + // and the in-order management of `TimerCallback`s currently relies on a maximum heap, + // so we need the reverse instruction here. + self.expired_time.cmp(&other.expired_time).reverse() + } +} diff --git a/kernel/aster-nix/src/time/mod.rs b/kernel/aster-nix/src/time/mod.rs index 95b2a643e..c800b19cb 100644 --- a/kernel/aster-nix/src/time/mod.rs +++ b/kernel/aster-nix/src/time/mod.rs @@ -1,15 +1,19 @@ // SPDX-License-Identifier: MPL-2.0 #![allow(non_camel_case_types)] -use core::time::Duration; -use aster_time::read_monotonic_time; +pub use core::{timer, Clock}; + +use ::core::time::Duration; +pub use system_time::{SystemTime, START_TIME}; +pub use timer::{Timer, TimerManager}; use crate::prelude::*; +pub mod clocks; +mod core; mod system_time; - -pub use system_time::{SystemTime, START_TIME}; +pub mod wait; pub type clockid_t = i32; pub type time_t = i64; @@ -17,32 +21,10 @@ pub type suseconds_t = i64; pub type clock_t = i64; pub(super) fn init() { - system_time::init_start_time(); + system_time::init(); + clocks::init(); } -#[derive(Debug, Copy, Clone, TryFromInt, PartialEq)] -#[repr(i32)] -pub enum ClockID { - CLOCK_REALTIME = 0, - CLOCK_MONOTONIC = 1, - CLOCK_PROCESS_CPUTIME_ID = 2, - CLOCK_THREAD_CPUTIME_ID = 3, - CLOCK_MONOTONIC_RAW = 4, - CLOCK_REALTIME_COARSE = 5, - CLOCK_MONOTONIC_COARSE = 6, - CLOCK_BOOTTIME = 7, -} - -/// A list of all supported clock IDs for time-related functions. -pub const ALL_SUPPORTED_CLOCK_IDS: [ClockID; 6] = [ - ClockID::CLOCK_REALTIME, - ClockID::CLOCK_REALTIME_COARSE, - ClockID::CLOCK_MONOTONIC, - ClockID::CLOCK_MONOTONIC_COARSE, - ClockID::CLOCK_MONOTONIC_RAW, - ClockID::CLOCK_BOOTTIME, -]; - #[repr(C)] #[derive(Debug, Default, Copy, Clone, Pod)] pub struct timespec_t { @@ -90,27 +72,6 @@ impl From for Duration { /// The various flags for setting POSIX.1b interval timers: pub const TIMER_ABSTIME: i32 = 0x01; -pub fn now_as_duration(clock_id: &ClockID) -> Result { - match clock_id { - ClockID::CLOCK_MONOTONIC - | ClockID::CLOCK_MONOTONIC_COARSE - | ClockID::CLOCK_MONOTONIC_RAW - | ClockID::CLOCK_BOOTTIME => Ok(read_monotonic_time()), - ClockID::CLOCK_REALTIME | ClockID::CLOCK_REALTIME_COARSE => { - let now = SystemTime::now(); - now.duration_since(&SystemTime::UNIX_EPOCH) - } - _ => { - warn!( - "unsupported clock_id: {:?}, treat it as CLOCK_REALTIME", - clock_id - ); - let now = SystemTime::now(); - now.duration_since(&SystemTime::UNIX_EPOCH) - } - } -} - /// Unix time measures time by the number of seconds that have elapsed since /// the Unix epoch, without adjustments made due to leap seconds. #[repr(C)] diff --git a/kernel/aster-nix/src/time/system_time.rs b/kernel/aster-nix/src/time/system_time.rs index bd18fb608..0b72d8705 100644 --- a/kernel/aster-nix/src/time/system_time.rs +++ b/kernel/aster-nix/src/time/system_time.rs @@ -13,9 +13,12 @@ use crate::prelude::*; pub struct SystemTime(PrimitiveDateTime); pub static START_TIME: Once = Once::new(); +pub(super) static START_TIME_AS_DURATION: Once = Once::new(); -pub(super) fn init_start_time() { +pub(super) fn init() { let start_time = convert_system_time(read_start_time()).unwrap(); + START_TIME_AS_DURATION + .call_once(|| start_time.duration_since(&SystemTime::UNIX_EPOCH).unwrap()); START_TIME.call_once(|| start_time); } @@ -37,6 +40,7 @@ impl SystemTime { /// Returns the current system time pub fn now() -> Self { + // The get real time result should always be valid START_TIME .get() .unwrap() diff --git a/kernel/aster-nix/src/vdso.rs b/kernel/aster-nix/src/vdso.rs index 077c2e593..6824d2101 100644 --- a/kernel/aster-nix/src/vdso.rs +++ b/kernel/aster-nix/src/vdso.rs @@ -16,7 +16,6 @@ use core::time::Duration; use aster_frame::{ sync::SpinLock, - timer::Timer, vm::{VmFrame, VmIo, PAGE_SIZE}, }; use aster_rights::Rights; @@ -27,7 +26,8 @@ use spin::Once; use crate::{ fs::fs_resolver::{FsPath, FsResolver, AT_FDCWD}, - time::{ClockID, SystemTime, START_TIME}, + syscall::ClockID, + time::{clocks::MonotonicClock, SystemTime, START_TIME}, vm::vmo::{Vmo, VmoOptions}, }; @@ -291,11 +291,9 @@ fn update_vdso_high_res_instant(instant: Instant, instant_cycles: u64) { } /// Update the `VdsoInstant` for clock IDs with coarse resolution in Vdso. -fn update_vdso_coarse_res_instant(timer: Arc) { +fn update_vdso_coarse_res_instant() { let instant = Instant::from(read_monotonic_time()); VDSO.get().unwrap().update_coarse_res_instant(instant); - - timer.set(Duration::from_millis(100)); } /// Init `START_SECS_COUNT`, which is used to record the seconds passed since 1970-01-01 00:00:00. @@ -321,8 +319,10 @@ pub(super) fn init() { // Coarse resolution clock IDs directly read the instant stored in VDSO data without // using coefficients for calculation, thus the related instant requires more frequent updating. - let coarse_instant_timer = Timer::new(update_vdso_coarse_res_instant).unwrap(); - coarse_instant_timer.set(Duration::from_millis(100)); + let coarse_instant_timer = + MonotonicClock::timer_manager().create_timer(update_vdso_coarse_res_instant); + coarse_instant_timer.set_interval(Duration::from_millis(100)); + coarse_instant_timer.set_timeout(Duration::from_millis(100)); } /// Return the VDSO vmo.