From f426ba00fc5905b0c91e28b8edfaef6c78f52cfd Mon Sep 17 00:00:00 2001 From: Johan Manuel Date: Thu, 3 Dec 2020 12:19:06 +0100 Subject: [PATCH] Working stdout implementation A process can now declare itself to be a terminal with the `maketty` system call. This gives it a new file descriptor corresponding to stdout, that is then inherited by the processes it executes, and the processes those execute, etc... It's the terminal's job to read from stdout (a strange concept, I know) and do something with it. `stdout` is backed by a circular buffer; old content may be overwritten if not read. --- README.md | 2 +- kernel/include/kernel/pipe.h | 12 ++++ kernel/include/kernel/proc.h | 3 +- kernel/include/kernel/uapi/uapi_fs.h | 2 + kernel/include/kernel/uapi/uapi_syscall.h | 3 +- kernel/src/misc/pipe.c | 85 +++++++++++++++++++++++ kernel/src/sys/proc.c | 72 ++++++++++++------- kernel/src/sys/syscall.c | 14 ++++ libc/include/stdio.h | 4 +- libc/include/unistd.h | 5 ++ libc/src/stdio/printf.c | 9 ++- libc/src/stdio/putchar.c | 14 ++-- modules/src/terminal.c | 47 ++++++++----- ui/include/ui.h | 2 +- 14 files changed, 215 insertions(+), 59 deletions(-) create mode 100644 kernel/include/kernel/pipe.h create mode 100644 kernel/src/misc/pipe.c diff --git a/README.md b/README.md index e7eb490..c2ea84e 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Run either make qemu # or make bochs -to test SnowflakeOS in a VM. +to test SnowflakeOS in a VM. See [the edit/debug cycle](https://github.com/29jm/SnowflakeOS/wiki/The-edit-debug-cycle) for more options on how to compile and run SnowflakeOS. Testing this project on real hardware is possible. You can copy `SnowflakeOS.iso` to an usb drive using `dd`, like you would when making a live usb of another OS, and boot it directly. Note that this is rarely ever tested, who knows what it'll do :) I'd love to hear about it if you try this, on which hardware, etc... diff --git a/kernel/include/kernel/pipe.h b/kernel/include/kernel/pipe.h new file mode 100644 index 0000000..35bdbef --- /dev/null +++ b/kernel/include/kernel/pipe.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +typedef struct pipe_t { + inode_t inode; + uint8_t* buf; + uint32_t index; +} pipe_t; + +inode_t* pipe_new(); +inode_t* pipe_clone(inode_t* pipe); \ No newline at end of file diff --git a/kernel/include/kernel/proc.h b/kernel/include/kernel/proc.h index b28a395..d5f0afd 100644 --- a/kernel/include/kernel/proc.h +++ b/kernel/include/kernel/proc.h @@ -68,10 +68,11 @@ void proc_enter_usermode(); void proc_switch_process(process_t* next); uint32_t proc_get_current_pid(); char* proc_get_cwd(); +void proc_add_fd(ft_entry_t* entry); + void proc_sleep(uint32_t ms); void* proc_sbrk(intptr_t size); int32_t proc_exec(const char* path, char** argv); -ft_entry_t* proc_fd_to_entry(uint32_t fd); uint32_t proc_open(const char* path, uint32_t flags); void proc_close(uint32_t fd); uint32_t proc_read(uint32_t fd, uint8_t* buf, uint32_t size); diff --git a/kernel/include/kernel/uapi/uapi_fs.h b/kernel/include/kernel/uapi/uapi_fs.h index 8b510bd..f6b1024 100644 --- a/kernel/include/kernel/uapi/uapi_fs.h +++ b/kernel/include/kernel/uapi/uapi_fs.h @@ -22,6 +22,8 @@ #define DENT_FILE 1 #define DENT_DIRECTORY 2 +#define FS_STDOUT_FILENO 1 + typedef struct { uint32_t inode; uint16_t entry_size; diff --git a/kernel/include/kernel/uapi/uapi_syscall.h b/kernel/include/kernel/uapi/uapi_syscall.h index b77176a..07a3499 100644 --- a/kernel/include/kernel/uapi/uapi_syscall.h +++ b/kernel/include/kernel/uapi/uapi_syscall.h @@ -24,7 +24,8 @@ #define SYS_GETCWD 18 #define SYS_UNLINK 19 #define SYS_RENAME 20 -#define SYS_MAX 21 // First invalid syscall number +#define SYS_MAKETTY 21 +#define SYS_MAX 22 // First invalid syscall number #define SYS_INFO_UPTIME 1 #define SYS_INFO_MEMORY 2 diff --git a/kernel/src/misc/pipe.c b/kernel/src/misc/pipe.c new file mode 100644 index 0000000..70f8476 --- /dev/null +++ b/kernel/src/misc/pipe.c @@ -0,0 +1,85 @@ +#include +#include + +#include +#include + +#define PIPE_SIZE 2048 + +typedef struct pipe_fs_t { + fs_t fs; + uint8_t* buf; + uint32_t buf_size; + uint32_t content_start; + uint32_t content_size; + uint32_t refcount; +} pipe_fs_t; + +uint32_t pipe_read(pipe_fs_t* pipe, uint32_t inode, uint32_t offset, uint8_t* buf, uint32_t size) { + UNUSED(inode); // There's only one "file" in this fs, the pipe itself + UNUSED(offset); + + for (uint32_t i = 0; i < size; i++) { + if (!pipe->content_size) { + return i; + } + + buf[i] = pipe->buf[pipe->content_start]; + pipe->content_start = (pipe->content_start + 1) % pipe->buf_size; + pipe->content_size--; + } + + return size; +} + +uint32_t pipe_append(pipe_fs_t* pipe, uint32_t inode, uint8_t* data, uint32_t size) { + UNUSED(inode); + + for (uint32_t i = 0; i < size; i++) { + if (pipe->content_size == PIPE_SIZE) { + pipe->buf[pipe->content_start] = data[i]; + pipe->content_start = (pipe->content_start + 1) % pipe->buf_size; + } else { + pipe->buf[(pipe->content_start + pipe->content_size) % pipe->buf_size] = data[i]; + pipe->content_size++; + } + } + + return size; +} + +int32_t pipe_close(pipe_fs_t* fs, uint32_t ino) { + UNUSED(ino); + + if (--fs->refcount == 0) { + kfree(fs->buf); + kfree(fs->fs.root); + kfree(fs); + } + + return 0; +} + +inode_t* pipe_new() { + // TODO: have root inodes not necessarily be folders? + inode_t* p = zalloc(sizeof(folder_inode_t)); + p->fs = zalloc(sizeof(pipe_fs_t)); + + pipe_fs_t* fs = (pipe_fs_t*) p->fs; + fs->buf = zalloc(PIPE_SIZE); + fs->buf_size = PIPE_SIZE; + fs->refcount = 1; + + p->fs->root = (folder_inode_t*) p; + p->fs->read = (fs_read_t) pipe_read; + p->fs->append = (fs_append_t) pipe_append; + p->fs->close = (fs_close_t) pipe_close; + + return p; +} + +inode_t* pipe_clone(inode_t* pipe) { + ((pipe_fs_t*) pipe->fs)->refcount++; + + return pipe; +} \ No newline at end of file diff --git a/kernel/src/sys/proc.c b/kernel/src/sys/proc.c index 518e8d4..011ab8d 100644 --- a/kernel/src/sys/proc.c +++ b/kernel/src/sys/proc.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -195,8 +196,6 @@ process_t* proc_run_code(uint8_t* code, uint32_t size, char** argv) { * Implements the `exit` system call. */ void proc_exit_current_process() { - printk("terminating process %d", current_process->pid); - // Free allocated pages: code, heap, stack, page directory directory_entry_t* pd = (directory_entry_t*) 0xFFFFF000; @@ -217,7 +216,8 @@ void proc_exit_current_process() { // Free the file descriptor list while (!list_empty(¤t_process->filetable)) { - fs_close(list_first_entry(¤t_process->filetable, ft_entry_t)->inode); // bit ugly + ft_entry_t* ent = list_first_entry(¤t_process->filetable, ft_entry_t); + fs_close(ent->inode); list_del(list_first(¤t_process->filetable)); } @@ -283,6 +283,35 @@ void proc_enter_usermode() { : "%eax"); } +/* Returns the filetable entry associated with fd, if any. + */ +ft_entry_t* proc_fd_to_entry(uint32_t fd) { + ft_entry_t* ent; + + list_for_each_entry(ent, ¤t_process->filetable) { + if (ent->fd == fd) { + return ent; + } + } + + return NULL; +} + +void proc_add_fd(ft_entry_t* entry) { + if (!proc_fd_to_entry(entry->fd)) { + list_add_front(¤t_process->filetable, entry); + } +} + +/* Returns a new and unused fd for the current process. + * TODO: make it use the lowest fd available. + */ +uint32_t proc_next_fd() { + static uint32_t avail = 3; + + return avail++; +} + uint32_t proc_get_current_pid() { if (current_process) { return current_process->pid; @@ -350,6 +379,7 @@ void* proc_sbrk(intptr_t size) { } int32_t proc_exec(const char* path, char** argv) { + /* Read the executable */ inode_t* in = fs_open(path, O_RDONLY); if (!in || in->type != DENT_FILE) { @@ -360,7 +390,18 @@ int32_t proc_exec(const char* path, char** argv) { uint32_t read = fs_read(in, 0, data, in->size); if (read == in->size && in->size) { - proc_run_code(data, in->size, argv); + process_t* p = proc_run_code(data, in->size, argv); + + /* If the process to be executed has a parent that has a terminal, + * make it this process's terminal too */ + if (proc_get_current_pid() != 0) { + ft_entry_t* stdout = proc_fd_to_entry(1); + + if (stdout) { + stdout->inode = pipe_clone(stdout->inode); + list_add_front(&p->filetable, stdout); + } + } } else { printke("exec failed while reading the executable"); return -1; @@ -369,29 +410,6 @@ int32_t proc_exec(const char* path, char** argv) { return 0; } -/* Returns the filetable entry associated with fd, if any. - */ -ft_entry_t* proc_fd_to_entry(uint32_t fd) { - ft_entry_t* ent; - - list_for_each_entry(ent, ¤t_process->filetable) { - if (ent->fd == fd) { - return ent; - } - } - - return NULL; -} - -/* Returns a new and unused fd for the current process. - * TODO: make it use the lowest fd available. - */ -uint32_t proc_next_fd() { - static uint32_t avail = 1; - - return avail++; -} - uint32_t proc_open(const char* path, uint32_t flags) { inode_t* in = fs_open((char*) path, flags); // TODO diff --git a/kernel/src/sys/syscall.c b/kernel/src/sys/syscall.c index 957a747..8c47112 100644 --- a/kernel/src/sys/syscall.c +++ b/kernel/src/sys/syscall.c @@ -6,6 +6,7 @@ #include #include #include +#include #include // for UNUSED macro #include @@ -36,6 +37,7 @@ static void syscall_chdir(registers_t* regs); static void syscall_getcwd(registers_t* regs); static void syscall_unlink(registers_t* regs); static void syscall_rename(registers_t* regs); +static void syscall_maketty(registers_t* regs); handler_t syscall_handlers[SYSCALL_NUM] = { 0 }; @@ -62,6 +64,7 @@ void init_syscall() { syscall_handlers[SYS_GETCWD] = syscall_getcwd; syscall_handlers[SYS_UNLINK] = syscall_unlink; syscall_handlers[SYS_RENAME] = syscall_rename; + syscall_handlers[SYS_MAKETTY] = syscall_maketty; } static void syscall_handler(registers_t* regs) { @@ -261,4 +264,15 @@ static void syscall_rename(registers_t* regs) { char* new_path = (char*) regs->ecx; regs->eax = fs_rename(old_path, new_path); +} + +static void syscall_maketty(registers_t* regs) { + ft_entry_t* entry = zalloc(sizeof(ft_entry_t)); + + entry->fd = FS_STDOUT_FILENO; + entry->inode = pipe_new(); + + proc_add_fd(entry); + + regs->eax = 0; } \ No newline at end of file diff --git a/libc/include/stdio.h b/libc/include/stdio.h index 4ca7450..d93f9f5 100644 --- a/libc/include/stdio.h +++ b/libc/include/stdio.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #define EOF -1 @@ -10,6 +10,8 @@ typedef struct { char* name; } FILE; +extern FILE* stdout; + int printf(const char* __restrict, ...); int putchar(int); int puts(const char*); diff --git a/libc/include/unistd.h b/libc/include/unistd.h index 07cf8b7..cb9760b 100644 --- a/libc/include/unistd.h +++ b/libc/include/unistd.h @@ -1,9 +1,14 @@ #pragma once #include +#include + +#define STDOUT_FILENO FS_STDOUT_FILENO #ifndef _KERNEL_ + int chdir(const char* path); char* getcwd(char* buf, size_t size); int unlink(const char* path); + #endif \ No newline at end of file diff --git a/libc/src/stdio/printf.c b/libc/src/stdio/printf.c index 696b9f0..0638a3c 100644 --- a/libc/src/stdio/printf.c +++ b/libc/src/stdio/printf.c @@ -6,9 +6,16 @@ #include #include -#include +static FILE __stdout = (FILE) { + .fd = STDOUT_FILENO, .name = "stdout" +}; + +FILE* stdout = &__stdout; static void print(const char* data, size_t data_length) { +#ifndef _KERNEL_ + fwrite(data, 1, data_length, stdout); +#endif for (size_t i = 0; i < data_length; i++) { putchar((int) data[i]); } diff --git a/libc/src/stdio/putchar.c b/libc/src/stdio/putchar.c index 853dd70..8c5b207 100644 --- a/libc/src/stdio/putchar.c +++ b/libc/src/stdio/putchar.c @@ -3,20 +3,18 @@ #ifdef _KERNEL_ #include #include +#else +#include #endif +extern int32_t syscall1(uint32_t eax, uint32_t ebx); + +/* Nothing to do with libc's putchar; this writes to serial output */ int putchar(int c) { #ifdef _KERNEL_ - // term_putchar(c); serial_write(c); #else - asm volatile ( - "mov $3, %%eax\n" - "mov %[c], %%ebx\n" - "int $0x30\n" - :: [c] "r" (c) - : "%eax", "%ebx" - ); + syscall1(SYS_PUTCHAR, c); #endif return c; } diff --git a/modules/src/terminal.c b/modules/src/terminal.c index 261421a..d21c83b 100644 --- a/modules/src/terminal.c +++ b/modules/src/terminal.c @@ -39,6 +39,8 @@ bool focused = true; int main() { win = snow_open_window("Terminal", twidth, theight, WM_NORMAL); + syscall(SYS_MAKETTY); + str_t* text_buf = str_new(prompt); str_t* input_buf = str_new(""); cursor = true; @@ -50,16 +52,16 @@ int main() { while (running) { wm_event_t event = snow_get_event(win); wm_kbd_event_t key = event.kbd; - bool focus_changed = false; + bool needs_redrawing = false; // Do we have focus? if (event.type & WM_EVENT_GAINED_FOCUS) { focused = true; - focus_changed = true; + needs_redrawing = true; } else if (event.type & WM_EVENT_LOST_FOCUS) { focused = false; cursor = false; - focus_changed = true; + needs_redrawing = true; } // Time & cursor blinks @@ -74,24 +76,37 @@ int main() { cursor = !cursor; event.type |= WM_EVENT_KBD; event.kbd.pressed = true; + needs_redrawing = true; } } - // Redraw on input & focus change - if (!(event.type & WM_EVENT_KBD && event.kbd.pressed) && !focus_changed) { - snow_sleep(10); - continue; + // Print things that have been output, if any, and append a prompt + const uint32_t buf_size = 256; + char buf[buf_size]; + uint32_t read; + bool anything_read = false; + + while ((read = fread(buf, 1, buf_size - 1, stdout))) { + buf[read] = '\0'; + str_append(text_buf, buf); + needs_redrawing = true; + anything_read = true; + } + + if (anything_read) { + str_append(text_buf, prompt); } - switch (event.kbd.keycode) { + if (event.type & WM_EVENT_KBD && event.kbd.pressed) { + needs_redrawing = true; + switch (key.keycode) { case KBD_ENTER: case KBD_KP_ENTER: str_append(text_buf, input_buf->buf); - str_append(text_buf, "\n"); interpret_cmd(text_buf, input_buf); + printf("\n"); input_buf->buf[0] = '\0'; input_buf->len = 0; - str_append(text_buf, prompt); break; case KBD_BACKSPACE: if (input_buf->len) { @@ -106,9 +121,12 @@ int main() { str_append(input_buf, str); } break; + } } - redraw(text_buf, input_buf); + if (needs_redrawing) { + redraw(text_buf, input_buf); + } } str_free(text_buf); @@ -228,13 +246,6 @@ void interpret_cmd(str_t* text_buf, str_t* input_buf) { if (!strcmp(input_buf->buf, "exit")) { running = false; return; - } else if (!strcmp(input_buf->buf, "log")) { - sys_info_t info = { .kernel_log = malloc(2048) }; - - syscall2(SYS_INFO, SYS_INFO_LOG, (uintptr_t) &info); - str_append(text_buf, info.kernel_log); - free(info.kernel_log); - return; } char* cmd = input_buf->buf; diff --git a/ui/include/ui.h b/ui/include/ui.h index 5952acd..65571e6 100644 --- a/ui/include/ui.h +++ b/ui/include/ui.h @@ -30,7 +30,7 @@ typedef struct { } point_t; /* Main widget class. Other widgets "inherit" it by having a `widget_t` as first - * struct member, so that they can be casted as `widget_t`. + * struct member, so that they can be cast as `widget_t`. */ typedef struct _widget_t { struct _widget_t* parent;