/dev/tty1..tty6, Alt+F1..F6 or `chvt N`. Each is an independent terminal --
its own screen, cursor, line discipline, foreground process group and
session. Two shells on two terminals are unrelated processes that share a
keyboard and a screen.
What made it possible is a backing store. The console drew straight into the
framebuffer and scrolled by moving pixels, which works exactly as long as
nothing else wants the screen: there was nowhere to put terminal 2 while
terminal 1 was visible, and nothing to repaint terminal 1 from. Each terminal
owns a grid of cells now and the framebuffer shows whichever grid is active,
so vt_switch() is set-index, repaint, return -- nothing is saved on the way
out because the outgoing grid has been current all along.
The rule the layer is built on: a terminal does not know whether it is
visible. Writes land in the grid, the cursor moves, the grid scrolls, all
unconditionally; drawing is one question asked at the bottom of vt.c. Every
alternative spreads "am I on screen" through the escape parser, and then a
terminal switched away from mid-sequence comes back wrong.
Everything that was a single global is per-terminal now: tty_fg_pgrp (deleted),
termios, the input ring, the escape parser, the UTF-8 decoder, and the cursor
-- which also stops being pixels in gfx.c and becomes cells in the terminal.
TIOCSCTTY was `return 0` and now records proc->ctty, which is what /dev/tty
resolves through; /dev/tty was an alias for /dev/console with the same minor,
and they are different questions.
kbd.c names Alt+F1..F6 as KEY_ALT_F1+n, above 0xFF on purpose -- every byte
value is already a character or a UTF-8 continuation, and a chord colliding
with one would be typed into the terminal instead of switching away from it.
What the chord means is decided in console.c, where the terminals are.
VT_ACTIVATE / VT_WAITACTIVE / VT_GETSTATE / VT_OPENQRY, Linux's numbers.
The argument to ACTIVATE is by value, not a pointer -- same shape as TCFLSH.
Two bugs found by screenshotting the result rather than reading the log:
- zsh emits 0x0F to leave alt-charset mode, because that is what the `linux`
terminfo says rmacs is, and the console drew CP437 glyph 0x0F: a sun, in
the prompt, on every line. A terminal never draws a C0 control character.
- SGR 7 was unimplemented, so zsh's partial-line marker rendered as a bare
'#' instead of a reversed block. Reverse video swaps the colours at draw
time, not at store time, so turning it off restores what was underneath.
Verified by screendump: tty1 and tty2 show different screens, and switching
back repaints tty1's earlier content from its grid. schfs-check passes.
docs/architecture/terminals.md, including what it does not do -- no
scrollback, no reflow, no VT_PROCESS mode for a display server to veto a
switch, which Schcom will need before it owns Alt+F7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
106 lines
5.6 KiB
Markdown
106 lines
5.6 KiB
Markdown
# The system, layer by layer
|
|
|
|
What SchOS is today. Everything in this directory is built and running; what is
|
|
merely intended lives in [../plan/](../plan/).
|
|
|
|
---
|
|
|
|
## The names, and what each one covers
|
|
|
|
`uname -s` reports `SchIFT`, the way every Unix reports its kernel rather than
|
|
its distribution.
|
|
|
|
| Layer | Name | What it is |
|
|
|---|---|---|
|
|
| System | **SchOS** | The distribution — kernel, userland, tooling |
|
|
| Kernel | **SchIFT** | Multi-core x86-64 kernel with a modular VFS |
|
|
| Filesystem | **SchFS** | Transactional, copy-on-write, the root filesystem |
|
|
| API | **SchAPI** | The native programming interface — *designed, not built* |
|
|
| Compositor | **Schcom** | Owns the display; v0 runs |
|
|
|
|
Six virtual terminals sit under all of it -- `/dev/tty1`..`tty6`, Alt+F1..F6 --
|
|
each with its own screen, line discipline and foreground process group. See
|
|
[terminals.md](terminals.md).
|
|
|
|
---
|
|
|
|
## The stack, bottom to top
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────┐
|
|
│ toybox mksh musl first-party cmd/ │ userland
|
|
├─────────────────────────────────────────────────┤
|
|
│ schinit — PID 1, job manifests, supervision │
|
|
├─────────────────────────────────────────────────┤
|
|
│ syscall table — Linux numbering, one .inc file │ ← the ABI seam
|
|
╞═════════════════════════════════════════════════╡
|
|
│ VFS │ sockets │ signals │ mmap │ poll │ procfs │
|
|
├──────┴─────────┴─────────┴──────┴──────┴────────┤
|
|
│ SchFS │ lwIP │ text/gfx │ buffer cache │
|
|
├─────────┴────────┴────────────┴─────────────────┤
|
|
│ device model — class vtables, bus binding │
|
|
├─────────────────────────────────────────────────┤
|
|
│ drivers: xHCI e1000 virtio-gpu VBE IDE PS/2│
|
|
├─────────────────────────────────────────────────┤
|
|
│ scheduler │ paging │ traps │ IRQ │ SMP │ DMA │ SchIFT core
|
|
└─────────────────────────────────────────────────┘
|
|
▲
|
|
UEFI loader (boot/uefi)
|
|
```
|
|
|
|
The two seams that matter are the syscall table and the device model. Above the
|
|
first, software is ordinary and portable — musl and toybox are unmodified.
|
|
Below the second, a driver never names its consumer and a consumer never names a
|
|
driver.
|
|
|
|
---
|
|
|
|
## Where it came from
|
|
|
|
SchOS began as [xv6](https://github.com/mit-pdos/xv6-public) and is now mostly
|
|
something else. What survives from xv6 is the *shape* of the process table, the
|
|
sleep/wakeup discipline, and the buffer cache. What has been replaced: the
|
|
scheduler and trap path (rewritten for x86-64 and for more than one processor),
|
|
the memory manager, the entire filesystem, the syscall layer, and every driver.
|
|
|
|
That inheritance is worth knowing when reading the code, because the parts that
|
|
still look like xv6 follow xv6's conventions and the parts that do not, do not.
|
|
|
|
---
|
|
|
|
## The documents
|
|
|
|
Read in this order if you are new:
|
|
|
|
| | |
|
|
|---|---|
|
|
| [boot.md](boot.md) | Firmware to the first userspace process. The shortest path through the whole system. |
|
|
| [kernel.md](kernel.md) | Memory, processes, scheduling, traps, signals, SMP. |
|
|
| [syscalls.md](syscalls.md) | The one table everything is generated from, and how a musl binary runs unmodified. |
|
|
| [filesystem.md](filesystem.md) | The VFS, the buffer cache, mounts, procfs. |
|
|
| [schfs.md](schfs.md) | The copy-on-write root filesystem. |
|
|
| [devices.md](devices.md) | Buses, classes, binding, and the vtables consumers talk to. |
|
|
| [usb.md](usb.md) | xHCI, the USB core, and the three class drivers. |
|
|
| [graphics.md](graphics.md) | Framebuffer, text rendering, the console, and PSOGM. |
|
|
| [networking.md](networking.md) | NIC drivers, lwIP, and both socket families. |
|
|
| [init.md](init.md) | `schinit` and the job manifests. |
|
|
|
|
---
|
|
|
|
## Three rules that hold across all of it
|
|
|
|
**The kernel and the host tools compile the same SchFS source.** `mkschfs`
|
|
cannot disagree with the kernel about the on-disk format, because there is only
|
|
one implementation. Format bugs are found on the host, where a test takes a
|
|
second instead of a boot.
|
|
|
|
**Syscalls are declared in exactly one place.** `kernel/sys/table.inc`.
|
|
The constants, the dispatch table and the Linux translation map are all
|
|
generated from it, so a syscall cannot be half-registered.
|
|
|
|
**A list you can read beats a list assembled by side effects.** Drivers are
|
|
written out by hand in `kernel/drivers/registry.c` rather than collected by a
|
|
linker section, and platform devices are registered in one function rather than
|
|
inside each driver's init. The tradeoff is deliberate: self-registration is
|
|
nicer to write, and it means the list of what is in your kernel is not written
|
|
down anywhere you can read.
|