Skip to main content

strat9_kernel/process/
kthread.rs

1//! Kernel thread API (`kthread`).
2//!
3//! Ergonomic wrapper over [`Task::new_kernel_task`] + `add_task` for spawning
4//! closure-based kernel threads. Kernel threads stay internal: they never go
5//! through `/thread/*` because `SYS_THREAD_CREATE` / `/thread/create` refuse
6//! kernel parents by design.
7//!
8//! ```ignore
9//! let tid = kthread::spawn("gc-worker", || {
10//!     loop { /* ... */ kthread::yield_now(); }
11//! })?;
12//! ```
13
14use crate::process::{
15    scheduler::add_task,
16    task::{Task, TaskId, TaskPriority},
17};
18use alloc::{boxed::Box, string::String, sync::Arc};
19
20/// Trampoline for closure-based kernel threads.
21///
22/// Rebuilds the boxed closure from the raw argument, runs it, then exits the
23/// calling task so the scheduler reclaims the kernel stack.
24extern "C" fn kthread_trampoline(arg0: u64) -> ! {
25    // SAFETY: `arg0` was produced by `Box::into_raw` in `spawn` for this task
26    // only; nobody else holds or frees that pointer.
27    let closure = unsafe { Box::from_raw(arg0 as *mut Box<dyn FnOnce() + Send>) };
28    // Consumes the boxed closure (outer Box included).
29    closure();
30    crate::process::scheduler::exit_current_task(0)
31}
32
33/// Spawn a kernel thread running `f`.
34///
35/// `name` is copied into a leaked allocation so it satisfies the `&'static str`
36/// field of `Task`; kernel threads are few and long-lived, so this is bounded.
37///
38/// Returns the `TaskId` of the new task once it is registered with the
39/// scheduler (it may not have started running yet).
40pub fn spawn<F: FnOnce() + Send + 'static>(name: &str, f: F) -> Result<TaskId, &'static str> {
41    let stored: Box<dyn FnOnce() + Send> = Box::new(f);
42    let arg0 = Box::into_raw(Box::new(stored)) as u64;
43    let leaked_name: &'static str = Box::leak(String::from(name).into_boxed_str());
44
45    // `Task::new_kernel_task_with_stack` declares `extern "C" fn() -> !`, while
46    // the scheduler bootstrap (`task_entry_trampoline` -> `task_post_switch_enter`)
47    // always invokes the entry with one register argument (RDI = r13 slot).
48    // Transmute here so our trampoline can receive the closure pointer : this
49    // mirrors the transmute performed by `task_post_switch_enter` itself.
50    let entry: extern "C" fn() -> ! =
51        unsafe { core::mem::transmute(kthread_trampoline as *const ()) };
52
53    let task = Task::new_kernel_task_with_stack(
54        entry,
55        leaked_name,
56        TaskPriority::Normal,
57        Task::DEFAULT_STACK_SIZE,
58    )?;
59
60    // CpuContext initial stack layout: r15, r14, r13(arg), r12(entry), rbp, rbx, ret.
61    // Seed r13 with the closure pointer: task_entry_trampoline forwards it to
62    // `kthread_trampoline` as its single argument, mirroring the user-thread
63    // bootstrap seeding in `thread_ops`.
64    unsafe {
65        let ctx = &mut *task.context.get();
66        let frame = ctx.saved_rsp as *mut u64;
67        *frame.add(2) = arg0;
68    }
69
70    let id = task.id;
71    add_task(task);
72    Ok(id)
73}
74
75/// Cooperative yield for kernel threads.
76pub fn yield_now() {
77    crate::process::yield_task();
78}
79
80/// `TaskId` of the calling kernel thread, if one is current.
81pub fn current_tid() -> Option<TaskId> {
82    crate::process::current_task_id()
83}
84
85/// Convenience: clone of the current task's `Arc<Task>`, if any.
86pub fn current_task() -> Option<Arc<Task>> {
87    crate::process::current_task_clone()
88}