Skip to content

Commit b364746

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

File tree

5 files changed

+272
-0
lines changed

5 files changed

+272
-0
lines changed

drivers/char/rust_example.rs

Lines changed: 56 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! {
@@ -125,6 +128,59 @@ impl KernelModule for RustExample {
125128
cv.free_waiters();
126129
}
127130

131+
// Test threads.
132+
{
133+
let mut a = 1;
134+
// XXX use a completion or Barrier
135+
let flag = Arc::try_new(AtomicBool::new(false))?;
136+
let other = flag.clone();
137+
138+
let t1 = Thread::new(
139+
move || {
140+
other.store(true, Ordering::Release);
141+
let b = Box::try_new(42)?;
142+
for _ in 0..20 {
143+
a = a + 1;
144+
println!("Hello Rust Thread {}", a + b.as_ref());
145+
}
146+
147+
Ok(())
148+
},
149+
cstr!("Rust thread"),
150+
)?;
151+
152+
t1.wake_up();
153+
154+
// Wait to observe the thread run
155+
while !flag.load(Ordering::Acquire) {
156+
schedule();
157+
}
158+
159+
// `t1` should exit normally
160+
t1.stop().expect("Rust thread should exit normally");
161+
}
162+
163+
// Test threads.
164+
{
165+
let mut a = 1;
166+
167+
let t1 = Thread::new(
168+
move || {
169+
let b = Box::try_new(42)?;
170+
for _ in 0..20 {
171+
a = a + 1;
172+
println!("Hello Rust Thread {}", a + b.as_ref());
173+
}
174+
175+
Ok(())
176+
},
177+
cstr!("Rust thread"),
178+
)?;
179+
180+
// Without `wake_up`, `stop` will cause the thread exits -EINTR
181+
t1.stop().expect_err("Rust thread should exit abnormally");
182+
}
183+
128184
// Including this large variable on the stack will trigger
129185
// stack probing on the supported archs.
130186
// 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/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub mod printk;
4242
pub mod random;
4343
mod static_assert;
4444
pub mod sync;
45+
pub mod thread;
4546

4647
#[cfg(CONFIG_SYSCTL)]
4748
pub mod sysctl;

rust/kernel/thread.rs

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! A kernel thread (kthread)
4+
//!
5+
//! This modules 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};
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 function pointer. No other user.
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+
let ret = c();
28+
29+
if ret.is_ok() {
30+
return 0;
31+
} else {
32+
// Already check `ret` is not `Ok`, won't panic here
33+
return ret.unwrap_err().to_kernel_errno();
34+
}
35+
}
36+
37+
/// A kernel thread handle
38+
pub struct Thread {
39+
/// Pointer to kernel thread
40+
task: *mut bindings::task_struct,
41+
}
42+
43+
impl Thread {
44+
/// Create a new thread
45+
///
46+
/// This function might sleep due to the memory allocation and waiting for completion in
47+
/// `kthread_create_on_node`. Therefore cannot call this in a atomic context.
48+
///
49+
/// # Examples:
50+
/// use kernel::thread::Thread;
51+
/// use alloc::boxed::Box;
52+
///
53+
/// let mut a = 1;
54+
///
55+
/// let t = Thread::new(
56+
/// move || {
57+
/// let b = Box::try_new(42)?;
58+
///
59+
/// for _ in 0..10 {
60+
/// a = a + 1;
61+
/// println!("Hello Rust Thread {}", a + b.as_ref());
62+
/// }
63+
/// Ok(())
64+
/// },
65+
/// cstr!("rust-thread")
66+
/// )?;
67+
///
68+
/// t.wake_up();
69+
pub fn new<F>(f: F, name: CStr) -> KernelResult<Self>
70+
where
71+
F: FnOnce() -> KernelResult<()>,
72+
F: Send + 'static,
73+
{
74+
// Allocate closure here, because this function maybe returns before `rust_thread_func`
75+
// (the function that use the closure) get executed.
76+
let bf: Box<dyn FnOnce() -> KernelResult<()> + 'static> = Box::try_new(f)?;
77+
78+
// Double boxing here because `dyn FnOnce` is a fat pointer, and we can only pass a usize
79+
// as the `data` for `kthread_create_on_node`.
80+
//
81+
// We `into_raw` from this side, and will `from_raw` at the other side to transfer the
82+
// ownership of the boxed data.
83+
let db = Box::into_raw(Box::try_new(bf)?) as *mut _;
84+
85+
let task;
86+
87+
// SAFETY:
88+
//
89+
// * `db` is a proper pointer (generated by `Box::into_raw()`), and if succeed, the new
90+
// thread will get the ownership.
91+
//
92+
// * `kthread_create_on_node` will copy the content of name, so we don't need to make the
93+
// name live longer.
94+
unsafe {
95+
task = bindings::kthread_create_on_node(
96+
Some(rust_thread_func),
97+
db,
98+
bindings::NUMA_NO_NODE,
99+
"%s".as_ptr() as _,
100+
name.as_ptr(),
101+
);
102+
}
103+
104+
let result = ptr_to_result(task);
105+
106+
if result.is_err() {
107+
// Creation fails, we need to get back the double boxed closure.
108+
//
109+
// SAFETY:
110+
//
111+
// `db` is a proper pointer generated by a `Box::into_raw()` from a box created by us,
112+
// if the thread creation fails, no one will consume that pointer.
113+
unsafe {
114+
Box::from_raw(db);
115+
}
116+
} else {
117+
// Increase the refcount of the task, so that it won't go away if it `do_exit`.
118+
//
119+
// SAFETY:
120+
//
121+
// `task` is a valid pointer to newly created thread
122+
unsafe {
123+
rust_helper_get_task_struct(task);
124+
}
125+
}
126+
127+
Ok(Thread { task: result? })
128+
}
129+
130+
/// Wake up the kernel thread, note that a newly created kthread will not run until `wake_up`
131+
/// is called.
132+
///
133+
/// This function might sleep, don't call in atomic contexts.
134+
pub fn wake_up(&self) {
135+
// SAFETY:
136+
//
137+
// `task` is a valid pointer to a kernel thread structure, and it's not stopped because
138+
// `stop` will consume the [`Thread`].
139+
unsafe {
140+
bindings::wake_up_process(self.task);
141+
}
142+
}
143+
144+
/// Stop the the kernel thread:
145+
///
146+
/// * If the thread hasn't been waken up after creation, the thread closure won't be called,
147+
/// and will return `EINTR`. (Note that a thread may not be waken up even after `wake_up`
148+
/// is called).
149+
/// * Otherwise, wait for the closure to return or the thread `do_exit` itself.
150+
///
151+
/// Consume the [`Thread`] so that it's not accessible. Return the result of the thread
152+
/// closure (or the exit code in KernelResult format).
153+
///
154+
/// This function might sleep, don't call in atomic contexts.
155+
pub fn stop(self) -> KernelResult<()> {
156+
let ret;
157+
// SAFETY:
158+
//
159+
// `task` is a valid pointer to a kernel thread structure, and it's not stopped because
160+
// `stop` will consume the [`Thread`].
161+
unsafe { ret = bindings::kthread_stop(self.task) }
162+
163+
if ret == 0 {
164+
Ok(())
165+
} else {
166+
Err(Error::from_kernel_errno(ret))
167+
}
168+
}
169+
}
170+
171+
impl Drop for Thread {
172+
fn drop(&mut self) {
173+
// Decrease the refcount of the thread, the actuall thread may still be running after we
174+
// `drop` the [`Thread`].
175+
//
176+
// SAFETY:
177+
//
178+
// At least one refcount is held by `Thread::new` and refcount of [`task_struct`] is
179+
// implemented by atomics.
180+
unsafe {
181+
rust_helper_put_task_struct(self.task);
182+
}
183+
}
184+
}
185+
186+
/// Try to give up the cpu and let another thread to run, mapping to kernel's `schedule` function.
187+
/// A similar concept to `std::thread::yield_now`.
188+
///
189+
/// This function might sleep, don't call in atomic contexts.
190+
pub fn schedule() {
191+
// SAFETY:
192+
//
193+
// If we can schedule back from other thread, then this can be treated as no-ops. A special
194+
// case are a thread sets its state to `TASK_DEAD`, and then `schedule` will not come.
195+
// Currently we don't have a way to do this safely in Rust, and in the future, we probably
196+
// still don't allow this.
197+
unsafe {
198+
bindings::schedule();
199+
}
200+
}

0 commit comments

Comments
 (0)