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

21
apps/libc/crt0.S Normal file
View File

@@ -0,0 +1,21 @@
/**
* @file crt0.S
* @brief C runtime startup for user-mode applications.
*
* Calls the C main() function and then exits with the return value.
*/
.section .text
.global _start
.extern main
_start:
/* Call main() */
call main
/* Exit with main's return value (in EAX) */
movl %eax, %ebx /* exit code = return value */
movl $0, %eax /* SYS_EXIT = 0 */
int $0x80
/* Should never reach here */
1: jmp 1b

147
apps/libc/syscalls.h Normal file
View File

@@ -0,0 +1,147 @@
/**
* @file syscalls.h
* @brief User-space system call wrappers for ClaudeOS.
*
* Provides inline functions that invoke INT 0x80 with the appropriate
* system call numbers and arguments.
*/
#ifndef USERSPACE_SYSCALLS_H
#define USERSPACE_SYSCALLS_H
typedef unsigned int uint32_t;
typedef int int32_t;
/* System call numbers (must match kernel's syscall.h) */
#define SYS_EXIT 0
#define SYS_WRITE 1
#define SYS_READ 2
#define SYS_FORK 3
#define SYS_GETPID 4
#define SYS_YIELD 5
#define SYS_WAITPID 6
#define SYS_EXEC 7
#define SYS_GETENV 8
#define SYS_SETENV 9
static inline int32_t syscall0(int num) {
int32_t ret;
__asm__ volatile("int $0x80" : "=a"(ret) : "a"(num));
return ret;
}
static inline int32_t syscall1(int num, uint32_t arg1) {
int32_t ret;
__asm__ volatile("int $0x80" : "=a"(ret) : "a"(num), "b"(arg1));
return ret;
}
static inline int32_t syscall2(int num, uint32_t arg1, uint32_t arg2) {
int32_t ret;
__asm__ volatile("int $0x80" : "=a"(ret)
: "a"(num), "b"(arg1), "c"(arg2));
return ret;
}
static inline int32_t syscall3(int num, uint32_t arg1, uint32_t arg2, uint32_t arg3) {
int32_t ret;
__asm__ volatile("int $0x80" : "=a"(ret)
: "a"(num), "b"(arg1), "c"(arg2), "d"(arg3));
return ret;
}
static inline void exit(int code) {
syscall1(SYS_EXIT, (uint32_t)code);
__builtin_unreachable();
}
static inline int32_t write(int fd, const void *buf, uint32_t len) {
return syscall3(SYS_WRITE, (uint32_t)fd, (uint32_t)buf, len);
}
static inline int32_t read(int fd, void *buf, uint32_t len) {
return syscall3(SYS_READ, (uint32_t)fd, (uint32_t)buf, len);
}
static inline int32_t fork(void) {
return syscall0(SYS_FORK);
}
static inline int32_t getpid(void) {
return syscall0(SYS_GETPID);
}
static inline void yield(void) {
syscall0(SYS_YIELD);
}
static inline int32_t waitpid(int32_t pid) {
return syscall1(SYS_WAITPID, (uint32_t)pid);
}
static inline int32_t exec(const char *path) {
return syscall1(SYS_EXEC, (uint32_t)path);
}
static inline int32_t getenv(const char *key, char *buf, uint32_t bufsize) {
return syscall3(SYS_GETENV, (uint32_t)key, (uint32_t)buf, bufsize);
}
static inline int32_t setenv(const char *key, const char *value) {
return syscall2(SYS_SETENV, (uint32_t)key, (uint32_t)value);
}
/* Basic string operations for user-space */
static inline uint32_t strlen(const char *s) {
uint32_t len = 0;
while (s[len]) len++;
return len;
}
static inline int strcmp(const char *a, const char *b) {
while (*a && *a == *b) { a++; b++; }
return (unsigned char)*a - (unsigned char)*b;
}
static inline int strncmp(const char *a, const char *b, uint32_t n) {
while (n && *a && *a == *b) { a++; b++; n--; }
return n ? ((unsigned char)*a - (unsigned char)*b) : 0;
}
static inline char *strcpy(char *dst, const char *src) {
char *d = dst;
while ((*d++ = *src++));
return dst;
}
static inline char *strncpy(char *dst, const char *src, uint32_t n) {
char *d = dst;
while (n && (*d++ = *src++)) n--;
while (n--) *d++ = '\0';
return dst;
}
static inline void *memset(void *s, int c, uint32_t n) {
unsigned char *p = (unsigned char *)s;
while (n--) *p++ = (unsigned char)c;
return s;
}
static inline void *memcpy(void *dst, const void *src, uint32_t n) {
unsigned char *d = (unsigned char *)dst;
const unsigned char *s = (const unsigned char *)src;
while (n--) *d++ = *s++;
return dst;
}
/** Print a string to stdout. */
static inline void puts(const char *s) {
write(1, s, strlen(s));
}
/** Print a single character to stdout. */
static inline void putchar(char c) {
write(1, &c, 1);
}
#endif /* USERSPACE_SYSCALLS_H */

180
apps/sh/sh.c Normal file
View File

@@ -0,0 +1,180 @@
/**
* @file sh.c
* @brief ClaudeOS shell.
*
* A simple interactive shell that reads commands from stdin,
* supports built-in commands (cd, exit, help, env), and
* executes external programs via fork+exec.
*/
#include "syscalls.h"
/** Maximum command line length. */
#define CMD_MAX 256
/** Read a line from stdin with echo and basic line editing.
* Returns length of the line (excluding newline). */
static int readline(char *buf, int maxlen) {
int pos = 0;
while (pos < maxlen - 1) {
char c;
int n = read(0, &c, 1);
if (n <= 0) {
/* No data yet: yield CPU and retry */
yield();
continue;
}
if (c == '\n' || c == '\r') {
putchar('\n');
break;
} else if (c == '\b' || c == 127) {
/* Backspace */
if (pos > 0) {
pos--;
puts("\b \b"); /* Move back, overwrite with space, move back */
}
} else if (c >= 32) {
/* Printable character */
buf[pos++] = c;
putchar(c);
}
}
buf[pos] = '\0';
return pos;
}
/** Skip leading whitespace, return pointer to first non-space. */
static char *skip_spaces(char *s) {
while (*s == ' ' || *s == '\t') s++;
return s;
}
/** Find the next space or end of string. */
static char *find_space(char *s) {
while (*s && *s != ' ' && *s != '\t') s++;
return s;
}
/** Built-in: cd <path> */
static void builtin_cd(char *arg) {
if (!arg || !*arg) {
arg = "/";
}
/* Update CWD environment variable */
setenv("CWD", arg);
/* Verify it was set */
char cwd[128];
if (getenv("CWD", cwd, sizeof(cwd)) >= 0) {
puts("cd: ");
puts(cwd);
putchar('\n');
}
}
/** Built-in: env - print all known env vars. */
static void builtin_env(void) {
char buf[128];
if (getenv("CWD", buf, sizeof(buf)) >= 0) {
puts("CWD=");
puts(buf);
putchar('\n');
}
if (getenv("PATH", buf, sizeof(buf)) >= 0) {
puts("PATH=");
puts(buf);
putchar('\n');
}
if (getenv("TEST", buf, sizeof(buf)) >= 0) {
puts("TEST=");
puts(buf);
putchar('\n');
}
}
/** Built-in: help */
static void builtin_help(void) {
puts("ClaudeOS Shell\n");
puts("Built-in commands:\n");
puts(" cd <path> - change working directory\n");
puts(" env - show environment variables\n");
puts(" help - show this message\n");
puts(" exit - exit the shell\n");
puts("External commands are loaded from initrd.\n");
}
/** Execute an external command via fork+exec. */
static void run_command(const char *cmd) {
int32_t pid = fork();
if (pid < 0) {
puts("sh: fork failed\n");
return;
}
if (pid == 0) {
/* Child: exec the command */
int32_t ret = exec(cmd);
if (ret < 0) {
puts("sh: ");
puts(cmd);
puts(": not found\n");
exit(127);
}
/* exec doesn't return on success */
exit(1);
}
/* Parent: wait for child */
waitpid(pid);
}
int main(void) {
puts("ClaudeOS Shell v0.1\n");
puts("Type 'help' for available commands.\n\n");
char cmd[CMD_MAX];
for (;;) {
/* Print prompt with CWD */
char cwd[128];
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
strcpy(cwd, "/");
}
puts(cwd);
puts("$ ");
/* Read command */
int len = readline(cmd, CMD_MAX);
if (len == 0) continue;
/* Parse command and arguments */
char *line = skip_spaces(cmd);
if (*line == '\0') continue;
char *arg_start = find_space(line);
char *arg = (char *)0;
if (*arg_start) {
*arg_start = '\0';
arg = skip_spaces(arg_start + 1);
if (*arg == '\0') arg = (char *)0;
}
/* Check built-in commands */
if (strcmp(line, "exit") == 0) {
puts("Goodbye!\n");
exit(0);
} else if (strcmp(line, "cd") == 0) {
builtin_cd(arg);
} else if (strcmp(line, "env") == 0) {
builtin_env();
} else if (strcmp(line, "help") == 0) {
builtin_help();
} else {
/* External command */
run_command(line);
}
}
return 0;
}