tty: six virtual terminals, and the plumbing that makes them terminals
/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
This commit is contained in:
parent
14f0e4d0e2
commit
1c1b0c9570
24 changed files with 1460 additions and 458 deletions
|
|
@ -18,6 +18,10 @@ its distribution.
|
|||
| 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
|
||||
|
|
|
|||
128
docs/architecture/terminals.md
Normal file
128
docs/architecture/terminals.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||
# Virtual terminals
|
||||
|
||||
Six of them, `/dev/tty1` through `/dev/tty6`, reached with **Alt+F1..Alt+F6**
|
||||
or with `chvt N`. Each one is an independent terminal: its own screen, its own
|
||||
cursor, its own line discipline, its own foreground process group and its own
|
||||
session. Two shells on two terminals are two unrelated processes that happen to
|
||||
share a keyboard and a screen.
|
||||
|
||||
Alt+F7 is deliberately unused. That is where a display server goes.
|
||||
|
||||
---
|
||||
|
||||
## What makes switching possible
|
||||
|
||||
A backing store, and nothing else.
|
||||
|
||||
Before this, the console drew straight into the framebuffer and scrolled by
|
||||
moving pixels. That works exactly as long as nothing else ever wants the
|
||||
screen: there was nowhere to put terminal 2 while terminal 1 was visible, and
|
||||
nothing to repaint terminal 1 from when it came back.
|
||||
|
||||
So each terminal owns a grid of cells and the framebuffer shows whichever grid
|
||||
is active. `vt_switch()` is three lines — set the active index, repaint from
|
||||
the grid, return — because nothing has to be *saved* on the way out. The
|
||||
outgoing terminal's grid has been current all along.
|
||||
|
||||
The rule the whole 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 of it
|
||||
unconditionally. Drawing is a separate question, asked once, at the bottom of
|
||||
`kernel/dev/vt.c`, answered by comparing against `vt_active`. Every alternative
|
||||
spreads "am I on screen" through the escape parser, and then a terminal
|
||||
switched away from mid-sequence comes back wrong.
|
||||
|
||||
## A cell
|
||||
|
||||
Four bytes:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `ch` | the glyph the font draws — **not** a codepoint |
|
||||
| `fg`, `bg` | palette indices, 0-15 ANSI plus two theme defaults |
|
||||
| `attr` | reverse video; room for underline and the rest |
|
||||
|
||||
`ch` stores the glyph rather than the codepoint because the console maps UTF-8
|
||||
to CP437 before it gets here, and that is what `include/font.h` contains.
|
||||
Storing the codepoint would mean re-running the mapping on every repaint and
|
||||
getting a different answer for anything the font cannot draw.
|
||||
|
||||
The colours are indices rather than RGB because a cell holding two 32-bit
|
||||
colours would be twelve bytes instead of four, and the grids are static: six
|
||||
terminals at 240x75 is 432 KB of BSS, paid once at link time rather than failed
|
||||
to pay at run time. There is no contiguous multi-page allocator to ask.
|
||||
|
||||
## What is per-terminal
|
||||
|
||||
Everything that used to be a single global:
|
||||
|
||||
| Was | Now |
|
||||
|---|---|
|
||||
| `int tty_fg_pgrp = 1;` | `v->fg_pgrp`, one per terminal |
|
||||
| one `struct termios tio` | `v->tio` |
|
||||
| one 256-byte input ring | `v->in` |
|
||||
| one escape-parser state | `v->esc_*` |
|
||||
| one UTF-8 decoder | `v->u8_*` |
|
||||
| one cursor, in pixels, in `gfx.c` | `v->cx`, `v->cy`, in cells |
|
||||
|
||||
`TIOCSCTTY` used to be `return 0` — it claimed to make a terminal the caller's
|
||||
controlling terminal and did nothing, because there was one terminal and
|
||||
nothing to choose between. It records `proc->ctty` now, which is what
|
||||
`/dev/tty` resolves through.
|
||||
|
||||
## The devices
|
||||
|
||||
| Path | Minor | What |
|
||||
|---|---|---|
|
||||
| `/dev/console` | 1 | the **active** terminal, so kernel messages follow a switch |
|
||||
| `/dev/tty` | 10 | the caller's **controlling** terminal, via `proc->ctty` |
|
||||
| `/dev/tty1`..`tty6` | 11-16 | that terminal, whether or not it is on screen |
|
||||
|
||||
`/dev/tty` was an alias for `/dev/console` with the same major and minor. They
|
||||
are different questions and now have different answers.
|
||||
|
||||
## The ioctls
|
||||
|
||||
Linux's numbers, because they do Linux's job.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `VT_ACTIVATE` | switch. Argument is a 1-based number **by value**, not a pointer |
|
||||
| `VT_WAITACTIVE` | returns immediately: `vt_switch` repaints synchronously, so there is nothing left to wait for |
|
||||
| `VT_GETSTATE` | active terminal and a bitmask of those in use |
|
||||
| `VT_OPENQRY` | the first terminal nobody has open |
|
||||
| `VT_GETMODE`/`VT_SETMODE` | accepted and ignored — the kernel drives switching and there is no process mode to set |
|
||||
|
||||
## The keyboard
|
||||
|
||||
`kbd.c` recognises Alt+F1..F6 and returns `KEY_ALT_F1 + n`, which is above
|
||||
0xFF on purpose: every value that fits in a byte is already either a character
|
||||
or a UTF-8 continuation byte, and a chord that collided 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. The
|
||||
keyboard driver names it and stops.
|
||||
|
||||
Input goes to whichever terminal is on screen. That is what makes a terminal a
|
||||
terminal: the keyboard follows the eyes.
|
||||
|
||||
## What it does not do
|
||||
|
||||
- **No scrollback.** The grid is exactly the visible screen. Shift+PageUp does
|
||||
nothing because there is nothing above the top row to show.
|
||||
- **No reflow on resize.** The geometry is read once at `vt_init()`. Nothing
|
||||
resizes a framebuffer console today; a compositor will, and that is when this
|
||||
has to grow.
|
||||
- **No process-mode switching.** `VT_SETMODE` is accepted and ignored. A
|
||||
display server that wants to veto a switch — which is what `VT_PROCESS` is
|
||||
for — cannot yet, and will need to before Schcom owns Alt+F7.
|
||||
- **No terminal past 6.** Linux allows 63. Six is what the chords reach and
|
||||
what the grids are sized for.
|
||||
- **Only 240x75 of any screen.** Larger framebuffers work; the grid is clamped
|
||||
and the remainder stays background. Raising `VT_MAXCOLS`/`VT_MAXROWS` is the
|
||||
fix, and costs BSS.
|
||||
- **No UTF-8 the font lacks.** CP437 has ~256 glyphs. Anything else draws as
|
||||
`?`, which is deliberate: drawing a *different* character would be worse.
|
||||
Loading…
Reference in a new issue