docs+code: spell out aspace/vaddr/paddr per coding standards
Expand the abbreviations flagged in docs/coding-standards.md (names spelled
out in full unless an acronym) across the kernel, runtime, ABI, tests, and
docs:
aspace -> address_space (AspaceRef -> AddressSpaceRef, retainAspace ->
retainAddressSpace, loaded_aspace -> loaded_address_space, the
liveAspaceCount/aspaceDestroyCount test hooks, etc.)
vaddr -> virtual_address
paddr -> physical_address
The kernel test case and its serial markers are renamed to match:
aspace-refcount -> address-space-refcount (kernel dispatch string and
test/qemu_test.py case name kept in sync). Prose in docs uses the natural
"address space"/"virtual address"; backticked field/identifier references
use the code spelling.
Also expand the bare "AS" abbreviation in three ABI comments and reframe the
set_thread_pointer ABI/handler docs to lead with the arch-neutral concept
(user-space TLS thread pointer; x86_64 IA32_FS_BASE, aarch64 TPIDR_EL0)
rather than x86 FS-first, matching scheduler.zig's existing framing.
Foreign ABI names preserved: the ELF p_vaddr field and mmap/mmio remain.
Verified: zig build, zig build test, and the full 25-case QEMU guardrail
suite all green.
This commit is contained in:
+48
-48
@@ -78,7 +78,7 @@ pub const Task = struct {
|
||||
ipc_wait_endpoint: ?*anyopaque = null,
|
||||
// Physical root of this task's address space, or 0 for a kernel task (which
|
||||
// runs on the shared kernel page tables). A user task carries its own.
|
||||
aspace: u64 = 0,
|
||||
address_space: u64 = 0,
|
||||
user_ip: u64 = 0, // user-mode entry point (user task only)
|
||||
user_sp: u64 = 0, // user-mode stack pointer (user task only)
|
||||
user_arg: u64 = 0, // value delivered in the user's first argument register at first entry
|
||||
@@ -95,8 +95,8 @@ pub const Task = struct {
|
||||
// aarch64. Restored on every context switch to this task (docs/threading-plan.md M10).
|
||||
thread_pointer: u64 = 0,
|
||||
// The mmap / MMIO grant-arena cursors moved from Task to the per-address-space object
|
||||
// (`AspaceRef`, below) so threads sharing one address space hand out disjoint grants
|
||||
// — see aspaceMmapNextPtr / aspaceDeviceMapNextPtr (docs/threading-plan.md M7).
|
||||
// (`AddressSpaceRef`, below) so threads sharing one address space hand out disjoint grants
|
||||
// — see addressSpaceMmapNextPtr / addressSpaceDeviceMapNextPtr (docs/threading-plan.md M7).
|
||||
// --- synchronous IPC (ipc_sync.zig) ---
|
||||
// Per-process handle table: a small-int handle names a kernel capability object.
|
||||
// Each entry tags its `kind` (an IPC endpoint or a shared-memory object) so the
|
||||
@@ -107,9 +107,9 @@ pub const Task = struct {
|
||||
// receive, cleared when it replies). A client, while blocked in Call, records
|
||||
// its message + reply buffers here and its result lands in `ipc_status`.
|
||||
ipc_client: ?*Task = null,
|
||||
ipc_send_ptr: u64 = 0, // client: outgoing message (vaddr in this task's AS)
|
||||
ipc_send_ptr: u64 = 0, // client: outgoing message (virtual_address in this task's address space)
|
||||
ipc_send_len: u64 = 0,
|
||||
ipc_reply_ptr: u64 = 0, // client: reply buffer (vaddr)
|
||||
ipc_reply_ptr: u64 = 0, // client: reply buffer (virtual_address)
|
||||
ipc_reply_cap: u64 = 0,
|
||||
ipc_status: i64 = 0, // client: reply length / -errno, written by the replier
|
||||
dma_map_next: u64 = 0, // bump pointer into this task's DMA arena (0 = unseeded)
|
||||
@@ -159,9 +159,9 @@ var tasks = [_]Task{.{}} ** maximum_tasks;
|
||||
// One live entry per address space; threads sharing an address space share this entry,
|
||||
// so their mmap/mmio grants bump one cursor and never overlap (docs/threading-plan.md M7).
|
||||
// `mmap_next`/`device_map_next` are 0 until process.zig seeds them to the arena base.
|
||||
const AspaceRef = struct { root: u64 = 0, count: u32 = 0, mmap_next: u64 = 0, device_map_next: u64 = 0 };
|
||||
var aspace_refs = [_]AspaceRef{.{}} ** maximum_tasks;
|
||||
var aspace_destroy_count: u64 = 0;
|
||||
const AddressSpaceRef = struct { root: u64 = 0, count: u32 = 0, mmap_next: u64 = 0, device_map_next: u64 = 0 };
|
||||
var address_space_refs = [_]AddressSpaceRef{.{}} ** maximum_tasks;
|
||||
var address_space_destroy_count: u64 = 0;
|
||||
|
||||
/// Total bytes of task **kernel** stacks currently allocated from the kernel heap —
|
||||
/// incremented when a task is created, decremented when the reaper frees a dead task's
|
||||
@@ -202,10 +202,10 @@ fn drainReapListLocked(pc: *PerCpu) void {
|
||||
/// Take a reference to address space `root` (0 = a kernel task, which owns none).
|
||||
/// Returns false only if the ref table is full — bounded by `maximum_tasks`, so in
|
||||
/// practice it never is. Caller holds the kernel lock.
|
||||
fn retainAspace(root: u64) bool {
|
||||
fn retainAddressSpace(root: u64) bool {
|
||||
if (root == 0) return true;
|
||||
var free: ?*AspaceRef = null;
|
||||
for (&aspace_refs) |*entry| {
|
||||
var free: ?*AddressSpaceRef = null;
|
||||
for (&address_space_refs) |*entry| {
|
||||
if (entry.count != 0 and entry.root == root) {
|
||||
entry.count += 1;
|
||||
return true;
|
||||
@@ -220,51 +220,51 @@ fn retainAspace(root: u64) bool {
|
||||
/// Drop a reference to `root`; destroy the address space when the **last** one drops.
|
||||
/// A `root` with no entry — never retained, e.g. a hand-built test space — is
|
||||
/// destroyed directly, preserving the pre-refcount behaviour. Caller holds the lock.
|
||||
fn releaseAspace(root: u64) void {
|
||||
fn releaseAddressSpace(root: u64) void {
|
||||
if (root == 0) return;
|
||||
for (&aspace_refs) |*entry| {
|
||||
for (&address_space_refs) |*entry| {
|
||||
if (entry.count == 0 or entry.root != root) continue;
|
||||
entry.count -= 1;
|
||||
if (entry.count == 0) {
|
||||
entry.root = 0;
|
||||
architecture.destroyAddressSpace(root);
|
||||
aspace_destroy_count += 1;
|
||||
address_space_destroy_count += 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
architecture.destroyAddressSpace(root);
|
||||
aspace_destroy_count += 1;
|
||||
address_space_destroy_count += 1;
|
||||
}
|
||||
|
||||
/// Test-observable: how many address spaces are live (entries with a nonzero count).
|
||||
pub fn liveAspaceCount() u32 {
|
||||
pub fn liveAddressSpaceCount() u32 {
|
||||
var live: u32 = 0;
|
||||
for (&aspace_refs) |*entry| {
|
||||
for (&address_space_refs) |*entry| {
|
||||
if (entry.count != 0) live += 1;
|
||||
}
|
||||
return live;
|
||||
}
|
||||
|
||||
/// Test-observable: total address-space destructions since boot.
|
||||
pub fn aspaceDestroyCount() u64 {
|
||||
return aspace_destroy_count;
|
||||
pub fn addressSpaceDestroyCount() u64 {
|
||||
return address_space_destroy_count;
|
||||
}
|
||||
|
||||
/// Pointer to the mmap grant-arena cursor for address space `root`, so the mmap syscall
|
||||
/// can read-and-bump it. Per-address-space (not per-task), so sibling threads get
|
||||
/// disjoint grants. **Caller holds the kernel lock** (the entry is stable while held).
|
||||
/// Null only if `root` was never retained — which can't happen for a live user task.
|
||||
pub fn aspaceMmapNextPtr(root: u64) ?*u64 {
|
||||
for (&aspace_refs) |*entry| {
|
||||
pub fn addressSpaceMmapNextPtr(root: u64) ?*u64 {
|
||||
for (&address_space_refs) |*entry| {
|
||||
if (entry.count != 0 and entry.root == root) return &entry.mmap_next;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Pointer to the MMIO grant-arena cursor for address space `root` (see
|
||||
/// `aspaceMmapNextPtr`). Caller holds the kernel lock.
|
||||
pub fn aspaceDeviceMapNextPtr(root: u64) ?*u64 {
|
||||
for (&aspace_refs) |*entry| {
|
||||
/// `addressSpaceMmapNextPtr`). Caller holds the kernel lock.
|
||||
pub fn addressSpaceDeviceMapNextPtr(root: u64) ?*u64 {
|
||||
for (&address_space_refs) |*entry| {
|
||||
if (entry.count != 0 and entry.root == root) return &entry.device_map_next;
|
||||
}
|
||||
return null;
|
||||
@@ -288,7 +288,7 @@ pub const PerCpu = struct {
|
||||
hw_id: u32 = 0, // the core's hardware id (Local APIC id on x86_64)
|
||||
index: u32 = 0, // dense 0-based core index
|
||||
online: bool = false, // has this core finished bring-up?
|
||||
loaded_aspace: u64 = 0, // the address-space root currently loaded on this core
|
||||
loaded_address_space: u64 = 0, // the address-space root currently loaded on this core
|
||||
loaded_thread_pointer: u64 = 0, // the TLS thread pointer currently loaded on this core (docs/threading-plan.md M10)
|
||||
// Tasks pinned to this core (affinity == index), per priority level + bitmap.
|
||||
pinned_head: [number_priorities]?*Task = .{null} ** number_priorities,
|
||||
@@ -331,7 +331,7 @@ var preemption_enabled = true;
|
||||
/// boot, before interrupts are enabled — so no lock is needed here.
|
||||
pub fn init(boot_priority: Priority) void {
|
||||
const pc = &cpus[0];
|
||||
pc.* = .{ .index = 0, .online = true, .loaded_aspace = architecture.kernelPageTable() };
|
||||
pc.* = .{ .index = 0, .online = true, .loaded_address_space = architecture.kernelPageTable() };
|
||||
architecture.setCpuLocal(0, @intFromPtr(pc));
|
||||
tasks[0] = .{ .id = 0, .state = .running, .priority = boot_priority };
|
||||
pc.current = &tasks[0];
|
||||
@@ -370,7 +370,7 @@ pub fn secondaryMain() callconv(.c) noreturn {
|
||||
pc.current = t;
|
||||
pc.idle = t;
|
||||
pc.online = true;
|
||||
pc.loaded_aspace = architecture.kernelPageTable(); // the AP adopted the kernel tables at bring-up
|
||||
pc.loaded_address_space = architecture.kernelPageTable(); // the AP adopted the kernel tables at bring-up
|
||||
sync.leave(flags);
|
||||
|
||||
architecture.enableInterrupts(); // the timer now preempts this idle context into work
|
||||
@@ -456,7 +456,7 @@ pub fn spawnOn(entry: *const fn () void, priority: Priority, cpu: u32) bool {
|
||||
return ok;
|
||||
}
|
||||
|
||||
/// Spawn a **user** task: a task with its own address space (`aspace`) that starts
|
||||
/// Spawn a **user** task: a task with its own address space (`address_space`) that starts
|
||||
/// in user mode at `entry` on `user_sp`, recorded under `name` (its argv[0]).
|
||||
/// `supervisor` is the id of the spawning process (0 = the kernel) — the kill
|
||||
/// authority — and `exit_endpoint` (an *ipc.Endpoint whose reference the caller
|
||||
@@ -465,14 +465,14 @@ pub fn spawnOn(entry: *const fn () void, priority: Priority, cpu: u32) bool {
|
||||
/// lands in `user_task_trampoline`.
|
||||
/// Returns the new process id, or null (creating nothing) if the table is full or
|
||||
/// out of memory.
|
||||
/// **Caller must hold the kernel lock** (the loader that builds `aspace` holds it
|
||||
/// **Caller must hold the kernel lock** (the loader that builds `address_space` holds it
|
||||
/// across the whole spawn, so the address space and the task appear atomically).
|
||||
pub fn spawnUserLocked(aspace: u64, entry: u64, user_sp: u64, user_arg: u64, priority: Priority, task_name: []const u8, supervisor: u32, exit_endpoint: ?*anyopaque) ?u32 {
|
||||
pub fn spawnUserLocked(address_space: u64, entry: u64, user_sp: u64, user_arg: u64, priority: Priority, task_name: []const u8, supervisor: u32, exit_endpoint: ?*anyopaque) ?u32 {
|
||||
const t = freeSlot() orelse return null;
|
||||
const stack = heap.allocator().alloc(u8, stack_size) catch return null;
|
||||
// Take this task's reference to the address space before we commit the slot, so a
|
||||
// failure here leaves nothing to unwind (the caller still owns the raw `aspace`).
|
||||
if (!retainAspace(aspace)) {
|
||||
// failure here leaves nothing to unwind (the caller still owns the raw `address_space`).
|
||||
if (!retainAddressSpace(address_space)) {
|
||||
heap.allocator().free(stack);
|
||||
return null;
|
||||
}
|
||||
@@ -482,7 +482,7 @@ pub fn spawnUserLocked(aspace: u64, entry: u64, user_sp: u64, user_arg: u64, pri
|
||||
.state = .ready,
|
||||
.priority = priority,
|
||||
.stack = stack,
|
||||
.aspace = aspace,
|
||||
.address_space = address_space,
|
||||
.user_ip = entry,
|
||||
.user_sp = user_sp,
|
||||
.user_arg = user_arg,
|
||||
@@ -561,7 +561,7 @@ fn schedule() void {
|
||||
/// Make `next` this core's running task: publish its kernel stack (TSS.rsp0, so a
|
||||
/// user-mode interrupt lands on a good stack) and its address space (only when
|
||||
/// it differs from what's loaded — every page-table switch is a full TLB flush),
|
||||
/// then switch registers/stacks. Kernel tasks (aspace == 0, no kstack_top used
|
||||
/// then switch registers/stacks. Kernel tasks (address_space == 0, no kstack_top used
|
||||
/// from user mode) resolve to the shared kernel page tables and skip the kernel-
|
||||
/// stack write, so this is a no-op beyond the register switch for a pure-kernel
|
||||
/// workload. The big kernel lock is held and interrupts are off throughout, so no
|
||||
@@ -569,10 +569,10 @@ fn schedule() void {
|
||||
/// `save_sp` receives the outgoing task's stack pointer.
|
||||
fn switchTo(pc: *PerCpu, save_sp: *usize, next: *Task) void {
|
||||
if (next.kstack_top != 0) architecture.setKernelStack(pc.index, next.kstack_top);
|
||||
const want = if (next.aspace != 0) next.aspace else architecture.kernelPageTable();
|
||||
if (want != pc.loaded_aspace) {
|
||||
const want = if (next.address_space != 0) next.address_space else architecture.kernelPageTable();
|
||||
if (want != pc.loaded_address_space) {
|
||||
architecture.loadPageTable(want);
|
||||
pc.loaded_aspace = want;
|
||||
pc.loaded_address_space = want;
|
||||
}
|
||||
// Restore the next task's user TLS thread pointer — only on change, the same
|
||||
// conditional-load discipline as CR3 above (docs/threading-plan.md M10).
|
||||
@@ -635,12 +635,12 @@ pub fn futexWaitLocked(addr: u64, timeout_ms: u64) FutexResult {
|
||||
}
|
||||
|
||||
/// Wake up to `count` tasks blocked in `futex_wait` on `addr` in address space
|
||||
/// `aspace`. Precondition: the big kernel lock is held. Returns how many woke.
|
||||
pub fn futexWakeLocked(aspace: u64, addr: u64, count: u32) u32 {
|
||||
/// `address_space`. Precondition: the big kernel lock is held. Returns how many woke.
|
||||
pub fn futexWakeLocked(address_space: u64, addr: u64, count: u32) u32 {
|
||||
var woken: u32 = 0;
|
||||
for (&tasks) |*t| {
|
||||
if (woken >= count) break;
|
||||
if (t.state == .blocked and t.aspace == aspace and t.futex_addr == addr) {
|
||||
if (t.state == .blocked and t.address_space == address_space and t.futex_addr == addr) {
|
||||
t.futex_addr = 0; // the "woken, not timed out" signal to futexWaitLocked
|
||||
t.wake_at = 0;
|
||||
t.state = .ready;
|
||||
@@ -907,7 +907,7 @@ fn reapKillPendingLocked() void {
|
||||
// task_trampoline, not switchTo's tail), its stack is still queued here. The dying
|
||||
// task switched away before this tick, so it is off its stack — drain now (M8).
|
||||
drainReapListLocked(pc);
|
||||
if (cur.kill_pending and cur.aspace != 0 and !cur.in_system_call) {
|
||||
if (cur.kill_pending and cur.address_space != 0 and !cur.in_system_call) {
|
||||
if (terminate_current_hook) |hook| hook(); // noreturn
|
||||
}
|
||||
if (reap_task_hook) |hook| {
|
||||
@@ -977,16 +977,16 @@ pub fn exitUser() noreturn {
|
||||
pub fn exitUserLocked() noreturn {
|
||||
const pc = thisCpu();
|
||||
const dying = pc.current;
|
||||
const as = dying.aspace;
|
||||
const as = dying.address_space;
|
||||
if (as != 0) {
|
||||
const kroot = architecture.kernelPageTable();
|
||||
architecture.loadPageTable(kroot); // off the process tables before freeing them
|
||||
pc.loaded_aspace = kroot;
|
||||
releaseAspace(as); // destroys only when this was the last task on the space
|
||||
pc.loaded_address_space = kroot;
|
||||
releaseAddressSpace(as); // destroys only when this was the last task on the space
|
||||
}
|
||||
dying.state = .reaping; // dead but its slot stays reserved until the stack is freed
|
||||
wakeJoinersLocked(dying.id); // let any thread_join(dying.id) return (M9)
|
||||
dying.aspace = 0;
|
||||
dying.address_space = 0;
|
||||
dying.kill_pending = false;
|
||||
dying.in_system_call = false;
|
||||
// Queue for reaping: the task we switch to (or the next tick) frees this stack (M8/M9).
|
||||
@@ -1007,9 +1007,9 @@ pub fn exitUserLocked() noreturn {
|
||||
/// task isn't running). The kernel stack is leaked, as in `exitUser` (no reaper
|
||||
/// yet). Precondition: the big kernel lock is held.
|
||||
pub fn destroyTaskLocked(t: *Task) void {
|
||||
if (t.aspace != 0) releaseAspace(t.aspace); // destroys only on the last reference
|
||||
if (t.address_space != 0) releaseAddressSpace(t.address_space); // destroys only on the last reference
|
||||
reapStackLocked(t); // safe to free now: `t` is not running on any core (M8)
|
||||
t.aspace = 0;
|
||||
t.address_space = 0;
|
||||
t.kill_pending = false;
|
||||
t.in_system_call = false;
|
||||
t.wake_at = 0;
|
||||
@@ -1052,7 +1052,7 @@ pub fn enumerate(out: []abi.ProcessDescriptor) u64 {
|
||||
|
||||
/// Whether the running task is a user process (has its own address space).
|
||||
pub fn currentIsUserProcess() bool {
|
||||
return current().aspace != 0;
|
||||
return current().address_space != 0;
|
||||
}
|
||||
|
||||
pub fn currentId() u32 {
|
||||
|
||||
Reference in New Issue
Block a user