CosmOS knows about all four drives
A mounted disk is EIGHT BYTES - where its directory starts, how many
blocks it is, how big the disk is, and where you are on it. They now sit
together in the data segment, and changing drives is one copy out and one
copy in. The other three thousand lines of filesystem go on reading the
same four names they always have and never learn there is more than one
disk, which is the whole reason this was affordable.
The version is not in the record. It is checked at mount and thrown away,
because a version one disk's zero parent already reads as "in the root".
Every drive is mounted at boot: the controller says how many are plugged
in and each is tried in turn. One with nothing in it, or a disk this
cannot read, is left unmounted rather than stopping the others, so a
machine with a good disk in drive 0 and a blank in drive 1 starts.
'drive' says which one, 'drive 1' goes to another, and the working
directory goes with it - where you are on a disk is part of which disk you
are on. A drive the machine has not got is refused, and refused
differently from one that is there with nothing readable in it.
Three things the assembly caught me on, all the same misunderstanding of
what survives a call:
- OR reads A and B, and the bit came back from sbfsDriveBit in Q, which
RET does not disturb - but RET does put A back. The mounted mask never
got set and drive 0 was reported unmountable.
- MVQA then RSTA throws away the copy it just made, so doubling a bit
doubled nothing. SHL does it in one instruction, because A and B are
one register to it.
- There is no move from A to B. INB reads a port straight into B, which
is what the drive count comparison wanted.
run.sh takes more than one image now, separated by a plus, since the
machine has four drives and a test that could only name one could not
check any of this.
The buffer note is forgotten on a drive change and that is DELIBERATELY
kept although nothing can currently reach it: only the file read-ahead
consults it, a directory scan does not, and finding a file requires a
scan which overwrites the note on the way past. Two disks were built with
the same file at the same block to try to catch it and the answer was
right either way. Three instructions to hold an invariant rather than a
story about a bug - and the comment says so instead of claiming a fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
b1538e0618
commit
5644c24113
@@ -234,6 +234,7 @@ CosmOS currently provides these built-in commands:
|
||||
| `do <script>` | Run the lines in a file as though they had been typed. See Scripts. |
|
||||
| `echo [words]` | Say the rest of the line, or a blank line with nothing after it. |
|
||||
| `clear` | Empty the screen. |
|
||||
| `drive [n]` | Say which disk the shell is on, or go to another. See Several Disks. |
|
||||
| `cd [path]` | Go to a directory, or to the root with nothing after it. |
|
||||
| `mkdir <path>` | Make a directory. |
|
||||
| `rmdir <path>` | Remove one, if it is empty. |
|
||||
@@ -797,6 +798,33 @@ An application may also include its own libraries or access hardware ports direc
|
||||
The services are an interface offered by the system, not the only way software is allowed
|
||||
to use the computer.
|
||||
|
||||
### Several Disks:
|
||||
|
||||
The machine has four drives behind one controller, and `drive` says which one the shell is
|
||||
standing on. `drive 1` goes to another, and **the working directory goes with it** - where you
|
||||
are on a disk is part of which disk you are on, not something the shell keeps on the side. Go
|
||||
back and you are where you were.
|
||||
|
||||
Every drive is mounted at boot: the controller says how many are plugged in and each is tried
|
||||
in turn. A drive with nothing in it, or a disk this cannot read, is left unmounted rather than
|
||||
stopping the others, so a machine with a good disk in drive 0 and a blank in drive 1 starts
|
||||
normally. `drive 1` then says there is nothing readable there, which is a different answer
|
||||
from there being no such drive.
|
||||
|
||||
**What a mounted disk is, is eight bytes**: where its directory starts, how many blocks it is,
|
||||
how big the disk is, and where you are on it. They sit together in the data segment on purpose,
|
||||
because changing drives is one copy out and one copy in - and the other three thousand lines
|
||||
of filesystem go on reading the same four names they always have and never learn that more
|
||||
than one disk exists. That is the whole reason this was affordable.
|
||||
|
||||
The version is not among them. It is checked at mount and thrown away, because a version one
|
||||
disk's zero parent already reads as "in the root", which is where all of its files are.
|
||||
|
||||
**Paths do not name a drive yet.** `drive 1` then a path is how you reach the other disk, so
|
||||
copying between two of them is not possible in one command. A prefix like `1:/notes` is the
|
||||
obvious next thing and it touches the path walker, which is why it is not in the same change
|
||||
as the record.
|
||||
|
||||
### Bank Numbers Are One Namespace:
|
||||
|
||||
A program that wants a device's memory registers it as a bank, and **bank numbers belong to
|
||||
|
||||
@@ -77,7 +77,7 @@ boot:
|
||||
; Find out whether there is a filesystem to talk to. Doing this once at boot rather than
|
||||
; once per command means a disk swapped underneath us is not noticed, which is honest
|
||||
; for a machine whose disk is a file named on the command line.
|
||||
CALL sbfsMount
|
||||
CALL sbfsMountAll
|
||||
SETD.0 DiskReady
|
||||
BNQ bootNoDisk
|
||||
INIA 0x01
|
||||
@@ -259,6 +259,11 @@ promptWhere:
|
||||
CALL textSame
|
||||
BRQ doRename
|
||||
|
||||
SETD.0 CommandLine
|
||||
SETD.1 DriveName
|
||||
CALL textSame
|
||||
BRQ doDrive
|
||||
|
||||
SETD.0 CommandLine
|
||||
SETD.1 ClearName
|
||||
CALL textSame
|
||||
@@ -1658,6 +1663,67 @@ fileComplain:
|
||||
CALL newLine
|
||||
BRI commandFailed
|
||||
|
||||
; ---- drive ----
|
||||
;
|
||||
; Which disk the shell is standing on. With nothing after it, says which; with a number, goes
|
||||
; there - and the working directory goes with it, because where you are on a disk is part of
|
||||
; which disk you are on rather than something the shell keeps on the side.
|
||||
doDrive:
|
||||
SETD.1 TextRest
|
||||
LDD.0.1
|
||||
LDA.0
|
||||
BRA driveSay
|
||||
|
||||
; One digit. Anything else is not a drive number, and the machines this is imitating never
|
||||
; had ten drives either.
|
||||
INIB 0d48
|
||||
CCF
|
||||
SUB
|
||||
MVQA
|
||||
INIB 0d10
|
||||
CCF
|
||||
SUB
|
||||
BNC driveNoSuch ; Ten or more, so it was not a digit at all.
|
||||
|
||||
; Is there such a drive on this machine?
|
||||
SETD.1 DriveWanted
|
||||
STA.1
|
||||
INB 0x25 ; How many drives, straight into B: there is no move from A to it.
|
||||
LDA.1
|
||||
CCF
|
||||
SUB ; Borrows when the wanted one is inside the count.
|
||||
BNC driveNoSuch
|
||||
|
||||
; And is there anything readable in it? A drive with no disk is a real drive and an empty
|
||||
; one, so this is a different answer from "there is no such drive".
|
||||
CALL sbfsDriveBit
|
||||
MVQA
|
||||
SETD.1 SbfsMounted
|
||||
LDB.1
|
||||
AND
|
||||
BRQ driveNotReadable
|
||||
|
||||
SETD.1 DriveWanted
|
||||
LDA.1
|
||||
CALL sbfsUse
|
||||
BRI prompt
|
||||
|
||||
driveSay:
|
||||
INA 0x24
|
||||
INIB 0d48
|
||||
CCF
|
||||
ADD
|
||||
OUTQ 0x00
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
|
||||
driveNoSuch:
|
||||
SETD.0 DriveNoSuch
|
||||
BRI fileComplain
|
||||
driveNotReadable:
|
||||
SETD.0 DriveNotReadable
|
||||
BRI fileComplain
|
||||
|
||||
; ---- clear ----
|
||||
;
|
||||
; The console has done this since before there was a screen to do it on: writing 1 to the
|
||||
@@ -3703,6 +3769,8 @@ PromptText:
|
||||
; not what went wrong, and what went wrong has already been said in words.
|
||||
LineFailed:
|
||||
0x00
|
||||
DriveWanted:
|
||||
0x00
|
||||
|
||||
|
||||
LastStatus:
|
||||
@@ -3721,6 +3789,10 @@ ScriptNotOne:
|
||||
"do: that is not a script - it wants #! on the first line"
|
||||
ScriptTooDeep:
|
||||
"do: scripts are only four deep"
|
||||
DriveNoSuch:
|
||||
"drive: this machine has no such drive"
|
||||
DriveNotReadable:
|
||||
"drive: nothing this can read is in that drive"
|
||||
StartupName:
|
||||
"/System/Boot/startup.sh"
|
||||
StartupNotOne:
|
||||
@@ -3916,6 +3988,8 @@ EchoName:
|
||||
"echo"
|
||||
ClearName:
|
||||
"clear"
|
||||
DriveName:
|
||||
"drive"
|
||||
LoadName:
|
||||
"load"
|
||||
RunName:
|
||||
|
||||
@@ -136,6 +136,232 @@ sbfsMountTooBig:
|
||||
ADD ; Q is not zero: not a disk this will mount.
|
||||
RET
|
||||
|
||||
; ---- Every drive the machine has ----
|
||||
;
|
||||
; Asked for rather than assumed: the controller says how many are plugged in, and each is
|
||||
; selected and mounted in turn. A drive with nothing in it, or a disk this cannot read, is
|
||||
; left unmounted rather than stopping the others - a machine with a good disk in drive 0 and
|
||||
; a blank in drive 1 should start.
|
||||
;
|
||||
; Q is zero if drive 0 mounted, because that is the one the system came off and the one the
|
||||
; shell will be standing in when it gets a prompt.
|
||||
sbfsMountAll:
|
||||
RSTA
|
||||
SETD.1 SbfsMounted
|
||||
STA.1
|
||||
SETD.1 SbfsDrive
|
||||
STA.1
|
||||
OUTA 0x24 ; Drive 0, whatever the controller was left on.
|
||||
|
||||
; The live record belongs to nobody yet, so every slot starts empty and a drive that fails
|
||||
; to mount keeps an empty one.
|
||||
SETD.0 SbfsMountTable
|
||||
INIB 0d32
|
||||
sbfsMountClear:
|
||||
RSTA
|
||||
STA.0
|
||||
INCD.0
|
||||
DECB
|
||||
BNB sbfsMountClear
|
||||
|
||||
INA 0x25
|
||||
SETD.1 SbfsDriveCount
|
||||
STA.1
|
||||
RSTA
|
||||
SETD.1 SbfsDriveAt
|
||||
STA.1
|
||||
|
||||
sbfsMountEach:
|
||||
SETD.1 SbfsDriveAt
|
||||
LDA.1
|
||||
SETD.1 SbfsDriveCount
|
||||
LDB.1
|
||||
CCF
|
||||
SUB
|
||||
BRQ sbfsMountAllDone ; Past the last one.
|
||||
|
||||
; Selected directly rather than through sbfsUse: there is nothing in the live record worth
|
||||
; putting back yet, and sbfsUse would copy eight bytes of nothing into a slot.
|
||||
SETD.1 SbfsDriveAt
|
||||
LDA.1
|
||||
OUTA 0x24
|
||||
SETD.1 SbfsDrive
|
||||
STA.1
|
||||
RSTA
|
||||
SETD.1 SbfsBufferKnown
|
||||
STA.1
|
||||
|
||||
CALL sbfsMount
|
||||
BNQ sbfsMountNext ; Nothing readable in that drive, and its slot stays empty.
|
||||
|
||||
; Mounted: remember which, and put the live record where it belongs.
|
||||
SETD.1 SbfsDriveAt
|
||||
LDA.1
|
||||
CALL sbfsDriveBit
|
||||
MVQA ; The bit is in Q, and RET put the old A back. OR reads A and B.
|
||||
SETD.1 SbfsMounted
|
||||
LDB.1
|
||||
OR
|
||||
STQ.1
|
||||
|
||||
SETD.1 SbfsDriveAt
|
||||
LDA.1
|
||||
CALL sbfsSlotAt
|
||||
PSHD.3
|
||||
POPD.1
|
||||
SETD.0 SbfsMountLive
|
||||
CALL sbfsCopyMount
|
||||
|
||||
sbfsMountNext:
|
||||
SETD.1 SbfsDriveAt
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
BRI sbfsMountEach
|
||||
|
||||
sbfsMountAllDone:
|
||||
; Back to drive 0 and its record, which is where a shell starts. A stays nought throughout,
|
||||
; which is the drive, the note on the buffer, and the slot to fetch.
|
||||
RSTA
|
||||
OUTA 0x24
|
||||
SETD.1 SbfsDrive
|
||||
STA.1
|
||||
SETD.1 SbfsBufferKnown
|
||||
STA.1
|
||||
CALL sbfsSlotAt
|
||||
PSHD.3
|
||||
POPD.0
|
||||
SETD.1 SbfsMountLive
|
||||
CALL sbfsCopyMount
|
||||
|
||||
; Is drive 0 one of the ones that mounted?
|
||||
INIA 0x01
|
||||
SETD.1 SbfsMounted
|
||||
LDB.1
|
||||
AND
|
||||
BRQ sbfsMountAllNone
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
sbfsMountAllNone:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; A = a drive number. Q is the bit that stands for it in SbfsMounted.
|
||||
sbfsDriveBit:
|
||||
INIB 0x01
|
||||
sbfsDriveBitStep:
|
||||
BRA sbfsDriveBitDone
|
||||
PSHA
|
||||
RSTA
|
||||
SHL ; A and B are one register to SHL, so with A nought this is B
|
||||
POPA ; doubled - which for four drives never reaches the top.
|
||||
DECA
|
||||
BRI sbfsDriveBitStep
|
||||
sbfsDriveBitDone:
|
||||
RSTA
|
||||
CCF
|
||||
ADD ; Q = B, which is the bit.
|
||||
RET
|
||||
|
||||
; ---- Changing which disk is the disk ----
|
||||
;
|
||||
; A holds the drive. Q is zero if it is now the one in use.
|
||||
;
|
||||
; The eight live bytes go back to the drive they belong to and the wanted drive's come in.
|
||||
; Everything below this line in the file goes on reading the same four names it always has and
|
||||
; never learns that more than one disk exists - which is the whole of why this is affordable.
|
||||
;
|
||||
; The buffer is FORGOTTEN, and that is not tidiness. The controller has one buffer shared by
|
||||
; every drive, so the note of which block is in it is wrong the moment the drive changes.
|
||||
; Leaving it would mean the next read of that block number quietly skipping the disk and
|
||||
; handing back the other drive's data.
|
||||
sbfsUse:
|
||||
SETD.1 SbfsDrive
|
||||
LDB.1
|
||||
CCF
|
||||
SUB
|
||||
BRQ sbfsUseAlready ; Already there, and swapping would be a long way round to nothing.
|
||||
|
||||
PSHA ; The drive that was asked for, kept across the copying.
|
||||
|
||||
; The live record back to the drive it belongs to. DP1 is still SbfsDrive, from the
|
||||
; comparison above.
|
||||
LDA.1
|
||||
CALL sbfsSlotAt
|
||||
PSHD.3
|
||||
POPD.1
|
||||
SETD.0 SbfsMountLive
|
||||
CALL sbfsCopyMount
|
||||
|
||||
; And the wanted drive's record into the live eight.
|
||||
POPA
|
||||
PSHA
|
||||
CALL sbfsSlotAt
|
||||
PSHD.3
|
||||
POPD.0
|
||||
SETD.1 SbfsMountLive
|
||||
CALL sbfsCopyMount
|
||||
|
||||
POPA
|
||||
SETD.1 SbfsDrive
|
||||
STA.1
|
||||
OUTA 0x24
|
||||
|
||||
; ---- The note on the buffer belongs to the drive that is leaving ----
|
||||
;
|
||||
; One buffer serves every drive, so "block 31 is in the buffer" stops being true the moment
|
||||
; the drive changes, and a read of block 31 that trusted it would hand back the other disk.
|
||||
;
|
||||
; IT CANNOT CURRENTLY BE REACHED, and that is worth writing down rather than leaving as an
|
||||
; implied claim. Only the file read-ahead consults the note - a directory scan deliberately
|
||||
; does not - and finding a file requires a scan, which overwrites the note on the way past.
|
||||
; Two disks were built with the same file at the same block to try to catch it and the
|
||||
; answer was right either way.
|
||||
;
|
||||
; Kept because it is three instructions and it holds an invariant rather than patching a
|
||||
; symptom: the note describes the selected drive. The day something reads two files without
|
||||
; a directory between them, this is already true instead of being a bug with a story.
|
||||
RSTA
|
||||
SETD.1 SbfsBufferKnown
|
||||
STA.1
|
||||
|
||||
sbfsUseAlready:
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; DP3 = the eight bytes belonging to the drive in A. Stepped rather than multiplied, because
|
||||
; this machine cannot multiply and there are at most three steps.
|
||||
sbfsSlotAt:
|
||||
SETD.3 SbfsMountTable
|
||||
BRA sbfsSlotThere
|
||||
sbfsSlotStep:
|
||||
DPUP.3 0d08
|
||||
DECA
|
||||
BNA sbfsSlotStep
|
||||
sbfsSlotThere:
|
||||
RET
|
||||
|
||||
; Eight bytes, DP0 to DP1.
|
||||
sbfsCopyMount:
|
||||
INIB 0d8
|
||||
sbfsCopyMountByte:
|
||||
LDA.0
|
||||
STA.1
|
||||
INCD.0
|
||||
INCD.1
|
||||
DECB
|
||||
BNB sbfsCopyMountByte
|
||||
RET
|
||||
|
||||
; ---- Finding something by path ----
|
||||
;
|
||||
; DP0 points at a path ending in a zero byte: names with '/' between them. A path that
|
||||
@@ -3118,10 +3344,44 @@ SbfsStateWants:
|
||||
SbfsMagic:
|
||||
"SBFS"
|
||||
|
||||
; ---- Which disk this is, in eight bytes ----
|
||||
;
|
||||
; THE ORDER AND THE ADJACENCY ARE LOAD BEARING. These four are everything that distinguishes
|
||||
; one mounted disk from another, and they are together so that changing drives is one copy
|
||||
; out and one copy in. Nothing else may be put between them.
|
||||
;
|
||||
; Everything else in this file is either a constant or scratch for the operation being done
|
||||
; now, and only one operation is ever being done - which is why a filesystem of 3,300 lines
|
||||
; needs an eight byte record to know more than one disk. The rest never learns there is more
|
||||
; than one.
|
||||
;
|
||||
; The version is not here. It is checked at mount and thrown away, because a version one
|
||||
; disk's zero parent already reads as "in the root", which is where all of its files are.
|
||||
SbfsMountLive:
|
||||
SbfsDirStart:
|
||||
0x00 0x00
|
||||
SbfsDirBlocks:
|
||||
0x00 0x00
|
||||
SbfsDiskBlocks:
|
||||
0x00 0x00
|
||||
SbfsCwd:
|
||||
0x00 0x00
|
||||
|
||||
; One record a drive, and the live eight above are whichever is selected. Four, because the
|
||||
; controller has four.
|
||||
SbfsMountTable:
|
||||
#Reserve 0d32
|
||||
; Which drive the live record belongs to, and which drives were found to have a disk on them
|
||||
; that this can read. A bit a drive, so drive n is bit n.
|
||||
SbfsDrive:
|
||||
0x00
|
||||
SbfsMounted:
|
||||
0x00
|
||||
SbfsDriveCount:
|
||||
0x00
|
||||
SbfsDriveAt:
|
||||
0x00
|
||||
|
||||
SbfsFileStart:
|
||||
0x00 0x00
|
||||
SbfsFileBlocks:
|
||||
@@ -3130,8 +3390,6 @@ SbfsFileTail:
|
||||
0x00
|
||||
SbfsBlock:
|
||||
0x00 0x00
|
||||
SbfsDiskBlocks:
|
||||
0x00 0x00
|
||||
SbfsWantBlocks:
|
||||
0x00 0x00
|
||||
SbfsCandidate:
|
||||
@@ -3165,8 +3423,6 @@ SbfsLeft:
|
||||
; thing that resolves a path is here. Keeping it in the shell would mean either handing it
|
||||
; down on every call or having the shell paste it onto the front of every name, and the
|
||||
; second of those is how a name that is already absolute gets ruined.
|
||||
SbfsCwd:
|
||||
0x00 0x00
|
||||
|
||||
; ---- What walking a path keeps ----
|
||||
;
|
||||
|
||||
Reference in New Issue
Block a user