- Writing a Linux-style Operating System From Scratch
- Chapter 2 — GDT, IDT, and Surviving Your First Kernel Crash
- Chapter 3 — Hardware Interrupts: PIC, PIT Timer, and Keyboard Input
- Chapter 4 — Reading the Memory Map and Building a Physical Page Allocator
- Chapter 20 — A Tiny Executable Format and User Program Loader
- Chapter 5 — Turning On Paging
- Chapter 6 — Building the First Kernel Heap
- Chapter 7 — A Real Virtual Memory Mapping Layer
- Chapter 8 – Moving the Heap onto Virtual Memory
- Chapter 9 — Cooperative Multitasking and Kernel Threads
- Chapter 10 — Timer-Driven Preemptive Multitasking
- Chapter 11 — Blocking Primitives, Sleep Queues, and Scheduler Hygiene
- Chapter 12 – Wait Queues and Blocking Keyboard Input
- Chapter 13 — Mutexes, Semaphores, and a Console Lock
- Chapter 14 — Terminal Line Discipline and a Kernel Monitor
- Chapter 15 — Command Tables, Argument Parsing, and Shift-Aware Keyboard Input
- Chapter 16 — Entering User Mode and Returning Through Syscalls
- Chapter 17 — Minimal Processes, User Memory Copying, and More Robust Syscalls
- Chapter 18 — File-Descriptor Syscalls and a Tiny User-Mode Console Program
- Chapter 19 — Per-Process Address Spaces and CR3 Switching
- Chapter 21 — Process Teardown and Address-Space Cleanup
- Chapter 23 — Building a Real User C Program and Embedding Its ELF
- Chapter 24 — User argc / argv and a Real Initial Stack
- Chapter 26 — Process Table, ps, runbg, and wait PID
- Chapter 27 — A Second User Program for Safe Background Execution
- Chapter 28 — Pattern-Based User Program Build System
- Chapter 29 — First Userland Runtime
- Chapter 30 — First User-Mode Shell
- Chapter 31 — SYS_EXEC, SYS_WAITPID, and Shell-Launched Programs
- Chapter 32 — Process Ownership, Waiting, and Job State
- Chapter 33 — Process Termination and Kill Checks
- Chapter 34 — First RAMFS and Core File APIs
- Chapter 35 — SYS_SEEK and Rewindable File Descriptors
- Chapter 36.5 – A Testing Detour and a Real Smoke Harness
- Chapter 37 — Directories, SYS_READDIR, and ls
- Chapter 38 — Turning /programs into a Real Directory
Post Stastics
- This post has 1370 words.
- Estimated read time is 6.52 minute(s).
Before reading the shell examples in this chapter, be explicit about the prompt transition:
toyix> run shell ush>
toyix> is the kernel monitor.
ush> is the user shell.
So when this chapter shows commands like:
ush> ls / ush> ls /programs ush> cat /programs/demo
those commands must be typed inside the user shell after toyix> run shell.
In Chapter 37, Toyix gained its first real directory support at the root:
ush> ls / file README dir programs
But /programs was still only a compatibility text file. That was useful as a transitional step, but it was not a realistic filesystem shape for a system that already knows about embedded user programs.
Chapter 38 fixes that.
After this chapter, /programs becomes a real directory and each embedded program appears as a small read-only pseudo-file:
/
├── README
└── programs/
├── demo
├── counter
├── shell
└── fstest
That means the user shell can now do:
ush> ls /programs file demo file counter file shell file fstest ush> cat /programs/demo Toyix embedded program: demo Description: interactive stdin/stdout demo
We are still not executing programs by filesystem path yet. This chapter only makes the namespace real. The next chapter can build on that structure.
1. What this chapter adds
Modify:
kernel/vfs.c kernel/program.c user/fstest.c user/shell.c tests/smoke.py README.md CHANGELOG.md index.md docs/roadmap.md
No new syscalls are needed.
No ELF loading changes are needed.
No scheduler changes are needed.
The work is entirely in the RAMFS layout, the user-visible shell behavior, and the smoke coverage.
2. Why this chapter exists
Toyix already has two separate concepts:
filesystem paths embedded program registry
Before Chapter 38, those concepts were still disconnected. The registry knew about:
demo counter shell fstest
but the filesystem only exposed /programs as a regular text file.
Turning /programs into a directory is the first structural bridge between:
program names in the registry
and:
program-like paths in the filesystem namespace
That gives later chapters a clean place to add path-based execution, executable metadata, and eventually more dynamic program lookup.
3. New RAMFS layout
Before:
/ directory /README regular file /programs regular file
After:
/ directory /README regular file /programs directory /programs/demo regular pseudo-file /programs/counter regular pseudo-file /programs/shell regular pseudo-file /programs/fstest regular pseudo-file
The node count therefore changes from:
3 node(s)
to:
7 node(s)
The root still has only two entries:
README programs
But now programs resolves to a directory instead of a regular file, so ls / prints:
file README dir programs
4. How /programs works in this chapter
Each child under /programs is still a simple RAMFS regular file.
For example:
/programs/demo
contains:
Toyix embedded program: demo Description: interactive stdin/stdout demo
The same pattern is used for:
/programs/counter /programs/shell /programs/fstest
These are not ELF images and they are not executable files yet.
They are only descriptive pseudo-files that let the filesystem expose the embedded program set in a realistic way using the code that already exists:
open read stat readdir seek
That choice keeps the implementation small and makes Chapter 38 a filesystem chapter instead of an execution chapter.
5. Kernel changes
The VFS/RAMFS work in kernel/vfs.c does four things:
- Replace the old
/programstext blob with per-program pseudo-file text. - Add a second directory-entry table for
/programs. - Change the
/programsRAMFS node type fromVFS_NODE_REGULARtoVFS_NODE_DIRECTORY. - Add four new regular-file nodes under
/programs.
The root directory table still looks like:
README -> /README programs -> /programs
and /programs gets its own child-entry table:
demo -> /programs/demo counter -> /programs/counter shell -> /programs/shell fstest -> /programs/fstest
The existing vfs_readdir(), vfs_stat(), vfs_size(), and vfs_seek() code from Chapter 37 already knows how to deal with directories, so no new API surface is needed here.
The only new logic is the expanded static RAMFS data.
6. User-visible shell behavior
The shell does not gain new commands in Chapter 38.
The existing commands simply become more interesting because /programs is now a directory.
Examples:
ush> stat /programs stat: path=/programs type=directory size=4
ush> ls /programs file demo file counter file shell file fstest
ush> stat /programs/demo stat: path=/programs/demo type=file size=...
ush> cat /programs/demo Toyix embedded program: demo Description: interactive stdin/stdout demo
One small shell cleanup is included here too:
dir programs
now prints with normal spacing. In Chapter 37 the directory label helper would have produced an awkward double-space once programs became a directory, so the type-label output was tightened as part of this chapter.
Absolute-path-only behavior is still unchanged:
ush> ls programs ls: could not stat programs
Current working directories and relative paths are still future work.
7. Filesystem smoke coverage
fstest now verifies more than root-directory enumeration.
It checks:
stat /programs stat /programs/demo readdir on / readdir on /programs read from /programs/demo
That gives user-mode coverage for the new nested directory layout instead of relying only on kernel-side VFS checks.
Expected fstest lines now include:
fstest: /programs type=directory size=4 fstest: /programs/demo type=file size=... fstest: first /programs entry: demo fstest: second /programs entry: counter fstest: third /programs entry: shell fstest: fourth /programs entry: fstest fstest: /programs/demo read: Toyix embedded
8. Shell smoke coverage
The scripted shell test in kernel/program.c now exercises:
cat /programs/demo stat /programs stat /programs/demo ls / ls /programs
This is important because the chapter is not just about static RAMFS data. It is about the actual user-facing workflow that a reader will try after boot.
That is also why the Chapter 37 release lesson still matters here:
test-mode scripted shell input must not be confused with normal interactive boot behavior
So the automated shell script remains test-only under make test, while ordinary boots still reach a clean toyix> prompt.
9. Update tests/smoke.py
Chapter 36.5 moved smoke assertions into Python, so Chapter 38 extends tests/smoke.py rather than reviving shell grep chains.
The normal-boot checks now include:
VFS: initialized RAMFS with 7 node(s) VFS test: /programs entries=4 type=directory VFS test: first /programs entry: demo VFS test: second /programs entry: counter VFS test: third /programs entry: shell VFS test: fourth /programs entry: fstest VFS test: /programs/demo bytes: Toyix embedded fstest: /programs type=directory size=4 fstest: /programs/demo type=file size=... fstest: first /programs entry: demo fstest: second /programs entry: counter fstest: third /programs entry: shell fstest: fourth /programs entry: fstest Toyix embedded program: demo stat: path=/programs type=directory size=4 stat: path=/programs/demo type=file size=... dir programs file demo file counter file shell file fstest
Run the full local suite with:
python3 tests/smoke.py
The Chapter 38 success banner is:
All Chapter 38 checks passed.
10. Interactive test
Boot normally, wait for the clean kernel monitor, then run:
toyix> run shell
Inside the shell:
ush> ls /
Expected:
file README dir programs
Then:
ush> ls /programs
Expected:
file demo file counter file shell file fstest
Then:
ush> cat /programs/demo
Expected:
Toyix embedded program: demo Description: interactive stdin/stdout demo
Then:
ush> stat /programs
Expected:
stat: path=/programs type=directory size=4
And:
ush> stat /programs/fstest
Expected:
stat: path=/programs/fstest type=file size=...
Remember:
`file demo` is ls output it is not a shell command
Also remember:
paths are still absolute-only in Chapter 38
So:
ush> ls programs
should still fail.
11. What changed from Chapter 37
Before:
/programs was a compatibility text file
Now:
/programs is a real directory
Before:
ls / showed programs as a file
Now:
ls / shows programs as a directory
Before:
cat /programs printed a plain list
Now:
cat /programs/demo prints a descriptive pseudo-file
That is the whole point of the chapter: the visible filesystem structure now matches the conceptual program namespace much more closely.
Next Chapter
Now that /programs is a real directory, the next natural step is to teach the program-launch path to understand entries like:
/programs/counter
That means the next chapter can start bridging from:
run counter
toward:
path-based program launching
without first needing more RAMFS structure work.
Resources
Closure
Chapter 38 turns /programs from a transitional text file into a real directory of pseudo-files, which gives Toyix a much more realistic filesystem namespace for the embedded program set and sets up the next step toward path-based execution.
Happy Coding!