Add support for a classic monolithic OS#2549
Draft
stlankes wants to merge 46 commits into
Draft
Conversation
Allows creating packet capture files in the pcap format. Co-authored-by: Martin Kröning <mkroening@posteo.net>
Allows creating packet capture files in the pcap format. Co-authored-by: Martin Kröning <mkroening@posteo.net>
Allows creating packet capture files in the pcap format. Co-authored-by: Martin Kröning <mkroening@posteo.net>
Allows creating packet capture files in the pcap format. Co-authored-by: Martin Kröning <mkroening@posteo.net>
- Add sys_fork syscall entry and prepare_fork_child_stack (x86_64) using a naked function that pushes fork_child_start as the child's return address via RIP-relative lea - Introduce restore_context_without_return! macro so the parent path can correctly skip the pushed address and return false - Mark user pages COW before duplicating the PML4; handle COW page faults and heap demand-paging in the page fault handler - Copy kernel stack to child's new stack allocation and compute the child's rsp from the parent-to-child base address offset - Add Task::new_fork and set_current_kernel_stack/Cr3 switch on context switch to load the child's page table - Mask TF in IA32_FMASK (SFMask) to prevent spurious #DB exceptions in child tasks that inherit the parent's RFLAGS via R11/sysretq The original PR was written by Vincent Feinendegen.
- load_application: initialise a fresh file-descriptor object map for the user process instead of sharing the kernel task's map - fork: clone the parent's object map into the child rather than sharing the same Arc, so parent and child have independent fd tables
- Replace redundant `as *mut PageTable` casts with turbofish syntax on `ptr::with_exposed_provenance_mut` - Replace `core::ptr::write_bytes` with pointer method `.write_bytes()` - Replace `core::ptr::copy_nonoverlapping` with `dst.copy_from_slice(src)` in physicalmem and `*new_pt = cur_pt.clone()` in mm/mod.rs - Add missing semicolon after `without_interrupts` call in scheduler
Before this change, the COW page-fault handler always allocated a fresh physical frame and mapped it, but never freed the original shared frame. This caused a 4 KiB leak per write fault on a COW page. Introduce a sparse per-frame reference count (BTreeMap<frame_nr, u32> protected by InterruptTicketMutex) that tracks how many page-table entries point to each COW-shared frame. Memory use scales with the number of actively shared frames, not with total physical memory. - mark_user_pages_copy_on_write: increment refcount for every newly COW-marked frame (parent's reference) - copy_current_root_page_table: increment refcount for every COW entry copied into the child's page table (child's reference) - COW fault handler: decrement refcount; if the last reference is dropped, remap the existing frame as writable directly instead of copying it (avoids an allocation in the single-sharer case); otherwise copy the frame and map the private copy
Register page faults as IRQ 14 in the interrupt counter so they show interrupt diagnostics. Useful for verifying COW behaviour after fork.
The spawn_process function is provided by the loader and declared as an external C symbol. The kernel routes syscall 16 to it. A stub returning -ENOSYS is provided for non-common-os builds. Also demote a log message in jump_to_user_land from info to debug.
Under `common-os`, each process receives its own `object_map` when the application is loaded, so the shared stdio map in `Task::new_idle` was never actually used. Gate the static and its stdin/stdout/stderr setup with `cfg(not(feature = "common-os"))` and give the idle task an empty map in the common-os case.
`copy_kernel_stack_to` allocated fresh physical frames via `copy_page` for every mapped page of the parent kernel stack and remapped them at the child's virtual base — but `TaskStacks::new` had already allocated and mapped the child's stack frames at that same address. The new mappings silently replaced the original ones, so `TaskStacks::Drop` later freed the unreachable frames while the frames actually installed in the page table leaked. Replace the `copy_page` + `map` loop with a plain `copy_nonoverlapping` between the parent's and child's stack ranges, which are both already mapped in the current page table. No extra frames are allocated, and `TaskStacks::Drop` once again frees the frames that are actually installed.
Extend SYSHANDLER_TABLE to 64 slots and register spawn, join, file metadata, condvar, task blocking and socket syscalls. Socket entries are gated behind `net`/`virtio-vsock`. Make the `addrinfo` submodule and its types `pub(crate)` so the table can take function pointers to `sys_getaddrinfo` and `sys_freeaddrinfo` without leaking private types.
Implement std::thread::spawn support when running as a common OS. Each spawned thread gets its own private TLS region, initialized from a pristine PT_TLS template extracted directly from the ELF binary. Threads share the parent's address space via Arc<RootPageTable>, heap, and file descriptor table, but have independent kernel/user stacks and TLS blocks. Key changes: - Add TlsTemplate struct holding the ELF's PT_TLS init image - Add RootPageTable wrapper with Arc-based lifetime management - Add allocate_thread_tls() to map fresh per-thread TLS pages - Add task_start_user() naked trampoline for ring-3 entry via iretq - Route sys_spawn/sys_spawn2 through spawn_thread() under common-os - Handle user thread exit via page fault at RIP=0 (clean exit path) - Register available_parallelism and getdents64 syscalls
Split drop_user_space into a reusable clear_pml4 helper that tears down user-space page tables without freeing the PML4 itself. clear_user_space uses it to wipe the current address space in-place and flush TLBs, enabling exec-style process replacement. drop_user_space continues to free the PML4 frame after clearing. Also register the new sys_exec syscall (number 56) in the dispatch table with a weak default stub.
Avoids leaking kernel-state register values to ring 3; the user entry sees only rdi (argc) and rsi (argv).
This series makes Hermit's aarch64 common-os kernel boot under macOS HVF on Apple Silicon, in addition to the existing TCG support. Three small adjustments are needed: virtual timer instead of physical, PSCI shutdown instead of semihosting, and FEAT_PAN-aware EL1 setup.
Mirrors the existing x86_64 common-os support in arch/aarch64/mm: * New software-defined PT-entry flag COW_MARKER (bit 58) plus the `copy_on_write()`, `user()`, `kernel()`, `execute_enable()` helpers on PageTableEntryFlags, and a PageTableEntryFlagsExt trait that matches the x86_64 API so common-os callers compile unchanged. * `mark_user_pages_copy_on_write()` walks the active TTBR0_EL1 user L0 entry and marks every writable user page READ_ONLY+COW_MARKER — the prep step before duplicating a page table for fork(). * `create_new_root_page_table` / `copy_current_root_page_table` / `drop_user_space` / `clear_user_space` / `copy_kernel_stack_to` manage per-process root tables. Kernel L0 entries (#0 kernel image, hermit-os#256+ kernel heap, hermit-os#511 self-ref) are SHARED across every task; only the user slot (#2 = LOADER_START >> 39) is private and deep-copied on fork. clear_l0 only walks the user slot and only frees a sub-table when the user-page sweep leaves it fully empty, so the kernel's own heap and stacks (which live in shared L0 entries) stay mapped. * L0 self-reference at 0x0000_FFFF_FFFF_F000 lets PT walks reach any level via virtual addresses, matching the existing x86_64 recursive PML4 idiom. mm/physicalmem.rs and scheduler/{mod,task/mod}.rs lift the `x86_64 + common-os` cfg-gates around frame_ref_inc/dec, copy_page, RootPageTable, Heap and the related Task fields to plain `feature = "common-os"`. Items still architecturally x86_64-only (fork(), spawn_thread, prepare_fork_child_stack, sys_fork/sys_waitpid) remain re-gated; aarch64 picks them up incrementally in the later stages of this series.
Common-os entry path for AArch64, modelled on the x86_64 sibling:
* `load_application(code_size, tls_size, func)` allocates the user
load region at LOADER_START (= 1 TiB), maps it with USER + writable
+ execute-enable flags, runs the loader closure to copy ELF segments
and the PT_TLS image, and installs TPIDR_EL0 for AArch64 Variant I
TLS (TCB at TPIDR_EL0, TLS image after a 16-byte reserved area).
Frame refcounts are bumped so the COW machinery in Stage 5 can drop
them safely on fork/exec teardown.
* `jump_to_user_land(entry, code_size, argv)` builds the user stack
with argv strings + pointer array (16-byte aligned per AAPCS64; no
red-zone trick), then transitions to EL0 with `eret`:
msr sp_el0, <stack> ; user SP
msr elr_el1, <pc> ; user PC
msr spsr_el1, #0 ; M[4:0]=0b00000 ⇒ EL0t, DAIF cleared
mov x0, argc; mov x1, argv
; zero scratch GPRs to avoid leaking EL1 state
eret
dsb nsh; isb ; speculation barrier behind ERET
No naked-asm "child entry stub" is needed (unlike x86_64's
fork_child_start) — every task return path goes through the trap
frame, which is built up exactly the same way.
Wires the user-space syscall path so EL0 binaries reach the existing
SYSHANDLER_TABLE.
* start.s: replace the `el0_sync_invalid` / `el0_irq_invalid` stubs
with real handlers that run trap_entry / do_sync(_irq) / trap_exit
/ eret. Hardware switches to SP_EL1 on entry from EL0, so no
explicit `msr spsel` is needed. The AArch32 Lower-EL slots stay on
the invalid path because we do not run 32-bit user code.
* interrupts.rs: do_sync now takes `&mut State` so it can write the
syscall return back into the trap frame. New dispatch_svc64 reads
x8 (syscall number) and x0..x5 (args) from the saved State, looks
the handler up via SyscallTable::handler(nr), and stores the result
in state.x0; trap_exit then ERETs with the right return value.
* syscalls/{mod,table}.rs: make the syscall table available on
aarch64 + common-os and provide an arch-conditional sys_invalid
(the x86_64 naked-asm inline trampoline stays unchanged; aarch64
uses an empty Rust stub used purely as a sentinel, with a pointer
comparison in the dispatcher to detect unfilled slots).
The Linux-like AArch64 SVC ABI matches what hermit-rs already emits:
svc #0; nr in x8; args x0..x5; return in x0.
Completes the common-os process model on AArch64: * paging.rs: do_cow_fault(virt) walks down to the L3 entry of a user-space write fault, dec/incs the per-frame refcount, copies the page if shared (talc-allocated) or just flips RO->RW + clears COW_MARKER if exclusively owned, and invalidates the single TLB entry via `tlbi vale1is`. flush_tlb_one helper added alongside. * interrupts.rs: do_sync's DataAbortLowerEL/CurrentEL branch now decodes ESR_EL1.ISS (DFSC, WnR), routes write+permission faults to do_cow_fault, and on unhandled faults prints a human-readable reason via a new dfsc_kind() table. A synthetic IRQ slot PAGE_FAULT_IRQ = 14 is registered in IRQ_NAMES so the page-fault count surfaces in print_statistics like on x86_64. * scheduler.rs: prepare_fork_child_stack mirrors the x86_64 helper but does not need a naked-asm child-entry stub — the trap frame pushed by trap_entry is already a complete EL0 return descriptor. The function copies the parent's kernel stack, snapshots the root page table, locates the equivalent State address in the child's copied stack, patches state.x0 = 0 (fork-returns-zero contract), and hands the child SP back to fork(). get_last_stack_pointer additionally installs the new task's root_page_table into TTBR0_EL1 with the full DSB ISHST / MSR / ISB / TLBI VMALLE1IS / DSB ISH / ISB sequence (ARM ARM D8.13.2). Without this the scheduler would leave the previous process's PT active and a freshly-forked child would run in its parent's address space. * mod.rs: set_user_tpidr_el0(value) updates the live register *and* patches the saved trap frame's tpidr_el0 field, so the value installed by load_application survives trap_exit. Used by both the TLS and the no-TLS branch of load_application; the latter resets TPIDR_EL0 to 0 so a TLS-less exec() does not inherit the previous program's thread pointer. scheduler/mod.rs gates fork() on common-os (was x86_64+common-os), syscalls/tasks.rs likewise for sys_waitpid, and syscalls/table.rs registers SYSNO_FORK/WAITPID for both architectures.
In the case of a monolithic kernel, the "GLOBAL" flag is set in the page table entries for the kernel space. This prevents TLB entries from being flushed for the kernel, because the kernel is always mapped to the address space of any application.
The Heap struct and the user-mode page-fault path that lazily mapped heap pages are no longer needed; user heap pages are mapped eagerly. Drops Task::heap, parent_heap propagation through fork()/new_thread(), and the corresponding USER_MODE branch in the x86_64 #PF handler.
- Move allocate_thread_tls into arch::mm so each arch implements its
own TLS variant (x86_64: Variant II, aarch64: Variant I via TPIDR_EL0).
- Add Task::create_user_stack_frame on aarch64 to craft the initial
trap frame; the existing trap_exit machinery eret's straight into EL0.
- Mark the user stack USER_ACCESSIBLE under common-os so a freshly
spawned thread can touch its own stack.
- Treat an EL0 instruction abort at PC=0 (entry wrapper returning into
zeroed LR) as a clean thread exit, mirroring the x86_64 behaviour.
- Generalise spawn_thread / NewTask::tls_base and route sys_spawn{,2}
through spawn_thread on aarch64 too.
copy_current_root_page_table only inc'd PAGE_REFCOUNTS for entries carrying the COW marker, but mark_user_pages_copy_on_write skips read-only pages (text, rodata). The child's clear_user_space then dec'd every USER_ACCESSIBLE entry, dropping the refcount to zero and freeing frames the parent still mapped — a use-after-free of the parent's text segment. Inc for every PRESENT|USER_ACCESSIBLE entry so the recorded refcount matches the number of address spaces actually pointing at the frame. Same fix on aarch64 and x86_64.
jump_to_user_land never returns — `eret`/`iretq` transfer to EL0 and any owned heap allocation on the kernel stack is leaked forever. Switch the argv parameter from a borrowed slice to an owned Vec<&str> and drop it explicitly after argv is materialised on the user stack but before the speculation-fenced eret. Mirrors the existing drop(elf)/drop(buffer)/drop(file) pattern in boot_image::loader.
Adds VirtualMemoryArea (start, end, protection) and a per-address-space
BTreeMap<VirtAddr, VMA> as Task::vmas. The map is propagated through
NewTask::from, PerCoreScheduler::{spawn,spawn_thread} and fork() so
threads share their parent's VMA list while a fresh process starts
empty and a forked child gets a deep-copied snapshot.
The new sys_mmap (SYSNO 57) supports two shapes: with a null `ret` it
creates a fresh anonymous VMA at HEAP_START_ADDR for the initial user
heap; with a non-null `ret` it extends the predecessor VMA in place,
bounded by the next VMA's start. No frames are mapped here — that is
handled lazily on the page-fault path.
load_application now records one VMA for the [LOADER_START .. LOADER_START + code_size) range (RWX, matching what's actually mapped) and a second one for the per-process TLS region (RW). With these in place the page-fault handler can later resolve faults in user-loaded binaries against the VMA table. The x86_64 path takes the long way around two VirtAddr newtypes: this file imports memory_addresses::VirtAddr, but Task::vmas is keyed by x86_64::VirtAddr. Convert at the insert boundary with .into() — the two are structurally identical, so the conversion is free at runtime.
On a user-mode translation fault (no existing mapping, no COW marker) walk the current task's VMA tree, allocate a fresh frame and map it with permissions derived from VMA::prot. This is what makes anonymous sys_mmap regions actually faultable — the BTreeMap entry alone reserves VA space, the first access pulls in a frame. The x86_64 sibling additionally restores user GS via `swapgs` before returning. The COW path was already doing this; the new VMA branch forgot it, so iret left the kernel GS-base installed and the next FS/GS access in ring 3 read kernel memory — rusty_demo would freeze just after printing "Arguments:" because env::args() touches TLS.
Task::{last,user}_stack_pointer is x86_64::VirtAddr; TaskStacks::get_*()
returns memory_addresses::VirtAddr. The two newtypes have identical
layouts but the type system rejects mixing them. Add .into() at the
two assignment sites in create_user_stack_frame and create_stack_frame.
HEAP_START_ADDR sat at 0x7100_0000_0000, which falls into L0[226] — outside the slot that aarch64's mark_user_pages_copy_on_write, copy_current_root_page_table and clear_l0 actually walk. The latter three only touch `USER_L0_INDEX = LOADER_START >> 39 = 2`, so the heap was effectively a *shared* mapping: parent and child were reading and writing the same physical frames. The fork_bench warmup hit this immediately — the child's first user-space write (`*pid = 0`) overwrote the parent's `pids[1]`, the parent then called `waitpid(0)`, queued on the idle task, and hung. Move HEAP_START_ADDR to 0x0140_0000_0000, well above any loaded LOADER image but still inside L0[2]. The constant remains canonical on x86_64 (bit 47 = 0). Also drop a stray Errno typo (`Nomems` → `Nomem`) the change uncovered, and tidy the riscv / x86_64 VirtAddr import order while we're here.
When the VMA-aware page-fault handler maps a fresh frame for an
anonymous user mapping, FrameAlloc can hand back a recycled frame
whose previous contents are stale. The user code then sees garbage
where it expects zero — most visibly in std startup paths where
allocator metadata, futex words and once-flags decide control
flow. Zero the page through its new VA before returning.
In addition, register the frame with the COW refcount table so a
subsequent fork() can correctly track sharing. Without this, the
child's clear_l0/clear_user_space would `frame_ref_dec` a frame
that was never `inc`'d, hit the "no refcount entry" warn path, and
miss a free.
Symmetric change in the aarch64 do_sync data-abort path and the
x86_64 #PF handler. Both gate frame_ref_inc on `feature = "fork"`
so the refcount table only exists when fork is enabled.
clear_user_space is called from sys_exec right before the new ELF
is loaded, and is supposed to leave the user side of the address
space in a "freshly created task" state. Two pieces were missing:
1. The VMA list still contained the old image's entries
(LOADER_START code segment, TLS region, mmap'd heap). The new
load_application would then either tip over inserts that
overlapped existing ranges, or — worse — the heap VMA would
survive while its backing L0 slot had just been wiped,
leaving the next sys_malloc to see a stale mapping.
2. File descriptors > 2 were inherited across exec. POSIX-style
behaviour is to keep stdin/stdout/stderr and close everything
else; emulate that by retaining `k <= STDERR_FILENO`.
Done symmetrically on aarch64 and x86_64.
Three follow-ups to the recent VMA / fork work, two of them load-bearing for spawn correctness on aarch64: * aarch64: move USER_STACK into L0[USER_L0_INDEX]. It used to sit at 0x0840_0000_0000 - USER_STACK_SIZE (= L0[16]). create_new_root_page_table deep-copies only L0[USER_L0_INDEX] and inherits every other L0 entry verbatim, so the L1/L2/L3 chain backing L0[16] was shared between spawner and spawnee. The first paging::map in the child rewrote the parent's stack PTEs; the parent then returned from join() with a corrupted return address (PC=0x1, EC=0x22). Place USER_STACK at the top of L0[2] instead. * x86_64: stop padding the code allocation with USER_STACK_SIZE in load_application. The user stack is allocated separately in jump_to_user_land since the VMA split, so the addend just reserves and refcounts unused frames.
The kernel already gave every task its own `TaskId`, but had no
notion of "this thread belongs to that process" — `sys_getpid()`
returned the calling thread's `tid`, so every thread of a
multi-threaded program saw a different pid.
Carry a `pid` on every `Task` (common-os only):
* Main thread of a new process (`Task::new`, `Task::new_idle`,
`Task::new_fork`): `pid = tid`.
* Sibling thread (`Task::new_thread`): `pid` inherited from
the spawning thread.
* `NewTask::thread_of` is widened from
`Option<Arc<RootPageTable>>` to
`Option<(Arc<RootPageTable>, ProcessId)>` so the parent's
pid travels with the page table; `spawn_thread` captures
it from the current task.
The syscall-return path used to execute, in order:
swapgs ; GS_BASE = user GS, KERNEL_GS_BASE = per-CPU
mfence
cli
That leaves a window between `swapgs` and `cli` where GS is
already the user-mode value but CS is still kernel. An
interrupt that hits inside this window enters the handler with
the kernel CS selector, so the swapgs guard
(`stack_frame.code_segment != SegmentSelector(8)`) decides
"already kernel, don't swap" — yet GS_BASE is the user's. Every
`gs:`-relative per-CPU access in the handler then targets the
user's GS area: garbage data at best, #GP / #PF at worst, and
sporadic because it depends on whether an IRQ lands in those
few cycles.
Reorder to `cli; mfence; swapgs;` so interrupts are masked
before the GS flip. The mfence stays between for the same
ordering reasons it did originally.
The per-CPU `user_stack` slot was the only place a task's user RSP
lived during a syscall, so any other task running a syscall while
the first was blocked (e.g. a parent in `waitpid` while its forked
child ran exec + several syscalls) clobbered it. When the blocked
task resumed, `mov rsp, gs:[user_stack]` loaded a foreign RSP and
`sysretq` faulted (PF/#UD) at a random address in some other task's
user stack.
Stash the user RSP onto the per-task kernel stack at syscall entry
(after the 14 saved registers, using `rcx` as scratch since it was
just pushed) and pop it back through the per-CPU slot under `cli`
on the return path. The per-CPU slot becomes a transient between
syscall entry and the push, plus between the pop and `sysretq` —
windows in which no other task can run.
`fork_child_start` previously reconstructed the user RSP from the
per-CPU slot too. Switch it to a per-task slot: write the user RSP
into the child's kernel stack `MARKER_SIZE` pad at `kernel_top - 8`
inside `prepare_fork_child_stack` (via `stash_user_rsp_in_child_stack`)
and load it from there before `sysretq`. Use `rsp` itself as the
scratch register for the two-step `[gs:{kernel_stack} + 8]` load so
`rax` (the child's `fork()` return value, set to 0) is preserved —
otherwise the child returned from `fork()` with a kernel pointer and
the userspace `try_into::<i32>().unwrap()` panicked.
Drop the now-unused `get_kernel_stack_top` helper.
* arch::{aarch64,x86_64}::load_application: replace the identical
12-line `if env::is_uhyve()` / else block that installed Uhyve-
vs. console-backed stdio with a single `fd::stdio::setup()` call,
so both architectures share one source of truth.
* Hoist commonly-aliased imports: `size_of` is in the prelude now,
`Arc`, `paging`, and the socket sys_* names are introduced once
per module instead of being requalified at every call site.
`syscalls/table.rs` shrinks accordingly.
* aarch64/mm/paging: replace `#[expect(dead_code)]` markers on
`is_table_or_4kib_page` and `get_page_table_entry` with the real
`#[cfg(all(feature = "common-os", feature = "fork"))]` gate that
matches the only configuration where they are reachable. Drop
`get_application_page_size` (no remaining callers).
* syscalls/entropy: lower the naive-fallback message from `warn!`
to `trace!`. It fires on every syscall when the host exposes no
entropy source and was drowning out everything else.
Route all scheduler/mm/task accesses through explicit arch::kernel and arch::mm paths instead of the re-exported arch::core_local and arch::* shortcuts. Split prepare_fork_child_stack (kernel) from prepare_mem_copy_on_write (mm) at the fork call site accordingly. Re-export prepare_fork_child_stack, BasePageSize/PageSize and clear_user_space for both arches, and gate the common_os module on aarch64 as well so common-os/fork builds there.
Reformat long expressions (VMA inserts, page-table walks, error messages) to rustfmt line-wrapping, normalize indentation in clear_user_space, and regroup/sort imports (module- vs. function-local, std/crate blocks). Reorder pub-use reexports in arch/mod.rs and fix the missing trailing newline. No functional change.
There was a problem hiding this comment.
Benchmark Results
Details
| Benchmark | Current: bc8fb70 | Previous: 2e23902 | Performance Ratio |
|---|---|---|---|
| startup_benchmark Build Time | 78.52 s |
80.34 s |
0.98 ❗ |
| startup_benchmark File Size | 0.80 MB |
0.80 MB |
1.00 ❗ |
| Startup Time - 1 core | 0.73 s (±0.02 s) |
0.75 s (±0.02 s) |
0.98 |
| Startup Time - 2 cores | 0.75 s (±0.02 s) |
0.74 s (±0.02 s) |
1.03 |
| Startup Time - 4 cores | 0.76 s (±0.01 s) |
0.74 s (±0.02 s) |
1.02 |
| multithreaded_benchmark Build Time | 78.45 s |
82.11 s |
0.96 ❗ |
| multithreaded_benchmark File Size | 0.89 MB |
0.86 MB |
1.03 ❗ |
| Multithreaded Pi Efficiency - 2 Threads | 86.92 % (±7.01 %) |
85.89 % (±6.61 %) |
1.01 |
| Multithreaded Pi Efficiency - 4 Threads | 43.18 % (±2.46 %) |
43.43 % (±2.56 %) |
0.99 |
| Multithreaded Pi Efficiency - 8 Threads | 25.29 % (±1.43 %) |
25.76 % (±1.53 %) |
0.98 |
| micro_benchmarks Build Time | 75.90 s |
80.40 s |
0.94 ❗ |
| micro_benchmarks File Size | 0.89 MB |
0.86 MB |
1.03 ❗ |
| Scheduling time - 1 thread | 63.99 ticks (±2.31 ticks) |
62.65 ticks (±4.06 ticks) |
1.02 |
| Scheduling time - 2 threads | 36.06 ticks (±5.29 ticks) |
34.08 ticks (±4.10 ticks) |
1.06 |
| Micro - Time for syscall (getpid) | 4.24 ticks (±0.64 ticks) |
3.45 ticks (±0.58 ticks) |
1.23 |
| Memcpy speed - (built_in) block size 4096 | 85453.08 MByte/s (±59163.44 MByte/s) |
82448.38 MByte/s (±56997.13 MByte/s) |
1.04 |
| Memcpy speed - (built_in) block size 1048576 | 30651.34 MByte/s (±24654.21 MByte/s) |
30585.98 MByte/s (±24707.84 MByte/s) |
1.00 |
| Memcpy speed - (built_in) block size 16777216 | 28879.03 MByte/s (±23723.07 MByte/s) |
26340.06 MByte/s (±21720.96 MByte/s) |
1.10 |
| Memset speed - (built_in) block size 4096 | 85592.58 MByte/s (±59265.27 MByte/s) |
82292.76 MByte/s (±56891.50 MByte/s) |
1.04 |
| Memset speed - (built_in) block size 1048576 | 31355.43 MByte/s (±25073.42 MByte/s) |
31323.85 MByte/s (±25145.86 MByte/s) |
1.00 |
| Memset speed - (built_in) block size 16777216 | 29627.01 MByte/s (±24157.99 MByte/s) |
27104.68 MByte/s (±22209.94 MByte/s) |
1.09 |
| Memcpy speed - (rust) block size 4096 | 74788.94 MByte/s (±52154.23 MByte/s) |
74097.96 MByte/s (±51811.44 MByte/s) |
1.01 |
| Memcpy speed - (rust) block size 1048576 | 30407.01 MByte/s (±24577.31 MByte/s) |
30361.60 MByte/s (±24602.37 MByte/s) |
1.00 |
| Memcpy speed - (rust) block size 16777216 | 28964.10 MByte/s (±23770.92 MByte/s) |
27625.34 MByte/s (±22806.88 MByte/s) |
1.05 |
| Memset speed - (rust) block size 4096 | 75098.62 MByte/s (±52350.58 MByte/s) |
74373.47 MByte/s (±51976.48 MByte/s) |
1.01 |
| Memset speed - (rust) block size 1048576 | 31140.42 MByte/s (±25002.30 MByte/s) |
31110.89 MByte/s (±25033.24 MByte/s) |
1.00 |
| Memset speed - (rust) block size 16777216 | 29687.99 MByte/s (±24180.31 MByte/s) |
28386.93 MByte/s (±23265.03 MByte/s) |
1.05 |
| alloc_benchmarks Build Time | 75.13 s |
74.76 s |
1.00 ❗ |
| alloc_benchmarks File Size | 0.88 MB |
0.87 MB |
1.00 ❗ |
| Allocations - Allocation success | 91.31 % |
91.31 % |
1 |
| Allocations - Deallocation success | 100.00 % |
100.00 % |
1 |
| Allocations - Pre-fail Allocations | 61.44 % |
61.44 % |
1 |
| Allocations - Average Allocation time | 5819.46 Ticks (±373.25 Ticks) |
5860.58 Ticks (±98.43 Ticks) |
0.99 |
| Allocations - Average Allocation time (no fail) | 6612.43 Ticks (±321.90 Ticks) |
6554.81 Ticks (±92.86 Ticks) |
1.01 |
| Allocations - Average Deallocation time | 2126.09 Ticks (±250.42 Ticks) |
1805.01 Ticks (±250.35 Ticks) |
1.18 |
| mutex_benchmark Build Time | 75.14 s |
79.82 s |
0.94 ❗ |
| mutex_benchmark File Size | 0.89 MB |
0.86 MB |
1.03 ❗ |
| Mutex Stress Test Average Time per Iteration - 1 Threads | 12.10 ns (±0.30 ns) |
12.10 ns (±0.41 ns) |
1 |
| Mutex Stress Test Average Time per Iteration - 2 Threads | 40.86 ns (±2.20 ns) |
40.26 ns (±1.68 ns) |
1.01 |
This comment was automatically generated by workflow using github-action-benchmark.
2a6e6c7 to
1592ef6
Compare
jump_to_user_land takes the argument vector and the environment as owned CStrings and places both as NULL-terminated pointer arrays on the user stack; envp is handed over in rdx (x86_64) / x2 (aarch64). argc, argv, and envp are bound as fixed register operands in the inline assembly. With plain in(reg) operands, the register allocator is free to hand rdi/rsi/rdx (x0-x2) to other operands, which the mov sequence then clobbered before eret/iretq. The weak symbols sys_spawn_process and sys_exec take optional NULL-terminated argv/envp arrays; the loader provides the actual implementation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In principle, this PR add options to support a common
forkand a system call, which is comparable toposic_spawn.The original code was developed by @Vinc-F I revised the code, rebase it to the current
mainbranch and remove some bugs,