From e22d78f04dba91f0b604ad25f815083239ad1578 Mon Sep 17 00:00:00 2001 From: Chen Chengjun Date: Fri, 31 May 2024 12:25:02 +0800 Subject: [PATCH] Enable tasklet mechanism --- kernel/aster-nix/src/lib.rs | 3 + kernel/aster-nix/src/taskless.rs | 232 +++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 kernel/aster-nix/src/taskless.rs diff --git a/kernel/aster-nix/src/lib.rs b/kernel/aster-nix/src/lib.rs index fc00c12bb..5281c6c8b 100644 --- a/kernel/aster-nix/src/lib.rs +++ b/kernel/aster-nix/src/lib.rs @@ -61,7 +61,9 @@ pub mod net; pub mod prelude; mod process; mod sched; +pub mod softirq_id; pub mod syscall; +mod taskless; pub mod thread; pub mod time; mod util; @@ -77,6 +79,7 @@ pub fn init() { fs::rootfs::init(boot::initramfs()).unwrap(); device::init().unwrap(); vdso::init(); + taskless::init(); } fn init_thread() { diff --git a/kernel/aster-nix/src/taskless.rs b/kernel/aster-nix/src/taskless.rs new file mode 100644 index 000000000..ac7101b94 --- /dev/null +++ b/kernel/aster-nix/src/taskless.rs @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::{boxed::Box, sync::Arc}; +use core::{ + cell::RefCell, + sync::atomic::{AtomicBool, Ordering}, +}; + +use aster_frame::{cpu_local, sync::SpinLock, trap::SoftIrqLine, CpuLocal}; +use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListAtomicLink}; + +use crate::softirq_id::{TASKLESS_SOFTIRQ_ID, TASKLESS_URGENT_SOFTIRQ_ID}; + +/// `Taskless` represents a _taskless_ job whose execution is deferred to a later time. +/// +/// # Overview +/// +/// `Taskless` provides one "bottom half" mechanism for interrupt handling. +/// With `Taskless`, one can defer the execution of certain logic +/// that would have been otherwise executed in interrupt handlers. +/// `Taskless` makes interrupt handlers finish more quickly, +/// thereby minimizing the periods of time when the interrupts are disabled. +/// +/// `Taskless` executes the deferred jobs via the softirq mechanism, +/// rather than doing them with `Task`s. +/// As such, these deferred, taskless jobs can be executed within only a small delay, +/// after the execution of an interrupt handler that schedules the taskless jobs. +/// As the taskless jobs are not executed in the task context, +/// they are not allowed to sleep. +/// +/// An `Taskless` instance may be scheduled to run multiple times, +/// but it is guaranteed that a single taskless job will not be run concurrently. +/// Also, a taskless job will not be preempted by another. +/// This makes the programming of a taskless job simpler. +/// Different taskless jobs are allowed to run concurrently. +/// Once a taskless has entered the execution state, it can be scheduled again. +/// +/// # Example +/// +/// Users can create a `Taskless` and schedule it at any place. +/// ```rust +/// #use aster_frame::softirq::Taskless; +/// +/// #fn my_func() {} +/// +/// let taskless = Taskless::new(my_func); +/// // This taskless job will be executed in softirq context soon. +/// taskless.schedule(); +/// +/// ``` +pub struct Taskless { + /// Whether the taskless job has been scheduled. + is_scheduled: AtomicBool, + /// Whether the taskless job is running. + is_running: AtomicBool, + /// The function that will be called when executing this taskless job. + callback: Box>, + /// Whether this `Taskless` is disabled. + is_disabled: AtomicBool, + link: LinkedListAtomicLink, +} + +intrusive_adapter!(TasklessAdapter = Arc: Taskless { link: LinkedListAtomicLink }); + +cpu_local! { + static TASKLESS_LIST: SpinLock> = SpinLock::new(LinkedList::new(TasklessAdapter::NEW)); + static TASKLESS_URGENT_LIST: SpinLock> = SpinLock::new(LinkedList::new(TasklessAdapter::NEW)); +} + +impl Taskless { + /// Creates a new `Taskless` instance with its callback function. + pub fn new(callback: F) -> Arc + where + F: FnMut() + Send + Sync + 'static, + { + // Since the same taskless will not be executed concurrently, + // it is safe to use a `RefCell` here though the `Taskless` will + // be put into an `Arc`. + #[allow(clippy::arc_with_non_send_sync)] + Arc::new(Self { + is_scheduled: AtomicBool::new(false), + is_running: AtomicBool::new(false), + callback: Box::new(RefCell::new(callback)), + is_disabled: AtomicBool::new(false), + link: LinkedListAtomicLink::new(), + }) + } + + /// Schedules this taskless job and it will be executed in later time. + /// + /// If the taskless job has been scheduled, this function will do nothing. + pub fn schedule(self: &Arc) { + do_schedule(self, &TASKLESS_LIST); + SoftIrqLine::get(TASKLESS_SOFTIRQ_ID).raise(); + } + + /// Schedules this taskless job and it will be executed urgently + /// in softirq context. + /// + /// If the taskless job has been scheduled, this function will do nothing. + pub fn schedule_urgent(self: &Arc) { + do_schedule(self, &TASKLESS_URGENT_LIST); + SoftIrqLine::get(TASKLESS_URGENT_SOFTIRQ_ID).raise(); + } + + /// Enables this `Taskless` so that it can be executed once it has been scheduled. + /// + /// A new `Taskless` is enabled by default. + pub fn enable(&self) { + self.is_disabled.store(false, Ordering::Release); + } + + /// Disables this `Taskless` so that it can not be scheduled. Note that if the `Taskless` + /// has been scheduled, it can still continue to complete this job. + pub fn disable(&self) { + self.is_disabled.store(true, Ordering::Release); + } +} + +fn do_schedule( + taskless: &Arc, + taskless_list: &'static CpuLocal>>, +) { + if taskless.is_disabled.load(Ordering::Acquire) { + return; + } + if taskless + .is_scheduled + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + return; + } + + CpuLocal::borrow_with(taskless_list, |list| { + list.lock_irq_disabled().push_front(taskless.clone()); + }); +} + +pub(super) fn init() { + SoftIrqLine::get(TASKLESS_URGENT_SOFTIRQ_ID) + .enable(|| taskless_softirq_handler(&TASKLESS_URGENT_LIST, TASKLESS_URGENT_SOFTIRQ_ID)); + SoftIrqLine::get(TASKLESS_SOFTIRQ_ID) + .enable(|| taskless_softirq_handler(&TASKLESS_LIST, TASKLESS_SOFTIRQ_ID)); +} + +/// Executes the pending taskless jobs in the input `taskless_list`. +/// +/// This function will retrieve each `Taskless` in the input `taskless_list` +/// and leave it empty. If a `Taskless` is running then this function will +/// ignore it and jump to the next `Taskless`, then put it to the input `taskless_list`. +/// +/// If the `Taskless` is ready to be executed, it will be set to not scheduled +/// and can be scheduled again. +fn taskless_softirq_handler( + taskless_list: &'static CpuLocal>>, + softirq_id: u8, +) { + let mut processing_list = CpuLocal::borrow_with(taskless_list, |list| { + let mut list_mut = list.lock_irq_disabled(); + LinkedList::take(&mut list_mut) + }); + + while let Some(taskless) = processing_list.pop_back() { + if taskless + .is_running + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + CpuLocal::borrow_with(taskless_list, |list| { + list.lock_irq_disabled().push_front(taskless); + SoftIrqLine::get(softirq_id).raise(); + }); + continue; + } + + taskless.is_scheduled.store(false, Ordering::Release); + + // The same taskless will not be executing concurrently, so it is safe to + // do `borrow_mut` here. + (taskless.callback.borrow_mut())(); + taskless.is_running.store(false, Ordering::Release); + } +} + +#[cfg(ktest)] +mod test { + use core::sync::atomic::AtomicUsize; + + use aster_frame::trap::enable_local; + + use super::*; + + fn init() { + static DONE: AtomicBool = AtomicBool::new(false); + if !DONE.load(Ordering::SeqCst) { + super::init(); + enable_local(); + DONE.store(true, Ordering::SeqCst); + } + } + + #[ktest] + fn schedule_taskless() { + static COUNTER: AtomicUsize = AtomicUsize::new(0); + const SCHEDULE_TIMES: usize = 10; + + fn add_counter() { + COUNTER.fetch_add(1, Ordering::Relaxed); + } + + init(); + let taskless = Taskless::new(add_counter); + let mut counter = 0; + + // Schedule this taskless for `SCHEDULE_TIMES`. + while taskless.is_scheduled.load(Ordering::Acquire) == false { + taskless.schedule(); + counter += 1; + if counter == SCHEDULE_TIMES { + break; + } + } + + // Wait for all taskless having finished. + while taskless.is_running.load(Ordering::Acquire) + || taskless.is_scheduled.load(Ordering::Acquire) + {} + + assert_eq!(counter, COUNTER.load(Ordering::Relaxed)); + } +}