Skip to content

Commit 9a24e3c

Browse files
committed
rust: thread: Add Thread support
Signed-off-by: Boqun Feng <[email protected]>
1 parent 178a67e commit 9a24e3c

File tree

6 files changed

+314
-3
lines changed

6 files changed

+314
-3
lines changed

drivers/char/rust_example.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
#![feature(test)]
88

99
use alloc::boxed::Box;
10+
use alloc::sync::Arc;
1011
use core::pin::Pin;
12+
use core::sync::atomic::{AtomicBool, Ordering};
1113
use kernel::prelude::*;
1214
use kernel::{
1315
chrdev, condvar_init, cstr,
1416
file_operations::FileOperations,
1517
miscdev, mutex_init, spinlock_init,
1618
sync::{CondVar, Mutex, SpinLock},
19+
thread::{schedule, Thread},
1720
};
1821

1922
module! {
@@ -133,6 +136,53 @@ impl KernelModule for RustExample {
133136
cv.free_waiters();
134137
}
135138

139+
// Test threads.
140+
{
141+
let mut a = 1;
142+
// FIXME: use a completion or a barrier.
143+
let flag = Arc::try_new(AtomicBool::new(false))?;
144+
let other = flag.clone();
145+
146+
let t1 = Thread::try_new(cstr!("rust-thread"), move || {
147+
other.store(true, Ordering::Release);
148+
let b = Box::try_new(42)?;
149+
for _ in 0..20 {
150+
a += 1;
151+
println!("Hello Rust Thread {}", a + b.as_ref());
152+
}
153+
154+
Ok(())
155+
})?;
156+
157+
t1.wake_up();
158+
159+
// Waits to observe the thread run.
160+
while !flag.load(Ordering::Acquire) {
161+
schedule();
162+
}
163+
164+
// `t1` should exit normally.
165+
t1.stop().expect("Rust thread should exit normally");
166+
}
167+
168+
// Test threads (not up for running).
169+
{
170+
let mut a = 1;
171+
172+
let t1 = Thread::try_new(cstr!("rust-thread"), move || {
173+
let b = Box::try_new(42)?;
174+
for _ in 0..20 {
175+
a += 1;
176+
println!("Hello Rust Thread {}", a + b.as_ref());
177+
}
178+
179+
Ok(())
180+
})?;
181+
182+
// Without `wake_up`, `stop` will cause the thread to exits with `-EINTR`.
183+
t1.stop().expect_err("Rust thread should exit abnormally");
184+
}
185+
136186
// Including this large variable on the stack will trigger
137187
// stack probing on the supported archs.
138188
// This will verify that stack probing does not lead to

rust/helpers.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <linux/build_bug.h>
55
#include <linux/uaccess.h>
66
#include <linux/sched/signal.h>
7+
#include <linux/sched/task.h>
78

89
void rust_helper_BUG(void)
910
{
@@ -60,6 +61,18 @@ int rust_helper_signal_pending(void)
6061
}
6162
EXPORT_SYMBOL(rust_helper_signal_pending);
6263

64+
void rust_helper_get_task_struct(struct task_struct *task)
65+
{
66+
(void)get_task_struct(task);
67+
}
68+
EXPORT_SYMBOL(rust_helper_get_task_struct);
69+
70+
void rust_helper_put_task_struct(struct task_struct *task)
71+
{
72+
put_task_struct(task);
73+
}
74+
EXPORT_SYMBOL(rust_helper_put_task_struct);
75+
6376
// See https://github.com/rust-lang/rust-bindgen/issues/1671
6477
static_assert(__builtin_types_compatible_p(size_t, uintptr_t),
6578
"size_t must match uintptr_t, what architecture is this??");

rust/kernel/bindings_helper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include <linux/version.h>
1111
#include <linux/miscdevice.h>
1212
#include <linux/poll.h>
13+
#include <linux/kthread.h>
14+
#include <linux/err.h>
1315

1416
// `bindgen` gets confused at certain things
1517
const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL;

rust/kernel/error.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ impl From<AllocError> for Error {
8686
}
8787
}
8888

89-
/// Convert a kernel pointer to [`KernelResult`]
89+
/// Converts a kernel pointer to [`KernelResult`].
9090
///
9191
/// # Pointer value range
9292
///
93-
/// (According to include/linux/err.h)
93+
/// According to `include/linux/err.h`,
9494
/// [0, .., `core::usize::MAX - bindings::MAX_ERRNO`) is the range for normal values of pointer,
95-
/// [`core::unsize::MAX - bindings::MAX_ERRNO`,..,`core::usize::MAX] is the range for error value
95+
/// [`core::unsize::MAX - bindings::MAX_ERRNO`,..,`core::usize::MAX] is the range for error values
9696
/// stored in pointer.
9797
pub fn ptr_to_result<T>(ptr: *mut T) -> Result<*mut T, Error> {
9898
let value = ptr as usize;

rust/kernel/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub mod printk;
4545
pub mod random;
4646
mod static_assert;
4747
pub mod sync;
48+
pub mod thread;
4849

4950
#[cfg(CONFIG_SYSCTL)]
5051
pub mod sysctl;

rust/kernel/thread.rs

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! A kernel thread (kthread).
4+
//!
5+
//! This module allows Rust code to create/wakup/stop a kernel thread.
6+
7+
use crate::c_types;
8+
use crate::error::{ptr_to_result, Error, KernelResult};
9+
use crate::{bindings, cstr, CStr};
10+
11+
use alloc::boxed::Box;
12+
use core::ops::FnOnce;
13+
14+
extern "C" {
15+
#[allow(improper_ctypes)]
16+
fn rust_helper_get_task_struct(task: *mut bindings::task_struct);
17+
#[allow(improper_ctypes)]
18+
fn rust_helper_put_task_struct(task: *mut bindings::task_struct);
19+
}
20+
21+
/// Function passed to `kthread_create_on_node` as the thread function pointer.
22+
#[no_mangle]
23+
unsafe extern "C" fn rust_thread_func(data: *mut c_types::c_void) -> c_types::c_int {
24+
// `Box::from_raw()` to get the ownership of the closure.
25+
let c = Box::from_raw(data as *mut Box<dyn FnOnce() -> KernelResult<()>>);
26+
27+
match c() {
28+
Ok(_) => 0,
29+
Err(e) => e.to_kernel_errno(),
30+
}
31+
}
32+
33+
/// A kernel thread handle.
34+
pub struct Thread {
35+
/// Pointer to the kernel thread.
36+
task: *mut bindings::task_struct,
37+
}
38+
39+
impl Thread {
40+
/// Creates a new thread using a C-style function pointer.
41+
///
42+
/// No extra memory allocation for thread creation. Use this when closure
43+
/// allocation overhead is unacceptable or there is already a C style
44+
/// thread function. Otherwise, please consider using [`Thread::try_new`].
45+
///
46+
/// # Safety
47+
///
48+
/// This function actually doesn't dereference `arg` or call `f`, so even if
49+
/// the users pass incorrect parameters this function won't run into
50+
/// trouble. But if the users provide incorrect `arg` or `f` the new
51+
/// thread will corrupt memory or do other unsafe behaviors, so
52+
/// make it `unsafe`. Otherwise, it is [`Thread::wake_up`] that should be
53+
/// made as `unsafe` because it will trigger the call of the unsafe function
54+
/// `f`, this is undesirable given that [`Thread::wake_up`] on a
55+
/// [`Thread::try_new`] created thread is safe.
56+
///
57+
/// The safety requirements of calling this function are:
58+
///
59+
/// - Make sure `arg` is a proper pointer that points to a valid memory
60+
/// location when the new thread begins to run.
61+
///
62+
/// - Make sure `f` is a proper function pointer and `f` handles `arg`
63+
/// correctly.
64+
///
65+
/// # Context
66+
///
67+
/// This function might sleep due to the memory allocation and waiting for
68+
/// completion in `kthread_create_on_node`. Therefore cannot call this
69+
/// in atomic contexts (i.e. preemption-off contexts).
70+
pub unsafe fn try_new_c_style(
71+
name: CStr,
72+
f: unsafe extern "C" fn(*mut c_types::c_void) -> c_types::c_int,
73+
arg: *mut c_types::c_void,
74+
) -> KernelResult<Self> {
75+
let task;
76+
77+
// SAFETY: `kthread_create_on_node` will copy the content of `name`,
78+
// so we don't need to make the `name` live longer.
79+
task = ptr_to_result(bindings::kthread_create_on_node(
80+
Some(f),
81+
arg,
82+
bindings::NUMA_NO_NODE,
83+
cstr!("%s").as_ptr() as _,
84+
name.as_ptr(),
85+
))?;
86+
87+
// Increases the refcount of the task, so that it won't go away if it `do_exit`.
88+
//
89+
// SAFETY: `task` is a proper pointer pointing to a newly created thread.
90+
rust_helper_get_task_struct(task);
91+
92+
Ok(Thread { task })
93+
}
94+
95+
/// Creates a new thread.
96+
///
97+
/// # Examples
98+
///
99+
/// ```
100+
/// use kernel::thread::Thread;
101+
/// use alloc::boxed::Box;
102+
///
103+
/// let mut a = 1;
104+
///
105+
/// let t = Thread::try_new(
106+
/// move || {
107+
/// let b = Box::try_new(42)?;
108+
///
109+
/// for _ in 0..10 {
110+
/// a = a + 1;
111+
/// println!("Hello Rust Thread {}", a + b.as_ref());
112+
/// }
113+
/// Ok(())
114+
/// },
115+
/// cstr!("rust-thread")
116+
/// )?;
117+
///
118+
/// t.wake_up();
119+
/// ```
120+
///
121+
/// # Context
122+
///
123+
/// This function might sleep due to the memory allocation and waiting for
124+
/// the completion in `kthread_create_on_node`. Therefore do not call this
125+
/// in atomic contexts (i.e. preemption-off contexts).
126+
pub fn try_new<F>(name: CStr, f: F) -> KernelResult<Self>
127+
where
128+
F: FnOnce() -> KernelResult<()>,
129+
F: Send + 'static,
130+
{
131+
// Allocate closure here, because this function maybe returns before
132+
// `rust_thread_func` (the function that uses the closure) get executed.
133+
let boxed_fn: Box<dyn FnOnce() -> KernelResult<()> + 'static> = Box::try_new(f)?;
134+
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) };
147+
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+
}
162+
}
163+
164+
/// Wakes up the thread.
165+
///
166+
/// Note that a newly created thread (e.g. via [`Thread::try_new`]) will not
167+
/// run until a [`Thread::wake_up`] is called.
168+
///
169+
/// # Context
170+
///
171+
/// This function might sleep, don't call it in atomic contexts.
172+
pub fn wake_up(&self) {
173+
// SAFETY: `task` is a valid pointer to a kernel thread structure, the refcount
174+
// of which is increased in `try_new*`, so it won't point to a freed
175+
// `task_struct`. And it's not stopped because `stop` will consume the
176+
// [`Thread`].
177+
unsafe {
178+
bindings::wake_up_process(self.task);
179+
}
180+
}
181+
182+
/// Stops the thread.
183+
///
184+
/// - If the thread hasn't been waken up after creation, the thread closure
185+
/// won't be called, and will return `-EINTR`. Note that a thread may not
186+
/// be waken up even after [`Thread::wake_up`] is called.
187+
///
188+
/// - Otherwise, waits for the closure to return or the thread `do_exit`
189+
/// itself.
190+
///
191+
/// Consumes the [`Thread`] so that it's not accessible. In case of error,
192+
/// returns the exit code of the thread.
193+
///
194+
/// # Context
195+
///
196+
/// This function might sleep, don't call it in atomic contexts.
197+
pub fn stop(self) -> KernelResult<()> {
198+
// SAFETY: `task` is a valid pointer to a kernel thread structure, the
199+
// refcount of which is increased in `try_new*`, so it won't point to
200+
// a freed `task_struct`. And it's not stopped because `stop` will
201+
// consume the [`Thread`].
202+
let ret = unsafe { bindings::kthread_stop(self.task) };
203+
204+
if ret == 0 {
205+
Ok(())
206+
} else {
207+
Err(Error::from_kernel_errno(ret))
208+
}
209+
}
210+
}
211+
212+
impl Drop for Thread {
213+
fn drop(&mut self) {
214+
// Decreases the refcount of the thread, the thread may still be running after
215+
// we `drop` the `Thread`.
216+
//
217+
// SAFETY: At least one refcount is held by `Thread::try_new*` and
218+
// the refcount of `task_struct` is implemented by atomics.
219+
unsafe {
220+
rust_helper_put_task_struct(self.task);
221+
}
222+
}
223+
}
224+
225+
/// Tries to give up the CPU and lets another thread run.
226+
///
227+
/// This maps to kernel's `schedule` function, which is similar to
228+
/// [`std::thread::yield_now`].
229+
///
230+
/// # Context
231+
///
232+
/// This function might sleep, don't call in atomic contexts.
233+
///
234+
/// [`std::thread::yield_now`]: https://doc.rust-lang.org/std/thread/fn.yield_now.html
235+
#[doc(alias = "yield_now")]
236+
pub fn schedule() {
237+
// SAFETY: ...
238+
// If we can schedule back from other thread, then this can be treated as a
239+
// no-op. A special case occurs when a thread sets its state to `TASK_DEAD`,
240+
// and then `schedule` will not come. Currently we don't have a way to do
241+
// this safely in Rust, and in the future, we probably still won't allow it.
242+
unsafe {
243+
bindings::schedule();
244+
}
245+
}

0 commit comments

Comments
 (0)