mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-28 20:03:22 +00:00
Don't race between enabling IRQs and halting CPU
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
b96c8f9ed2
commit
35e0918bce
@ -111,8 +111,7 @@ fn ap_init() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
crate::thread::Thread::yield_now();
|
ostd::task::halt_cpu();
|
||||||
ostd::cpu::sleep_for_interrupt();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,10 +155,10 @@ fn init_thread() {
|
|||||||
karg.get_initproc_envp().to_vec(),
|
karg.get_initproc_envp().to_vec(),
|
||||||
)
|
)
|
||||||
.expect("Run init process failed.");
|
.expect("Run init process failed.");
|
||||||
|
|
||||||
// Wait till initproc become zombie.
|
// Wait till initproc become zombie.
|
||||||
while !initproc.status().is_zombie() {
|
while !initproc.status().is_zombie() {
|
||||||
crate::thread::Thread::yield_now();
|
ostd::task::halt_cpu();
|
||||||
ostd::cpu::sleep_for_interrupt();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: exit via qemu isa debug device should not be the only way.
|
// TODO: exit via qemu isa debug device should not be the only way.
|
||||||
|
@ -7,17 +7,3 @@ pub mod extension;
|
|||||||
pub mod local;
|
pub mod local;
|
||||||
|
|
||||||
pub use extension::{has_extensions, IsaExtensions};
|
pub use extension::{has_extensions, IsaExtensions};
|
||||||
|
|
||||||
/// Halts the CPU.
|
|
||||||
///
|
|
||||||
/// This function halts the CPU until the next interrupt is received. By
|
|
||||||
/// halting, the CPU might consume less power. Internally it is implemented
|
|
||||||
/// using the `wfi` instruction.
|
|
||||||
///
|
|
||||||
/// Since the function sleeps the CPU, it should not be used within an atomic
|
|
||||||
/// mode ([`crate::task::atomic_mode`]).
|
|
||||||
#[track_caller]
|
|
||||||
pub fn sleep_for_interrupt() {
|
|
||||||
crate::task::atomic_mode::might_sleep();
|
|
||||||
riscv::asm::wfi();
|
|
||||||
}
|
|
||||||
|
@ -31,7 +31,31 @@ impl IrqRemapping {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Mark this as unsafe. See
|
||||||
|
// <https://github.com/asterinas/asterinas/issues/1120#issuecomment-2748696592>.
|
||||||
pub(crate) fn enable_local() {
|
pub(crate) fn enable_local() {
|
||||||
|
// SAFETY: The safety is upheld by the caller.
|
||||||
|
unsafe { riscv::interrupt::enable() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables local IRQs and halts the CPU to wait for interrupts.
|
||||||
|
///
|
||||||
|
/// This method guarantees that no interrupts can occur in the middle. In other words, IRQs must
|
||||||
|
/// either have been processed before this method is called, or they must wake the CPU up from the
|
||||||
|
/// halting state.
|
||||||
|
//
|
||||||
|
// FIXME: Mark this as unsafe. See
|
||||||
|
// <https://github.com/asterinas/asterinas/issues/1120#issuecomment-2748696592>.
|
||||||
|
pub(crate) fn enable_local_and_halt() {
|
||||||
|
// RISC-V Instruction Set Manual, Machine-Level ISA, Version 1.13 says:
|
||||||
|
// "The WFI instruction can also be executed when interrupts are disabled. The operation of WFI
|
||||||
|
// must be unaffected by the global interrupt bits in `mstatus` (MIE and SIE) [..]"
|
||||||
|
//
|
||||||
|
// So we can use `wfi` even if IRQs are disabled. Pending IRQs can still wake up the CPU, but
|
||||||
|
// they will only occur later when we enable local IRQs.
|
||||||
|
riscv::asm::wfi();
|
||||||
|
|
||||||
|
// SAFETY: The safety is upheld by the caller.
|
||||||
unsafe { riscv::interrupt::enable() }
|
unsafe { riscv::interrupt::enable() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,16 +4,3 @@
|
|||||||
|
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod local;
|
pub mod local;
|
||||||
|
|
||||||
/// Halts the CPU.
|
|
||||||
///
|
|
||||||
/// This function halts the CPU until the next interrupt is received. By
|
|
||||||
/// halting, the CPU will enter the C-0 state and consume less power.
|
|
||||||
///
|
|
||||||
/// Since the function sleeps the CPU, it should not be used within an atomic
|
|
||||||
/// mode ([`crate::task::atomic_mode`]).
|
|
||||||
#[track_caller]
|
|
||||||
pub fn sleep_for_interrupt() {
|
|
||||||
crate::task::atomic_mode::might_sleep();
|
|
||||||
x86_64::instructions::hlt();
|
|
||||||
}
|
|
||||||
|
@ -50,6 +50,8 @@ impl IrqRemapping {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Mark this as unsafe. See
|
||||||
|
// <https://github.com/asterinas/asterinas/issues/1120#issuecomment-2748696592>.
|
||||||
pub(crate) fn enable_local() {
|
pub(crate) fn enable_local() {
|
||||||
x86_64::instructions::interrupts::enable();
|
x86_64::instructions::interrupts::enable();
|
||||||
// When emulated with QEMU, interrupts may not be delivered if a STI instruction is immediately
|
// When emulated with QEMU, interrupts may not be delivered if a STI instruction is immediately
|
||||||
@ -58,6 +60,29 @@ pub(crate) fn enable_local() {
|
|||||||
x86_64::instructions::nop();
|
x86_64::instructions::nop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enables local IRQs and halts the CPU to wait for interrupts.
|
||||||
|
///
|
||||||
|
/// This method guarantees that no interrupts can occur in the middle. In other words, IRQs must
|
||||||
|
/// either have been processed before this method is called, or they must wake the CPU up from the
|
||||||
|
/// halting state.
|
||||||
|
//
|
||||||
|
// FIXME: Mark this as unsafe. See
|
||||||
|
// <https://github.com/asterinas/asterinas/issues/1120#issuecomment-2748696592>.
|
||||||
|
pub(crate) fn enable_local_and_halt() {
|
||||||
|
// SAFETY:
|
||||||
|
// 1. `sti` is safe to use because its safety requirement is upheld by the caller.
|
||||||
|
// 2. `hlt` is safe to use because it halts the CPU for interrupts.
|
||||||
|
unsafe {
|
||||||
|
// Intel(R) 64 and IA-32 Architectures Software Developer's Manual says:
|
||||||
|
// "If IF = 0, maskable hardware interrupts remain inhibited on the instruction boundary
|
||||||
|
// following an execution of STI."
|
||||||
|
//
|
||||||
|
// So interrupts will only occur at or after the HLT instruction, which guarantee that
|
||||||
|
// interrupts won't occur between enabling the local IRQs and halting the CPU.
|
||||||
|
core::arch::asm!("sti", "hlt", options(nomem, nostack, preserves_flags),)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn disable_local() {
|
pub(crate) fn disable_local() {
|
||||||
x86_64::instructions::interrupts::disable();
|
x86_64::instructions::interrupts::disable();
|
||||||
}
|
}
|
||||||
|
@ -80,11 +80,17 @@ pub fn determine_tsc_freq_via_pit() -> u64 {
|
|||||||
static FREQUENCY: AtomicU64 = AtomicU64::new(0);
|
static FREQUENCY: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
// Wait until `FREQUENCY` is ready
|
// Wait until `FREQUENCY` is ready
|
||||||
x86_64::instructions::interrupts::enable();
|
loop {
|
||||||
while !IS_FINISH.load(Ordering::Acquire) {
|
crate::arch::irq::enable_local_and_halt();
|
||||||
x86_64::instructions::hlt();
|
|
||||||
|
// Disable local IRQs so they won't come after checking `IS_FINISH`
|
||||||
|
// but before halting the CPU.
|
||||||
|
crate::arch::irq::disable_local();
|
||||||
|
|
||||||
|
if IS_FINISH.load(Ordering::Acquire) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
x86_64::instructions::interrupts::disable();
|
|
||||||
|
|
||||||
// Disable PIT
|
// Disable PIT
|
||||||
drop(irq);
|
drop(irq);
|
||||||
|
@ -120,11 +120,17 @@ fn init_periodic_mode_config() {
|
|||||||
apic.set_timer_init_count(0xFFFF_FFFF);
|
apic.set_timer_init_count(0xFFFF_FFFF);
|
||||||
|
|
||||||
// Wait until `CONFIG` is ready
|
// Wait until `CONFIG` is ready
|
||||||
x86_64::instructions::interrupts::enable();
|
loop {
|
||||||
while !CONFIG.is_completed() {
|
crate::arch::irq::enable_local_and_halt();
|
||||||
x86_64::instructions::hlt();
|
|
||||||
|
// Disable local IRQs so they won't come after checking `CONFIG`
|
||||||
|
// but before halting the CPU.
|
||||||
|
crate::arch::irq::disable_local();
|
||||||
|
|
||||||
|
if CONFIG.is_completed() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
x86_64::instructions::interrupts::disable();
|
|
||||||
|
|
||||||
// Disable PIT
|
// Disable PIT
|
||||||
drop(irq);
|
drop(irq);
|
||||||
|
@ -24,7 +24,7 @@ use spin::Once;
|
|||||||
use utils::ForceSync;
|
use utils::ForceSync;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
preempt::{disable_preempt, DisabledPreemptGuard},
|
preempt::{disable_preempt, halt_cpu, DisabledPreemptGuard},
|
||||||
scheduler::info::{AtomicCpuId, TaskScheduleInfo},
|
scheduler::info::{AtomicCpuId, TaskScheduleInfo},
|
||||||
};
|
};
|
||||||
pub(crate) use crate::arch::task::{context_switch, TaskContext};
|
pub(crate) use crate::arch::task::{context_switch, TaskContext};
|
||||||
|
@ -27,7 +27,6 @@ pub(in crate::task) fn should_preempt() -> bool {
|
|||||||
PREEMPT_INFO.load() == 0
|
PREEMPT_INFO.load() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(dead_code)]
|
|
||||||
pub(in crate::task) fn need_preempt() -> bool {
|
pub(in crate::task) fn need_preempt() -> bool {
|
||||||
PREEMPT_INFO.load() & NEED_PREEMPT_MASK == 0
|
PREEMPT_INFO.load() & NEED_PREEMPT_MASK == 0
|
||||||
}
|
}
|
||||||
|
@ -4,3 +4,34 @@ pub(super) mod cpu_local;
|
|||||||
mod guard;
|
mod guard;
|
||||||
|
|
||||||
pub use self::guard::{disable_preempt, DisabledPreemptGuard};
|
pub use self::guard::{disable_preempt, DisabledPreemptGuard};
|
||||||
|
|
||||||
|
/// Halts the CPU until interrupts if no preemption is required.
|
||||||
|
///
|
||||||
|
/// This function will return if:
|
||||||
|
/// - preemption is required when calling this function,
|
||||||
|
/// - preemption is required during halting the CPU, or
|
||||||
|
/// - interrupts occur during halting the CPU.
|
||||||
|
///
|
||||||
|
/// This function will perform preemption before returning if
|
||||||
|
/// preemption is required.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic if it is called in the atomic mode
|
||||||
|
/// ([`crate::task::atomic_mode`]).
|
||||||
|
#[track_caller]
|
||||||
|
pub fn halt_cpu() {
|
||||||
|
crate::task::atomic_mode::might_sleep();
|
||||||
|
|
||||||
|
let irq_guard = crate::trap::irq::disable_local();
|
||||||
|
|
||||||
|
if cpu_local::need_preempt() {
|
||||||
|
drop(irq_guard);
|
||||||
|
} else {
|
||||||
|
core::mem::forget(irq_guard);
|
||||||
|
// IRQs were previously enabled (checked by `might_sleep`). So we can re-enable them now.
|
||||||
|
crate::arch::irq::enable_local_and_halt();
|
||||||
|
}
|
||||||
|
|
||||||
|
super::scheduler::might_preempt();
|
||||||
|
}
|
||||||
|
@ -72,7 +72,7 @@ pub(super) fn switch_to_task(next_task: Arc<Task>) {
|
|||||||
PREVIOUS_TASK_PTR.store(current_task_ptr);
|
PREVIOUS_TASK_PTR.store(current_task_ptr);
|
||||||
|
|
||||||
// We must disable IRQs when switching, see `after_switching_to`.
|
// We must disable IRQs when switching, see `after_switching_to`.
|
||||||
let _ = core::mem::ManuallyDrop::new(irq_guard);
|
core::mem::forget(irq_guard);
|
||||||
|
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// 1. We have exclusive access to both the current context and the next context (see above).
|
// 1. We have exclusive access to both the current context and the next context (see above).
|
||||||
|
Reference in New Issue
Block a user