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:
21
apps/libc/crt0.S
Normal file
21
apps/libc/crt0.S
Normal 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
147
apps/libc/syscalls.h
Normal 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
180
apps/sh/sh.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user