Implement Ring 3 process subsystem with syscalls and context switching (AI)

Add complete user-mode process support:

- TSS (tss.c/h): Task State Segment for Ring 3->0 transitions, installed
  as GDT entry 5 (selector 0x28). ESP0 updated per-process for kernel
  stack switching.

- Process management (process.c/h): Process table with up to 64 processes.
  process_create() clones kernel page directory, maps user code at
  0x08048000 and user stack at 0xBFFFF000, copies flat binary code.
  Round-robin scheduler via schedule_tick() modifies the interrupt frame
  in-place for zero-copy context switching.

- System calls (syscall.c/h): INT 0x80 dispatcher with 8 syscalls:
  SYS_EXIT, SYS_WRITE (to debug port + VGA), SYS_READ, SYS_FORK,
  SYS_GETPID, SYS_YIELD, SYS_WAITPID, SYS_EXEC. IDT gate at 0x80
  uses DPL=3 (flags 0xEE) so user code can invoke it.

- Assembly stubs (interrupts.S): isr128 for INT 0x80, tss_flush for
  loading the Task Register, enter_usermode for initial iret to Ring 3.

- Paging extensions (paging.c/h): paging_clone_directory() to create
  per-process page directories, paging_map_page_in() for mapping into
  non-active directories, paging_switch_directory() for CR3 switching.

- GDT expanded from 5 to 6 entries to accommodate TSS descriptor.
  gdt_set_gate() exposed in header for TSS initialization.

- ISR handler routes timer IRQ (32) to scheduler and INT 0x80 to
  syscall dispatcher. Exception handler now prints EIP/CS/ERR for
  debugging.

- Kernel boots a test user program that writes 'Hello from Ring 3!'
  via SYS_WRITE and exits with code 42 via SYS_EXIT. Verified working
  in QEMU.

Context switching approach: Timer/syscall interrupts save all registers
via the ISR stub. schedule_tick() copies saved_regs between PCBs and
overwrites the interrupt frame, so the existing iret restores the next
process's state without separate switch assembly.
This commit is contained in:
AI
2026-02-23 12:10:46 +00:00
parent 313aeb5872
commit 71e2ae482a
17 changed files with 1118 additions and 12 deletions

109
docs/process.md Normal file
View File

@@ -0,0 +1,109 @@
# Process Subsystem
## Overview
The process subsystem enables user-mode (Ring 3) process execution on ClaudeOS.
It provides process creation, context switching via the timer interrupt, and
system calls via `INT 0x80`.
## Architecture
### Ring Transition
x86 protected mode uses privilege rings 03. The kernel runs in Ring 0 (full
hardware access) and user processes run in Ring 3 (restricted). The GDT
defines segment descriptors for both:
| GDT Entry | Selector | Purpose | DPL |
|-----------|----------|-----------------|-----|
| 0 | 0x00 | Null | |
| 1 | 0x08 | Kernel Code | 0 |
| 2 | 0x10 | Kernel Data | 0 |
| 3 | 0x18 | User Code | 3 |
| 4 | 0x20 | User Data | 3 |
| 5 | 0x28 | TSS | 0 |
User-mode selectors include RPL=3: code = 0x1B, data = 0x23.
### Task State Segment (TSS)
The TSS (`tss.c`) stores the kernel stack pointer (SS0:ESP0) used when the CPU
transitions from Ring 3 to Ring 0 on an interrupt. Before running each process,
the scheduler updates TSS.ESP0 to that process's kernel stack top.
### Memory Layout
Each process gets its own page directory, cloned from the kernel's:
```
0x00000000 0x07FFFFFF : Identity-mapped (kernel/device access)
0x08048000 ... : User code (loaded from binary image)
0xBFFF7000 0xBFFFF000 : User stack (2 pages, grows downward)
0xD0000000 0xF0000000 : Kernel heap (shared across all processes)
```
### Process Control Block
```c
typedef struct process {
uint32_t pid;
process_state_t state; // UNUSED, READY, RUNNING, BLOCKED, ZOMBIE
registers_t saved_regs; // Full interrupt frame
uint32_t kernel_stack; // Base of per-process kernel stack
uint32_t kernel_stack_top; // TSS ESP0 value
uint32_t page_directory; // Physical address of page directory
uint32_t user_stack; // User stack virtual address
uint32_t entry_point; // User code entry point
int32_t exit_code; // Set on exit
uint32_t parent_pid;
char name[32];
} process_t;
```
## Context Switching
Context switching uses the interrupt frame directly:
1. Timer IRQ (or `INT 0x80` for SYS_YIELD) fires
2. CPU pushes SS/ESP/EFLAGS/CS/EIP onto the process's kernel stack
3. ISR stub pushes the rest (pusha + DS) forming a `registers_t`
4. `schedule_tick()` is called with a pointer to these registers
5. Current process's registers are saved into its PCB
6. Next READY process's saved registers are written over the interrupt frame
7. TSS.ESP0 is updated, CR3 is switched to the new page directory
8. ISR stub restores the (now different) registers and `iret` enters the new
process in user mode
This avoids separate context-switch assembly — the existing ISR stub handles
everything.
## System Calls
System calls use `INT 0x80` with the call number in EAX:
| Number | Name | Arguments |
|--------|-------------|------------------------------|
| 0 | SYS_EXIT | EBX = exit code |
| 1 | SYS_WRITE | EBX = fd, ECX = buf, EDX = len |
| 2 | SYS_READ | (not implemented) |
| 3 | SYS_FORK | (returns child PID/0) |
| 4 | SYS_GETPID | (returns PID in EAX) |
| 5 | SYS_YIELD | (voluntary preemption) |
| 6 | SYS_WAITPID | EBX = child PID |
| 7 | SYS_EXEC | (not implemented) |
The INT 0x80 IDT gate has DPL=3 (flags 0xEE) so user-mode code can invoke it.
## Initial Process Entry
`process_run_first()` performs the initial transition to user mode using an
`iret` instruction that sets up Ring 3 segment selectors, the user stack
pointer, and the entry point. This is a one-way transition — the function
does not return.
## Files
- `tss.h` / `tss.c` — TSS structure and initialization
- `process.h` / `process.c` — Process table, creation, scheduling, exit, fork
- `syscall.h` / `syscall.c` — System call dispatch and handlers
- `interrupts.S` — Assembly stubs: `isr128` (INT 0x80), `tss_flush`, `enter_usermode`