- Added paging_clone_directory_from(): deep-copies user-space pages so parent and child have independent memory. Kernel pages are shared. - Fixed process_fork() to accept registers_t* for accurate child state, and to clone from the parent's page directory (not the kernel's). - Refactored process_exit() to properly context-switch to next process using new process_switch_to_user assembly stub (loads full registers_t and performs iret), instead of halting unconditionally. - Fixed sys_waitpid() to use proper blocking: marks process BLOCKED, invokes scheduler, and resumes with exit code when child dies. - Added SYSCALL_SWITCHED mechanism to prevent syscall_handler from clobbering the next process's EAX after a context switch. - Created fork-test user app that validates fork + waitpid. - Added docs/fork.md with architecture documentation. Tested: fork-test creates child, both print messages, parent waits for child exit (code 7), parent reaps and exits (code 0). hello-world also verified to still work correctly after the process_exit refactor.
177 lines
4.8 KiB
C
177 lines
4.8 KiB
C
/**
|
|
* @file syscall.c
|
|
* @brief System call handler implementation.
|
|
*
|
|
* Dispatches INT 0x80 system calls to the appropriate kernel function.
|
|
* System call number is in EAX, arguments in EBX, ECX, EDX, ESI, EDI.
|
|
* Return value is placed in EAX.
|
|
*/
|
|
|
|
#include "syscall.h"
|
|
#include "process.h"
|
|
#include "port_io.h"
|
|
#include "vga.h"
|
|
#include <stddef.h>
|
|
|
|
/** Magic return value indicating the syscall blocked and switched processes.
|
|
* syscall_handler must NOT overwrite regs->eax in this case. */
|
|
#define SYSCALL_SWITCHED 0x7FFFFFFF
|
|
|
|
/* Debug print helpers defined in kernel.c */
|
|
extern void offset_print(const char *str);
|
|
extern void print_hex(uint32_t val);
|
|
|
|
/** IDT gate setup (from idt.c) */
|
|
extern void set_idt_gate_from_c(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags);
|
|
|
|
/** INT 0x80 assembly stub */
|
|
extern void isr128(void);
|
|
|
|
/**
|
|
* Handle SYS_EXIT: terminate the current process.
|
|
*/
|
|
static int32_t sys_exit(registers_t *regs) {
|
|
process_exit((int32_t)regs->ebx);
|
|
/* Never returns */
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Handle SYS_WRITE: write bytes to a file descriptor.
|
|
* Currently only supports fd=1 (stdout) -> debug port + VGA.
|
|
*/
|
|
static int32_t sys_write(registers_t *regs) {
|
|
int fd = (int)regs->ebx;
|
|
const char *buf = (const char *)regs->ecx;
|
|
uint32_t len = regs->edx;
|
|
|
|
if (fd == 1 || fd == 2) {
|
|
/* stdout or stderr: write to debug port and VGA */
|
|
for (uint32_t i = 0; i < len; i++) {
|
|
outb(0xE9, buf[i]);
|
|
vga_putchar(buf[i]);
|
|
}
|
|
return (int32_t)len;
|
|
}
|
|
|
|
return -1; /* Invalid fd */
|
|
}
|
|
|
|
/**
|
|
* Handle SYS_READ: read bytes from a file descriptor.
|
|
* Stub for now.
|
|
*/
|
|
static int32_t sys_read(registers_t *regs) {
|
|
(void)regs;
|
|
return -1; /* Not implemented */
|
|
}
|
|
|
|
/**
|
|
* Handle SYS_FORK: fork the current process.
|
|
*/
|
|
static int32_t sys_fork(registers_t *regs) {
|
|
return process_fork(regs);
|
|
}
|
|
|
|
/**
|
|
* Handle SYS_GETPID: return the current process ID.
|
|
*/
|
|
static int32_t sys_getpid(registers_t *regs) {
|
|
(void)regs;
|
|
process_t *cur = process_current();
|
|
return cur ? (int32_t)cur->pid : -1;
|
|
}
|
|
|
|
/**
|
|
* Handle SYS_YIELD: voluntarily yield the CPU.
|
|
*/
|
|
static int32_t sys_yield(registers_t *regs) {
|
|
(void)regs;
|
|
schedule();
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Handle SYS_WAITPID: wait for a child to exit.
|
|
*
|
|
* If the child is already a zombie, reaps immediately and returns the code.
|
|
* Otherwise, blocks the current process and switches to the next one.
|
|
* When the child exits, process_exit() will unblock the waiting parent
|
|
* and set its saved_regs.eax to the exit code.
|
|
*/
|
|
static int32_t sys_waitpid(registers_t *regs) {
|
|
uint32_t pid = regs->ebx;
|
|
process_t *child = process_get(pid);
|
|
if (!child) {
|
|
return -1;
|
|
}
|
|
|
|
/* If child already exited, reap immediately */
|
|
if (child->state == PROCESS_ZOMBIE) {
|
|
int32_t code = child->exit_code;
|
|
child->state = PROCESS_UNUSED;
|
|
return code;
|
|
}
|
|
|
|
/* Block the current process until the child exits */
|
|
process_t *cur = process_current();
|
|
cur->state = PROCESS_BLOCKED;
|
|
cur->waiting_for_pid = pid;
|
|
|
|
/* Save the current syscall registers so we resume here when unblocked.
|
|
* The return value (eax) will be set by process_exit when the child dies. */
|
|
cur->saved_regs = *regs;
|
|
|
|
/* Schedule the next process. This modifies *regs to the next process's
|
|
* saved state, so when the ISR stub does iret, it enters the next process. */
|
|
schedule_tick(regs);
|
|
|
|
/* Tell syscall_handler not to overwrite regs->eax, since regs now
|
|
* points to the next process's registers on the kernel stack. */
|
|
return SYSCALL_SWITCHED;
|
|
}
|
|
|
|
/**
|
|
* Handle SYS_EXEC: placeholder.
|
|
*/
|
|
static int32_t sys_exec(registers_t *regs) {
|
|
(void)regs;
|
|
return -1; /* Not implemented yet */
|
|
}
|
|
|
|
/** System call dispatch table. */
|
|
typedef int32_t (*syscall_fn)(registers_t *);
|
|
static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
|
[SYS_EXIT] = sys_exit,
|
|
[SYS_WRITE] = sys_write,
|
|
[SYS_READ] = sys_read,
|
|
[SYS_FORK] = sys_fork,
|
|
[SYS_GETPID] = sys_getpid,
|
|
[SYS_YIELD] = sys_yield,
|
|
[SYS_WAITPID] = sys_waitpid,
|
|
[SYS_EXEC] = sys_exec,
|
|
};
|
|
|
|
void syscall_handler(registers_t *regs) {
|
|
uint32_t num = regs->eax;
|
|
|
|
if (num >= NUM_SYSCALLS || !syscall_table[num]) {
|
|
offset_print(" SYSCALL: invalid syscall ");
|
|
print_hex(num);
|
|
regs->eax = (uint32_t)-1;
|
|
return;
|
|
}
|
|
|
|
int32_t ret = syscall_table[num](regs);
|
|
if (ret != SYSCALL_SWITCHED) {
|
|
regs->eax = (uint32_t)ret;
|
|
}
|
|
}
|
|
|
|
void init_syscalls(void) {
|
|
/* Install INT 0x80 as a user-callable interrupt gate.
|
|
* Flags: 0xEE = Present(1) DPL(11) 0 Type(1110) = 32-bit Interrupt Gate, Ring 3 callable */
|
|
set_idt_gate_from_c(0x80, (uint32_t)isr128, 0x08, 0xEE);
|
|
offset_print(" SYSCALL: INT 0x80 installed\n");
|
|
}
|