cpu_machdep(9)
π cpu_machdep β machine-dependent interfaces to handle CPU and thread state π¦
ok so bestie, this is the page for the architecture-specific glue code β the stuff each CPU port has to implement so the machine-independent kernel can do its thing. It lives in the Kernel Developer's Manual, and honestly? iconic.
β¨ NAME β¨
cpu_machdep, cpu_copy_thread, cpu_exec_vmspace_reuse, cpu_exit, cpu_fetch_syscall_args, cpu_fork, cpu_fork_kthread_handler, cpu_idle, cpu_idle_wakeup, cpu_procctl, cpu_set_syscall_retval, cpu_set_upcall, cpu_set_user_tls, cpu_switch, cpu_sync_core, cpu_thread_alloc, cpu_thread_clean, cpu_thread_exit, cpu_thread_free, cpu_throw, cpu_update_pcb β machine-dependent interfaces to handle CPU and thread state π
πΈ SYNOPSIS πΈ
#include <sys/proc.h>
#include <sys/ptrace.h>
void
cpu_copy_thread(struct thread *td, struct thread *td0);
bool
cpu_exec_vmspace_reuse(struct proc *p, struct vm_map *map);
void
cpu_exit(struct thread *td);
int
cpu_fetch_syscall_args(struct thread *td);
void
cpu_fork(struct thread *td1, struct proc *p2, struct thread *td2,
int flags);
void
cpu_fork_kthread_handler(struct thread *td, void (*func)(void *),
void *arg);
void
cpu_idle(int busy);
int
cpu_idle_wakeup(int cpu);
int
cpu_procctl(struct thread *td, int idtype, id_t id, int com,
void *data);
int
cpu_ptrace(struct thread *_td, int req, void *addr, int data);
void
cpu_set_syscall_retval(struct thread *td, int error);
int
cpu_set_upcall(struct thread *td, void (*entry)(void *), void *arg,
stack_t *stack);
int
cpu_set_user_tls(struct thread *td, void *tls_base, int thr_flags);
void
cpu_switch(struct thread *old, struct thread *new, struct mtx *mtx);
void
cpu_sync_core(void);
void
cpu_thread_alloc(struct thread *td);
void
cpu_thread_clean(struct thread *td);
void
cpu_thread_exit(struct thread *td);
void
cpu_thread_free(struct thread *td);
void
cpu_throw(struct thread *old, struct thread *new);
void
cpu_update_pcb(struct thread *td);
π₯ DESCRIPTION π₯
okay so the whole vibe here: these functions are the architecture-specific implementations of machine-independent abstractions. Each CPU port fills in the blanks. no cap, that's the entire premise. π«Ά
cpu_exec_vmspace_reuse()β returnstrueifexec_new_vmspace()can reuse an existingstruct vmspace(map) for the processpduringexecve(2). Heads up: this is only invoked ifmapis not shared with any other consumers. And if it returnsfalse,exec_new_vmspace()will create a newstruct vmspaceinstead. πcpu_exit()β releases machine-dependent resources other than the address space for the process containingtd, during process exit.cpu_fork()β copies and updates machine-dependent state (like the pcb and user registers, for example) from the forking threadtd1in an existing process over to the new threadtd2in the new processp2. This function must set up the new thread's kernel stack and pcb so thattd2callsfork_exit()when it begins execution, passing a pointer tofork_return()as the callout argument andtd2as the arg argument. πcpu_fork_kthread_handler()β adjusts a new thread's initial pcb and/or kernel stack to passfuncandargas the callout and arg arguments tofork_exit(). It must be called before a new thread is scheduled to run, and it's the thing that sets the βmainβ function for kernel threads. βcpu_copy_thread()β copies machine-dependent state (pcb and user registers, say) fromtdtotd0when you're creating a new thread in the same process. Just likecpu_fork(), this function must set up the new thread's kernel stack and pcb so thattd0callsfork_exit()when it begins execution, passing a pointer tofork_return()as the callout argument andtd0as the arg argument.cpu_set_upcall()β updates a new thread's initial user register state so it callsentrywithargas the sole argument, using the user stack described instack.cpu_set_user_tls()β sets a new thread's initial user thread pointer register to reference the user TLS base pointertls_base. Thethr_flagsargument provides flags bits, from the same namespace as theflagsmember of thestruct thr_paramargument to thethr_new(2)syscall. πΈcpu_update_pcb()β updates the pcb of the current thread with current user register values. This gets invoked before writing out register notes in a core dump. Typically it only has to update user registers for the current thread that are saved in the pcb during context switches rather than in the trapframe on kernel entry.
π note that whencpu_update_pcb()is used, threads in a process other than the current thread are stopped β typically bythread_single(). The pcbs of those stopped threads should already be updated bycpu_switch().
cpu_fetch_syscall_args()β fetches the current system call arguments for the native FreeBSD ABI from the current thread's user register state and/or user stack. The arguments are saved in thetd_samember oftd.cpu_set_syscall_retval()β updates the user register state fortdto store system call error and return values. The breakdown, because it's giving very specific:- If
erroris0, indicate success and return the two values intd_retval. - If
errorisERESTART, adjust the user PC to re-invoke the current system call after returning to user mode. - If
errorisEJUSTRETURN, leave the current user register state unchanged. - For any other value of
error, indicate error and returnerroras the error code.
- If
cpu_idle()β waits for the next interrupt to occur on the current CPU. If the architecture supports low power idling, this function should place the CPU into a low power state while waiting.busyis a hint from the scheduler:- If
busyis non-zero, the scheduler expects a short sleep, so the CPU should prefer low-latency over maximum power savings. - If
busyis zero, the CPU should maximize power savings, including deferring unnecessary clock interrupts viacpu_idleclock(). π
- If
cpu_idle_wakeup()β awakens the idle CPU with the IDcpufrom a low-power state.cpu_procctl()β handles any machine-dependentprocctl(2)requests.cpu_ptrace()β handles any machine-dependentptrace(2)requests.cpu_switch()β switches the current CPU between threads by swapping register state. It saves the current CPU register state in the pcb ofoldand loads register values from the pcb ofnewbefore returning. While the pcb generally contains caller-save kernel register state, it can also contain user registers that are not saved in the trapframe. π
After saving the current CPU register state of old, cpu_switch() stores mtx in the td_lock member of old, transferring ownership of the old thread. No data belonging to old can be accessed after that store. Specifically: the old thread's kernel stack must not be accessed after this point. π¨
When SCHED_ULE is being used, this function must wait (via spinning) for the td_lock member of new to change to a value not equal to &blocked_lock before loading register values from new or accessing its kernel stack.
From the caller's perspective, cpu_switch() returns when old is rescheduled in the future, possibly on a different CPU. However, the implementation of cpu_switch() returns immediately on the same CPU into the previously-saved context of new. (yes, the way that works is a whole mind-bender, iykyk)
cpu_throw()β similar tocpu_switch(), but it does not save any state foroldand does not write to the old thread'std_lockmember.cpu_sync_core()β ensures that all possible speculation and out-of-order execution is serialized on the current CPU. Note that this is called from an IPI handler, so it only has to handle additional serialization beyond that provided by handling an IPI.
π§ Thread Object Lifecycle π§
These functions support the management of machine-dependent thread state in conjunction with a thread object's lifecycle. π
The general model: a thread object is allocated each time a new kernel thread is created β either by system calls like fork(2) or thr_new(2), or when kernel-only threads are created via kproc_create(9), kproc_kthread_add(9), or kthread_add(9). When a kernel thread exits, the thread object is freed.
BUT. There's one special case, and it's an optimization: each free process object caches a thread object. When a process exits, the last thread object is not freed but remains attached to the process. When the process object is later reused for a new process in fork(2), the kernel recycles that last thread object and uses it as the initial thread in the new process. When a thread is recycled, some of the steps in the thread allocation and free cycle are skipped as an optimization. β¨
cpu_thread_alloc()β initializes machine-dependent fields intdafter allocating a new kernel stack. Typically it sets thetd_pcband initialtd_framepointers. It's called both when allocating a new thread object and when a recycled thread allocates a new kernel stack. Heads up though: this function is not called if a recycled thread reuses its existing kernel stack.cpu_thread_clean()β releases any machine-dependent resources for the last thread in a process duringwait(2). That thread is a candidate for recycling, so it should be reset to run as a new thread in case it gets recycled by a futurefork(2). β»οΈcpu_thread_exit()β cleans any machine-dependent state intdwhile it's exiting. Called by the exiting thread itself, so it cannot free state needed during in-kernel execution.cpu_thread_free()β releases any machine-dependent state intdwhen it's being freed. Called for any thread that was not the last thread in a process, once it has finished execution.
π SEE ALSO π
fork(2), procctl(2), ptrace(2), thr_new(2), wait(2), kproc_create(9), kproc_kthread_add(9), kthread_add(9), mi_switch(9)
π AUTHORS π
This manual page was developed by SRI International, the University of Cambridge Computer Laboratory (Department of Computer Science and Technology), and Capabilities Limited under contract (FA8750-24-C-B047) (βDECβ).
ok that's the whole page bestie, go forth and port an architecture π β¨ this one's from Debian, dated January 31, 2025 β mwah, see u in the next manpage π«Άπ
π¦ where this page comes from
- freebsd 15.1: base 15.1
- π the page's license: BSD-2-Clause
- Copyright (c) 2024 (holder)
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms