forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request torvalds#104 from Rust-for-Linux/condvar
Add a condition variable implementation to the `sync` module.
- Loading branch information
Showing
5 changed files
with
184 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
|
||
//! A condition variable. | ||
//! | ||
//! This module allows Rust code to use the kernel's [`struct wait_queue_head`] as a condition | ||
//! variable. | ||
use super::{Guard, Lock, NeedsLockClass}; | ||
use crate::{bindings, c_types, CStr}; | ||
use core::{cell::UnsafeCell, marker::PhantomPinned, mem::MaybeUninit, pin::Pin}; | ||
|
||
extern "C" { | ||
fn rust_helper_init_wait(wq: *mut bindings::wait_queue_entry); | ||
fn rust_helper_signal_pending() -> c_types::c_int; | ||
} | ||
|
||
/// Safely initialises a [`CondVar`] with the given name, generating a new lock class. | ||
#[macro_export] | ||
macro_rules! condvar_init { | ||
($condvar:expr, $name:literal) => { | ||
$crate::init_with_lockdep!($condvar, $name) | ||
}; | ||
} | ||
|
||
// TODO: `bindgen` is not generating this constant. Figure out why. | ||
const POLLFREE: u32 = 0x4000; | ||
|
||
/// Exposes the kernel's [`struct wait_queue_head`] as a condition variable. It allows the caller to | ||
/// atomically release the given lock and go to sleep. It reacquires the lock when it wakes up. And | ||
/// it wakes up when notified by another thread (via [`CondVar::notify_one`] or | ||
/// [`CondVar::notify_all`]) or because the thread received a signal. | ||
/// | ||
/// [`struct wait_queue_head`]: ../../../include/linux/wait.h | ||
pub struct CondVar { | ||
pub(crate) wait_list: UnsafeCell<bindings::wait_queue_head>, | ||
|
||
/// A condvar needs to be pinned because it contains a [`struct list_head`] that is | ||
/// self-referential, so it cannot be safely moved once it is initialised. | ||
_pin: PhantomPinned, | ||
} | ||
|
||
// SAFETY: `CondVar` only uses a `struct wait_queue_head`, which is safe to use on any thread. | ||
unsafe impl Send for CondVar {} | ||
|
||
// SAFETY: `CondVar` only uses a `struct wait_queue_head`, which is safe to use on multiple threads | ||
// concurrently. | ||
unsafe impl Sync for CondVar {} | ||
|
||
impl CondVar { | ||
/// Constructs a new conditional variable. | ||
/// | ||
/// # Safety | ||
/// | ||
/// The caller must call `CondVar::init` before using the conditional variable. | ||
pub unsafe fn new() -> Self { | ||
Self { | ||
wait_list: UnsafeCell::new(bindings::wait_queue_head::default()), | ||
_pin: PhantomPinned, | ||
} | ||
} | ||
|
||
/// Atomically releases the given lock (whose ownership is proven by the guard) and puts the | ||
/// thread to sleep. It wakes up when notified by [`CondVar::notify_one`] or | ||
/// [`CondVar::notify_all`], or when the thread receives a signal. | ||
/// | ||
/// Returns whether there is a signal pending. | ||
pub fn wait<L: Lock>(&self, g: &Guard<L>) -> bool { | ||
let l = g.lock; | ||
let mut wait = MaybeUninit::<bindings::wait_queue_entry>::uninit(); | ||
|
||
// SAFETY: `wait` points to valid memory. | ||
unsafe { rust_helper_init_wait(wait.as_mut_ptr()) }; | ||
|
||
// SAFETY: Both `wait` and `wait_list` point to valid memory. | ||
unsafe { | ||
bindings::prepare_to_wait_exclusive( | ||
self.wait_list.get(), | ||
wait.as_mut_ptr(), | ||
bindings::TASK_INTERRUPTIBLE as _, | ||
); | ||
} | ||
|
||
// SAFETY: The guard is evidence that the caller owns the lock. | ||
unsafe { l.unlock() }; | ||
|
||
// SAFETY: No arguments, switches to another thread. | ||
unsafe { bindings::schedule() }; | ||
|
||
l.lock_noguard(); | ||
|
||
// SAFETY: Both `wait` and `wait_list` point to valid memory. | ||
unsafe { bindings::finish_wait(self.wait_list.get(), wait.as_mut_ptr()) }; | ||
|
||
// SAFETY: No arguments, just checks `current` for pending signals. | ||
unsafe { rust_helper_signal_pending() != 0 } | ||
} | ||
|
||
/// Calls the kernel function to notify the appropriate number of threads with the given flags. | ||
fn notify(&self, count: i32, flags: u32) { | ||
// SAFETY: `wait_list` points to valid memory. | ||
unsafe { | ||
bindings::__wake_up( | ||
self.wait_list.get(), | ||
bindings::TASK_NORMAL, | ||
count, | ||
flags as _, | ||
) | ||
}; | ||
} | ||
|
||
/// Wakes a single waiter up, if any. This is not 'sticky' in the sense that if no thread is | ||
/// waiting, the notification is lost completely (as opposed to automatically waking up the | ||
/// next waiter). | ||
pub fn notify_one(&self) { | ||
self.notify(1, 0); | ||
} | ||
|
||
/// Wakes all waiters up, if any. This is not 'sticky' in the sense that if no thread is | ||
/// waiting, the notification is lost completely (as opposed to automatically waking up the | ||
/// next waiter). | ||
pub fn notify_all(&self) { | ||
self.notify(0, 0); | ||
} | ||
|
||
/// Wakes all waiters up. If they were added by `epoll`, they are also removed from the list of | ||
/// waiters. This is useful when cleaning up a condition variable that may be waited on by | ||
/// threads that use `epoll`. | ||
pub fn free_waiters(&self) { | ||
self.notify(1, bindings::POLLHUP | POLLFREE); | ||
} | ||
} | ||
|
||
impl NeedsLockClass for CondVar { | ||
unsafe fn init(self: Pin<&Self>, name: CStr<'static>, key: *mut bindings::lock_class_key) { | ||
bindings::__init_waitqueue_head(self.wait_list.get(), name.as_ptr() as _, key); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters