docs: README, plan, guides and the site describe what is built now
README: rewritten around what runs (Linux software, Xorg, apk, Linux's drivers), the honest gaps, measured numbers. plan/: 1.0.md is the finish line, where each clause of the acceptance test stands, and the register (one failing clause: a root that survives a reboot); built plans (foundations, hybrid, alpine, xorg) and the decided-against one (wayland) are gone, their content lives in architecture/; roadmap.md is only what is still open. guides: building/layout/testing/adding-a-driver (Linux code may be used). history: four entries (ls -lR, TLB shootdowns, MDIO 0x02460246, netlink MSG_TRUNC). Website: cards, stack, download page, docs index. Code comments that pointed at deleted plan docs point at the architecture docs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
parent
2556a955b3
commit
cb3d28e3d6
57 changed files with 685 additions and 2121 deletions
|
|
@ -298,17 +298,8 @@ exported entry points, `nameifrom()`/`nameiparentfrom()`. `sys_fs.c` gained
|
|||
Every `*at()` syscall now uses these instead of its own inline
|
||||
absolute-path/`AT_FDCWD`-only check.
|
||||
|
||||
**Known follow-on issue, not yet root-caused**: heavy `ls`/`ls -lR` use
|
||||
across many commands in one long-running shell session can eventually make
|
||||
a *later*, unrelated directory hang (reproduced with `ls -lR /var` after
|
||||
~10 prior commands in the same session; the identical command succeeds
|
||||
immediately on a fresh boot). Not an inode-table or file-table exhaustion
|
||||
— both `iget()` and `filealloc()` fail cleanly (panic / return 0
|
||||
respectively) when full, and neither fired. Smells like a lock or
|
||||
reference-count leak somewhere in the *at() paths above, or in toybox's
|
||||
own fd handling, that only manifests after enough accumulated opens/closes.
|
||||
Next step: instrument `itable`/`ftable` occupancy counts and watch them
|
||||
across a long `ls`-heavy session to see what's actually climbing.
|
||||
**Known follow-on issue** (heavy `ls -lR` in one long shell session eventually hanging a
|
||||
*later* directory): hunted, and not reproducible — see section 24.
|
||||
|
||||
## 6. Boot speed
|
||||
|
||||
|
|
@ -1255,3 +1246,86 @@ is at least as long as the width, and that the aperture holds that many rows.
|
|||
If not, it goes back to the previous mode and says so in the menu. This proves
|
||||
nothing about a mode that passes, but it stops a lying one from reaching the
|
||||
kernel.
|
||||
|
||||
---
|
||||
|
||||
## 24. The `ls -lR` hang that was hunted and not found
|
||||
|
||||
**Symptom.** After about ten `ls`-heavy commands in one long shell session, a later and
|
||||
unrelated `ls -lR /var` would hang; the same command succeeded at once on a fresh boot. It
|
||||
was not inode-table or file-table exhaustion (both fail cleanly), and the first hypothesis
|
||||
was a lock or reference-count leak in the `*at()` paths.
|
||||
|
||||
**What was done.** `/proc/fsstat` was given the occupancy counters the hypothesis asked
|
||||
for (inode, file and buffer peaks, the SchFS volume lock and transaction owner), and the
|
||||
workload was made heavier than the report: ten rounds of `ls -lR /bin` interleaved with
|
||||
writes and `cd` — 33 execs and 13,378 tree descents. Nothing climbed:
|
||||
|
||||
inode_peak 9 / 200 file_peak 9 / 512
|
||||
buf_pinned_peak 1 / 256 schfs0 locked 0 tx_owner_pid 0 tx_depth 0
|
||||
|
||||
**Conclusion.** The hypothesis is eliminated, and the bug is very likely gone — fixed in passing
|
||||
by the `*at()` dirfd rewrite, the `getdents` cookie rewrite, or SchFS becoming the root, all of
|
||||
which landed after it was reported. It was closed rather than chased. The instrumentation
|
||||
stayed: if it happens again, `/proc/fsstat` names the table, or shows `locked 1` with
|
||||
`tx_owner_pid 0`, which is an orphaned volume lock.
|
||||
|
||||
Two real defects turned up on the way and were fixed, neither proven to be the cause:
|
||||
`holdingsleep()` never compared the owning pid, which disarmed all seven `panic()` assertions
|
||||
built on it; and SchFS's `tx_mine()` compared a bare `struct proc *`, which a recycled ptable
|
||||
slot makes ambiguous.
|
||||
|
||||
**The transferable part.** A hang you cannot reproduce still earns its instrumentation. The
|
||||
absence of a result *is* the result, and the right place to leave it is where the next person
|
||||
with the same symptom will look.
|
||||
|
||||
## 25. Epiphany's heap was corrupted by a page nobody had unmapped yet
|
||||
|
||||
**Symptom.** Epiphany (WebKit: forty threads on four processors) died at a different address
|
||||
on every run; musl's allocator was the first to notice, a group's metadata no longer pointing
|
||||
back at its group.
|
||||
|
||||
**Diagnosis.** Nothing was freed twice. One thread unmapped a range, `mmap` handed the same
|
||||
addresses straight back, and a thread on *another* processor went on reading and writing the
|
||||
**old** pages through its TLB: stores vanished, loads were stale. Deferring the free of the
|
||||
unmapped pages had already kept a stale translation from reaching a page someone else owned;
|
||||
it did not keep one address space *coherent*.
|
||||
|
||||
**Fix.** Unmapping from a shared address space interrupts the processors running it and waits
|
||||
until each has reloaded `CR3` (`tlb_shootdown()`, `kernel/arch/x86_64/vm.c`).
|
||||
|
||||
**The transferable part.** Threads turned an invisible assumption — one CPU's TLB is the only
|
||||
one that matters — into the loudest bug in the tree, in the one program that used the most
|
||||
threads. Memory corruption that moves every run is a coherence bug until proven otherwise.
|
||||
|
||||
## 26. Every MDIO read returned `0x02460246`
|
||||
|
||||
**Symptom.** Linux's `r8169` under LDLS, on the laptop, read its PHY's ID as `0x02460246`.
|
||||
|
||||
**Diagnosis.** `0x246` is a value of `RFLAGS`, not a register. `mdio-bus` keeps the word it
|
||||
has just read in `r11` across `preempt_enable()`, which on x86 is `decl %gs:__preempt_count; je`
|
||||
to a call that preserves every register. LDLS had given `__preempt_count` zeroes, so the count
|
||||
reached zero after the decrement and the call was made — into a logging stub whose `syscall`
|
||||
instruction leaves `RFLAGS` in `r11`.
|
||||
|
||||
**Fix.** `__preempt_count` starts at `PREEMPT_ENABLED` (x86 keeps `NEED_RESCHED` inverted), and
|
||||
`preempt_schedule` and its notrace twin are a bare `ret`.
|
||||
|
||||
**The transferable part.** A stub that returns zero is not safe when zero changes which path
|
||||
runs — the same family as the five in [../architecture/ldls.md](../architecture/ldls.md). The
|
||||
tell here was a value that was a plausible *flag word* rather than a plausible *register*.
|
||||
|
||||
## 27. GLib's network monitor spun at 100%
|
||||
|
||||
**Symptom.** Epiphany's main thread consumed a full core in `poll()` returning at once.
|
||||
|
||||
**Diagnosis.** GLib sizes each netlink datagram with `recvmsg(MSG_PEEK|MSG_TRUNC)` and no buffer.
|
||||
The kernel answered 0, GLib read 0 bytes, the datagram stayed queued, and `poll()` said readable
|
||||
again, for ever. A drained netlink socket also reported hangup, because `sock_hup` took "no peer"
|
||||
for "closed".
|
||||
|
||||
**Fix.** `recvmsg` on netlink peeks without consuming and with `MSG_TRUNC` returns what is waiting.
|
||||
|
||||
**The transferable part.** A kernel that accepts a flag and does nothing with it turns a
|
||||
well-formed program into a busy loop. Do it, or refuse it.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue