Repository navigation
A thread carries its process's handles, and a lookup takes no lock but the table's - #1246
Conversation
Review r1 of #1246 at c61225bArchitecture. This is Step A as approved.
Call-site audit: no lost invariant found.
Blockers
Evidence
What metal must show
SEND BACK. This is for the evidence and the rebase only; no code change is requested beyond the rebase. Once those are done, it can land when |
…t the table's Every handle-taking syscall went through `process::with_process_data`: the machine-wide `PROCESS_TABLE` lock, a walk of the process table, an `Arc` clone of the process's data, and that data's lock, which also guards its accounting, space, endowments and job. Every thread of every process serialized on the one table lock for each lookup (pipeperf: 60 ns on the T14 and 33 ns on the AMD bench per `pread` before the read itself). The handle table now sits behind a lock of its own in `process::Handles`, which each thread's scheduler record carries beside its address space and image, set at spawn and at thread creation. `process::with_handles` reads it off the running task and takes only the table's lock. `ProcessData` keeps an `Arc` to it for readers outside the process: teardown's `close_all`, which still empties it in place when the last thread leaves, and the device-claim inventory. The pager role moves with it. A served file's read (`SYS_PAGED_READ`) refuses a pager's process, so the role has to be readable in the same place as the holder's handle, or the read would take the process table again: whether the process was started as a pager is a plain field, and whether it made its pager object an atomic claim, which replaces `PagerRole`. `sys_job_own` and a spawn's commit read the job off `ProcessData` before they take the table's lock rather than under one lock with it; the job never changes. Comments that named the process's lock as the one a lookup holds now name the table's, and `kernel/CLAUDE.md`'s park rule names `with_handles`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
c61225b to
6d9c867
Compare
|
Response to review r1, head 6d9c867.
No code change beyond the rebase. 🤖 Generated with Claude Code |
Review r2 of #1246 at 6d9c867 (follow-up to r1 at c61225b)Scope. This round checks the r1 blockers and the rebase. The design and the call-site audit are settled. Architecture. Unchanged since r1: Step A as approved, with the pager role moved alongside the handles. r1 blockers
Evidence validity
FindingsNone. What must arrive before landing
LAND, pending the guest runs and metal results above. |
Both benches are down. #1246 lands on its review, host checks and guest rows, and `issues/a-memory-filesystem-call-costs-toyos-up-to-35-times-linuxs.md`, the record of the served-file reads it targets, records what only metal can establish: the T14's whole `testcases`, `testcases-watchdog` and `shared-debug` boots of main after it, and the A/B of main before and after it on both benches with the Linux comparison. Its speed-up is not yet measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Orchestrator: landing under the benches-down rule. Review r2 LAND. Head 9619ad2 is 6d9c867 plus one record (verified); 6d9c867's patch equals c61225b's but for one import main removed. Guest runs 43064648373 (x86, c61225b) and 43065189798 (aarch64 |
…o longer uses #1241 deleted the watch's gate model and this change deleted the two-submitter model. Those were `loom_watch`'s two uses of `CPU1`, so after both the import is unused, and `cargo clippy -p kernel-loom --all-targets` refuses it. The rebase's other resolutions are in the commits it replays: `resolve` and `ThisProcess` look up through #1246's `with_handles`, and the watch imports `poll::Via` beside main's `Arc` and `CellLock`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ved-write pieces Main moved 64 commits. Four files conflicted, and one call stopped compiling: - kernel/src/object/paged.rs: #1245's read returns once a copy reaches the read's end. Here that return sits where no write met the read, before it reads again. - kernel/src/syscall/paged.rs: #1246 looks a holder up under the handle table's own lock. The lookup keeps that form and checks the right each call needs (READ for a read and a statement, WRITE for a write). - lib/bcachefs/src/engine/tests/data.rs and the memory filesystem's record: both sides added beside each other, and both are kept. - #1216 split write_run into span, fill, seal and point_extent, and gave reserved writes their own planned path, dropping the reserved sector write_run took. The placed pool's writes take the sector their pool holds again: write_run has the optional placement it had, now over main's pieces, and the placed write checks the volume is writable as a reserved write does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
Every handle-taking syscall resolved its handle through
process::with_process_data: the machine-widePROCESS_TABLElock, a walk of the process table, anArcclone of the process'sProcessData, and thatdata's lock, which also guards the process's accounting, space, endowments and job. Every thread of every
process serialized on the one table lock for each lookup. pipeperf measured the lookup at 60 ns on the T14 and
33 ns on the AMD bench per
pread(SYS_PAGED_READ, every served file's read), before the read itself.Resulting behavior
process::Handles), and each thread's scheduler recordcarries it (
KernelPayload::handles), beside its address space and image, set at spawn and at threadcreation.
process::with_handlesreads it off the running task and takes only the table's lock: noPROCESS_TABLE, no table walk, noProcessDatalock.ProcessDatakeeps anArcto it for readers outside the process: teardown'sclose_all(which stillempties the table in place when the last thread leaves, so a thread record outliving it finds an empty
table) and the device-claim inventory.
with_process_datatowith_handles; comments that named theprocess's lock as the one a lookup holds now name the table's;
kernel/CLAUDE.md's park rule nameswith_handles.Decisions
ProcessData's lock — with one addition. The pagerrole moves with it:
SYS_PAGED_READrefuses a pager's process, so the role must be readable where theholder's handle is, or the measured
preadpath would take the process table again. Whether the processwas started as a pager is a plain field (no call changes it); whether it made its one pager object is an
atomic claim. This replaces
PagerRole; the semantics ofSYS_PAGER_CREATE(PermissionDenied,AlreadyExists, the claim given back when the install fails) are unchanged.sys_job_ownand a spawn's commit read the job offProcessDatabefore taking the table's lock, notunder one lock with it; the job never changes. No call site holds both locks.
Arcoff the running task (driver::current_handles, ascurrent_address_spacedoes) and takes its lock: two atomic RMWs on the line the lock's word shares. Aborrow without the refcount is possible (the running task's record keeps the
Arcuntil it is released,after it last leaves its CPU) but needs an
unsafedereference; left out until a measurement says the twoRMWs matter.
Checks
Targeted per the testing policy. The branch is rebased onto main
b0533900e(head6d9c8671c); the rebaseresolved #1211's kernel-stack changes in
process.rs,loader/start.rsandscheduler.rs(main droppedOwnedAllocandwrite_stack_canary; nothing of this change moved). The kernel binary (kernel/src) has nohost tests; nothing host-tested changed (
kernel/pure,kernel/proclifeand the loom models are untouched).6d9c8671ccargo clippy -p kernel --target x86_64-unknown-nonewithbuild/clippy.rs's adopted lints,-D warnings: default,boot-actuators,test-actuators,boot-actuators, and the instrument setaarch64-unknown-none-softfloat: default,boot-actuators,test-actuators, the AArch64 instrument setGuest rows:
No test matches filter "handle_lifetime"before building a guest:fourteen of its fifteen names are shared-boot members, which guest rows do not select. It established
nothing.
c61225bca(the same patch before the rebase; only one import line differs):kernel_objects_refused_alone,std_threads_count,libc_children,a_spawn_from_an_image,stream_holders,session_lifecycle,virt_smp,virt_user_mode. Queued.6d9c8671c:virt_jobs_at_el2. Queued.The head after
6d9c8671cadds an issue record only.Owed on metal
Both benches are down, so this lands on its review, host checks and the guest rows above. The speed-up is
not yet measured on metal.
issues/a-memory-filesystem-call-costs-toyos-up-to-35-times-linuxs.mdrecordswhat is owed:
testcases,testcases-watchdogandshared-debugboots of main after this PR, everymember passing, among them
handle_lifetime,abuse_handle_table,abuse_thread_table,messages_are_whole,stream_handles, the threespawn_*actuator members,endowment_denied,a_served_file_reads_through_its_paged_object,a_pager_s_end_answers_its_waiting_holders_gone,std_threads,inbox_cancel_wakesandport_badge;osbench files,ipc,time,sched; four boots a side, rotated; exact two-sided Mann-Whitney over per-boot p50s), and Linux'sosbench filesandipcon each bench. Claimed:fs.mem.pread_8,fs.mem.pread_4k,fs.mem.direct.pread_4k,fs.mem.read_4k,fs.mem.read_4k_at,fs.mem.direct.read_4k_at,ipc.reqrep.8and
ipc.reqrep_handle.8lower or not separated, each set against the 60 ns (T14) and 33 ns (AMD) lookup;no other row 5% or more worse unless this change explains it. A missing gain does not reject this change;
it decides whether Step B is worth building.
Risks
released as well as its table entry; it is charged to its job until then, as before.
🤖 Generated with Claude Code