5 Commits

Author SHA1 Message Date
Karina caf2d2b4a8 v0.5.3: syscalls, minilibc.
- fix(gs): was a nightmare. now its solved. Both MSR_GS_BASE and MSR_KERNEL_GS_BASE are now initialized to , preventing null GS_BASE when interrupt occurs during the first kernel task run
- chore(syscall.asm): stability improvements. User RSP is now saved via kernel stack instead of global g_cpu, preventing stack theft when task switch occurs during a syscall
- feat(interrupts): all irq and isr handlers now conditionally perform swapgs based on the CS selector (checkin if comfing from ring 3)
- upgrade(scheduler): big update: 1) added TASK_BLOCKED state from process sync; 2) syncronized PID and Task ID to fix wakeup logic; 3) implemented sched_block and sched_exit() for proper wait/exit syscalls
2026-01-30 07:12:57 +04:00
Karina 888bc5abdd v0.5.3-pre: refactor syscall handlers and introduce libterm
Moved handlers to syscalls/, implemented libterm (malloc, stdio)
2026-01-30 04:58:43 +04:00
Karina 9c103218d0 docs: readme and roadmap 2026-01-30 00:52:43 +04:00
Karina 21270a3cc8 feat: introduce HOT! executable format and Ring 3 process isolation (v0.5.2)
- Implement custom 'HOT!' binary format and Rust-based elf2hot converter.
- Upgrade kernel loader with segment-based loading and BSS zeroing.
- Refactor scheduler for Ring 3 IRET frames and fix CS/SS selector swap.
- Add user stack allocation (0x70000000) and linker scripts for binary cleanup.
2026-01-30 00:12:11 +04:00
Karina 7d32444da2 feat(core): v0.5.1 - process isolation and syscalls
- Implemented CR3 switching in scheduler for process isolation

- Added 'process' structure and PML4 cloning (vmm_create_address_space)

- Added NASM crt0 and C entry point for userspace apps
2026-01-29 22:28:41 +04:00
62 changed files with 1494 additions and 212 deletions
+3 -1
View File
@@ -2,4 +2,6 @@
build*
.venv
initrd/image.cpio
initramfs/*
initramfs/*
target
-1
View File
@@ -3,7 +3,6 @@ build
.venv
kernel/data
*.md
tools/
bootloader/
bootloader/src/uefi
initramfs/
+1 -1
View File
@@ -42,7 +42,7 @@ if(MCOPY_EXE AND MKFS_EXE AND CPIO_EXE)
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}
COMMAND sh -c "find . -mindepth 1 ! -name '*.cpio' -print0 | ${CPIO_EXE} --null -ov -H newc > \"${INITRAMFS_CPIO_FILE}\""
WORKING_DIRECTORY ${INITRAMFS_SRC_DIR}
DEPENDS ${INIT_FILES} init
DEPENDS ${INIT_FILES} init debug
VERBATIM
COMMENT "Packing initramfs to cpio..."
)
+35 -2
View File
@@ -4,6 +4,39 @@
<img src='assets/logo.png'>
</p>
Just a hobby, won't be big and professional like Linux
> "Just a hobby, won't be big and professional like Linux."
> — *Linus Torvalds (and now me)*
Really WIP. And really just a hobby.
**termOS** is a 64-bit, UNIX-hating, bespoke operating system written from scratch in C.
Current Kernel: **Dewar** (v0.5.x)
## Philosophy
- **Zero Bloat:** We don't port libraries; we write them.
- **Custom Everything:** Why use ELF when you can invent **HOT!**? Why use GRUB when you can write your own bootloader (soon)?
## Features (v0.5.2)
- **Architecture:** x86_64 / UEFI.
- **Memory Management:** PMM (Bitmap), VMM (PML4 + Higher Half Direct Map), Kernel Heap.
- **Multitasking:** Preemptive scheduler with Round Robin.
- **Isolation:** Ring 0 (Kernel) / Ring 3 (Userspace) protection.
- **Binaries:** Custom **HOT!** executable format (parsed via custom `elf2hot` toolchain).
- **Filesystem:** VFS abstraction with CPIO Initramfs support.
- **Graphics:** `ShitGUI` (yes, really) linear framebuffer driver.
- **Shell:** `ksh` (Kernel Shell) -> transitioning to userspace `ush`
## 🔥 The HOT! Format
termOS uses its own executable format called **HOT!**.
Because parsing ELF headers is too mainstream. HOT! is a flat, segment-based format designed specifically for the Dewar kernel loader.
Made only because ELF is *Not-invented-here*
## Roadmap
See [ROADMAP.md](ROADMAP.md) for the detailed plan of world domination (or at least self-hosting).
## License
**GPL-3.0-or-later**.
+110
View File
@@ -0,0 +1,110 @@
# termOS Roadmap
## Legend
-**Done**: Implemented and working.
- 🚧 **WIP**: Currently under active development.
- 📅 **Planned**: Scheduled for future releases.
- 🤔 **Vision**: Long-term goals for v1.0.
---
## Current Status: v0.5.2 (Alpha)
*Where we are now.*
- **Core:** Ring 0 / Ring 3 isolation implemented.
- **Mem:** PMM (bitmap), VMM (PML4, HHDM).
- **Exec:** Custom **HOT!** executable format (static loader).
- **FS:** VFS + Initramfs (CPIO, read-only).
- **User:** Basic jump to userspace (hardcoded stack, no heap yet).
---
## 📅 Phase 1
*Goal: Transform the decorative userspace into a functional mechanism.*
### v0.5.3 (Immediate Priority)
*Focus: Lifecycle & Memory*
- [x] **Syscall: Memory Management**
- Implement `sys_mem`.
- Allow userspace to dynamically allocate pages (User Heap).
- [x] **Syscall: Process Lifecycle**
- `sys_spawn(path)`: Load `.hot` files via VFS, create new process structures.
- `sys_exit(code)`: Proper termination, ZOMBIE state, resource cleanup in scheduler.
- [x] **Userspace Lib (mini-libc)**
- Wrappers: `malloc`, `free`, `exec`, `exit`.
- `crt0.asm` for proper `main()` entry.
### v0.5.4 (The Shell Update)
*Focus: Interactive Userspace*
- [ ] **Userspace Shell (`ush`)**
- Porting `ksh` logic into a standalone `.hot` binary.
- Standard I/O abstraction (stdin/stdout) to pass keyboard input to active process.
- [ ] **Basic Utils**
- `ls`, `cat`, `echo`, `ver` as standalone programs.
- Remove built-in commands from the kernel.
---
## 📅 Phase 2
*Goal: Total independence from external libraries and architectural hygiene.*
### v0.6.0 (Architecture Overhaul)
- [ ] **Bootloader Independence**
- Remove `posix-uefi` library.
- Write custom UEFI entry point (pure PE).
- Kernel itself becomes a valid `.hot` executable.
- [ ] **HOT! Format Hardening**
- Fix segment alignment (Page Alignment) in `elf2hot` and kernel loader.
- Add metadata headers (stack size, permissions).
- [ ] **Concurrency Fixes**
- Implement spinlocks (`spinlock`).
- Protect allocator and process lists from interrupt race conditions.
---
## 📅 Phase 3
*Goal: Prepare the environment for self-hosting. We cannot compile on a Read-Only FS.*
### v0.7.0 (Filesystem & Input)
- [ ] **Write Support**
- Storage driver (NVMe/AHCI or Writable RAMDisk).
- Writable Filesystem (FAT32 or custom simple FS).
- [ ] **Input Subsystem**
- Input buffering, Pipe (`|`) support in shell.
- Basic TTY/PTY abstraction.
### v0.8.0 (The Editor)
- [ ] **Text Editor**
- Write a simple editor (nano-like) in userspace.
- Capabilities: Open, Edit, Save files.
---
## 📅 Phase 4
*Goal: Self-Hosting.*
### v0.9.0 (The Toolchain)
- [ ] **Linker (`hld`)**
- Native linker capable of outputting `.hot` files (running inside termOS).
- [ ] **Compiler Port**
- Port **TCC (Tiny C Compiler)** to termOS.
- Adapt libc headers for the OS environment.
### 🤔 v1.0.0 (Self-Hosting)
- [ ] **Bootstrapping**
- Compile the Dewar kernel *inside* termOS.
- Boot from the newly compiled kernel.
- Total victory.
---
## 📜 History
| Version | Date | Key Features |
| :--- | :--- | :--- |
| **v0.5.2** | 2026-01-30 | **HOT!** format, Ring 3 isolation, VFS, CPIO parsing. |
| **v0.5.1** | 2026-01-29 | Initial userspace jumps, syscall mechanics. |
| **v0.5.0** | 2026-01-29 | Kernel officially named "Dewar". Versioning standardized. |
| **v0.4.x** | 2026-01-28 | Basic VFS, Shell improvements, basic multitasking. |
| **v0.3.x** | 2025-12-30 | Scheduler, ~~Composer~~, Heap manager. |
| **v0.2.x** | 2025-12-28 | `ksh`, Keyboard driver, HHDM, PMM/VMM. |
| **v0.1.x** | 2025-12-21 | First boot, GDT/IDT, Serial/Console, Panic handler. |
+23
View File
@@ -0,0 +1,23 @@
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
#define HOT_MAGIC 0x21544F48
typedef struct hot_segment {
u64 type; // 1 = rx 2 = rw
u64 vaddr;
u64 offset;
u64 filesz;
u64 memsz;
} hot_segment;
typedef struct hot_header {
u32 magic; // "HOT!"
u8 version; // 1
u8 reserved_pad[3];
u64 entry_point;
u64 segments_count;
u64 reserved;
} hot_header;
+1
View File
@@ -23,6 +23,7 @@ file(GLOB_RECURSE KERNEL_SOURCES CMAKE_CONFIGURE_DEPENDS
"src/mm/*.c"
"src/shell/*.c"
"src/fs/*.c"
"src/syscalls/*.c"
"data/*.c"
)
+9 -5
View File
@@ -3,18 +3,22 @@
#pragma once
#define MSR_EFER 0xC0000080
#define MSR_STAR 0xC0000081
#define MSR_LSTAR 0xC0000082
#define MSR_FMASK 0xC0000084
#define MSR_EFER 0xC0000080
#define MSR_STAR 0xC0000081
#define MSR_LSTAR 0xC0000082
#define MSR_FMASK 0xC0000084
#define MSR_GS_BASE 0xC0000101
#define MSR_KERNEL_GS_BASE 0xC0000102
#define EFER_SCE 0x01 // System Call Enable
typedef enum {
SYS_EXIT,
SYS_EXEC,
SYS_SPAWN,
SYS_MEM,
SYS_WRITE,
SYS_READ,
SYS_WAIT,
} syscalls;
void syscall_init();
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#pragma once
#include <types.h>
#include <core/scheduler.h>
u64 load_hot(process* proc, u8* data);
+1 -1
View File
@@ -5,5 +5,5 @@
#include <types.h>
bool exec_init(const char* path);
i32 process_spawn(const char* path, const char* name);
void init_task_entry();
+27 -2
View File
@@ -3,15 +3,40 @@
#pragma once
#include <types.h>
typedef enum process_state {
DEAD,
RUNNING,
READY,
BLOCKED,
SLEEPING,
} process_state;
typedef struct process {
u64 pid;
process_state state;
u64 pml4_phys;
struct process* parent;
char name[32];
u64 heap_start;
u64 heap_cur;
} process;
typedef struct task {
u64 rsp;
struct task* next;
u32 id;
u32 sleep_ticks;
process_state task_state; // reusing process_state cuz wn
u64 kernel_stack_top;
process* proc;
i32 waiting_on_pid;
} task;
void sched_init();
task* sched_spawn(void(*entry)());
task* sched_spawn(void(*entry)(), process* owner, bool is_user, u64 fixed_user_stack);
u64 sched_next(u64 curr_rsp);
void yield(u64 ticks);
void yield(u64 ticks);
void sched_block(u32 pid);
void sched_wakeup(u32 pid);
void sched_exit(); // suicide
+1
View File
@@ -13,6 +13,7 @@ void console_set_color(u32 color);
void console_set_default_color(u32 color);
void console_set_cursor_pos(SG_Point *p);
char console_getc();
void console_putc(char c);
void kprint(const char *str);
void kprintf(const char *fmt, ...);
void kgets(char* buff, u32 lim);
+1 -1
View File
@@ -7,4 +7,4 @@
void timer_init(u32 freq);
u64 timer_handler(Registers *regs);
void sleep(u64 ms);
u64 get_uptime();
u64 get_uptime();
+4
View File
@@ -37,3 +37,7 @@
void vmm_init(Bootinfo* info);
u64* vmm_map_page(u64* pml4, u64 phys, u64 virt, u64 flags);
u64 vmm_create_address_space();
u64 vmm_get_current_cr3();
void load_cr3(u64 pml4_addr);
void vmm_setup_user_stack(u64* pml4_phys);
+2 -1
View File
@@ -11,4 +11,5 @@ void print_regs();
void cmd_sleep();
void cmd_debug();
void cmd_rand();
void cmd_ver();
void cmd_ver();
void cmd_userspace();
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#pragma once
#include <types.h>
u64 sys_write(u64 fd, u64 buff, u64 len);
u64 sys_read(u64 fd, u64 buff, u64 count);
@@ -2,5 +2,6 @@
// Copyright (c) 2026 0xKarinyash
#pragma once
#include <types.h>
void jump_to_userspace(void* entry, void* user_stack_top);
u64 sys_mem(u64 size);
+10
View File
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#pragma once
#include <types.h>
u64 sys_exit(i32 code);
u64 sys_spawn(const char* path);
u64 sys_wait(u64 pid);
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include "types.h"
#include <types.h>
#include <cpuinfo.h>
#define MSR_GS_BASE 0xC0000101
+31 -5
View File
@@ -63,16 +63,37 @@ isr%1:
global %2
%2:
push 0 ; dummy err code
push %1
push %1 ; int_no
; [rsp] = int_no (8)
; [rsp + 8] = err_code (8)
; [rsp + 16] = RIP (8)
; [rsp + 24] = CS (8)
test qword [rsp + 24], 3
jz .skip_swap
swapgs
.skip_swap:
PUSHALL
mov rdi, rsp
cld
call irq_handler_c
mov rsp, rax
mov rsp, rax
; PUSHALL - 15 * 8 = 120 bytes
; + int_no (8) + err_code (8) = 136 bytes
; [rsp + 136] = RIP
; [rsp + 144] = CS
test qword [rsp + 144], 3
jz .skip_swap_back
swapgs
.skip_swap_back:
POPALL
add rsp, 16
iretq
@@ -112,13 +133,18 @@ ISR_ERRCODE 30 ; Security Exception
ISR_NOERRCODE 31 ; Reserved
isr_common_stub:
test qword [rsp + 24], 3
jz .skip_swap
swapgs
.skip_swap:
PUSHALL
mov rdi, rsp
call isr_handler_c
test qword [rsp + 144], 3
jz .skip_swap_back
swapgs
.skip_swap_back:
POPALL
add rsp, 16
iretq
+1 -1
View File
@@ -21,7 +21,7 @@ u64 irq_handler_c(Registers *regs) {
switch (regs->int_no) {
case 32: curr_rsp = timer_handler(regs); break;
case 33: kb_handler(); break;
default: outb(SLAVE_COMMAND, 0x20); break;
default: break;
}
outb(MASTER_COMMAND, 0x20);
+2 -3
View File
@@ -58,10 +58,9 @@ syscall_entry:
pop rbp
pop rcx ; rip
pop r11 ; rflags
add rsp, 8 ; skip rsp
pop rsp
mov [gs:8], rsp
mov rsp, [gs:0]
swapgs
o64 sysret
+14 -14
View File
@@ -8,6 +8,10 @@
#include <mm/pmm.h>
#include <mm/vmm.h>
#include <syscalls/proc.h>
#include <syscalls/mem.h>
#include <syscalls/io.h>
static inline void wrmsr(u32 msr, u64 val) {
u32 low = (u32)val;
u32 high = (u32)(val >> 32);
@@ -43,24 +47,20 @@ void syscall_init() {
g_cpu.kernel_rsp = (u64)stack_phys + HHDM_OFFSET + 4096;
}
wrmsr(MSR_GS_BASE, (u64)&g_cpu);
wrmsr(MSR_KERNEL_GS_BASE, (u64)&g_cpu);
}
u64 syscall_dispatch(u64 id, u64 arg1, u64 arg2, u64 arg3, u64 arg4, u64 arg5) {
switch (id) {
case SYS_EXIT: {
kprintf("\n[Dewar] process exited with code %d", arg1);
while(1) __asm__ ("hlt"); // stub
return 0;
}
case SYS_EXEC: {
kprintf("\n[Dewar] process called exec syscall");
return 0; // stub as well
}
case SYS_WRITE: {
kprintf("%s", (char*)arg1);
return 0;
}
default: kprintf("[Dewar] Unknown syscall %d\n", id); return -1;
case SYS_EXIT: return sys_exit(arg1);
case SYS_SPAWN: return sys_spawn((const char*)arg1);
case SYS_MEM: return sys_mem(arg1);
case SYS_WRITE: return sys_write(arg1, arg2, arg3);
case SYS_READ: return sys_read(arg1, arg2, arg3);
case SYS_WAIT: return sys_wait(arg1);
default:
kprintf("[Dewar] Unknown syscall %d\n", id);
return -1;
}
}
+49
View File
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include <core/hot.h>
#include <core/scheduler.h>
#include <core/string.h>
#include <mm/pmm.h>
#include <mm/vmm.h>
#include <mm/heap.h>
#include <mm/memory.h>
#include "../../common/hot_header.h"
u64 load_hot(process* proc, u8* data) {
hot_header* header = (hot_header*)data;
if (header->magic != HOT_MAGIC) {
return -1;
}
hot_segment* segments = (hot_segment*)(data + sizeof(hot_header));
for (u64 i = 0; i < header->segments_count; i++) {
hot_segment* seg = &segments[i];
if (seg->memsz == 0) continue;
u64 start = seg->vaddr & ~(0xFFFULL);
u64 end = (seg->vaddr + seg->memsz + 0xFFF) & ~(0xFFFULL);
for (u64 addr = start; addr < end; addr += PAGE_SIZE) {
void* phys = pmm_alloc_page();
vmm_map_page((u64*)proc->pml4_phys, (u64)phys, addr, PTE_USER | PTE_RW | PTE_PRESENT);
void* kernel_virt = (void*)((u64)phys + HHDM_OFFSET);
memset(kernel_virt, 0, PAGE_SIZE);
u64 page_overleap_start = (addr > seg->vaddr) ? addr : seg->vaddr;
u64 page_overleap_end = (addr + PAGE_SIZE < seg->vaddr + seg->filesz)
? (addr + PAGE_SIZE)
: (seg->vaddr + seg->filesz);
if (page_overleap_start < page_overleap_end) {
u64 copy_size = page_overleap_end - page_overleap_start;
u64 src_offset = seg->offset + (page_overleap_start - seg->vaddr);
u64 dst_offset = page_overleap_start - addr;
memcpy((u8*)kernel_virt + dst_offset, data + src_offset, copy_size);
}
}
}
return header->entry_point;
}
+38 -53
View File
@@ -1,14 +1,17 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include <core/panic.h>
#include <core/hot.h>
#include <core/loader.h>
#include <core/userspace.h>
#include <core/scheduler.h>
#include <core/string.h>
#include <shell/ksh.h>
#include <mm/pmm.h>
#include <mm/vmm.h>
#include <mm/heap.h>
#include <mm/memory.h>
#include <fs/vfs.h>
@@ -16,67 +19,49 @@
#include <drivers/console.h>
#include <types.h>
extern u64* pml4_kernel;
extern task* curr_task;
extern u32 next_pid;
bool exec_init(const char* path) {
kprintf("[Loader] loading %s...\n", path);
#define USER_STACK_TOP 0x70000000
#define HEAP_START 0x40000000
i32 process_spawn(const char* path, const char* name) {
fs_node* file = vfs_open(path);
if (!file) {
kprintf("[Loader] Error: %s not found in initramfs!\n", path);
return false;
if (!file) return -1;
process* new_proc = (process*)malloc(sizeof(process));
if (!new_proc) return -2;
memset(new_proc, 0, sizeof(process));
new_proc->pid = next_pid++;
new_proc->state = RUNNING;
new_proc->pml4_phys = vmm_create_address_space();
new_proc->heap_start = HEAP_START;
new_proc->heap_cur = HEAP_START;
strncpy(new_proc->name, name, 31);
u8* file_buffer = (u8*)malloc(file->len);
if (!file_buffer) {
free(new_proc);
return -3;
}
vfs_read(file, 0, file->len, file_buffer);
u64 virt_code = 0x400000;
u64 virt_stack = 0x800000;
u64 stack_size = 8192;
u64 entry = load_hot(new_proc, file_buffer);
if (!entry) return -4;
u64 bytes_left = file->len;
u64 offset = 0;
u64 page_idx = 0;
free(file_buffer);
while (bytes_left > 0) {
void* phys = pmm_alloc_page();
if (!phys) {
kprintf("Loader: OOM!\n");
return false;
}
vmm_setup_user_stack((u64*)new_proc->pml4_phys);
sched_spawn((void(*)())entry, new_proc, true, USER_STACK_TOP);
vmm_map_page(pml4_kernel, (u64)phys, virt_code + (page_idx * 4096), PTE_PRESENT | PTE_RW | PTE_USER);
void* k_ptr = (void*)(HHDM_OFFSET + (u64)phys);
memset(k_ptr, 0, 4096);
u64 chunk = (bytes_left > 4096) ? 4096 : bytes_left;
vfs_read(file, offset, chunk, (u8*)k_ptr);
bytes_left -= chunk;
offset += chunk;
page_idx++;
}
for (u64 i = 0; i < (stack_size / 4096); i++) {
void* phys = pmm_alloc_page();
vmm_map_page(pml4_kernel, (u64)phys, virt_stack + (i * 4096), PTE_PRESENT | PTE_RW | PTE_USER);
memset((void*)(HHDM_OFFSET + (u64)phys), 0, 4096);
}
__asm__ volatile(
"mov %%cr3, %%rax\n\t"
"mov %%rax, %%cr3\n\t"
::: "rax", "memory"
);
kprintf("[Loader] Transferring control to userspace...\n");
jump_to_userspace((void*)virt_code, (void*)(virt_stack + stack_size));
return true; // unreachable
return new_proc->pid;
}
void init_task_entry() {
if (!exec_init("/init")) {
kprintf("FATAL: Could not load /init\n");
sched_spawn(ksh);
while(1) __asm__("hlt");
i32 pid = process_spawn("/init", "init");
if (pid < 0) {
panic("FATAL: Failed to spawn /init");
}
while (1) { __asm__("sti; hlt"); }
}
-1
View File
@@ -32,7 +32,6 @@ const char* fun_messages[] = {
"Code have been eaten by Aliens",
"That's all, folks!",
"Raiden, answer me, Raiden, respond! Raiden?! RAIDEEEEEEEEEN!",
"Fatal error has been occurred.\n\t\t\t\t Your device will be terminated in 30 seconds.",
"I'll be back",
"Hastla la vista, baby",
"Ti chego mne tut nagovoril...",
+84 -16
View File
@@ -3,7 +3,10 @@
#include <core/panic.h>
#include <core/scheduler.h>
#include <core/string.h>
#include <mm/heap.h>
#include <mm/vmm.h>
#include <mm/memory.h>
#include <cpuinfo.h>
#include <gdt.h>
@@ -11,30 +14,56 @@ task* curr_task = nullptr;
u32 next_pid = 1;
extern void irq0_handler();
extern u64 pml4_kernel_phys;
void sched_init() {
task* kt = (task*)malloc(sizeof(task));
static process kernel_process;
kt->id = 0;
kt->sleep_ticks = 0;
kt->next = kt;
curr_task = kt;
void idle_task() {
while (1) {
__asm__ volatile ("hlt");
}
}
task* sched_spawn(void(*entry)()) {
void sched_init() {
kernel_process.pid = 0;
kernel_process.state = RUNNING;
kernel_process.pml4_phys = pml4_kernel_phys;
strcpy(kernel_process.name, "kernel");
task* kt = (task*)malloc(sizeof(task));
memset(kt, 0, sizeof(task));
kt->id = 0;
kt->proc = &kernel_process;
kt->sleep_ticks = 0;
kt->next = kt;
kt->task_state = RUNNING;
kt->waiting_on_pid = -1;
curr_task = kt;
sched_spawn(idle_task, &kernel_process, false, 0);
}
task* sched_spawn(void(*entry)(), process* owner, bool is_user, u64 fixed_user_stack) {
task* t = (task*)malloc(sizeof(task));
if (!t) return nullptr;
if (!owner) owner = &kernel_process;
u64 stack_size = 16384;
u8* stack_base = (u8*)malloc(stack_size);
if (!stack_base) panic("OOM for task stack");
u64* rsp = (u64*)(stack_base + stack_size);
*--rsp = 0x10; // SS -- Kernel data
*--rsp = (u64)stack_base + stack_size; // rsp
*--rsp = 0x202; // RFLAGS -- Interrupts Enabled | Reserved bit;
*--rsp = 0x08; // CS -- Kernel Code;
u64 cs = is_user ? 0x23 : 0x08;
u64 ss = is_user ? 0x1b : 0x10;
u64 rflags = 0x202;
u64 target_rsp = 0;
if (is_user) target_rsp = fixed_user_stack;
else target_rsp = (u64)stack_base + stack_size;
*--rsp = ss; // SS -- Kernel data
*--rsp = target_rsp; // rsp
*--rsp = rflags; // RFLAGS -- Interrupts Enabled | Reserved bit;
*--rsp = cs; // CS -- Kernel Code;
*--rsp = (u64)entry; // RIP
*--rsp = 0; // int no
@@ -43,10 +72,13 @@ task* sched_spawn(void(*entry)()) {
for (u8 i = 0; i < 15; i++) *--rsp = 0; // R15 .. RAX
t->rsp = (u64)rsp;
t->id = next_pid++;
t->proc = owner;
t->id = owner->pid;
t->sleep_ticks = 0;
t->next = curr_task->next;
t->kernel_stack_top = (u64)stack_base + stack_size;
t->task_state = RUNNING;
t->waiting_on_pid = -1;
curr_task->next = t;
return t;
}
@@ -64,16 +96,52 @@ u64 sched_next(u64 curr_rsp) {
if (curr_task->sleep_ticks > 0) curr_task->sleep_ticks--;
task* next = curr_task->next;
while (next != curr_task && next->sleep_ticks > 0) next = next->next; // what the fuck i just wrote
task* next = curr_task;
while (1) {
// TODO: add gc here;
next = next->next;
if (next->task_state == SLEEPING && next->sleep_ticks == 0) next->task_state = RUNNING;
if (next->task_state == RUNNING) break;
if (next == curr_task) {
if (curr_task->task_state == RUNNING) break;
panic("no running tasks");
}
}
if (next->proc->pml4_phys != curr_task->proc->pml4_phys) load_cr3(next->proc->pml4_phys);
curr_task = next;
tss.rsp0 = curr_task->kernel_stack_top;
g_cpu.kernel_rsp = curr_task->kernel_stack_top;
return curr_task->rsp;
}
void sched_block(u32 pid) {
curr_task->task_state = BLOCKED;
curr_task->waiting_on_pid = pid;
__asm__ volatile("int $32");
}
void sched_wakeup(u32 pid) {
task* it = curr_task;
do {
if (it->task_state == BLOCKED && it->waiting_on_pid == (i32)pid) {
it->task_state = RUNNING;
it->waiting_on_pid = -1;
}
it = it->next;
} while (it != curr_task);
}
void sched_exit() {
u32 my_pid = curr_task->id;
curr_task->task_state = DEAD;
sched_wakeup(my_pid);
__asm__ volatile("int $32");
}
void yield(u64 ticks) {
curr_task->sleep_ticks = ticks;
curr_task->task_state = SLEEPING;
__asm__ volatile("hlt");
}
-34
View File
@@ -1,34 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include <core/userspace.h>
#include <types.h>
#define USER_DS (0x18 | 3)
#define USER_CS (0x20 | 3)
#define RFLAGS_IF 0x202
void jump_to_userspace(void* entry, void* user_stack_top) {
__asm__ volatile (
"mov %0, %%ds\n"
"mov %0, %%es\n"
"mov %0, %%fs\n"
:: "r" ((u64)USER_DS) : "memory"
);
__asm__ volatile (
"pushq %0\n" // SS (User Data Selector)
"pushq %1\n" // RSP (User Stack Pointer)
"pushq %2\n" // RFLAGS
"pushq %3\n" // CS (User Code Selector)
"pushq %4\n" // RIP (Entry Point)
"iretq\n"
:
: "r" ((u64)USER_DS),
"r" ((u64)user_stack_top),
"r" ((u64)RFLAGS_IF),
"r" ((u64)USER_CS),
"r" ((u64)entry)
: "memory"
);
}
+1 -1
View File
@@ -80,7 +80,7 @@ void console_set_cursor_pos(SG_Point *p) {
s_cursor_pos.y = p->y;
}
static void console_putc(char c) {
void console_putc(char c) {
serial_writec(c);
if (!ctx_ptr) return;
if (c == '\n') {
-1
View File
@@ -111,5 +111,4 @@ void kb_handler() {
default: break;
}
}
outb(MASTER_COMMAND, 0x20);
}
+1 -1
View File
@@ -26,10 +26,10 @@ void timer_init(u32 freq) {
u64 timer_handler(Registers *regs) {
ticks++;
outb(MASTER_COMMAND, 0x20);
return sched_next((u64)regs);
}
void sleep(u64 ms) {
u64 start = ticks;
while (ticks < start + ms) __asm__ volatile ("hlt");
+2 -2
View File
@@ -101,8 +101,8 @@ void kmain(Bootinfo* info) {
c = console_getc();
if (c != '\n') staying_in_ksh = true;
if (staying_in_ksh) sched_spawn(ksh);
else sched_spawn(init_task_entry);
if (staying_in_ksh) sched_spawn(ksh, nullptr, false, 0);
else sched_spawn(init_task_entry, nullptr, false, 0);
__asm__ volatile("sti");
+56 -13
View File
@@ -4,13 +4,19 @@
#include <mm/pmm.h>
#include <mm/memory.h>
#include <core/panic.h>
#include <gdt.h>
#include <idt.h>
#include <types.h>
#include "bootinfo.h"
#define USER_STACK_TOP 0x70000000
#define USER_STACK_SIZE 0x4000
u64* pml4_kernel = nullptr;
u64 pml4_kernel_phys = 0;
static bool is_initialized = false;
extern u64 _kernel_start;
@@ -89,26 +95,30 @@ void vmm_unmap_page(u64* pml4, u64 virt) {
u64 pdpt_idx = VMM_PDPT_INDEX(virt);
u64 pml4_idx = VMM_PML4_INDEX(virt);
if (!(pml4[pml4_idx] & PTE_PRESENT)) return;
u64* pdpt = get_table_virt(PTE_GET_ADDR(pml4[pml4_idx]));
if (!(pdpt[pdpt_idx] & PTE_PRESENT)) return;
u64* pd = get_table_virt(PTE_GET_ADDR(pdpt[pdpt_idx]));
if (!(pd[pd_idx] & PTE_PRESENT)) return;
u64* pt = get_table_virt(PTE_GET_ADDR(pd[pd_idx]));
u64* pml4_virt = pml4;
if (is_initialized) pml4_virt = (u64*)PHYS_TO_HHDM((u64)pml4);
pt[pt_idx] = 0;
if (!(pml4_virt[pml4_idx] & PTE_PRESENT)) return;
u64* pdpt_virt = get_table_virt(PTE_GET_ADDR(pml4_virt[pml4_idx]));
if (!(pdpt_virt[pdpt_idx] & PTE_PRESENT)) return;
u64* pd_virt = get_table_virt(PTE_GET_ADDR(pdpt_virt[pdpt_idx]));
if (!(pd_virt[pd_idx] & PTE_PRESENT)) return;
u64* pt_virt = get_table_virt(PTE_GET_ADDR(pd_virt[pd_idx]));
pt_virt[pt_idx] = 0;
__asm__ volatile("invlpg (%0)" :: "r" (virt) : "memory");
}
static inline void load_cr3(u64 pml4_addr) {
void load_cr3(u64 pml4_addr) {
__asm__ volatile ("mov %0, %%cr3" :: "r"(pml4_addr) : "memory");
}
void vmm_init(Bootinfo* info) {
pml4_kernel = pmm_alloc_page();
pml4_kernel_phys = (u64)pmm_alloc_page();
pml4_kernel = (u64*)pml4_kernel_phys;
memset(pml4_kernel, 0, PAGE_SIZE);
u64 k_virt_start = (u64)&_kernel_start;
@@ -126,6 +136,39 @@ void vmm_init(Bootinfo* info) {
bitmap = (u8*)PHYS_TO_HHDM((u64)bitmap);
info->framebuffer.base = (u32*)FB_VIRT_BASE;
load_cr3((u64)pml4_kernel);
load_cr3(pml4_kernel_phys);
is_initialized = true;
}
}
u64 vmm_create_address_space() {
u64 phys = (u64)pmm_alloc_page();
if (!phys) return 0;
u64* virt = (u64*)PHYS_TO_HHDM(phys);
memset(virt, 0, PAGE_SIZE);
u64* kernel_pml4_virt = get_table_virt((u64)pml4_kernel);
for (u32 i = 256; i < 512; i++) {
virt[i] = kernel_pml4_virt[i];
}
return phys;
}
u64 vmm_get_current_cr3() {
u64 cr3;
__asm__ volatile("mov %%cr3, %0" : "=r"(cr3));
return cr3;
}
void vmm_setup_user_stack(u64* pml4_phys) {
u64 stack_bottom = USER_STACK_TOP - USER_STACK_SIZE;
for (u64 addr = stack_bottom; addr < USER_STACK_TOP; addr += 4096) {
void* phys = pmm_alloc_page();
if (!phys) panic("OOM in user stack setup");
memset((void*)PHYS_TO_HHDM((u64)phys), 0, 4096);
vmm_map_page((u64*)pml4_phys, (u64)phys, addr, PTE_PRESENT | PTE_RW | PTE_USER);
}
}
+14 -1
View File
@@ -9,10 +9,19 @@
#include <drivers/timer.h>
#include <core/rand.h>
#include <core/loader.h>
#include <core/scheduler.h>
#include <core/string.h>
#include <mm/vmm.h>
#include <mm/heap.h>
#include <shell/dbgcmd.h>
#include "../data/cats.h"
extern task* curr_task;
const char* ascii_logo[] = {
" /\\___/\\ ",
" | > < | ",
@@ -48,7 +57,6 @@ void cmd_meow() {
void cmd_help() {
kprintf("Welcome to ^ptermOS^!'s ^gk^!ernel ^gsh^!ell!\n");
kprintf("It loads when userspace is failed to load and acts as a basic rescue environment\n");
kprintf("At this moment i dont have userspace so it loads always\n");
kprintf("Available commands:\n");
kprintf("\t^rDebug^!:\n");
@@ -58,6 +66,7 @@ void cmd_help() {
kprintf("\t\t^ypanic^! \t\tPanics (lol)\n");
kprintf("\t\t^yud2^! \t\tPanics with #UD\n");
kprintf("\t\t^ypf^! \t\tPanics with #PF\n");
kprintf("\t\t^yuserspace^! \t\tAttempt to jump in ring 3\n");
kprintf("\t^pFun^!:\n");
kprintf("\t\t^ysplash^! \t\tShows splash (works kinda unstable)\n");
@@ -112,4 +121,8 @@ void cmd_ver() {
kprintf("termOS version %s\n", TERMOS_VERSION);
kprintf("Dewar Kernel (x86_64), build: %s %s\n", __DATE__, __TIME__);
kprintf("License: GPL-3.0-or-later\n");
}
void cmd_userspace() {
sched_spawn(init_task_entry, nullptr, false, 0);
}
+5 -2
View File
@@ -33,6 +33,7 @@ typedef enum {
TOKEN_PANIC,
TOKEN_PANIC_UD2,
TOKEN_PANIC_PF,
TOKEN_USERSPACE,
TOKEN_CLEAR,
TOKEN_BLINKING,
@@ -61,6 +62,7 @@ static const ksh_command_map token_map[] = {
{"panic", TOKEN_PANIC},
{"ud2", TOKEN_PANIC_UD2},
{"pf", TOKEN_PANIC_PF},
{"userspace", TOKEN_USERSPACE},
// fun
{"meow", TOKEN_MEOW},
@@ -84,7 +86,7 @@ ksh_token char2token(char* token) {
}
void ksh() {
sched_spawn(cursor_blinker_sched_task);
sched_spawn(cursor_blinker_sched_task, nullptr, false, 0);
while (true) {
kprintf("ksh_> ");
char cmdbuff[256];
@@ -103,7 +105,8 @@ void ksh() {
case TOKEN_PANIC: panic("Manually initiated panic");
case TOKEN_PANIC_UD2: __asm__ volatile ("ud2");
case TOKEN_PANIC_PF: u64* bad_ptr = (u64*)0xDEADBEEF; *bad_ptr = 666;
case TOKEN_USERSPACE: cmd_userspace(); break;
case TOKEN_SPLASH: show_splash(console_get_context()); break;
case TOKEN_KFETCH: cmd_kfetch(); break;
case TOKEN_MEOW: cmd_meow(); break;
+27
View File
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include <syscalls/io.h>
#include <drivers/console.h>
u64 sys_write(u64 fd, u64 buff, u64 len) {
if (fd == 1 || fd == 2) {
char* str = (char*)buff;
for (u64 i = 0; i < len; i++) {
console_putc(str[i]);
}
return len;
}
return 0;
}
u64 sys_read(u64 fd, u64 buff, u64 count) {
char* buf = (char*)buff;
if (fd == 0) {
for (u64 i = 0; i < count; i++) {
buf[i] = console_getc();
}
return count;
}
return 0;
}
+32
View File
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include <syscalls/mem.h>
#include <core/scheduler.h>
#include <mm/pmm.h>
#include <mm/vmm.h>
#include <mm/memory.h>
extern task* curr_task;
u64 sys_mem(u64 size) {
if (size == 0) return 0;
process* proc = curr_task->proc;
u64 addr_to_ret = proc->heap_cur;
u64 pages_needed = (size + (PAGE_SIZE-1)) / PAGE_SIZE;
for (u64 i = 0; i < pages_needed; i++) {
void* phys = pmm_alloc_page();
if (!phys) return 0;
vmm_map_page((u64*)proc->pml4_phys, (u64)phys, proc->heap_cur, PTE_PRESENT | PTE_RW | PTE_USER);
memset((void*)PHYS_TO_HHDM((u64)phys), 0, PAGE_SIZE);
proc->heap_cur += 4096;
}
return addr_to_ret;
}
+23
View File
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include <syscalls/proc.h>
#include <core/scheduler.h>
#include <drivers/console.h>
#include <core/loader.h>
extern task* curr_task;
u64 sys_exit(i32 code) {
kprintf("\n[Dewar] process %s exited with code %d", curr_task->proc->name, code);
sched_exit();
}
u64 sys_spawn(const char* path) {
return process_spawn(path, path);
}
u64 sys_wait(u64 pid) {
sched_block(pid);
}
+88
View File
@@ -0,0 +1,88 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "elf2hot"
version = "0.1.0"
dependencies = [
"goblin",
]
[[package]]
name = "goblin"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4db6758c546e6f81f265638c980e5e84dfbda80cfd8e89e02f83454c8e8124bd"
dependencies = [
"log",
"plain",
"scroll",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "plain"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "scroll"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add"
dependencies = [
"scroll_derive",
]
[[package]]
name = "scroll_derive"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "elf2hot"
version = "0.1.0"
edition = "2024"
[dependencies]
goblin = "0.10.4"
+98
View File
@@ -0,0 +1,98 @@
use goblin::elf::program_header::PT_LOAD;
use std::env;
use std::fs::File;
use std::io::{Read, Write};
#[repr(C, packed)]
#[derive(Default, Debug)]
struct HotHeader {
magic: u32,
version: u8,
reserved_pad: [u8; 3],
entry_point: u64,
segments_count: u64,
reserved: u64,
}
#[repr(C, packed)]
#[derive(Default, Debug, Clone, Copy)]
struct HotSegment {
stype: u64,
vaddr: u64,
offset: u64,
filesz: u64,
memsz: u64,
}
const HOT_MAGIC: u32 = 0x21544F48; // "HOT!"
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
if args.len() < 3 {
println!("Usage: elf2hot <input_elf> <output_hot>");
return Ok(());
}
let input_path = &args[1];
let output_path = &args[2];
let mut f = File::open(input_path)?;
let mut buffer = Vec::new();
f.read_to_end(&mut buffer)?;
let elf = goblin::elf::Elf::parse(&buffer)?;
let mut hot_segments = Vec::new();
let mut segment_data = Vec::new();
let mut current_offset = std::mem::size_of::<HotHeader>() as u64;
let load_segments: Vec<_> = elf.program_headers.iter()
.filter(|ph| ph.p_type == PT_LOAD)
.collect();
current_offset += (load_segments.len() * std::mem::size_of::<HotSegment>()) as u64;
for ph in load_segments {
let is_code = ph.is_executable();
let data = &buffer[ph.p_offset as usize..(ph.p_offset + ph.p_filesz) as usize];
segment_data.push(data);
hot_segments.push(HotSegment {
stype: if is_code { 1 } else { 2 },
vaddr: ph.p_vaddr,
offset: current_offset,
filesz: ph.p_filesz,
memsz: ph.p_memsz,
});
current_offset += ph.p_filesz;
}
let header = HotHeader {
magic: HOT_MAGIC,
version: 1,
entry_point: elf.entry,
segments_count: hot_segments.len() as u64,
..Default::default()
};
let mut out = File::create(output_path)?;
let header_bytes: [u8; std::mem::size_of::<HotHeader>()] = unsafe { std::mem::transmute(header) };
out.write_all(&header_bytes)?;
for seg in hot_segments {
let seg_bytes: [u8; std::mem::size_of::<HotSegment>()] = unsafe { std::mem::transmute(seg) };
out.write_all(&seg_bytes)?;
}
for data in segment_data {
out.write_all(data)?;
}
println!("Successfully converted {} to {}!", input_path, output_path);
println!("Entry point: 0x{:X}", elf.entry);
Ok(())
}
+3 -1
View File
@@ -7,4 +7,6 @@ set(CMAKE_C_EXTENSIONS OFF)
message(STATUS "Building termOS's userspace")
add_subdirectory(init)
add_subdirectory(libterm)
add_subdirectory(init)
add_subdirectory(debug)
+6
View File
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.20)
project(termOSdbg LANGUAGES C)
file(GLOB_RECURSE debug_SOURCES "src/*.c")
add_termos_executable(debug "${debug_SOURCES}")
+85
View File
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include <stdio.h>
#include <malloc.h>
#define VECTOR_BUFFER_SIZE 8
typedef struct {
i32* data;
u64 size;
u64 capacity;
} Vector;
void vector_init(Vector* vec) {
vec->data = nullptr;
vec->size = 0;
vec->capacity = 0;
}
u8 vector_append(Vector* vec, const i32 num) {
if (vec->size >= vec->capacity) {
u64 new_cap = (vec->capacity == 0) ? VECTOR_BUFFER_SIZE : vec->capacity * 2;
i32* new_data = realloc(vec->data, new_cap * sizeof(i32));
if (!new_data) return 2; // Out of memory
vec->data = new_data;
vec->capacity = new_cap;
}
vec->data[vec->size] = num;
vec->size++;
return 0;
}
void vector_reset(Vector* vec) {
vec->size = 0;
}
u8 vector_reserve(Vector* vec, u64 new_cap) {
if (new_cap <= vec->capacity) return 0;
i32* new_data = realloc(vec->data, new_cap * sizeof(i32));
if (!new_data) return 2;
vec->data = new_data;
vec->capacity = new_cap;
return 0;
}
void vector_free(Vector* vec) {
free(vec->data);
vec->data = nullptr;
vec->size = vec->capacity = 0;
}
void render_list(Vector* numbers) {
printf("\nNumbers [%d/%d]: [", numbers->size, numbers->capacity);
if (numbers->size == 0) printf("]");
for (u64 i = 0; i < numbers->size; i++) {
printf("%d", numbers->data[i]);
if (i + 1 >= numbers->size) {
printf("]");
break;
}
printf(", ");
}
}
int main() {
Vector nums;
vector_init(&nums);
printf("Heap test\n");
printf("press any key to add 1 number press 'q' to exit\n");
char c = 'd';
u64 i = 0;
while (c != 'q') {
render_list(&nums);
vector_append(&nums, i);
i++;
c = getchar();
}
vector_free(&nums);
}
+3 -29
View File
@@ -1,32 +1,6 @@
cmake_minimum_required(VERSION 3.20)
project(termOSinit LANGUAGES C ASM_NASM)
project(termOSinit LANGUAGES C)
set(CMAKE_C_STANDARD 23)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
file(GLOB_RECURSE INIT_SOURCES "src/*.c")
message(STATUS "Building termOS's init")
file(GLOB_RECURSE INIT_SOURCES CMAKE_CONFIGURE_DEPENDS
"src/*.asm"
"src/*.c"
)
add_executable(init ${INIT_SOURCES})
set_target_properties(init PROPERTIES
SUFFIX ""
LINKER_LANGUAGE C
)
target_link_options(init PRIVATE
-nostdlib
-static
-Wl,--oformat=binary
-Wl,-Ttext=0x400000
-Wl,-e,start
)
add_custom_command(TARGET init POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:init> ${CMAKE_SOURCE_DIR}/initramfs/init
COMMENT "Installing init binary to initramfs"
)
add_termos_executable(init "${INIT_SOURCES}")
-17
View File
@@ -1,17 +0,0 @@
[BITS 64]
start:
mov rax, 2 ; SYS_WRITE
mov rdi, msg_hello ; Адрес строки
syscall
mov rax, 1 ; SYS_EXEC
syscall
mov rax, 0 ; SYS_EXIT
mov rdi, 1337
syscall
jmp $
msg_hello: db "Hello from init!", 0xA, 0
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include <process.h>
#include <stdio.h>
#include <malloc.h>
int main() {
wait(spawn("debug"));
printf("\nStill here?\n");
while (1) {
printf("1");
}
}
+63
View File
@@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.16)
project(libterm LANGUAGES C ASM_NASM)
set(USER_C_FLAGS
-ffreestanding
-nostdlib
-nostdinc
-fno-stack-protector
-fno-pic
-fno-pie
-m64
-mno-red-zone
-mcmodel=small
-Wall
-Wextra
-Werror
-O2
)
set(CMAKE_ASM_NASM_FLAGS "-f elf64")
set(LIBTERM_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/syscalls.asm
${CMAKE_CURRENT_SOURCE_DIR}/src/stdio.c
${CMAKE_CURRENT_SOURCE_DIR}/src/malloc.c
${CMAKE_CURRENT_SOURCE_DIR}/src/string.c
${CMAKE_CURRENT_SOURCE_DIR}/src/ctype.c
${CMAKE_CURRENT_SOURCE_DIR}/src/process.c
)
add_library(term STATIC ${LIBTERM_SOURCES})
target_compile_options(term PRIVATE $<$<COMPILE_LANGUAGE:C>:${USER_C_FLAGS}>)
target_include_directories(term PUBLIC inc)
add_library(crt0_obj OBJECT src/crt0.asm)
target_compile_options(crt0_obj PRIVATE $<$<COMPILE_LANGUAGE:C>:${USER_C_FLAGS}>)
function(add_termos_executable NAME SOURCES)
add_executable(${NAME} ${SOURCES})
target_sources(${NAME} PRIVATE $<TARGET_OBJECTS:crt0_obj>)
target_compile_options(${NAME} PRIVATE $<$<COMPILE_LANGUAGE:C>:${USER_C_FLAGS}>)
target_link_libraries(${NAME} PRIVATE term)
target_link_options(${NAME} PRIVATE
-T ${libterm_SOURCE_DIR}/linker.ld
-nostdlib
-static
)
set(ELF2HOT_DIR "${CMAKE_SOURCE_DIR}/tools/elf2hot")
set(FINAL_HOT_PATH "${CMAKE_SOURCE_DIR}/initramfs/${NAME}")
add_custom_command(TARGET ${NAME} POST_BUILD
COMMAND cargo run --release --quiet -- $<TARGET_FILE:${NAME}> ${FINAL_HOT_PATH}
WORKING_DIRECTORY ${ELF2HOT_DIR}
COMMENT "elf2hot: Converting ${NAME} to HOT! format..."
VERBATIM
)
endfunction()
+19
View File
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#pragma once
#include <types.h>
#define HEADER_MAGIC 0x1CE1CE
typedef struct block_header {
u64 magic;
struct block_header* next;
struct block_header* prev;
u64 size;
bool is_free;
} block_header;
void* malloc(u64 size);
void free(void* ptr);
void* realloc(void* ptr, u64 new_size);
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#pragma once
#include <types.h>
u64 spawn(const char* path);
u64 wait(u64 pid);
+8
View File
@@ -0,0 +1,8 @@
#pragma once
typedef __builtin_va_list va_list;
#define va_start(v, l) __builtin_va_start(v, l)
#define va_end(v) __builtin_va_end(v)
#define va_arg(v, l) __builtin_va_arg(v, l)
#define va_copy(d, s) __builtin_va_copy(d, s)
+10
View File
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#pragma once
#include <types.h>
void printf(const char *fmt, ...);
int getchar();
char* gets(char* str);
char* gets_s(char* str, u64 size);
+13
View File
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#pragma once
#include <types.h>
void *memset(void *ptr, int value, usize num);
void* memcpy(void* dest, const void* src, u64 n);
i32 strcmp(const char *s1, const char *s2);
i32 strncmp(const char* s1, const char* s2, u64 n);
char* strcpy(char* dest, const char* src);
char* strncpy(char* dest, const char* src, u64 n);
u64 strlen(const char* str);
+23
View File
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2025 0xKarinyash
#pragma once
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef signed char i8;
typedef signed short i16;
typedef signed int i32;
typedef signed long long i64;
typedef u64 usize;
typedef u64 uintptr_t;
#ifndef __cplusplus
#define bool _Bool
#define true 1
#define false 0
#endif
+35
View File
@@ -0,0 +1,35 @@
ENTRY(_start)
SECTIONS
{
. = 0x400000;
.text : {
*(.text)
}
.rodata : {
*(.rodata)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
*(COMMON)
}
/DISCARD/ : {
*(.note.gnu.build-id)
*(.note.GNU-stack)
*(.note.gnu.property)
*(.comment)
*(.interp)
*(.dynsym)
*(.dynstr)
*(.hash)
*(.gnu.hash)
}
}
+14
View File
@@ -0,0 +1,14 @@
; SPDX-License-Identifier: GPL-3.0-or-later
; Copyright (c) 2026 0xKarinyash
[bits 64]
section .text
global _start
extern main
extern sys_exit
_start:
call main
mov rdi, rax
call sys_exit
View File
+103
View File
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
// brutally copypasting from kernel/mm/heap.c
#include <malloc.h>
#include <string.h>
static block_header* heap_list_head = nullptr;
extern u64 sys_mem(u64 size);
void combine_forward(block_header* curr) {
if (!curr->next || !curr->next->is_free) return;
curr->size += sizeof(block_header) + curr->next->size;
curr->next = curr->next->next;
if (curr->next) curr->next->prev = curr; // what the fuck
}
void* malloc(u64 size) {
if (size == 0) return nullptr;
u64 aligned_size = (size + 15) & ~15;
block_header* curr = heap_list_head;
block_header* last = nullptr;
while (curr) {
if (curr->is_free && curr->size >= aligned_size) {
if (curr->size > aligned_size + sizeof(block_header) + 16) {
block_header* new_block = (block_header*)((u64)curr + sizeof(block_header) + aligned_size);
new_block->size = curr->size - aligned_size - sizeof(block_header);
new_block->is_free = true;
new_block->next = curr->next;
new_block->prev = curr;
new_block->magic = HEADER_MAGIC;
if (curr->next) curr->next->prev = new_block;
curr->next = new_block;
curr->size = aligned_size;
}
curr->is_free = false;
return (void*)((u64)curr + sizeof(block_header));
}
last = curr;
curr = curr->next;
}
u64 need_to_alloc = aligned_size + sizeof(block_header);
u64 page_aligned_size = (need_to_alloc + 4095) & ~4095;
u64 new_mem_addr = sys_mem(page_aligned_size);
if (new_mem_addr == 0) return nullptr;
block_header* new_block = (block_header*)new_mem_addr;
new_block->size = page_aligned_size - sizeof(block_header);
new_block->is_free = true;
new_block->magic = HEADER_MAGIC;
new_block->next = nullptr;
new_block->prev = last;
if (last) {
last->next = new_block;
} else {
heap_list_head = new_block;
}
return malloc(size);
}
void free(void* ptr) {
if (!ptr) return;
block_header* curr = (block_header*)((u64)ptr - sizeof(block_header));
if (curr->magic != HEADER_MAGIC) return;
curr->is_free = true;
if (curr->next && curr->next->is_free) combine_forward(curr);
if (curr->prev && curr->prev->is_free) combine_forward(curr->prev);
}
void* realloc(void* ptr, u64 new_size) {
if (!ptr) return malloc(new_size);
if (new_size == 0) {
free(ptr);
return nullptr;
}
block_header* curr = (block_header*)((u64)ptr - sizeof(block_header));
if (curr->size >= new_size) return ptr;
if (curr->next &&
curr->next->is_free &&
(curr->size + sizeof(block_header) + curr->next->size) >= new_size) { // why ts so fucking unreadable
combine_forward(curr);
return ptr;
}
void* new_ptr = malloc(new_size);
if (!new_ptr) return nullptr;
memcpy(new_ptr, ptr, curr->size);
free(ptr);
return new_ptr;
}
+15
View File
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include <process.h>
extern u64 sys_spawn(const char* path);
extern u64 sys_wait(u64 pid);
u64 spawn(const char* path) {
return sys_spawn(path);
}
u64 wait(u64 pid) {
return sys_wait(pid);
}
+132
View File
@@ -0,0 +1,132 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include <stdio.h>
#include <stdarg.h>
#include <types.h>
#include <string.h>
#define EOF (-1)
extern u64 sys_read(u64 fd, void* buf, u64 len);
extern u64 sys_write(u64 fd, const void* buf, u64 len);
static void putchar(char c) {
sys_write(1, &c, 1);
}
static void print_str(const char* str) {
if (!str) str = "(null)";
sys_write(1, str, strlen(str));
}
static void print_dec(long long n) {
if (n < 0) {
putchar('-');
n = -n;
}
unsigned long long u = (unsigned long long)n;
char buffer[32];
int i = 0;
do {
buffer[i++] = (u % 10) + '0';
u /= 10;
} while (u > 0);
while (--i >= 0) putchar(buffer[i]);
}
static void print_hex(unsigned long long u, int padding) {
char buffer[16];
int i = 0;
const char* hex_chars = "0123456789ABCDEF";
do {
buffer[i++] = hex_chars[u % 16];
u /= 16;
} while (u > 0);
while (i < padding) buffer[i++] = '0';
print_str("0x");
while (--i >= 0) putchar(buffer[i]);
}
void printf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
for (int i = 0; fmt[i] != '\0'; i++) {
if (fmt[i] == '%') {
i++;
switch (fmt[i]) {
case 's': print_str(va_arg(args, const char*)); break;
case 'c': putchar((char)va_arg(args, int)); break;
case 'd': print_dec(va_arg(args, int)); break;
case 'x': print_hex(va_arg(args, unsigned long long), 0); break;
case 'X': print_hex(va_arg(args, unsigned long long), 16); break;
case '%': putchar('%'); break;
default: putchar(fmt[i]); break;
}
}
else {
putchar(fmt[i]);
}
}
va_end(args);
}
int getchar() {
char c;
unsigned long long res = sys_read(0, &c, 1);
if (res <= 0) return EOF;
return (int)(unsigned char)c;
}
char* gets(char* str) {
int i = 0;
int c;
while (1) {
c = getchar();
if (c == EOF || c == '\n' || c == '\r') {
break;
}
str[i++] = (char)c;
char ch = (char)c;
sys_write(1, &ch, 1);
}
str[i] = '\0';
char nl = '\n';
sys_write(1, &nl, 1);
return str;
}
char* gets_s(char* str, u64 size) {
if (size == 0) return str;
u64 i = 0;
int c;
while (i < size - 1) {
c = getchar();
if (c == EOF || c == '\n' || c == '\r') break;
str[i++] = (char)c;
char ch = (char)c;
sys_write(1, &ch, 1);
}
str[i] = '\0';
char nl = '\n';
sys_write(1, &nl, 1);
return str;
}
+77
View File
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 0xKarinyash
#include <string.h>
void *memset(void *ptr, int value, usize num) {
u8 *p = (u8 *)ptr;
while (num--) {
*p++ = (u8)value;
}
return ptr;
}
void* memcpy(void* dest, const void* src, u64 n) {
u8* d = (u8*)dest;
const u8* s = (const u8*)src;
while (n >= 8) {
*(u64*)d = *(const u64*)s;
d += 8;
s += 8;
n -= 8;
}
while (n > 0) {
*d++ = *s++;
n--;
}
return dest;
}
i32 strcmp(const char *s1, const char *s2) {
while (*s1 && (*s1 == *s2)) {
s1++;
s2++;
}
return *(const unsigned char*)s1 - *(const unsigned char*)s2;
}
i32 strncmp(const char* s1, const char* s2, u64 n) {
while (n > 0) {
if (*s1 != *s2) return *(unsigned char*)s1 - *(unsigned char*)s2;
if (*s1 == '\0') return 0;
s1++;
s2++;
n--;
}
return 0;
}
char* strcpy(char* dest, const char* src) {
char* saved = dest;
while (*src) *dest++ = *src++;
*dest = 0;
return saved;
}
char* strncpy(char* dest, const char* src, u64 n) {
char* saved = dest;
while (*src && n > 0) {
*dest++ = *src++;
n--;
}
while (n > 0) {
*dest++ = 0;
n--;
}
return saved;
}
u64 strlen(const char* str) {
u64 res = 0;
for (res = 0; str[res]; res++);
return res;
}
+43
View File
@@ -0,0 +1,43 @@
; SPDX-License-Identifier: GPL-3.0-or-later
; Copyright (c) 2026 0xKarinyash
bits 64
section .text
global sys_exit
global sys_spawn
global sys_mem
global sys_write
global sys_read
global sys_wait
sys_exit:
mov rax, 0
syscall
ret
sys_spawn:
mov rax, 1
syscall
ret
sys_mem:
mov rax, 2
syscall
ret
sys_write:
mov rax, 3
syscall
ret
sys_read:
mov rax, 4
syscall
ret
sys_wait:
mov rax, 5
syscall
ret