-
Notifications
You must be signed in to change notification settings - Fork 0
Process Management
ModuOS implements preemptive multitasking with a round-robin scheduler, allowing multiple processes to run concurrently.
┌──────────────────────────────────────────────────────┐
│ Process Management Layer │
│ │
│ ┌────────────────┐ ┌─────────────────────┐ │
│ │ Process Table │◄────►│ Scheduler │ │
│ │ (256 max) │ │ (Round-Robin) │ │
│ └────────┬───────┘ └──────────┬──────────┘ │
│ │ │ │
│ ┌────────▼──────────────────────────▼──────────┐ │
│ │ Context Switcher (ASM) │ │
│ │ - Save CPU state │ │
│ │ - Switch stacks │ │
│ │ - Restore CPU state │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
File: include/moduos/kernel/process/process.h
typedef struct process {
uint32_t pid; // Process ID
uint32_t parent_pid; // Parent process ID
char name[PROCESS_NAME_MAX]; // Process name (64 chars)
process_state_t state; // Current state
int exit_code; // Exit status
cpu_state_t cpu_state; // Saved CPU registers
uint64_t page_table; // Page table (PML4)
void *kernel_stack; // Kernel stack (8KB)
void *user_stack; // User stack (64KB)
void *fd_table[MAX_OPEN_FILES]; // File descriptors (16 max)
uint64_t time_slice; // Remaining time slice
uint64_t total_time; // Total CPU time
int priority; // Priority (0=highest)
int argc; // Argument count
char **argv; // Argument vector
struct process *next; // Linked list pointer
} process_t;typedef enum {
PROCESS_STATE_READY = 0, // Ready to run
PROCESS_STATE_RUNNING, // Currently executing
PROCESS_STATE_BLOCKED, // Waiting for I/O
PROCESS_STATE_SLEEPING, // Sleeping (timer)
PROCESS_STATE_ZOMBIE, // Terminated, waiting for parent
PROCESS_STATE_TERMINATED // Fully terminated
} process_state_t;State Transitions:
┌──────────────────────┐
│ READY │
└──┬────────────────▲──┘
│ │
schedule() yield()
│ │
┌──▼────────────────┴──┐
│ RUNNING │
└──┬────────┬──────────┘
│ │
exit() sleep()/block()
│ │
┌──▼──┐ ┌──▼─────┐
│ZOMBIE│ │BLOCKED │
└─────┘ │SLEEPING│
└──┬─────┘
│
wake_up()
│
┌──▼──┐
│READY│
└─────┘
File: include/moduos/kernel/process/process.h
typedef struct {
uint64_t r15; // +0 - Callee-saved
uint64_t r14; // +8 - Callee-saved
uint64_t r13; // +16 - Callee-saved
uint64_t r12; // +24 - Callee-saved
uint64_t rbx; // +32 - Callee-saved
uint64_t rbp; // +40 - Frame pointer
uint64_t rip; // +48 - Instruction pointer
uint64_t rsp; // +56 - Stack pointer
uint64_t rflags; // +64 - CPU flags (includes interrupt enable)
} cpu_state_t;Why only these registers?
- ModuOS follows System V ABI calling convention
- Callee-saved registers (r15, r14, r13, r12, rbx, rbp) must be preserved across function calls
- Caller-saved registers (rax, rcx, rdx, rsi, rdi, r8-r11) are automatically saved by the compiler
- rflags is critical for preserving interrupt enable flag
void process_init(void);Steps:
- Initialize process table
- Set all slots to empty
- Create idle process (PID 0)
- Set current process to idle
process_t* process_create(const char *name,
void (*entry_point)(void),
int priority);
process_t* process_create_with_args(const char *name,
void (*entry_point)(void),
int priority,
int argc,
char **argv);Steps:
- Find free PID in process table
- Allocate process structure
- Allocate kernel stack (8KB)
- Allocate user stack (64KB)
- Initialize CPU state with entry point
- Set up initial stack frame
- Add to scheduler ready queue
- Return process pointer
Stack Setup:
// Set up initial stack frame
uint64_t *stack = (uint64_t*)((uint8_t*)proc->kernel_stack + KERNEL_STACK_SIZE);
stack--; // Stack grows downward
// Push return address (entry point)
proc->cpu_state.rip = (uint64_t)entry_point;
proc->cpu_state.rsp = (uint64_t)stack;
proc->cpu_state.rbp = (uint64_t)stack;
proc->cpu_state.rflags = 0x202; // IF=1 (interrupts enabled)void process_exit(int exit_code);
void process_kill(uint32_t pid);Exit Flow:
- Set process state to ZOMBIE
- Store exit code
- Close all open file descriptors
- Free user stack (keep kernel stack for cleanup)
- Call scheduler to switch to next process
- Parent reaps zombie and frees remaining resources
process_t* process_get_current(void); // Get current running process
process_t* process_get_by_pid(uint32_t pid); // Find process by PIDvoid process_yield(void); // Voluntarily yield CPU
void process_sleep(uint64_t milliseconds); // Sleep for specified time
void process_wake(uint32_t pid); // Wake sleeping processFile: src/kernel/process/process.c
void scheduler_init(void);Steps:
- Initialize ready queue (linked list)
- Set up timer interrupt (10ms time slice)
- Enable preemption
Round-Robin with Priority:
void schedule(void) {
process_t *current = current_process;
process_t *next = NULL;
// Find next ready process
for (int priority = 0; priority < MAX_PRIORITY; priority++) {
for (process_t *p = ready_queue; p != NULL; p = p->next) {
if (p->state == PROCESS_STATE_READY && p->priority == priority) {
next = p;
goto found;
}
}
}
found:
if (next == NULL) {
next = idle_process; // No process ready, run idle
}
if (next == current) {
return; // Continue running current process
}
// Switch processes
if (current) {
current->state = PROCESS_STATE_READY;
}
next->state = PROCESS_STATE_RUNNING;
current_process = next;
context_switch(¤t->cpu_state, &next->cpu_state);
}Features:
- Priority-based: Lower priority number = higher priority
- Round-robin: Equal priority processes share CPU time
- Preemptive: Timer interrupt forces context switch
- Fair: All processes eventually get CPU time
void scheduler_tick(void) {
process_t *current = current_process;
if (current) {
current->time_slice--;
current->total_time++;
if (current->time_slice == 0) {
// Time slice expired, reschedule
current->time_slice = TIME_SLICE_DEFAULT;
schedule();
}
}
}Time Slice: Default 10ms (adjustable)
void scheduler_add_process(process_t *proc);
void scheduler_remove_process(process_t *proc);File: src/arch/AMD64/syscall/context_switch.asm
global context_switch
; void context_switch(cpu_state_t *old_state, cpu_state_t *new_state)
; RDI = old_state, RSI = new_state
context_switch:
; Check if old_state is NULL
test rdi, rdi
jz .load_new
; Save current process state
mov [rdi + 0], r15 ; r15
mov [rdi + 8], r14 ; r14
mov [rdi + 16], r13 ; r13
mov [rdi + 24], r12 ; r12
mov [rdi + 32], rbx ; rbx
mov [rdi + 40], rbp ; rbp
; Save return address (RIP)
lea rax, [.return]
mov [rdi + 48], rax ; rip
; Save stack pointer
mov [rdi + 56], rsp ; rsp
; Save RFLAGS
pushfq
pop rax
mov [rdi + 64], rax ; rflags
.load_new:
; Restore new process state
mov r15, [rsi + 0] ; r15
mov r14, [rsi + 8] ; r14
mov r13, [rsi + 16] ; r13
mov r12, [rsi + 24] ; r12
mov rbx, [rsi + 32] ; rbx
mov rbp, [rsi + 40] ; rbp
mov rsp, [rsi + 56] ; rsp
; Restore RFLAGS
mov rax, [rsi + 64]
push rax
popfq
; Jump to new process (restore RIP)
jmp [rsi + 48]
.return:
retHow it Works:
- Save: Store all callee-saved registers to old process structure
- Switch Stack: Load new process's stack pointer
- Restore: Load all registers from new process structure
- Jump: Transfer control to new process
Key Points:
- Atomic: Interrupts should be disabled during switch
- Stack Switch: RSP changes from old to new stack
- Preserves RFLAGS: Maintains interrupt enable flag
File: src/kernel/loader/elf.c
ModuOS can load ELF64 (Executable and Linkable Format) binaries.
int elf_load(const char *path, void **entry_point);Steps:
- Read ELF file from filesystem
- Verify ELF magic number (0x7F 'E' 'L' 'F')
- Parse ELF header and program headers
- Allocate memory for segments
- Load segments into memory
- Set up initial stack
- Return entry point address
ELF Header (simplified):
typedef struct {
uint8_t e_ident[16]; // Magic number and class
uint16_t e_type; // Object file type (EXEC)
uint16_t e_machine; // Machine type (x86-64)
uint32_t e_version; // ELF version
uint64_t e_entry; // Entry point address
uint64_t e_phoff; // Program header offset
uint64_t e_shoff; // Section header offset
// ...
} Elf64_Ehdr;void exec_program(const char *path, int argc, char **argv) {
void *entry_point;
// Load ELF executable
if (elf_load(path, &entry_point) != 0) {
return; // Load failed
}
// Create new process
process_t *proc = process_create_with_args(path, entry_point, 0, argc, argv);
// Add to scheduler
scheduler_add_process(proc);
}Ring 0 (Kernel):
- Full hardware access
- All instructions available
- Can modify CR3, IDT, GDT
- Runs kernel code and drivers
Ring 3 (User):
- Limited hardware access
- Restricted instructions
- Cannot modify system tables
- Runs user applications
User → Kernel (System Call):
User code → INT 0x80 → Syscall handler (Ring 0) →
Process request → Return to user (Ring 3)
Kernel → User (Process Start):
Kernel creates process → Sets up stack →
IRET instruction → CPU switches to Ring 3
ModuOS currently has basic synchronization:
- Process blocking on I/O
- Sleep/wake mechanism
- Yield for voluntary context switch
// Future API
typedef struct {
int locked;
process_t *owner;
process_t *wait_queue;
} mutex_t;
void mutex_lock(mutex_t *mutex);
void mutex_unlock(mutex_t *mutex);User Stack (grows down)
0x00007FFFFFFFFFFF
|
v
[ User Stack ]
[ 64KB ]
...
[ Code Segment ]
[ Data Segment ]
[ BSS Segment ]
^
|
Heap (grows up)
0x0000000000400000
────────────────────────────
Kernel Space
0xFFFFFFFF80000000
[ Kernel Stack ]
[ 8KB per process ]
[ Kernel Code/Data ]
Special process (PID 0) that runs when no other process is ready:
void idle_process_entry(void) {
while (1) {
__asm__ volatile("hlt"); // Halt CPU until interrupt
}
}Purpose:
- Saves power when system is idle
- Always ready to run
- Lowest priority
void process_list(void) {
for (int i = 0; i < MAX_PROCESSES; i++) {
process_t *p = &process_table[i];
if (p->state != PROCESS_STATE_TERMINATED) {
printf("PID: %d, Name: %s, State: %d\n",
p->pid, p->name, p->state);
}
}
}void print_stack_trace(process_t *proc) {
uint64_t *rbp = (uint64_t*)proc->cpu_state.rbp;
for (int i = 0; i < 10 && rbp; i++) {
uint64_t rip = *(rbp + 1);
printf(" [%d] 0x%llx\n", i, rip);
rbp = (uint64_t*)*rbp;
}
}Typical overhead: ~1-5 microseconds
- Save registers: ~10 instructions
- Switch stack: 1 instruction
- Restore registers: ~10 instructions
- Cache pollution: varies
- Minimize Time Slice: Shorter = more responsive, but more overhead
- Priority Tuning: Give interactive tasks higher priority
- Reduce Context Switches: Batch operations when possible
- System Calls - User-kernel interface
- Memory Management - Per-process memory
- Interrupt Handling - Timer interrupts
- Kernel API - Process functions reference