Skip to content

Commit 77c70dc

Browse files
committed
rust: thread: thread_try_new! macro
Signed-off-by: Boqun Feng <[email protected]>
1 parent 9a24e3c commit 77c70dc

File tree

2 files changed

+142
-29
lines changed

2 files changed

+142
-29
lines changed

drivers/char/rust_example.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@
99
use alloc::boxed::Box;
1010
use alloc::sync::Arc;
1111
use core::pin::Pin;
12-
use core::sync::atomic::{AtomicBool, Ordering};
12+
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1313
use kernel::prelude::*;
1414
use kernel::{
1515
chrdev, condvar_init, cstr,
1616
file_operations::FileOperations,
1717
miscdev, mutex_init, spinlock_init,
1818
sync::{CondVar, Mutex, SpinLock},
1919
thread::{schedule, Thread},
20+
thread_try_new,
2021
};
2122

2223
module! {
@@ -183,6 +184,35 @@ impl KernelModule for RustExample {
183184
t1.stop().expect_err("Rust thread should exit abnormally");
184185
}
185186

187+
// Test threads
188+
{
189+
let arc = Arc::try_new(AtomicUsize::new(0))?;
190+
191+
let t1 = thread_try_new!(
192+
cstr!("rust-thread"),
193+
|x: Arc<AtomicUsize>| -> KernelResult<()> {
194+
for _ in 0..10 {
195+
x.fetch_add(1, Ordering::Release);
196+
println!("x is {}", x.load(Ordering::Relaxed));
197+
}
198+
Ok(())
199+
},
200+
arc.clone()
201+
)?;
202+
203+
t1.wake_up();
204+
205+
// Waits to observe the thread run.
206+
while arc.load(Ordering::Acquire) != 10 {
207+
schedule();
208+
}
209+
210+
println!("main thread: x is {}", arc.load(Ordering::Relaxed));
211+
212+
// `t1` should exit normally.
213+
t1.stop().expect("Rust thread should exit abnormally");
214+
}
215+
186216
// Including this large variable on the stack will trigger
187217
// stack probing on the supported archs.
188218
// This will verify that stack probing does not lead to

rust/kernel/thread.rs

Lines changed: 111 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,111 @@ extern "C" {
1818
fn rust_helper_put_task_struct(task: *mut bindings::task_struct);
1919
}
2020

21+
fn call_as_closure(closure: Box<Box<dyn FnOnce() -> KernelResult<()>>>) -> KernelResult<()> {
22+
closure()
23+
}
24+
25+
/// Generates a bridge function from Rust to C ABI.
26+
///
27+
/// `func` should be a `fn(arg: arg_type) -> KernelResult<()>` where arg_type
28+
/// is the same size as `*mut c_types::c_void` ([`core::intrinsics::transmute`]
29+
/// checks that at the compile time.
30+
#[macro_export]
31+
macro_rules! bridge {
32+
($func:expr) => {{
33+
unsafe extern "C" fn _func(data: *mut $crate::c_types::c_void) -> $crate::c_types::c_int {
34+
let arg = core::intrinsics::transmute(data);
35+
let f: fn(_) -> _ = $func; // Makes sure `$func` is a function pointer
36+
37+
match f(arg) {
38+
Ok(()) => 0,
39+
Err(e) => e.to_kernel_errno(),
40+
}
41+
}
42+
43+
_func
44+
}};
45+
}
46+
47+
/// Creates a new thread without extra memory allocation.
48+
///
49+
/// This macro tasks a Rust function pointer `$func` and an argument `$arg`,
50+
/// and creates a thread doing `$func($arg)`, the return value of $func is
51+
/// [`KernelError<()>`].
52+
///
53+
/// # Examples
54+
///
55+
/// ```
56+
/// use kernel::thread::{schedule, Thread};
57+
/// use kernel::thread_try_new;
58+
/// use alloc::sync::Arc;
59+
/// use core::sync::atomic::{AtomicUsize, Ordering};
60+
///
61+
/// let arc = Arc::try_new(AtomicUsize::new(0))?;
62+
///
63+
/// let t = thread_try_new!(
64+
/// cstr!("rust-thread"),
65+
/// |x: Arc<AtomicUsize>| -> KernelResult<()> {
66+
/// for _ in 0..10 {
67+
/// x.fetch_add(1, Ordering::Release);
68+
/// println!("x is {}", x.load(Ordering::Relaxed));
69+
/// }
70+
/// Ok(())
71+
/// },
72+
/// arc.clone()
73+
/// )?;
74+
///
75+
/// t.wake_up();
76+
///
77+
/// while arc.load(Ordering::Acquire) != 10 {
78+
/// schedule();
79+
/// }
80+
///
81+
/// println!("main thread: x is {}", arc.load(Ordering::Relaxed));
82+
/// ```
83+
///
84+
/// # Context
85+
///
86+
/// This macro might sleep due to the memory allocation and waiting for
87+
/// the completion in `kthread_create_on_node`. Therefore do not call this
88+
/// in atomic contexts (i.e. preemption-off contexts).
89+
#[macro_export]
90+
macro_rules! thread_try_new {
91+
($name:expr, $func:expr, $arg:expr) => {{
92+
// In case of failure, we need to `transmute` the `$arg` back, `_arg` is
93+
// used here to inference the type of `$arg`, so that the `transmute`
94+
// in the failure path knows the type.
95+
let mut _arg = $arg;
96+
97+
// TYPE CHECK: `$arg` should be the same as `*mut c_void`, and
98+
// `transmute` only works if two types are of the same size.
99+
//
100+
// SAFETY: In the bridge funciton, the `$arg` is `transmute` back.
101+
let data = unsafe { core::intrinsics::transmute(_arg) };
102+
103+
// SAFTEY: a) the bridge function is a valid function pointer, and b)
104+
// the bridge function `transmute` back what we just `transmute`.
105+
let result =
106+
unsafe { $crate::thread::Thread::try_new_c_style($name, $crate::bridge!($func), data) };
107+
108+
if let Err(e) = result {
109+
// Creation fails, we need to `transmute` back the `$arg` because
110+
// there is no new thread to own it, we should let the current
111+
// thread own it.
112+
//
113+
// SAFETY: We `transmute` back waht we just `transmute`, and since
114+
// the new thread is not created, so no one touches `data`.
115+
unsafe {
116+
_arg = core::intrinsics::transmute(data);
117+
}
118+
119+
Err(e)
120+
} else {
121+
result
122+
}
123+
}};
124+
}
125+
21126
/// Function passed to `kthread_create_on_node` as the thread function pointer.
22127
#[no_mangle]
23128
unsafe extern "C" fn rust_thread_func(data: *mut c_types::c_void) -> c_types::c_int {
@@ -103,6 +208,7 @@ impl Thread {
103208
/// let mut a = 1;
104209
///
105210
/// let t = Thread::try_new(
211+
/// cstr!("rust-thread"),
106212
/// move || {
107213
/// let b = Box::try_new(42)?;
108214
///
@@ -111,8 +217,7 @@ impl Thread {
111217
/// println!("Hello Rust Thread {}", a + b.as_ref());
112218
/// }
113219
/// Ok(())
114-
/// },
115-
/// cstr!("rust-thread")
220+
/// }
116221
/// )?;
117222
///
118223
/// t.wake_up();
@@ -132,33 +237,11 @@ impl Thread {
132237
// `rust_thread_func` (the function that uses the closure) get executed.
133238
let boxed_fn: Box<dyn FnOnce() -> KernelResult<()> + 'static> = Box::try_new(f)?;
134239

135-
// Double boxing here because `dyn FnOnce` is a fat pointer, and we can only
136-
// pass a `usize` as the `data` for `kthread_create_on_node`.
137-
//
138-
// We `Box::into_raw` from this side, and will `Box::from_raw` at the other
139-
// side to transfer the ownership of the boxed data.
140-
let double_box_ptr = Box::into_raw(Box::try_new(boxed_fn)?) as *mut _;
141-
142-
// SAFETY: a) `double_box_ptr` is a proper pointer (generated by `Box::into_raw`),
143-
// and if succeed, the new thread will get the ownership. And b) `rust_thread_func`
144-
// is provided by us and correctly handles the dereference of the `double_box_ptr`
145-
// (via `Box::from_raw`).
146-
let result = unsafe { Self::try_new_c_style(name, rust_thread_func, double_box_ptr) };
240+
// Double boxing here because `dyn FnOnce` is a fat pointer, and we cannot
241+
// `transmute` it to `*mut c_void`.
242+
let double_box = Box::try_new(boxed_fn)?;
147243

148-
if let Err(e) = result {
149-
// Creation fails, we need to get back the double boxed closure.
150-
//
151-
// SAFETY: `double_box_ptr` is a proper pointer generated by a `Box::into_raw()`
152-
// from a box created by us. If the thread creation fails, no one will reference
153-
// that pointer.
154-
unsafe {
155-
Box::from_raw(double_box_ptr);
156-
}
157-
158-
Err(e)
159-
} else {
160-
result
161-
}
244+
thread_try_new!(name, call_as_closure, double_box)
162245
}
163246

164247
/// Wakes up the thread.

0 commit comments

Comments
 (0)