feat: shell (sh) with keyboard driver, SYS_READ, SYS_EXEC

- PS/2 keyboard driver: IRQ1 handler, scancode set 1 translation,
  ring buffer, shift key support
- SYS_READ (fd=0): non-blocking keyboard read for stdin
- SYS_YIELD: calls schedule_tick directly (no nested INT 0x80)
- SYS_EXEC: loads binary from CPIO initrd, replaces process image
- User-space libc: crt0.S (C runtime startup), syscalls.h (inline
  syscall wrappers, basic string functions)
- Shell app (sh): readline with echo/backspace, builtins (cd, env,
  help, exit), fork+exec for external commands
- Updated build_apps.sh: C app support with crt0 linking, libc
  include path, .bss section in objcopy

Tested: shell boots, keyboard input works, hello-world runs via
fork+exec, env shows CWD, exit cleanly terminates.
This commit is contained in:
AI
2026-02-23 13:08:06 +00:00
parent e9b66cd60e
commit c25ba1fccd
10 changed files with 745 additions and 16 deletions

View File

@@ -12,7 +12,12 @@
#include "env.h"
#include "port_io.h"
#include "vga.h"
#include "keyboard.h"
#include "cpio.h"
#include "paging.h"
#include "pmm.h"
#include <stddef.h>
#include <string.h>
/** Magic return value indicating the syscall blocked and switched processes.
* syscall_handler must NOT overwrite regs->eax in this case. */
@@ -60,11 +65,24 @@ static int32_t sys_write(registers_t *regs) {
/**
* Handle SYS_READ: read bytes from a file descriptor.
* Stub for now.
* fd=0 (stdin) reads from the keyboard buffer (non-blocking).
* Returns 0 if no data available; caller should yield and retry.
*/
static int32_t sys_read(registers_t *regs) {
(void)regs;
return -1; /* Not implemented */
int fd = (int)regs->ebx;
char *buf = (char *)regs->ecx;
uint32_t len = regs->edx;
if (fd == 0) {
/* stdin: non-blocking read from keyboard */
if (keyboard_has_data()) {
uint32_t n = keyboard_read(buf, len);
return (int32_t)n;
}
return 0; /* No data available */
}
return -1; /* Invalid fd */
}
/**
@@ -85,11 +103,11 @@ static int32_t sys_getpid(registers_t *regs) {
/**
* Handle SYS_YIELD: voluntarily yield the CPU.
* Calls schedule_tick directly to potentially switch to another process.
*/
static int32_t sys_yield(registers_t *regs) {
(void)regs;
schedule();
return 0;
schedule_tick(regs);
return SYSCALL_SWITCHED;
}
/**
@@ -133,11 +151,103 @@ static int32_t sys_waitpid(registers_t *regs) {
}
/**
* Handle SYS_EXEC: placeholder.
* Handle SYS_EXEC: replace the current process image with a new program.
* EBX = path to binary (C string), e.g. "hello-world".
* Loads the binary from the initrd and replaces the current process's
* code and stack. Does not return on success.
*/
static int32_t sys_exec(registers_t *regs) {
(void)regs;
return -1; /* Not implemented yet */
const char *path = (const char *)regs->ebx;
if (!path) return -1;
process_t *cur = process_current();
if (!cur) return -1;
/* Look up the binary in the initrd */
cpio_entry_t entry;
if (cpio_find(path, &entry) != 0) {
return -1; /* Not found */
}
uint32_t *pd = (uint32_t *)cur->page_directory;
/* Unmap and free old user code pages (0x08048000 region).
* We don't know exactly how many pages were mapped, so scan a
* reasonable range. */
for (uint32_t vaddr = USER_CODE_START;
vaddr < USER_CODE_START + 0x100000; /* up to 1 MiB of code */
vaddr += 4096) {
uint32_t pd_idx = vaddr >> 22;
uint32_t pt_idx = (vaddr >> 12) & 0x3FF;
if (!(pd[pd_idx] & 0x001)) break; /* No page table */
uint32_t *pt = (uint32_t *)(pd[pd_idx] & 0xFFFFF000);
if (!(pt[pt_idx] & 0x001)) break; /* No page */
phys_addr_t old_phys = pt[pt_idx] & 0xFFFFF000;
pt[pt_idx] = 0;
pmm_free_page(old_phys);
}
/* Map new code pages */
uint32_t code_pages = (entry.datasize + 4095) / 4096;
for (uint32_t i = 0; i < code_pages; i++) {
phys_addr_t phys = pmm_alloc_page(PMM_ZONE_NORMAL);
if (phys == 0) return -1;
uint32_t vaddr = USER_CODE_START + i * 4096;
paging_map_page_in(pd, vaddr, phys,
PAGE_PRESENT | PAGE_WRITE | PAGE_USER);
uint32_t offset = i * 4096;
uint32_t bytes = entry.datasize - offset;
if (bytes > 4096) bytes = 4096;
memcpy((void *)phys, (const uint8_t *)entry.data + offset, bytes);
if (bytes < 4096) {
memset((void *)(phys + bytes), 0, 4096 - bytes);
}
}
/* Zero the user stack pages (reuse existing stack mappings) */
uint32_t stack_base = USER_STACK_TOP - USER_STACK_PAGES * 4096;
for (uint32_t i = 0; i < USER_STACK_PAGES; i++) {
uint32_t vaddr = stack_base + i * 4096;
uint32_t pd_idx = vaddr >> 22;
uint32_t pt_idx = (vaddr >> 12) & 0x3FF;
if ((pd[pd_idx] & 0x001)) {
uint32_t *pt = (uint32_t *)(pd[pd_idx] & 0xFFFFF000);
if ((pt[pt_idx] & 0x001)) {
phys_addr_t phys = pt[pt_idx] & 0xFFFFF000;
memset((void *)phys, 0, 4096);
}
}
}
/* Flush TLB */
paging_switch_directory(cur->page_directory);
/* Update process name */
uint32_t nlen = strlen(path);
if (nlen > 31) nlen = 31;
memcpy(cur->name, path, nlen);
cur->name[nlen] = '\0';
/* Set up registers for the new program */
regs->eip = USER_CODE_START;
regs->useresp = USER_STACK_TOP;
regs->esp = USER_STACK_TOP;
regs->eax = 0;
regs->ebx = 0;
regs->ecx = 0;
regs->edx = 0;
regs->esi = 0;
regs->edi = 0;
regs->ebp = 0;
regs->cs = 0x1B;
regs->ds = 0x23;
regs->ss = 0x23;
regs->eflags = 0x202; /* IF=1 */
/* Return SYSCALL_SWITCHED so syscall_handler doesn't overwrite regs */
return SYSCALL_SWITCHED;
}
/**