1 Commits

Author SHA1 Message Date
xamidev 04900fbd74 File descriptors but bad 2026-05-06 10:44:55 +02:00
21 changed files with 100 additions and 301 deletions
+5 -6
View File
@@ -1,5 +1,3 @@
USER_PROGRAMS := pedicel.raw apex.raw
USER_FILES := wow.txt
BUILDDIR := build
ELFFILE := pepperk
@@ -23,6 +21,10 @@ CC_FLAGS=-Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fstack-protector -fn
LD := x86_64-elf-ld
$(ELFFILE): $(BUILDDIR) $(OBJFILES)
nasm -f bin user/hello.S -o $(BUILDDIR)/hello
nasm -f bin user/pedicel.S -o $(BUILDDIR)/pedicel
tar cvf $(BUILDDIR)/initfs.tar -C $(BUILDDIR) hello pedicel
nasm -f elf64 src/arch/x86/idt.S -o $(BUILDDIR)/idt_stub.o
$(LD) -o $(ELFFILE) -T linker.ld $(OBJFILES) $(BUILDDIR)/idt_stub.o
# Get the symbols for debugging
@@ -43,10 +45,7 @@ limine/limine:
git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1
$(MAKE) -C limine
.PHONY: user
user:
$(MAKE) -C user
tar cvf $(BUILDDIR)/initfs.tar -C $(BUILDDIR) $(USER_PROGRAMS) -C ../user $(USER_FILES)
initfs:
build-iso: limine/limine $(ELFFILE)
rm -rf iso_root
+27 -6
View File
@@ -1,5 +1,5 @@
# <img width="40" height="40" alt="red-pepper" src="https://i.ibb.co/mrHH6d1m/pixil-frame-0-4.png" /> pepperOS: "will never be done"
<a href="https://ibb.co/KxbfxmFB"><img src="https://i.ibb.co/GQn8QFcG/pepper.png" alt="pepper" border="0"></a>
## Description
PepperOS is a 64-bit freely-licensed monolithic kernel for x86 processors, with round-robin preemptive scheduling and 4-level paging. See the [manual](docs/MANUAL.md) for more.
@@ -23,9 +23,8 @@ CC := gcc
LD := ld
```
Then, to compile the kernel and make an ISO image file, run: `make`.
To build the user programs and the initial filesystem, do `make user`.
To run it with QEMU, do: `make run`.
Then, to compile the kernel and make an ISO image file, run: `make build-iso`
To run it with QEMU, do: `make run`
## Trying the kernel on real hardware
@@ -49,9 +48,31 @@ These features can be activated by setting them to "true" at the end of the make
make UBSAN=true
```
## Writing software for PepperOS
## TODO
If you want to write software for PepperOS, take a look at the [Software Developer's guide](docs/SOFTWARE.md).
The basics that I'm targeting are:
### Basic utility of what we call a "kernel"
- Implement tasks, and task switching + context switching and spinlock acquire/release
- Load an executable
- Filesystem (TAR for read-only initfs, then maybe read-write using FAT12/16/32 or easier fs) w/ VFS layer
- Getting to userspace (ring 3 switching, syscall interface)
- Porting musl libc or equivalent
### Scalability/maintenance/expansion features
- Documentation
- SOME error handling in functions
- Unit tests
- Good error codes (like Linux kernel: ENOMEM, ENOENT, ...)
### Optional features
In the future, maybe?
- SMP support (Limine provides functionality to make this easier)
- Parsing the ACPI tables and using them for something
- Replacing the PIT timer with APIC
## Thanks
+2 -6
View File
@@ -41,12 +41,8 @@ The recommended hardware to run PepperOS is the following:
## III. Syscall table
The syscall interface in the Pepper kernel uses the System V ABI convention for argument order.
It vaguely mimics Unix-like systems.
Name | Number (%rax) | arg0 (%rdi) | arg1 (%rsi) | arg2 (%rdx) |
|---|---|---|---|---|
| sys_read | 0 | unsigned int fd | char* buf | size_t count |
| sys_write | 1 | unsigned int fd | const char* buf | size_t count |
| sys_open | 2 | const char* filename | int flags | |
| sys_close | 3 | unsigned int fd | | |
| sys_exit | 60 | int error_code | | |
| sys_write | 1 | unsigned int fd | const char* buf | size_t count | |
| sys_exit | 60 | int error_code | | | |
-57
View File
@@ -1,57 +0,0 @@
# Writing software for PepperOS
## Why would you want to do that?
Honestly I have no idea. Maybe you have too much free time.
Keep in mind that the Pepper kernel is a personal project and it's full of bugs, inconsistencies, weird ways of doing things (and I don't care because it's my toy).
Now if you still want to write something for this OS, thank you. Follow along.
## 1. Write the source code
PepperOS is able to run programs written in x86 assembly, and C programs.
### x86 Assembly
Start your assembly file with the `bits 64` instruction, to emit 64-bit code.
You can add sections `.text`, `.data`, `.bss` as you need.
The three things to take in consideration here are:
- PepperOS does not use the `syscall` instruction, instead it uses the old-fashioned `int 0x80` to trigger a system call.
- The entry point should be labelled as `_start`.
- At the end of the file, there should be an exit system call followed by a loop, like so:
```nasm
.end:
mov rax, 0x3C
mov rdi, 0x0
int 0x80
.loop:
jmp .loop
```
For an example, look at the file [pedicel.S](../user/pedicel.S).
### C program
You will find relevant headers in the `libc` directory. They contain system call wrappers, utility functions, and more. See what's implemented there and what's not.
To invoke a system call you can use the functions defined in `libc/syscall.h`.
## 2. Add the Makefile rule and variable
Now that your code is complete, add a Makefile rule to `user/Makefile` with your program name. You can just copy-paste the rule that applies to you (either from an Assembly source or C source) and change the name of the files (.raw, .elf, etc...) in the rule.
For clarity, raw binaries have the `.raw` extension, and ELF ones have `.elf`.
You also now have to add the name of the executable to the `USER_PROGRAMS` variable at the top of the global Makefile.
Finally, do `make user` to compile your program.
## 3. Run your program
You can now boot up PepperOS, in a VM or on real hardware, and use the kernel's shell to `list` files in the filesystem (to see if your executable was properly added), and then, run it with the `load` command. Congratulations, you made a program for a random hobby OS!
## 4. (Optional) debugging
Use GDB with the `make debug` rule!
For your information, user programs are loaded at `0x400000`. Can be good to know to set breakpoints.
## 5. (Optional) contribute!
If you like what you've done and you think it could be nice to add it to PepperOS, send it to me by e-mail: `xamidev (at) riseup (dot) net`. It may or may not be added in a future release... who knows?
-8
View File
@@ -2,14 +2,6 @@
This document describes the coding style for the Pepper kernel. It is used as a guideline across all source files.
## Setting up a language server (optional)
Before you do anything you might want to setup a language server with your editor. This will save you lots of time correcting errors and stuff. I use `clangd`, and generate my `compile_commands.json` like so:
```
bear -- make
```
## Indentation
Indentations should be 4 characters long.
-1
View File
@@ -66,5 +66,4 @@
/* fs */
#define FDT_MAX 8 // Maximum amount of file descriptors per process
#endif
+1 -2
View File
@@ -11,8 +11,7 @@
int initfs_init(struct limine_file* tar_file);
int tar_lookup(unsigned char* archive, char* filename, char** out);
int tar_read(char* filename, char** buf);
int tar_exists(const char* filename);
int tar_read(char* filename, char* out, int count, int offset);
void tar_list();
#endif
+1 -2
View File
@@ -14,8 +14,7 @@ enum ErrorCodes {
EIO, // Input/output error
ENOENT, // No entry
EBADFD, // Bad file descriptor
EMFILE, // Too many open files
EINVAL // Invalid argument
EMFILE // Too many open files
};
#define CLEAR_INTERRUPTS __asm__ volatile("cli")
-1
View File
@@ -50,7 +50,6 @@ void process_exit(void);
void process_display_list(struct process* processes_list);
void process_create_user_raw(char* file, int size, char* name);
#endif
+18 -26
View File
@@ -70,12 +70,10 @@ int sys_read(unsigned int fd, char* buf, size_t count)
return -EBADFD; // File descriptor wasn't open
}
// Here fd refers to a valid opened file..
int sz = tar_read(current_process->fdt[fd].filename, buf, count,
current_process->fdt[fd].cursor);
int sz = tar_read(current_process->fdt[fd].filename,&buf);
if (sz == 0) {
return -ENOENT;
} else {
current_process->fdt[fd].cursor += sz;
return sz;
}
}
@@ -84,25 +82,27 @@ int sys_read(unsigned int fd, char* buf, size_t count)
}
// TODO: Should have a return value: number of bytes written on success, -1 on error (errno set)
int sys_write(unsigned int fd, const char* buf, size_t count)
void sys_write(unsigned int fd, const char* buf, size_t count)
{
switch (fd) {
case 1: //stdout
for (size_t i=0; i<count; i++) {
internal_putc(buf[i], NULL);
}
break;
case 2: //stderr
for (size_t i=0; i<count; i++) {
internal_putc(buf[i], NULL);
}
return count;
default:
return -EBADFD;
break;
}
}
int sys_exit(int error_code)
void sys_exit(int error_code)
{
current_process->status = DEAD;
DEBUG("(pid=%u, name=%s)", current_process->pid, current_process->name);
return error_code;
DEBUG("exiting process PID=%u name=%s", current_process->pid, current_process->name);
}
/*
@@ -125,36 +125,28 @@ int sys_exit(int error_code)
*/
struct cpu_status* syscall_handler(struct cpu_status* regs)
{
DEBUG("Syscall %lx with (arg0=%lx arg1=%lx)", regs->rax, regs->rdi, regs->rsi);
switch (regs->rax)
{
case 0:
DEBUG("sys_read(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx);
case 0: //sys_read
regs->rax = sys_read(regs->rdi, (char*)regs->rsi, regs->rdx);
break;
case 1:
DEBUG("sys_write(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx);
regs->rax = sys_write(regs->rdi, (char*)regs->rsi, regs->rdx);
case 1: //sys_write
sys_write(regs->rdi, (char*)regs->rsi, regs->rdx);
break;
case 2:
DEBUG("sys_open(filename=%s, flags=%u)", regs->rdi, regs->rsi);
regs->rax = sys_open((const char*)regs->rdi, regs->rsi);
break;
case 3:
DEBUG("sys_close(fd=%u)", regs->rdi);
regs->rax = sys_close(regs->rdi);
break;
case 60:
DEBUG("sys_exit(error_code=%d)", regs->rdi);
regs->rax = sys_exit(regs->rdi);
case 60: //sys_exit
sys_exit(regs->rdi);
break;
default:
DEBUG("Bad syscall! (rax=%p, rdi=%p, rsi=%p, rdx=%p)",
regs->rax, regs->rdi, regs->rsi, regs->rdx);
default: // bad syscall
regs->rax = 0xbad515ca11;
break;
}
DEBUG("returned rax=%p (%u)", regs->rax, regs->rax);
return regs;
}
+7 -42
View File
@@ -42,7 +42,7 @@ int tar_oct2bin(unsigned char* str, int size)
*
* Return:
* $filesize - size of the file, if found
* $-ENOENT - file not found
* $0 - file not found
*/
int tar_lookup(unsigned char* archive, char* filename, char** out)
{
@@ -56,55 +56,21 @@ int tar_lookup(unsigned char* archive, char* filename, char** out)
}
ptr += (((filesize + 511) / 512) + 1) * 512;
}
return -ENOENT;
return 0;
}
/*
* tar_list - list all files present in archive
*/
void tar_list()
{
printf("++ Contents of initial filesystem ++\r\n\r\n");
unsigned char *ptr = archive_start_addr;
while (!memcmp(ptr + 257, "ustar", 5)) {
int filesize = tar_oct2bin(ptr + 0x7c, 11);
char* filename = (char*)ptr;
printf("file: %s\r\n", filename);
ptr += (((filesize + 511) / 512) + 1) * 512;
}
}
/*
* tar_read - read a file in the TAR archive
* tar_read - read a file in the TAR file
* @filename: file to read (absolute path)
* @out: where to store file data if found
* @count: amount of bytes to read
* @offset: read from byte offset (0 for none)
* @out: out buffer (if file is found)
*
* Return:
* $filesize - size of the file, if found
* $-ENOENT - file not found
* $0 - file not found
*/
int tar_read(char* filename, char* out, int count, int offset)
int tar_read(char* filename, char** buf)
{
char* file_data;
int filesize = tar_lookup(archive_start_addr, filename, &file_data);
if (filesize <= 0) {
return filesize;
}
if (offset >= filesize) {
return -EINVAL;
}
int remaining = filesize - offset;
int to_read = remaining < count ? remaining : count;
memcpy(out, file_data + offset, to_read);
return to_read;
return tar_lookup(archive_start_addr, filename, buf);
}
/*
@@ -129,7 +95,6 @@ int tar_exists(const char* filename)
return -ENOENT;
}
/*
* initfs_init - initialize the TAR initial filesystem
* @tar_file: pointer to the Limine-loaded archive
+11 -16
View File
@@ -4,7 +4,6 @@
* @license GPL-3.0-only
*/
#include "fs/initfs.h"
#include <io/term/term.h>
#include <config.h>
#include <io/kbd/ps2.h>
@@ -43,16 +42,17 @@ void pedicel_main(void* arg)
keyboard_getline(input_buf, PEDICEL_INPUT_SIZE);
if (strncmp(input_buf, "help", 4) == 0) {
printf("++ shell builtins ++\r\n\r\n"
"\tclear - clear the screen\r\n"
"\tpanic - trigger a test panic\r\n"
"\tsyscall - trigger int 0x80\r\n"
"\tpf - trigger a page fault\r\n"
"\tnow - get current date\r\n"
"\tsmash - smash the stack\r\n"
"\tmem - get used heap info\r\n"
"\tload - load an user executable\r\n"
"\tlist - list initfs.tar contents\r\n");
printf("\r\nYou are currently running the test kernel shell. This is not\r\n"
"a fully-fledged shell like you'd find in a complete operating system,\r\n"
"but rather a toy to play around in the meantime.\r\n\r\n"
"clear - clear the screen\r\n"
"panic - trigger a test panic\r\n"
"syscall - trigger int 0x80\r\n"
"pf - trigger a page fault\r\n"
"now - get current date\r\n"
"smash - smash the stack\r\n"
"mem - get used heap info\r\n"
"load - load an user executable\r\n");
continue;
}
@@ -102,11 +102,6 @@ void pedicel_main(void* arg)
continue;
}
if (strncmp(input_buf, "list", 4) == 0) {
tar_list();
continue;
}
printf("%s: command not found\r\n", input_buf);
}
}
+2 -1
View File
@@ -236,6 +236,7 @@ extern struct tss tss;
*/
void process_create_user_raw(char* file, int size, char* name)
{
// Need to refactor this mess
CLEAR_INTERRUPTS;
struct process* proc = (struct process*)kmalloc(sizeof(struct process));
struct cpu_status* ctx = (struct cpu_status*)kmalloc(sizeof(struct cpu_status));
@@ -254,7 +255,7 @@ void process_create_user_raw(char* file, int size, char* name)
proc->context->iret_cs = USER_CODE_SEGMENT | 3;
proc->context->iret_flags = 0x202; // Interrupt Flag set
/* Set basic entries for the process's File Descriptor Table */
/* Set basic entries for the process's File Descriptor Table */
proc->fdt[0].fd = 0;
proc->fdt[0].open = true;
proc->fdt[0].cursor = 0;
-18
View File
@@ -1,18 +0,0 @@
CC := x86_64-elf-gcc
CC_FLAGS := -ffreestanding -nostdlib -fno-pic -mno-red-zone -Ilibc
LD := x86_64-elf-ld
BUILDDIR := ../build
LIBDIR := libc
all: pedicel apex
pedicel:
nasm -f bin pedicel.S -o $(BUILDDIR)/pedicel.raw
apex:
$(CC) $(CC_FLAGS) -c apex.c -o $(BUILDDIR)/apex.o
nasm -f elf64 $(LIBDIR)/crt0.S -o $(BUILDDIR)/crt0.o
$(LD) -T $(LIBDIR)/linker.ld $(BUILDDIR)/crt0.o $(BUILDDIR)/apex.o -o $(BUILDDIR)/apex.elf
objcopy -O binary $(BUILDDIR)/apex.elf $(BUILDDIR)/apex.raw
-7
View File
@@ -1,7 +0,0 @@
#include <syscall.h>
int main() {
const char* msg = "hi from C userland\r\n";
write(1, msg, 21);
return 42;
}
+21
View File
@@ -0,0 +1,21 @@
bits 64
section .data
hi db "hi from userland :) we did it man", 0x0A, 0x0d, 0
section .text
hello:
mov rax, 0x1 ;sys_write
mov rdi, 0x1 ;stdout
lea rsi, [rel hi] ;char* buf
mov rdx, 35 ;count
int 0x80
.end:
mov rax, 0x3C ;sys_exit
mov rdi, 0x0 ;error_code
int 0x80
.loop:
jmp .loop
-18
View File
@@ -1,18 +0,0 @@
bits 64
global _start
extern main
section .text
; Begin the program with main() function
_start:
call main
; Exit the program by exit() syscall
.exit:
mov rdi, rax ; put the value of "return X;" (rax) as arg1 (error_code)
mov rax, 60 ; sys_exit
int 0x80
.loop:
jmp .loop
-22
View File
@@ -1,22 +0,0 @@
ENTRY(_start)
SECTIONS
{
. = 0x400000;
.text : {
*(.text*)
}
.rodata : {
*(.rodata*)
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
}
-30
View File
@@ -1,30 +0,0 @@
#pragma once
// 3 because 3 arguments to the call, get it??
static inline long syscall3(long n, long a, long b, long c) {
long ret;
__asm__ volatile (
"int $0x80"
: "=a"(ret)
: "a"(n), "D"(a), "S"(b), "d"(c)
: "memory"
);
return ret;
}
static inline void write(int fd, const char* buf, long len) {
syscall3(1, fd, (long)buf, len);
}
static inline void exit(int code) {
__asm__ volatile (
"int $0x80"
:
: "a"(60), "D"(code)
: "memory"
);
for (;;);
}
+2 -28
View File
@@ -1,8 +1,7 @@
bits 64
section .data
hello db 0x0A, 0x0D, "TEST PROGRAM...", 0x0A, 0x0D, 0
filename db "wow.txt", 0
hello db 0x0A, 0x0D, "User program 2 speaking", 0x0A, 0x0D, 0
section .text
@@ -10,29 +9,7 @@ _start:
mov rax, 0x1 ;sys_write
mov rdi, 0x1 ;stdout
lea rsi, [rel hello]
mov rdx, 19 ;count
int 0x80
; Open a file
mov rax, 0x2 ;sys_open
lea rdi, [rel filename] ;filename
mov rsi, 0x0 ;flags
int 0x80
mov rdi, rax ;fd
mov rax, 0x0 ;sys_read
lea rsi, [rel buf] ;buf
mov rdx, 33 ;count
int 0x80
mov rax, 0x1 ;sys_write
mov rdi, 0x1 ;stdout
lea rsi, [rel buf] ;buf
mov rdx, 33 ;count
int 0x80
mov rax, 0x3 ;sys_close
mov rdi, 0x3 ;fd
mov rdx, 27 ;count
int 0x80
; when we are ready to have an os specific toolchain,
@@ -46,6 +23,3 @@ _start:
.loop:
jmp .loop
section .bss
buf resb 10
-1
View File
@@ -1 +0,0 @@
hi from a file opened in usermode