Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Overview

Next step in our kernel — execution!

KFS_5 is all about processes. A process is the actual execution of the instructions in a program. This means we’re also going to be dealing with threads, scheduling, and multitasking — right now we only have one thread of execution with no concurrency at all.

In computing, a process is an instance of a computer program being executed. It contains the program code and its current activity. Several processes may be associated with the same program — opening multiple instances of the same application means multiple processes running simultaneously.

Multitasking is how multiple processes share a single CPU. The CPU can only execute one task at a time, but by switching between tasks rapidly — on I/O operations, voluntary yields, or hardware interrupts — it creates the illusion of parallelism.

Processes have relationships and metadata we need to track:

  • PID — unique integer to identify the process
  • Parent/Children — process family tree
  • Owner — which user owns this process
  • Dedicated memory — each process gets its own stack (KFS_3 paging finally earning its keep)
  • Signals — pending signal queue plus per-process handler table
  • Sockets — inter-process communication

This is gonna be a big one. And it was.

Connection to KFS_4

A lot of what we built in KFS_4 directly feeds into this — it wasn’t busywork:

  • InterruptFrame becomes the process context save/restore structure for context switching. Every process has a heap-allocated frame that the scheduler saves into on every timer tick.
  • The signal system gains per-process handler tables — separate from the global SIGNAL_TABLE, individual processes can now register their own handlers for specific signals.
  • The get_line() deadlock from KFS_4 is now architecturally resolved. It deadlocked because it was called from interrupt context — now the shell runs as its own execution context and get_line() works correctly.
  • The syscall stubs (sys_write, sys_read, sys_exit) get real process-aware implementations when KFS_7 arrives. The groundwork is in place.

What We Built

The Process Structure

A Process struct that tracks everything the kernel needs to know — pid, status, parent/child/sibling tree, stack pointer and size, heap region, signal queue, per-process signal handlers, owner id, and the all-important state pointer to the saved InterruptFrame. The child/sibling pointers form a proper Unix process family tree without any extra heap allocation per relationship.

Key Functions

All the mandatory syscall helpers: fork (full process copy including stack contents via memcpy), exit (frees stack, marks Zombie, sends SIGCHLD to parent), kill (queues signal or calls exit for SIGKILL), getuid (returns owner id), signal (registers per-process handler), and wait (spins until a child becomes Zombie).

exec_fn loads a function as a process — creates the process, allocates a stack via vmalloc_user, builds a dummy_frame pointing to the function, and registers it in the process table. Demonstrated with sys exec and sys proc in the shell.

Sockets

Inter-process communication via a socket table. socket_create, socket_write, socket_read, socket_close — mirrors Unix socket semantics so the KFS_7 syscall layer can plug straight in. Buffers backed by vmalloc, socket metadata by kmalloc.

The Scheduler

Timer-driven round-robin scheduler. On every tick: save current process state via rust_timer_handler, pick the next valid process via get_next_frame, switch ESP to its saved frame, popa and iretd into it. Process 0 (kernel idle) is excluded from scheduling and runs directly in the main loop.

The scheduler infrastructure is complete. It is currently disabled because of a fundamental ring 0 limitation — iretd at ring 0 cannot switch stacks, so processes don’t run on their allocated stacks and corruption follows. The proper fix is ring 3 processes, which is KFS_7’s job.

The TSS

The Task State Segment is implemented and loaded. esp0 points to the kernel stack top, ss0 = 0x10. The GDT entry at index 5 is properly filled and ltr 0x28 is called at boot. When KFS_7 flips the switch to ring 3, the TSS is already doing its job — telling the CPU which kernel stack to switch to on every ring 3 → ring 0 transition.

Ring 3 Groundwork

While true ring 3 execution is KFS_7, the foundations are all in place: user GDT segments have been there since KFS_2, vmalloc_user maps pages with PAGE_USER set, InterruptFrame has esp_user and ss_user fields for the extended ring 3 frame layout, and dummy_frame knows how to configure a ring 3 entry. Nothing structural needs to change in KFS_7 — it’s a configuration flip.

Mandatory

Data

A full process structure containing:

  • PID
  • Status (Running, Zombie, Thread)
  • Pointers to parent and children
  • Stack and heap with size tracking
  • Current signal queue
  • Per-process signal handler table
  • Owner ID
  • Saved CPU state (*mut InterruptFrame)

And functions:

  • create_process — allocate and register a process
  • Queue a signal to a process, delivered on the next CPU tick
  • Socket communication helpers between processes
  • Memory functions (vmalloc_user for process stacks)
  • fork — copy an entire process

Plus syscall helpers: wait, exit, getuid, signal, kill

Memory

In KFS_3 we implemented paging — now we actually use it. Each process gets a vmalloc_user’d stack on creation, mapped with PAGE_USER flags so it’s accessible from ring 3 when that arrives. The stack and stack_size fields on the process struct track the allocation so fork can copy it correctly.

Execution

The kernel handles multitasking via a round-robin timer-based scheduler. The infrastructure is complete — timer saves state, get_next_frame picks the next process, iretd jumps into it.

Testing

exec_fn loads a function as a process and registers it in the process table. Demonstrated in the shell:

> sys exec
Creating test process...
test_process addr: 0x00105900
Process created!

> sys proc
=== Process Table ===
PID 0: state=0x00261060 stack=0x00000000
PID 1: state=0x00261110 stack=0x00900000

Process 0 is the kernel idle process. Process 1 is the test process with its own vmalloc’d stack at 0x00900000. Shell remains fully responsive throughout.

Bonus

Once the mandatory is done, the fun stuff:

  • mmap — let a process query and manage its virtual memory
  • Link IDT and processes to follow future syscall signals
  • Create BSS and data sectors in the process structure

These are deferred — the mandatory is solid and the bonus items depend on having ring 3 processes working first anyway.