diff --git a/Cargo.lock b/Cargo.lock index 0445ac644..2df8b5563 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -804,6 +804,7 @@ version = "0.1.0" dependencies = [ "component", "jinux-frame", + "jinux-util", "log", "spin 0.9.8", ] @@ -816,6 +817,7 @@ dependencies = [ "jinux-frame", "jinux-rights", "jinux-rights-proc", + "ktest", "pod", "typeflags-util", ] diff --git a/framework/jinux-frame/src/arch/x86/kernel/tsc.rs b/framework/jinux-frame/src/arch/x86/kernel/tsc.rs index 7e2af95a7..f6cd07aa0 100644 --- a/framework/jinux-frame/src/arch/x86/kernel/tsc.rs +++ b/framework/jinux-frame/src/arch/x86/kernel/tsc.rs @@ -1,11 +1,23 @@ +use core::sync::atomic::AtomicU64; use x86::cpuid::cpuid; +/// The frequency of tsc. The unit is Hz. +pub(crate) static TSC_FREQ: AtomicU64 = AtomicU64::new(0); + +const TSC_DEADLINE_MODE_SUPPORT: u32 = 1 << 24; + +/// Determine if the current system supports tsc_deadline mode. +pub fn is_tsc_deadline_mode_supported() -> bool { + let cpuid = cpuid!(1); + (cpuid.ecx & TSC_DEADLINE_MODE_SUPPORT) > 0 +} + /// Determine TSC frequency via CPUID. If the CPU does not support calculating TSC frequency by /// CPUID, the function will return None. The unit of the return value is KHz. /// /// Ref: function `native_calibrate_tsc` in linux `arch/x86/kernel/tsc.c` /// -pub fn tsc_freq() -> Option { +pub fn determine_tsc_freq_via_cpuid() -> Option { // Check the max cpuid supported let cpuid = cpuid!(0); let max_cpuid = cpuid.eax; diff --git a/framework/jinux-frame/src/arch/x86/mod.rs b/framework/jinux-frame/src/arch/x86/mod.rs index 478868b65..5d0dc6db4 100644 --- a/framework/jinux-frame/src/arch/x86/mod.rs +++ b/framework/jinux-frame/src/arch/x86/mod.rs @@ -12,6 +12,8 @@ pub mod qemu; pub(crate) mod tdx_guest; pub(crate) mod timer; +use core::{arch::x86_64::_rdtsc, sync::atomic::Ordering}; + use kernel::apic::ioapic; use log::{info, warn}; @@ -50,6 +52,17 @@ pub(crate) fn interrupts_ack() { } } +/// Return the frequency of TSC. The unit is Hz. +pub fn tsc_freq() -> u64 { + kernel::tsc::TSC_FREQ.load(Ordering::Acquire) +} + +/// Reads the current value of the processor’s time-stamp counter (TSC). +pub fn read_tsc() -> u64 { + // Safety: It is safe to read a time-related counter. + unsafe { _rdtsc() } +} + fn enable_common_cpu_features() { use x86_64::registers::{control::Cr4Flags, model_specific::EferFlags, xcontrol::XCr0Flags}; let mut cr4 = x86_64::registers::control::Cr4::read(); diff --git a/framework/jinux-frame/src/arch/x86/timer/apic.rs b/framework/jinux-frame/src/arch/x86/timer/apic.rs index e68c84ff1..a84386150 100644 --- a/framework/jinux-frame/src/arch/x86/timer/apic.rs +++ b/framework/jinux-frame/src/arch/x86/timer/apic.rs @@ -1,55 +1,45 @@ -use core::arch::x86_64::_rdtsc; -use core::sync::atomic::{AtomicBool, Ordering}; - use alloc::sync::Arc; +use core::arch::x86_64::_rdtsc; +use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; + use log::info; use spin::Once; use trapframe::TrapFrame; -use x86::cpuid::cpuid; use x86::msr::{wrmsr, IA32_TSC_DEADLINE}; use crate::arch::kernel::apic::ioapic::IO_APIC; +use crate::arch::kernel::tsc::is_tsc_deadline_mode_supported; +use crate::arch::x86::kernel::apic::{DivideConfig, APIC_INSTANCE}; +use crate::arch::x86::kernel::tsc::{determine_tsc_freq_via_cpuid, TSC_FREQ}; +use crate::config::TIMER_FREQ; use crate::trap::IrqLine; -use crate::{ - arch::x86::kernel::{ - apic::{DivideConfig, APIC_INSTANCE}, - tsc::tsc_freq, - }, - config::TIMER_FREQ, -}; pub fn init() { - if tsc_mode_support() { - info!("[Timer]: Enable APIC-TSC deadline mode."); - tsc_mode_init(); + init_tsc_freq(); + if is_tsc_deadline_mode_supported() { + info!("[Timer]: Enable APIC TSC deadline mode."); + init_tsc_mode(); } else { - info!("[Timer]: Enable APIC-periodic mode."); - periodic_mode_init(); + info!("[Timer]: Enable APIC periodic mode."); + init_periodic_mode(); } } -fn tsc_mode_support() -> bool { - let tsc_rate = tsc_freq(); - if tsc_rate.is_none() { - return false; - } - let cpuid = cpuid!(0x1); - // bit 24 - cpuid.ecx & 0x100_0000 != 0 -} - pub(super) static APIC_TIMER_CALLBACK: Once> = Once::new(); -fn tsc_mode_init() { +fn init_tsc_freq() { + let tsc_freq = determine_tsc_freq_via_cpuid() + .map_or(determine_tsc_freq_via_pit(), |freq| freq as u64 * 1000); + TSC_FREQ.store(tsc_freq, Ordering::Relaxed); + info!("TSC frequency:{:?} Hz", tsc_freq); +} + +fn init_tsc_mode() { let mut apic_lock = APIC_INSTANCE.get().unwrap().lock(); // Enable tsc deadline mode. apic_lock.set_lvt_timer(super::TIMER_IRQ_NUM.load(Ordering::Relaxed) as u64 | (1 << 18)); - - let tsc_step = { - let tsc_rate = tsc_freq().unwrap() as u64; - info!("TSC frequency:{:?} Hz", tsc_rate * 1000); - tsc_rate * 1000 / TIMER_FREQ - }; + drop(apic_lock); + let tsc_step = TSC_FREQ.load(Ordering::Relaxed) / TIMER_FREQ; let callback = move || unsafe { let tsc_value = _rdtsc(); @@ -61,7 +51,54 @@ fn tsc_mode_init() { APIC_TIMER_CALLBACK.call_once(|| Arc::new(callback)); } -fn periodic_mode_init() { +/// When kernel cannot get the TSC frequency from CPUID, it can leverage +/// the PIT to calculate this frequency. +fn determine_tsc_freq_via_pit() -> u64 { + let mut irq = IrqLine::alloc_specific(super::TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap(); + irq.on_active(pit_callback); + let mut io_apic = IO_APIC.get().unwrap().get(0).unwrap().lock(); + debug_assert_eq!(io_apic.interrupt_base(), 0); + io_apic.enable(2, irq.clone()).unwrap(); + drop(io_apic); + + super::pit::init(); + + x86_64::instructions::interrupts::enable(); + static IS_FINISH: AtomicBool = AtomicBool::new(false); + static FREQUENCY: AtomicU64 = AtomicU64::new(0); + while !IS_FINISH.load(Ordering::Acquire) { + x86_64::instructions::hlt(); + } + x86_64::instructions::interrupts::disable(); + drop(irq); + return FREQUENCY.load(Ordering::Acquire); + + fn pit_callback(trap_frame: &TrapFrame) { + static mut IN_TIME: u64 = 0; + static mut TSC_FIRST_COUNT: u64 = 0; + // Set a certain times of callbacks to calculate the frequency. + const CALLBACK_TIMES: u64 = TIMER_FREQ / 10; + unsafe { + if IN_TIME < CALLBACK_TIMES || IS_FINISH.load(Ordering::Acquire) { + // drop the first entry, since it may not be the time we want + if IN_TIME == 0 { + TSC_FIRST_COUNT = _rdtsc(); + } + IN_TIME += 1; + return; + } + let mut io_apic = IO_APIC.get().unwrap().get(0).unwrap().lock(); + io_apic.disable(2).unwrap(); + drop(io_apic); + let tsc_count = _rdtsc(); + let freq = (tsc_count - TSC_FIRST_COUNT) * (TIMER_FREQ / CALLBACK_TIMES); + FREQUENCY.store(freq, Ordering::Release); + } + IS_FINISH.store(true, Ordering::Release); + } +} + +fn init_periodic_mode() { let mut apic_lock = APIC_INSTANCE.get().unwrap().lock(); let mut irq = IrqLine::alloc_specific(super::TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap(); irq.on_active(init_function); @@ -108,7 +145,6 @@ fn periodic_mode_init() { 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 diff --git a/services/comps/time/Cargo.toml b/services/comps/time/Cargo.toml index 9bd5822a6..032189fed 100644 --- a/services/comps/time/Cargo.toml +++ b/services/comps/time/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] jinux-frame = { path = "../../../framework/jinux-frame" } +jinux-util = { path = "../../libs/jinux-util" } component = { path = "../../libs/comp-sys/component" } log = "0.4" spin = "0.9.4" diff --git a/services/comps/time/src/clocksource.rs b/services/comps/time/src/clocksource.rs new file mode 100644 index 000000000..ef59320f7 --- /dev/null +++ b/services/comps/time/src/clocksource.rs @@ -0,0 +1,218 @@ +//! This module provides abstractions for hardware-assisted timing mechanisms, encapsulated by the `ClockSource` struct. +//! A `ClockSource` can be constructed from any counter with a stable frequency, enabling precise time measurements to be taken +//! by retrieving instances of `Instant`. +//! +//! The `ClockSource` module is a fundamental building block for timing in systems that require high precision and accuracy. +//! It can be integrated into larger systems to provide timing capabilities, or used standalone for time tracking and elapsed time measurements. + +use alloc::sync::Arc; +use core::{cmp::max, ops::Add, time::Duration}; +use jinux_frame::sync::SpinLock; +use jinux_util::coeff::Coeff; + +use crate::NANOS_PER_SECOND; + +/// `ClockSource` is an abstraction for hardware-assisted timing mechanisms. +/// A `ClockSource` can be created based on any counter that operates at a stable frequency. +/// Users are able to measure time by retrieving `Instant` from this source. +/// +/// # Implementation +/// The `ClockSource` relies on obtaining the frequency of the counter and the method for reading the cycles in order to measure time. +/// The **cycles** here refer the counts of the base time counter. +/// Additionally, the `ClockSource` also holds a last recorded instant, which acts as a reference point for subsequent time retrieval. +/// To prevent numerical overflow during the calculation of `Instant`, this last recorded instant **must be periodically refreshed**. +/// The maximum interval for these updates must be determined at the time of the `ClockSource` initialization. +/// +/// # Examples +/// Suppose we have a counter called `counter` which have the frequency `counter.freq`, and the method to read its cycles called `read_counter()`. +/// We can create a corresponding `ClockSource` and use it as follows: +/// +/// ```rust +/// // here we set the max_delay_secs = 10 +/// let max_delay_secs = 10; +/// // create a clocksource named counter_clock +/// let counter_clock = ClockSource::new(counter.freq, max_delay_secs, Arc::new(read_counter)); +/// // read an instant. +/// let instant = counter_clock.read_instant(); +/// ``` +/// +/// If using this `ClockSource`, you must ensure its internal instant will be updated +/// at least once within a time interval of not more than `max_delay_secs. +pub struct ClockSource { + read_cycles: Arc u64 + Sync + Send>, + base: ClockSourceBase, + coeff: Coeff, + last_instant: SpinLock, + last_cycles: SpinLock, +} + +impl ClockSource { + /// Create a new `ClockSource` instance. + /// Require basic information of based time counter, including the function for reading cycles, the frequency + /// and the maximum delay seconds to update this `ClockSource`. + /// The `ClockSource` also calculates a reliable `Coeff` based on the counter's frequency and the maximum delay seconds. + /// This `Coeff` is used to convert the number of cycles into the duration of time that has passed for those cycles. + pub fn new( + freq: u64, + max_delay_secs: u64, + read_cycles: Arc u64 + Sync + Send>, + ) -> Self { + let base = ClockSourceBase::new(freq, max_delay_secs); + // Too big `max_delay_secs` will lead to a low resolution Coeff. + debug_assert!(max_delay_secs < 600); + let coeff = Coeff::new(NANOS_PER_SECOND as u64, freq, max_delay_secs * freq); + Self { + read_cycles, + base, + coeff, + last_instant: SpinLock::new(Instant::zero()), + last_cycles: SpinLock::new(0), + } + } + + fn cycles_to_nanos(&self, cycles: u64) -> u64 { + self.coeff * cycles + } + + /// Use the instant cycles to calculate the instant. + /// It first calculates the difference between the instant cycles and the last recorded cycles stored in the clocksource. + /// Then `ClockSource` will convert the passed cycles into passed time and calculate the current instant. + fn calculate_instant(&self, instant_cycles: u64) -> Instant { + let delta_nanos = { + let delta_cycles = instant_cycles - self.last_cycles(); + self.cycles_to_nanos(delta_cycles) + }; + let duration = Duration::from_nanos(delta_nanos); + self.last_instant() + duration + } + + /// Use an input instant to update the internal instant in the `ClockSource`. + fn update_last_instant(&self, instant: &Instant) { + *self.last_instant.lock() = *instant; + } + + /// Use an input cycles to update the internal instant in the `ClockSource`. + fn update_last_cycles(&self, cycles: u64) { + *self.last_cycles.lock() = cycles; + } + + /// read current cycles of the `ClockSource`. + pub fn read_cycles(&self) -> u64 { + (self.read_cycles)() + } + + /// Return the last instant recorded in the `ClockSource`. + pub fn last_instant(&self) -> Instant { + return *self.last_instant.lock(); + } + + /// Return the last cycles recorded in the `ClockSource`. + pub fn last_cycles(&self) -> u64 { + return *self.last_cycles.lock(); + } + + /// Return the maximum delay seconds for updating of the `ClockSource`. + pub fn max_delay_secs(&self) -> u64 { + self.base.max_delay_secs() + } + + /// Return the reference to the generated cycles coeff of the `ClockSource`. + pub fn coeff(&self) -> &Coeff { + &self.coeff + } + + /// Return the frequency of the counter used in the `ClockSource`. + pub fn freq(&self) -> u64 { + self.base.freq() + } + + /// Calibrate the recorded `Instant` to zero, and record the instant cycles. + pub(crate) fn calibrate(&self, instant_cycles: u64) { + self.update_last_cycles(instant_cycles); + self.update_last_instant(&Instant::zero()); + } + + /// Get the instant to update the internal instant in the `ClockSource`. + pub(crate) fn update(&self) { + let instant_cycles = self.read_cycles(); + let instant = self.calculate_instant(instant_cycles); + self.update_last_cycles(instant_cycles); + self.update_last_instant(&instant); + } + + /// Read the instant corresponding to the current time. + /// When trying to read an instant from the clocksource, it will use the reading method to read instant cycles. + /// Then leverage it to calculate the instant. + pub(crate) fn read_instant(&self) -> Instant { + let instant_cycles = self.read_cycles(); + self.calculate_instant(instant_cycles) + } +} + +/// `Instant` captures a specific moment, storing the duration of time +/// elapsed since a reference point (typically the system boot time). +/// The `Instant` is expressed in seconds and the fractional part is expressed in nanoseconds. +#[derive(Debug, Default, Copy, Clone)] +pub struct Instant { + secs: u64, + nanos: u32, +} + +impl Instant { + pub const fn zero() -> Self { + Self { secs: 0, nanos: 0 } + } + + pub fn new(secs: u64, nanos: u32) -> Self { + Self { secs, nanos } + } + + /// Return the seconds recorded in the Instant. + pub fn secs(&self) -> u64 { + self.secs + } + + /// Return the nanoseconds recorded in the Instant. + pub fn nanos(&self) -> u32 { + self.nanos + } +} + +impl Add for Instant { + type Output = Instant; + + fn add(self, other: Duration) -> Self::Output { + let mut secs = self.secs + other.as_secs(); + let mut nanos = self.nanos + other.subsec_nanos(); + if nanos >= NANOS_PER_SECOND { + secs += 1; + nanos -= NANOS_PER_SECOND; + } + Instant::new(secs, nanos) + } +} + +/// The basic properties of `ClockSource`. +#[derive(Debug, Copy, Clone)] +struct ClockSourceBase { + freq: u64, + max_delay_secs: u64, +} + +impl ClockSourceBase { + fn new(freq: u64, max_delay_secs: u64) -> Self { + let max_delay_secs = max(2, max_delay_secs); + ClockSourceBase { + freq, + max_delay_secs, + } + } + + fn max_delay_secs(&self) -> u64 { + self.max_delay_secs + } + + fn freq(&self) -> u64 { + self.freq + } +} diff --git a/services/comps/time/src/lib.rs b/services/comps/time/src/lib.rs index bb9c69c4d..b6c1e33f7 100644 --- a/services/comps/time/src/lib.rs +++ b/services/comps/time/src/lib.rs @@ -2,15 +2,30 @@ #![no_std] #![forbid(unsafe_code)] -use component::{init_component, ComponentInitError}; -use core::sync::atomic::Ordering::Relaxed; -use rtc::{get_cmos, is_updating, read, CENTURY_REGISTER}; +extern crate alloc; +use alloc::sync::Arc; +use component::{init_component, ComponentInitError}; +use core::{sync::atomic::Ordering::Relaxed, time::Duration}; +use jinux_frame::sync::Mutex; +use spin::Once; + +use clocksource::ClockSource; +use rtc::{get_cmos, is_updating, CENTURY_REGISTER}; + +pub use clocksource::Instant; + +mod clocksource; mod rtc; +mod tsc; + +pub const NANOS_PER_SECOND: u32 = 1_000_000_000; +pub static VDSO_DATA_UPDATE: Once> = Once::new(); #[init_component] fn time_init() -> Result<(), ComponentInitError> { rtc::init(); + tsc::init(); Ok(()) } @@ -23,6 +38,7 @@ pub struct SystemTime { pub hour: u8, pub minute: u8, pub second: u8, + pub nanos: u64, } impl SystemTime { @@ -35,6 +51,7 @@ impl SystemTime { hour: 0, minute: 0, second: 0, + nanos: 0, } } @@ -88,7 +105,55 @@ impl SystemTime { } } +pub(crate) static READ_TIME: Mutex = Mutex::new(SystemTime::zero()); +pub(crate) static START_TIME: Once = Once::new(); + /// get real time pub fn get_real_time() -> SystemTime { read() } + +pub fn read() -> SystemTime { + update_time(); + *READ_TIME.lock() +} + +/// read year,month,day and other data +/// ref: https://wiki.osdev.org/CMOS#Reading_All_RTC_Time_and_Date_Registers +fn update_time() { + let mut last_time: SystemTime; + + let mut lock = READ_TIME.lock(); + + lock.update_from_rtc(); + + last_time = *lock; + + lock.update_from_rtc(); + + while *lock != last_time { + last_time = *lock; + lock.update_from_rtc(); + } + let register_b: u8 = get_cmos(0x0B); + + lock.convert_bcd_to_binary(register_b); + lock.convert_12_hour_to_24_hour(register_b); + lock.modify_year(); +} + +/// Return the `START_TIME`, which is the actual time when doing calibrate. +pub fn read_start_time() -> SystemTime { + *START_TIME.get().unwrap() +} + +/// Return the monotonic time from the tsc clocksource. +pub fn read_monotonic_time() -> Duration { + let instant = tsc::read_instant(); + Duration::new(instant.secs(), instant.nanos()) +} + +/// Return the tsc clocksource. +pub fn default_clocksource() -> Arc { + tsc::CLOCK.get().unwrap().clone() +} diff --git a/services/comps/time/src/rtc.rs b/services/comps/time/src/rtc.rs index 45924da8b..978d16079 100644 --- a/services/comps/time/src/rtc.rs +++ b/services/comps/time/src/rtc.rs @@ -1,15 +1,9 @@ use core::sync::atomic::AtomicU8; use core::sync::atomic::Ordering::Relaxed; - -use crate::SystemTime; - use jinux_frame::arch::x86::device::cmos::{get_century_register, CMOS_ADDRESS, CMOS_DATA}; -use jinux_frame::sync::Mutex; pub(crate) static CENTURY_REGISTER: AtomicU8 = AtomicU8::new(0); -static READ_TIME: Mutex = Mutex::new(SystemTime::zero()); - pub fn init() { let Some(century_register) = get_century_register() else { return; @@ -26,34 +20,3 @@ pub fn is_updating() -> bool { CMOS_ADDRESS.write(0x0A); CMOS_DATA.read() & 0x80 != 0 } - -pub fn read() -> SystemTime { - update_time(); - *READ_TIME.lock() -} - -/// read year,month,day and other data -/// ref: https://wiki.osdev.org/CMOS#Reading_All_RTC_Time_and_Date_Registers -fn update_time() { - let mut last_time: SystemTime; - - let mut lock = READ_TIME.lock(); - - lock.update_from_rtc(); - - last_time = *lock; - - lock.update_from_rtc(); - - while *lock != last_time { - last_time = *lock; - - lock.update_from_rtc(); - } - - let register_b: u8 = get_cmos(0x0B); - - lock.convert_bcd_to_binary(register_b); - lock.convert_12_hour_to_24_hour(register_b); - lock.modify_year(); -} diff --git a/services/comps/time/src/tsc.rs b/services/comps/time/src/tsc.rs new file mode 100644 index 000000000..0a4d28dc9 --- /dev/null +++ b/services/comps/time/src/tsc.rs @@ -0,0 +1,72 @@ +//! This module provide a instance of `ClockSource` based on TSC. +//! +//! Use `init` to initialize this module. +use alloc::sync::Arc; +use core::time::Duration; +use jinux_frame::arch::{read_tsc, x86::tsc_freq}; +use jinux_frame::timer::Timer; +use spin::Once; + +use crate::clocksource::{ClockSource, Instant}; +use crate::{START_TIME, VDSO_DATA_UPDATE}; + +/// A instance of TSC clocksource. +pub static CLOCK: Once> = Once::new(); + +const MAX_DELAY_SECS: u64 = 60; + +/// Init tsc clocksource module. +pub(super) fn init() { + init_clock(); + calibrate(); + init_timer(); +} + +fn init_clock() { + CLOCK.call_once(|| { + Arc::new(ClockSource::new( + tsc_freq(), + MAX_DELAY_SECS, + Arc::new(read_tsc), + )) + }); +} + +/// Calibrate the TSC and system time based on the RTC time. +fn calibrate() { + let clock = CLOCK.get().unwrap(); + let cycles = clock.read_cycles(); + clock.calibrate(cycles); + START_TIME.call_once(crate::read); +} + +/// Read an `Instant` of tsc clocksource. +pub(super) fn read_instant() -> Instant { + let clock = CLOCK.get().unwrap(); + clock.read_instant() +} + +fn update_clocksource(timer: Arc) { + let clock = CLOCK.get().unwrap(); + clock.update(); + + // Update vdso data. + if VDSO_DATA_UPDATE.is_completed() { + VDSO_DATA_UPDATE.get().unwrap()(clock.last_instant(), clock.last_cycles()); + } + // Setting the timer as `clock.max_delay_secs() - 1` is to avoid + // the actual delay time is greater than the maximum delay seconds due to the latency of execution. + timer.set(Duration::from_secs(clock.max_delay_secs() - 1)); +} + +fn init_timer() { + let timer = Timer::new(update_clocksource).unwrap(); + // The initial timer should be set as `clock.max_delay_secs() >> 1` or something much smaller than `max_delay_secs`. + // This is because the initialization of this timer occurs during system startup. + // Afterwards, the system will undergo additional initialization processes, during which time interrupts are disabled. + // This results in the actual trigger time of the timer being delayed by about 5 seconds compared to the set time. + // TODO: This is a temporary handle, and should be modified in the future. + timer.set(Duration::from_secs( + CLOCK.get().unwrap().max_delay_secs() >> 1, + )); +} diff --git a/services/libs/jinux-std/src/time/mod.rs b/services/libs/jinux-std/src/time/mod.rs index f8b8e43ee..dd7c6aff3 100644 --- a/services/libs/jinux-std/src/time/mod.rs +++ b/services/libs/jinux-std/src/time/mod.rs @@ -3,8 +3,10 @@ use core::time::Duration; use crate::prelude::*; +use jinux_time::read_monotonic_time; + mod system_time; -use jinux_frame::timer::read_monotonic_milli_seconds; + pub use system_time::SystemTime; pub type clockid_t = i32; @@ -76,13 +78,8 @@ pub fn now_as_duration(clock_id: &ClockID) -> Result { match clock_id { ClockID::CLOCK_MONOTONIC | ClockID::CLOCK_MONOTONIC_COARSE - | ClockID::CLOCK_MONOTONIC_RAW => { - let time_ms = read_monotonic_milli_seconds(); - - let seconds = time_ms / 1000; - let nanos = time_ms % 1000 * 1_000_000; - Ok(Duration::new(seconds, nanos as u32)) - } + | 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) diff --git a/services/libs/jinux-std/src/time/system_time.rs b/services/libs/jinux-std/src/time/system_time.rs index b885aeca7..e1584953d 100644 --- a/services/libs/jinux-std/src/time/system_time.rs +++ b/services/libs/jinux-std/src/time/system_time.rs @@ -1,7 +1,8 @@ use core::time::Duration; +use jinux_time::{read_monotonic_time, read_start_time}; +use time::{Date, Month, PrimitiveDateTime, Time}; use crate::prelude::*; -use time::{Date, Month, PrimitiveDateTime, Time}; /// This struct corresponds to `SystemTime` in Rust std. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -25,9 +26,13 @@ impl SystemTime { /// Returns the current system time pub fn now() -> Self { - let system_time = jinux_time::get_real_time(); + let start = read_start_time(); + // The get real time result should always be valid - convert_system_time(system_time).unwrap() + convert_system_time(start) + .unwrap() + .checked_add(read_monotonic_time()) + .unwrap() } /// Add a duration to self. If the result does not exceed inner bounds return Some(t), else return None. @@ -73,7 +78,12 @@ fn convert_system_time(system_time: jinux_time::SystemTime) -> Result date, Err(_) => return_errno_with_message!(Errno::EINVAL, "Invalid system date"), }; - let time_ = match Time::from_hms(system_time.hour, system_time.minute, system_time.second) { + let time_ = match Time::from_hms_nano( + system_time.hour, + system_time.minute, + system_time.second, + system_time.nanos.try_into().unwrap(), + ) { Ok(time_) => time_, Err(_) => return_errno_with_message!(Errno::EINVAL, "Invalid system time"), }; diff --git a/services/libs/jinux-util/Cargo.toml b/services/libs/jinux-util/Cargo.toml index e890bfd06..77daec7cb 100644 --- a/services/libs/jinux-util/Cargo.toml +++ b/services/libs/jinux-util/Cargo.toml @@ -12,5 +12,5 @@ typeflags-util = { path = "../typeflags-util" } jinux-rights-proc = { path = "../jinux-rights-proc" } jinux-rights = { path = "../jinux-rights" } bitvec = { version = "1.0", default-features = false, features = ["alloc"] } - +ktest = { path = "../../../framework/libs/ktest" } [features] diff --git a/services/libs/jinux-util/src/coeff.rs b/services/libs/jinux-util/src/coeff.rs new file mode 100644 index 000000000..08e7158d9 --- /dev/null +++ b/services/libs/jinux-util/src/coeff.rs @@ -0,0 +1,139 @@ +//! This module provides an abstraction `Coeff` to server for efficient and accurate calculation +//! of fraction multiplication. + +use core::ops::Mul; + +use ktest::if_cfg_ktest; + +/// A `Coeff` is used to do a fraction multiplication operation with an unsigned integer. +/// It can achieve accurate and efficient calculation and avoid numeric overflow at the same time. +/// +/// # Example +/// +/// Let's say we want to multiply a fraction (23456 / 56789) with the target integer `a`, +/// which will be no larger than `1_000_000_000`, we can use the following code snippets +/// to get an accurate result. +/// +/// ``` +/// let a = input(); +/// let coeff = Coeff::new(23456, 56789, 1_000_000_000); +/// let result = coeff * a; +/// ``` +/// +/// # How it works +/// `Coeff` is used in the calculation of a fraction value multiplied by an integer. +/// Here is a simple example of such calculation: +/// +/// ```rust +/// let result = (a / b) * c; +/// ``` +/// +/// In this equation, `a`, `b`, `c` and `result` are all integers. To acquire a more precise result, we will +/// generally calculate `a * c` first and then divide the multiplication result with `b`. +/// However, this simple calculation above has two complications: +/// - The calculation of `a * c` may overflow if they are too large. +/// - The division operation is much more expensive than integer multiplication, which can easily create performance bottlenecks. +/// +/// `Coeff` is implemented to address these two issues. It can be used to replace the fraction in this calculation. +/// For example, a `Coeff` generated from (a / b) can modify the calculation above to ensure that: +/// +/// ``` +/// coeff * c ~= (a / b) * c +/// ``` +/// +/// In principle, `Coeff` actually turns the multiplication and division into a combination of multiplication and bit operation. +/// When creating a `Coeff`, it needs to know the numerator and denominator of the represented fraction +/// and the max multiplier it will be multiplied by. Then, a `mult` and a `shift` will be chosen to achieve the replacement of calculation. +/// Taking the previous calculation as an example again, `coeff * c` will turn into `mult * c >> shift`, ensuring that: +/// +/// ``` +/// mult * c >> shift ~= (a / b) * c +/// ``` +/// +/// and +/// +/// `mult * c` will not result in numeric overflow (i.e., `mult * c` will stay below MAX_U64). +/// +/// This is how `Coeff` achieves accuracy and efficiency at the same time. +#[derive(Debug, Copy, Clone)] +pub struct Coeff { + mult: u32, + shift: u32, + max_multiplier: u64, +} + +impl Coeff { + /// Create a new coeff, which is essentially equivalent to (`numerator` / `denominator`) when being multiplied to an integer; + /// Here users should make sure the multiplied integer should not be larger than `max_multiplier`. + pub fn new(numerator: u64, denominator: u64, max_multiplier: u64) -> Self { + let mut shift_acc: u32 = 32; + // Too large `max_multiplier` will make the generated coeff imprecise + debug_assert!(max_multiplier < (1 << 40)); + let mut tmp = max_multiplier >> 32; + // Counts the number of 0 in front of the `max_multiplier`. + // `shift_acc` indicates the maximum number of bits `mult` can have. + while tmp > 0 { + tmp >>= 1; + shift_acc -= 1; + } + + // Try the `shift` from 32 to 0. + let mut shift = 32; + let mut mult = 0; + while shift > 0 { + mult = numerator << shift; + mult += denominator / 2; + mult /= denominator; + if (mult >> shift_acc) == 0 { + break; + } + shift -= 1; + } + Self { + mult: mult as u32, + shift, + max_multiplier, + } + } + + /// Return the `mult` of the Coeff. + /// Only used for the VdsoData and will be removed in the future. + pub fn mult(&self) -> u32 { + self.mult + } + + /// Return the `shift` of the Coeff. + /// Only used for the VdsoData and will be removed in the future. + pub fn shift(&self) -> u32 { + self.shift + } +} + +impl Mul for Coeff { + type Output = u64; + fn mul(self, rhs: u64) -> Self::Output { + debug_assert!(rhs <= self.max_multiplier); + (rhs * self.mult as u64) >> self.shift + } +} + +impl Mul for Coeff { + type Output = u32; + fn mul(self, rhs: u32) -> Self::Output { + debug_assert!(rhs as u64 <= self.max_multiplier); + ((rhs as u64 * self.mult as u64) >> self.shift) as u32 + } +} + +#[if_cfg_ktest] +mod test { + use super::*; + use ktest::ktest; + #[ktest] + fn calculation() { + let coeff = Coeff::new(23456, 56789, 1_000_000_000); + assert!(coeff * 0 as u64 == 0); + assert!(coeff * 100 as u64 == 100 * 23456 / 56789); + assert!(coeff * 1_000_000_000 as u64 == 1_000_000_000 * 23456 / 56789); + } +} diff --git a/services/libs/jinux-util/src/lib.rs b/services/libs/jinux-util/src/lib.rs index 48426aef5..48ffec6cc 100644 --- a/services/libs/jinux-util/src/lib.rs +++ b/services/libs/jinux-util/src/lib.rs @@ -4,6 +4,7 @@ extern crate alloc; +pub mod coeff; pub mod dup; pub mod id_allocator; pub mod safe_ptr;