Skip to content

Commit 1a69d7b

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

File tree

5 files changed

+270
-0
lines changed

5 files changed

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

0 commit comments

Comments
 (0)