diff --git a/framework/aster-frame/src/arch/x86/trap.rs b/framework/aster-frame/src/arch/x86/trap.rs index 3c53de8f4..2036849b8 100644 --- a/framework/aster-frame/src/arch/x86/trap.rs +++ b/framework/aster-frame/src/arch/x86/trap.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +use core::sync::atomic::{AtomicBool, Ordering}; + use align_ext::AlignExt; use log::debug; #[cfg(feature = "intel_tdx")] @@ -14,6 +16,7 @@ use crate::arch::{ }; use crate::{ cpu::{CpuException, PageFaultErrorCode, PAGE_FAULT}, + cpu_local, trap::call_irq_callback_functions, vm::{ kspace::{KERNEL_PAGE_TABLE, LINEAR_MAPPING_BASE_VADDR, LINEAR_MAPPING_VADDR_RANGE}, @@ -22,6 +25,17 @@ use crate::{ }, }; +cpu_local! { + static IS_KERNEL_INTERRUPTED: AtomicBool = AtomicBool::new(false); +} + +/// Returns true if this function is called within the context of an IRQ handler +/// and the IRQ occurs while the CPU is executing in the kernel mode. +/// Otherwise, it returns false. +pub fn is_kernel_interrupted() -> bool { + IS_KERNEL_INTERRUPTED.load(Ordering::Acquire) +} + /// Only from kernel #[no_mangle] extern "sysv64" fn trap_handler(f: &mut TrapFrame) { @@ -43,7 +57,9 @@ extern "sysv64" fn trap_handler(f: &mut TrapFrame) { } } } else { + IS_KERNEL_INTERRUPTED.store(true, Ordering::Release); call_irq_callback_functions(f); + IS_KERNEL_INTERRUPTED.store(false, Ordering::Release); } } diff --git a/kernel/aster-nix/src/lib.rs b/kernel/aster-nix/src/lib.rs index 5281c6c8b..69321dccb 100644 --- a/kernel/aster-nix/src/lib.rs +++ b/kernel/aster-nix/src/lib.rs @@ -80,6 +80,7 @@ pub fn init() { device::init().unwrap(); vdso::init(); taskless::init(); + process::init(); } fn init_thread() { diff --git a/kernel/aster-nix/src/process/mod.rs b/kernel/aster-nix/src/process/mod.rs index be309e032..82115bacb 100644 --- a/kernel/aster-nix/src/process/mod.rs +++ b/kernel/aster-nix/src/process/mod.rs @@ -32,3 +32,7 @@ pub use program_loader::{check_executable_file, load_program_to_vm}; pub use rlimit::ResourceType; pub use term_status::TermStatus; pub use wait::{wait_child_exit, WaitOptions}; + +pub(super) fn init() { + process::init(); +} diff --git a/kernel/aster-nix/src/process/posix_thread/builder.rs b/kernel/aster-nix/src/process/posix_thread/builder.rs index 5ed3db918..190b4d013 100644 --- a/kernel/aster-nix/src/process/posix_thread/builder.rs +++ b/kernel/aster-nix/src/process/posix_thread/builder.rs @@ -11,6 +11,7 @@ use crate::{ Credentials, Process, }, thread::{status::ThreadStatus, task, thread_table, Thread, Tid}, + time::{clocks::ProfClock, TimerManager}, }; /// The builder to build a posix thread @@ -94,6 +95,11 @@ impl PosixThreadBuilder { let thread = Arc::new_cyclic(|thread_ref| { let task = task::create_new_user_task(user_space, thread_ref.clone()); let status = ThreadStatus::Init; + + let prof_clock = ProfClock::new(); + let virtual_timer_manager = TimerManager::new(prof_clock.user_clock().clone()); + let prof_timer_manager = TimerManager::new(prof_clock.clone()); + let posix_thread = PosixThread { process, is_main_thread, @@ -106,6 +112,9 @@ impl PosixThreadBuilder { sig_context: Mutex::new(None), sig_stack: Mutex::new(None), robust_list: Mutex::new(None), + prof_clock, + virtual_timer_manager, + prof_timer_manager, }; Thread::new(tid, task, posix_thread, status) diff --git a/kernel/aster-nix/src/process/posix_thread/mod.rs b/kernel/aster-nix/src/process/posix_thread/mod.rs index d49fe0ee0..c7f2ffd53 100644 --- a/kernel/aster-nix/src/process/posix_thread/mod.rs +++ b/kernel/aster-nix/src/process/posix_thread/mod.rs @@ -21,6 +21,7 @@ use crate::{ prelude::*, process::signal::constants::SIGCONT, thread::{thread_table, Tid}, + time::{clocks::ProfClock, Timer, TimerManager}, util::write_val_to_user, }; @@ -62,6 +63,15 @@ pub struct PosixThread { /// FIXME: This field may be removed. For glibc applications with RESTORER flag set, the sig_context is always equals with rsp. sig_context: Mutex>, sig_stack: Mutex>, + + /// A profiling clock measures the user CPU time and kernel CPU time in the thread. + prof_clock: Arc, + + /// A manager that manages timers based on the user CPU time of the current thread. + virtual_timer_manager: Arc, + + /// A manager that manages timers based on the profiling clock of the current thread. + prof_timer_manager: Arc, } impl PosixThread { @@ -148,10 +158,39 @@ impl PosixThread { return_errno_with_message!(Errno::EPERM, "sending signal to the thread is not allowed."); } - pub(in crate::process) fn enqueue_signal(&self, signal: Box) { + /// Enqueues a thread-directed signal. This method should only be used for enqueue kernel + /// signal and fault signal. + pub fn enqueue_signal(&self, signal: Box) { self.sig_queues.enqueue(signal); } + /// Returns a reference to the profiling clock of the current thread. + pub fn prof_clock(&self) -> &Arc { + &self.prof_clock + } + + /// Creates a timer based on the profiling CPU clock of the current thread. + pub fn create_prof_timer(&self, func: F) -> Arc + where + F: Fn() + Send + Sync + 'static, + { + self.prof_timer_manager.create_timer(func) + } + + /// Creates a timer based on the user CPU clock of the current thread. + pub fn create_virtual_timer(&self, func: F) -> Arc + where + F: Fn() + Send + Sync + 'static, + { + self.virtual_timer_manager.create_timer(func) + } + + /// Checks the `TimerCallback`s that are managed by the `prof_timer_manager`. + /// If any have timed out, call the corresponding callback functions. + pub fn process_expired_timers(&self) { + self.prof_timer_manager.process_expired_timers(); + } + pub fn dequeue_signal(&self, mask: &SigMask) -> Option> { self.sig_queues.dequeue(mask) } diff --git a/kernel/aster-nix/src/process/process/mod.rs b/kernel/aster-nix/src/process/process/mod.rs index f907fe4c0..f6b6a5c94 100644 --- a/kernel/aster-nix/src/process/process/mod.rs +++ b/kernel/aster-nix/src/process/process/mod.rs @@ -1,15 +1,13 @@ // SPDX-License-Identifier: MPL-2.0 +use self::timer_manager::PosixTimerManager; use super::{ posix_thread::PosixThreadExt, process_table, process_vm::{Heap, InitStackReader, ProcessVm}, rlimit::ResourceLimits, signal::{ - constants::{SIGALRM, SIGCHLD}, - sig_disposition::SigDispositions, - sig_mask::SigMask, - signals::{kernel::KernelSignal, Signal}, + constants::SIGCHLD, sig_disposition::SigDispositions, sig_mask::SigMask, signals::Signal, Pauser, }, status::ProcessStatus, @@ -20,12 +18,8 @@ use crate::{ fs::{file_table::FileTable, fs_resolver::FsResolver, utils::FileCreationMask}, prelude::*, sched::nice::Nice, - thread::{ - allocate_tid, - work_queue::{submit_work_item, work_item::WorkItem}, - Thread, - }, - time::{clocks::RealTimeClock, Timer}, + thread::{allocate_tid, Thread}, + time::clocks::ProfClock, vm::vmar::Vmar, }; @@ -34,6 +28,7 @@ mod job_control; mod process_group; mod session; mod terminal; +mod timer_manager; use aster_rights::Full; use atomic::Atomic; @@ -52,6 +47,10 @@ pub type Sid = u32; pub type ExitCode = i32; +pub(super) fn init() { + timer_manager::init(); +} + /// Process stands for a set of threads that shares the same userspace. pub struct Process { // Immutable Part @@ -60,8 +59,7 @@ pub struct Process { process_vm: ProcessVm, /// Wait for child status changed children_pauser: Arc, - /// The timer counts down in real (i.e., wall clock) time - alarm_timer: Arc, + // Mutable Part /// The executable path. executable_path: RwLock, @@ -91,26 +89,12 @@ pub struct Process { // Signal /// Sig dispositions sig_dispositions: Arc>, -} -fn create_process_timer_callback(process_ref: &Weak) -> impl Fn() { - let current_process = process_ref.clone(); - let sent_signal = move || { - let signal = KernelSignal::new(SIGALRM); - if let Some(process) = current_process.upgrade() { - process.enqueue_signal(signal); - } - }; + /// A profiling clock measures the user CPU time and kernel CPU time of the current process. + prof_clock: Arc, - let work_func = Box::new(sent_signal); - let work_item = Arc::new(WorkItem::new(work_func)); - - move || { - submit_work_item( - work_item.clone(), - crate::thread::work_queue::WorkPriority::High, - ); - } + /// A manager that manages timer resources and utilities of the process. + timer_manager: PosixTimerManager, } impl Process { @@ -135,28 +119,26 @@ impl Process { Pauser::new_with_mask(sigmask) }; - Arc::new_cyclic(|process_ref: &Weak| { - let callback = create_process_timer_callback(process_ref); - let alarm_timer = RealTimeClock::timer_manager().create_timer(callback); + let prof_clock = ProfClock::new(); - Self { - pid, - threads: Mutex::new(threads), - executable_path: RwLock::new(executable_path), - process_vm, - children_pauser, - alarm_timer, - status: Mutex::new(ProcessStatus::Uninit), - parent: Mutex::new(parent), - children: Mutex::new(BTreeMap::new()), - process_group: Mutex::new(Weak::new()), - file_table, - fs, - umask, - sig_dispositions, - resource_limits: Mutex::new(resource_limits), - nice: Atomic::new(nice), - } + Arc::new_cyclic(|process_ref: &Weak| Self { + pid, + threads: Mutex::new(threads), + executable_path: RwLock::new(executable_path), + process_vm, + children_pauser, + status: Mutex::new(ProcessStatus::Uninit), + parent: Mutex::new(parent), + children: Mutex::new(BTreeMap::new()), + process_group: Mutex::new(Weak::new()), + file_table, + fs, + umask, + sig_dispositions, + resource_limits: Mutex::new(resource_limits), + nice: Atomic::new(nice), + timer_manager: PosixTimerManager::new(&prof_clock, process_ref), + prof_clock, }) } @@ -233,8 +215,14 @@ impl Process { self.pid } - pub fn alarm_timer(&self) -> &Arc { - &self.alarm_timer + /// Gets the profiling clock of the process. + pub fn prof_clock(&self) -> &Arc { + &self.prof_clock + } + + /// Gets the timer resources and utilities of the process. + pub fn timer_manager(&self) -> &PosixTimerManager { + &self.timer_manager } pub fn threads(&self) -> &Mutex>> { @@ -288,7 +276,7 @@ impl Process { // *********** Process group & Session*********** - /// Returns the process group id of the process. + /// Returns the process group ID of the process. pub fn pgid(&self) -> Pgid { if let Some(process_group) = self.process_group.lock().upgrade() { process_group.pgid() @@ -341,7 +329,7 @@ impl Process { /// /// This method may return the following errors: /// * `EPERM`, if the process is a process group leader, or some existing session - /// or process group has the same id as the process. + /// or process group has the same ID as the process. pub fn to_new_session(self: &Arc) -> Result> { if self.is_session_leader() { return Ok(self.session().unwrap()); @@ -439,7 +427,7 @@ impl Process { if pgid != self.pid() { return_errno_with_message!( Errno::EPERM, - "the new process group should have the same id as the process." + "the new process group should have the same ID as the process." ); } @@ -637,8 +625,6 @@ pub fn current() -> Arc { #[cfg(ktest)] mod test { - use spin::Once; - use super::*; fn new_process(parent: Option>) -> Arc { diff --git a/kernel/aster-nix/src/process/process/timer_manager.rs b/kernel/aster-nix/src/process/process/timer_manager.rs new file mode 100644 index 000000000..776ff9ccc --- /dev/null +++ b/kernel/aster-nix/src/process/process/timer_manager.rs @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::{ + boxed::Box, + sync::{Arc, Weak}, + vec::Vec, +}; +use core::time::Duration; + +use aster_frame::{ + arch::{ + timer::{self, TIMER_FREQ}, + x86::trap::is_kernel_interrupted, + }, + sync::Mutex, +}; +use id_alloc::IdAlloc; + +use super::Process; +use crate::{ + process::{ + posix_thread::PosixThreadExt, + signal::{constants::SIGALRM, signals::kernel::KernelSignal}, + }, + thread::{ + work_queue::{submit_work_item, work_item::WorkItem}, + Thread, + }, + time::{ + clocks::{ProfClock, RealTimeClock}, + Timer, TimerManager, + }, +}; + +/// Updates the CPU time recorded in the CPU clocks of current Process. +/// +/// This function will be invoked at the system timer interrupt, and +/// invoke the callbacks of expired timers which are based on the updated +/// CPU clock. +fn update_cpu_time() { + let current_thread = Thread::current(); + if let Some(posix_thread) = current_thread.as_posix_thread() { + let process = posix_thread.process(); + let timer_manager = process.timer_manager(); + let jiffies_interval = Duration::from_millis(1000 / TIMER_FREQ); + // Based on whether the timer interrupt occurs in kernel mode or user mode, + // the function will add the duration of one timer interrupt interval to the + // corresponding CPU clocks. + if is_kernel_interrupted() { + posix_thread + .prof_clock() + .kernel_clock() + .add_time(jiffies_interval); + process + .prof_clock() + .kernel_clock() + .add_time(jiffies_interval); + } else { + posix_thread + .prof_clock() + .user_clock() + .add_time(jiffies_interval); + process.prof_clock().user_clock().add_time(jiffies_interval); + timer_manager + .virtual_timer() + .timer_manager() + .process_expired_timers(); + } + timer_manager + .prof_timer() + .timer_manager() + .process_expired_timers(); + posix_thread.process_expired_timers(); + } +} + +/// Registers a function to update the CPU clock in processes and +/// threads during the system timer interrupt. +pub(super) fn init() { + timer::register_callback(update_cpu_time); +} + +/// Represents timer resources and utilities for a POSIX process. +pub struct PosixTimerManager { + /// A real-time countdown timer, measuring in wall clock time. + alarm_timer: Arc, + /// A timer based on user CPU clock. + virtual_timer: Arc, + /// A timer based on the profiling clock. + prof_timer: Arc, + /// An ID allocator to allocate unique timer IDs. + id_allocator: Mutex, + /// A container managing all POSIX timers created by `timer_create()` syscall + /// within the process context. + posix_timers: Mutex>>>, +} + +fn create_process_timer_callback(process_ref: &Weak) -> impl Fn() + Clone { + let current_process = process_ref.clone(); + let sent_signal = move || { + let signal = KernelSignal::new(SIGALRM); + if let Some(process) = current_process.upgrade() { + process.enqueue_signal(signal); + } + }; + + let work_func = Box::new(sent_signal); + let work_item = Arc::new(WorkItem::new(work_func)); + + move || { + submit_work_item( + work_item.clone(), + crate::thread::work_queue::WorkPriority::High, + ); + } +} + +impl PosixTimerManager { + pub(super) fn new(prof_clock: &Arc, process_ref: &Weak) -> Self { + const MAX_NUM_OF_POSIX_TIMERS: usize = 10000; + + let callback = create_process_timer_callback(process_ref); + + let alarm_timer = RealTimeClock::timer_manager().create_timer(callback.clone()); + + let virtual_timer = + TimerManager::new(prof_clock.user_clock().clone()).create_timer(callback.clone()); + let prof_timer = TimerManager::new(prof_clock.clone()).create_timer(callback); + + Self { + alarm_timer, + virtual_timer, + prof_timer, + id_allocator: Mutex::new(IdAlloc::with_capacity(MAX_NUM_OF_POSIX_TIMERS)), + posix_timers: Mutex::new(Vec::new()), + } + } + + /// Gets the alarm timer of the corresponding process. + pub fn alarm_timer(&self) -> &Arc { + &self.alarm_timer + } + + /// Gets the virtual timer of the corresponding process. + pub fn virtual_timer(&self) -> &Arc { + &self.virtual_timer + } + + /// Gets the profiling timer of the corresponding process. + pub fn prof_timer(&self) -> &Arc { + &self.prof_timer + } + + /// Creates a timer based on the profiling CPU clock of the current process. + pub fn create_prof_timer(&self, func: F) -> Arc + where + F: Fn() + Send + Sync + 'static, + { + self.prof_timer.timer_manager().create_timer(func) + } + + /// Creates a timer based on the user CPU clock of the current process. + pub fn create_virtual_timer(&self, func: F) -> Arc + where + F: Fn() + Send + Sync + 'static, + { + self.virtual_timer.timer_manager().create_timer(func) + } + + /// Adds a POSIX timer to the managed `posix_timers`, and allocate a timer ID for this timer. + /// Return the timer ID. + pub fn add_posix_timer(&self, posix_timer: Arc) -> usize { + let mut timers = self.posix_timers.lock(); + // Holding the lock of `posix_timers` is required to operate the `id_allocator`. + let timer_id = self.id_allocator.lock().alloc().unwrap(); + if timers.len() < timer_id + 1 { + timers.resize(timer_id + 1, None); + } + // The ID allocated is not used by any other timers so this index in `timers` + // must be `None`. + timers[timer_id] = Some(posix_timer); + timer_id + } + + /// Finds a POSIX timer by the input `timer_id`. + pub fn find_posix_timer(&self, timer_id: usize) -> Option> { + self.posix_timers.lock()[timer_id].clone() + } + + /// Removes the POSIX timer with the ID `timer_id`. + pub fn remove_posix_timer(&self, timer_id: usize) -> Option> { + let mut timers = self.posix_timers.lock(); + let timer = timers[timer_id].take(); + if timer.is_some() { + // Holding the lock of `posix_timers` is required to operate the `id_allocator`. + self.id_allocator.lock().free(timer_id); + } + timer + } +} diff --git a/kernel/aster-nix/src/time/clocks/cpu_clock.rs b/kernel/aster-nix/src/time/clocks/cpu_clock.rs new file mode 100644 index 000000000..df36f1d98 --- /dev/null +++ b/kernel/aster-nix/src/time/clocks/cpu_clock.rs @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::sync::Arc; +use core::time::Duration; + +use aster_frame::sync::SpinLock; + +use crate::time::Clock; + +/// A clock used to record the CPU time for processes and threads. +pub struct CpuClock { + time: SpinLock, +} + +/// A profiling clock that contains a user CPU clock and a kernel CPU clock. +/// These two clocks record the CPU time in user mode and kernel mode respectively. +/// Reading this clock directly returns the sum of both times. +pub struct ProfClock { + user_clock: Arc, + kernel_clock: Arc, +} + +impl CpuClock { + /// Creates a new `CpuClock`. The recorded time is initialized to 0. + pub fn new() -> Arc { + Arc::new(Self { + time: SpinLock::new(Duration::ZERO), + }) + } + + /// Adds `interval` to the original recorded time to update the `CpuClock`. + pub fn add_time(&self, interval: Duration) { + *self.time.lock_irq_disabled() += interval; + } +} + +impl Clock for CpuClock { + fn read_time(&self) -> Duration { + *self.time.lock_irq_disabled() + } +} + +impl ProfClock { + /// Creates a new `ProfClock`. The recorded time is initialized to 0. + pub fn new() -> Arc { + Arc::new(Self { + user_clock: CpuClock::new(), + kernel_clock: CpuClock::new(), + }) + } + + /// Returns a reference to the user CPU clock in this profiling clock. + pub fn user_clock(&self) -> &Arc { + &self.user_clock + } + + /// Returns a reference to the kernel CPU clock in this profiling clock. + pub fn kernel_clock(&self) -> &Arc { + &self.kernel_clock + } +} + +impl Clock for ProfClock { + fn read_time(&self) -> Duration { + self.user_clock.read_time() + self.kernel_clock.read_time() + } +} diff --git a/kernel/aster-nix/src/time/clocks/mod.rs b/kernel/aster-nix/src/time/clocks/mod.rs index 93665606c..6c5d734ba 100644 --- a/kernel/aster-nix/src/time/clocks/mod.rs +++ b/kernel/aster-nix/src/time/clocks/mod.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: MPL-2.0 +pub use cpu_clock::*; pub use system_wide::*; +mod cpu_clock; mod system_wide; pub(super) fn init() { diff --git a/kernel/aster-nix/src/time/clocks/system_wide.rs b/kernel/aster-nix/src/time/clocks/system_wide.rs index d95556671..cbdaaa116 100644 --- a/kernel/aster-nix/src/time/clocks/system_wide.rs +++ b/kernel/aster-nix/src/time/clocks/system_wide.rs @@ -124,6 +124,11 @@ impl BootTimeClock { pub fn get() -> &'static Arc { CLOCK_BOOTTIME_INSTANCE.get().unwrap() } + + /// Get the cpu-local system-wide `TimerManager` singleton of this clock. + pub fn timer_manager() -> &'static Arc { + CLOCK_BOOTTIME_MANAGER.get().unwrap() + } } impl Clock for JiffiesClock { @@ -232,7 +237,7 @@ define_system_clocks! { CLOCK_BOOTTIME => BootTimeClock, } -define_timer_managers![CLOCK_REALTIME, CLOCK_MONOTONIC,]; +define_timer_managers![CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_BOOTTIME,]; /// Init the system-wide clocks. fn init_system_wide_clocks() {