mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-24 01:43:22 +00:00
Fix Waiter
-related memory orders
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
15603e4aad
commit
414a3a389e
@ -6,6 +6,34 @@ use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
|||||||
use super::SpinLock;
|
use super::SpinLock;
|
||||||
use crate::task::{add_task, current_task, schedule, Task, TaskStatus};
|
use crate::task::{add_task, current_task, schedule, Task, TaskStatus};
|
||||||
|
|
||||||
|
// # Explanation on the memory orders
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// [CPU 1 (the waker)] [CPU 2 (the waiter)]
|
||||||
|
// cond = true;
|
||||||
|
// wake_up();
|
||||||
|
// wait();
|
||||||
|
// if cond { /* .. */ }
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// As soon as the waiter is woken up by the waker, it must see the true condition. This is
|
||||||
|
// trivially satisfied if `wake_up()` and `wait()` synchronize with a lock. But if they synchronize
|
||||||
|
// with an atomic variable, `wake_up()` must access the variable with `Ordering::Release` and
|
||||||
|
// `wait()` must access the variable with `Ordering::Acquire`.
|
||||||
|
//
|
||||||
|
// Examples of `wake_up()`:
|
||||||
|
// - `WaitQueue::wake_one()`
|
||||||
|
// - `WaitQueue::wake_all()`
|
||||||
|
// - `Waker::wake_up()`
|
||||||
|
//
|
||||||
|
// Examples of `wait()`:
|
||||||
|
// - `WaitQueue::wait_until()`
|
||||||
|
// - `Waiter::wait()`
|
||||||
|
// - `Waiter::drop()`
|
||||||
|
//
|
||||||
|
// Note that dropping a waiter must be treated as a `wait()` with zero timeout, because we need to
|
||||||
|
// make sure that the wake event isn't lost in this case.
|
||||||
|
|
||||||
/// A wait queue.
|
/// A wait queue.
|
||||||
///
|
///
|
||||||
/// One may wait on a wait queue to put its executing thread to sleep.
|
/// One may wait on a wait queue to put its executing thread to sleep.
|
||||||
@ -136,13 +164,16 @@ impl WaitQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
self.num_wakers.load(Ordering::Acquire) == 0
|
// On x86-64, this generates `mfence; mov`, which is exactly the right way to implement
|
||||||
|
// atomic loading with `Ordering::Release`. It performs much better than naively
|
||||||
|
// translating `fetch_add(0)` to `lock; xadd`.
|
||||||
|
self.num_wakers.fetch_add(0, Ordering::Release) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enqueue(&self, waker: Arc<Waker>) {
|
fn enqueue(&self, waker: Arc<Waker>) {
|
||||||
let mut wakers = self.wakers.lock_irq_disabled();
|
let mut wakers = self.wakers.lock_irq_disabled();
|
||||||
wakers.push_back(waker);
|
wakers.push_back(waker);
|
||||||
self.num_wakers.fetch_add(1, Ordering::Release);
|
self.num_wakers.fetch_add(1, Ordering::Acquire);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +252,7 @@ impl Waker {
|
|||||||
/// delivered, _or_ that the waiter will be dropped after being woken. It's up to the caller to
|
/// delivered, _or_ that the waiter will be dropped after being woken. It's up to the caller to
|
||||||
/// handle the latter case properly to avoid missing the wake event.
|
/// handle the latter case properly to avoid missing the wake event.
|
||||||
pub fn wake_up(&self) -> bool {
|
pub fn wake_up(&self) -> bool {
|
||||||
if self.has_woken.swap(true, Ordering::AcqRel) {
|
if self.has_woken.swap(true, Ordering::Release) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,10 +275,10 @@ impl Waker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn do_wait(&self) {
|
fn do_wait(&self) {
|
||||||
while !self.has_woken.load(Ordering::Acquire) {
|
while !self.has_woken.swap(false, Ordering::Acquire) {
|
||||||
let mut task = self.task.inner_exclusive_access();
|
let mut task = self.task.inner_exclusive_access();
|
||||||
// After holding the lock, check again to avoid races
|
// After holding the lock, check again to avoid races
|
||||||
if self.has_woken.load(Ordering::Acquire) {
|
if self.has_woken.swap(false, Ordering::Acquire) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
task.task_status = TaskStatus::Sleepy;
|
task.task_status = TaskStatus::Sleepy;
|
||||||
@ -255,12 +286,12 @@ impl Waker {
|
|||||||
|
|
||||||
schedule();
|
schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.has_woken.store(false, Ordering::Release);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close(&self) {
|
fn close(&self) {
|
||||||
self.has_woken.store(true, Ordering::Release);
|
// This must use `Ordering::Acquire`, although we do not care about the return value. See
|
||||||
|
// the memory order explanation at the top of the file for details.
|
||||||
|
let _ = self.has_woken.swap(true, Ordering::Acquire);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user