Chapter 40 — Shell Job References with %1

Chapter 40 — Shell Job References with %1
This entry is part 37 of 38 in the series Writing A Linux Style Operating System From Scratch

Table of Contents

Writing A Linux Style Operating System From Scratch

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 9 — Cooperative Multitasking and Kernel Threads

Chapter 10 — Timer-Driven Preemptive Multitasking

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 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

Chapter 39 — Path-Based Program Launching with /programs

Chapter 40 — Shell Job References with %1

Post Stastics

  • This post has 985 words.
  • Estimated read time is 4.69 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> runbg counter victim
ush> jobs
ush> kill %1
ush> wait %1

those commands must be typed inside the user shell after toyix> run shell.

Chapter 39 made /programs/<name> usable as a launch target. That fixed the namespace bridge between the filesystem and the embedded program registry, but one brittle test problem remained:

kill 5
wait 5

Those commands only work if every earlier step in the boot script creates exactly the same number of processes.

That is too fragile for a chapter series that keeps growing.

Chapter 40 fixes that by giving the shell its own job references:

%1
%2
%3

The shell still launches the same background children.

The kernel still tracks the same PIDs.

But the shell now gives each background job a local handle that can be used in jobs, kill, and wait.


1. What this chapter adds

Modify:

user/shell.c
kernel/program.c
tests/smoke.py
README.md
CHANGELOG.md
index.md
docs/roadmap.md

No syscall changes are needed.

No kernel scheduler changes are needed.

No VFS changes are needed.

This is a shell-facing workflow improvement built on top of the background-job table that already exists.


2. Why this chapter exists

Toyix already had two related but separate ideas:

kernel PID
shell background job

A PID is a kernel identity.

A shell job reference is a user-facing convenience.

Before this chapter, the shell exposed only the kernel PID:

shell: runbg counter pid=5

That meant the test script had to remember 5 and assume nothing else changed before that step.

Chapter 40 makes the shell record a local job number as well:

shell: runbg counter pid=5 job=%1

That lets the shell refer to the same job in a stable way even if process IDs shift later.


3. Supported job references

This chapter supports both of these forms:

5
%1

5 means “use PID 5 directly.”

%1 means “look up shell job 1 and use the PID associated with it.”

Plain PIDs are still supported for compatibility.

The new %N syntax is a shell-local alias, not a kernel feature.

That distinction matters:

kernel PID  -> actual process identity
shell job %N -> stable shell handle for a background child

4. Shell job table changes

In user/shell.c, each job entry now stores a shell job ID in addition to the PID:

typedef struct shell_job {
    toyix_u32 job_id;
    toyix_u32 pid;
    int active;
    char name[SHELL_JOB_NAME_MAX];
} shell_job_t;

The shell keeps a small counter for new job IDs:

static toyix_u32 shell_next_job_id = 1;

That means the first background job in a shell session becomes %1, the next becomes %2, and so on.

The counter is shell-local. It resets when the shell starts.

That is exactly what we want.


5. Parsing %N

The shell needs one small helper to resolve either form:

kill 5
kill %1
wait 5
wait %1

The helper checks whether the argument begins with %.

If it does, the shell looks up the matching job ID in its table and uses that job’s PID.

If it does not, the shell parses the value as a PID directly.

That gives us a narrow and predictable rule:

%N  -> job lookup
N   -> PID lookup

The kernel never sees %1.

Only the shell does.


6. Updated shell commands

runbg now prints the shell job reference when a launch succeeds:

shell: runbg counter pid=5 job=%1

jobs now shows the job reference for each active entry:

shell jobs:
  %1 pid=5 parent=<shell pid> name=counter state=running

kill accepts either a PID or a job reference:

ush> kill %1
shell: kill requested job=%1 pid=5

wait does the same:

ush> wait %1
shell: wait job=%1 pid=5 name=counter code=128

Plain PID form still works too:

ush> kill 5
ush> wait 5

That compatibility keeps the shell usable for direct debugging while the new job references make scripted tests more robust.


7. Test flow update

The Chapter 40 test sequence in kernel/program.c now uses %1 for the background job:

runbg counter victim
jobs
kill %1
jobs
wait %1
jobs

That change removes the dependency on a hardcoded PID for the shell job-control part of the boot script.

The rest of the scripted flow stays the same.

That is deliberate.

The goal is to test the new shell job reference feature without destabilizing unrelated chapter checks.


8. What the user should see

Inside the user shell:

ush> runbg counter victim
shell: runbg counter pid=5 job=%1

ush> jobs
shell jobs:
  %1 pid=5 parent=<shell pid> name=counter state=running

ush> kill %1
shell: kill requested job=%1 pid=5

ush> wait %1
shell: wait job=%1 pid=5 name=counter code=128

The local shell job number is the stable reference.

The PID remains visible for debugging.

Both are useful, and both are now shown.


9. What this is not

This chapter does not add:

process groups
job control signals
foreground/background terminal control
shell pipelines
shell command history

It is only the first shell-local job reference layer.

That keeps the scope small and makes the chapter easy to verify.


10. Files changed in this chapter

The final file set for this chapter should be:

user/shell.c
kernel/program.c
tests/smoke.py
README.md
CHANGELOG.md
index.md
docs/roadmap.md
articles/chapter_40.md

That is a good sign that the chapter stays focused on shell-facing behavior rather than sprawling into unrelated subsystems.


Next Chapter

Now that the shell has stable job references, the next natural step is to keep tightening the program-launch path around the embedded registry and filesystem namespace.

That means Chapter 41 can move on to dynamic /programs behavior driven by the registry instead of static launch tables.


Resources

Closure

Chapter 40 gives Toyix shell-local %N job references so background work can be managed by stable shell handles instead of brittle hardcoded PIDs, while keeping the underlying kernel process model unchanged.

Happy Coding!

Writing A Linux Style Operating System From Scratch

Chapter 39 — Path-Based Program Launching with /programs

Leave a Reply

Your email address will not be published. Required fields are marked *