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:
151
src/syscall.c
Normal file
151
src/syscall.c
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* @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>
|
||||
|
||||
/* 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) {
|
||||
(void)regs;
|
||||
return process_fork();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static int32_t sys_waitpid(registers_t *regs) {
|
||||
uint32_t pid = regs->ebx;
|
||||
process_t *child = process_get(pid);
|
||||
if (!child) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Busy-wait until child is zombie */
|
||||
while (child->state != PROCESS_ZOMBIE) {
|
||||
schedule();
|
||||
}
|
||||
|
||||
int32_t code = child->exit_code;
|
||||
child->state = PROCESS_UNUSED;
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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");
|
||||
}
|
||||
Reference in New Issue
Block a user