should be right? #20

Merged
xamidev merged 5 commits from sys_fix into main 2026-05-08 12:59:06 +02:00
10 changed files with 169 additions and 50 deletions
Showing only changes of commit eb8a03facd - Show all commits
+4 -3
View File
@@ -1,3 +1,5 @@
USER_PROGRAMS := pedicel.raw apex.raw
USER_FILES := wow.txt
BUILDDIR := build
ELFFILE := pepperk
@@ -43,9 +45,8 @@ limine/limine:
.PHONY: user
user:
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 -C ../user wow.txt
$(MAKE) -C user
tar cvf $(BUILDDIR)/initfs.tar -C $(BUILDDIR) $(USER_PROGRAMS) -C ../user $(USER_FILES)
build-iso: limine/limine $(ELFFILE)
rm -rf iso_root
+5 -26
View File
@@ -23,8 +23,9 @@ CC := gcc
LD := ld
```
Then, to compile the kernel and make an ISO image file, run: `make build-iso`
To run it with QEMU, do: `make run`
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`.
## Trying the kernel on real hardware
@@ -48,31 +49,9 @@ These features can be activated by setting them to "true" at the end of the make
make UBSAN=true
```
## TODO
## Writing software for PepperOS
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
If you want to write software for PepperOS, take a look at the [Software Developer's guide](docs/SOFTWARE.md).
## Thanks
+57
View File
@@ -0,0 +1,57 @@
# 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,6 +2,14 @@
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.
+18
View File
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,7 @@
#include <syscall.h>
int main() {
const char* msg = "hi from C userland\r\n";
write(1, msg, 21);
return 42;
}
-21
View File
@@ -1,21 +0,0 @@
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
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,22 @@
ENTRY(_start)
SECTIONS
{
. = 0x400000;
.text : {
*(.text*)
}
.rodata : {
*(.rodata*)
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
}
+30
View File
@@ -0,0 +1,30 @@
#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 (;;);
}