├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ ├── build-mac.yml │ └── build-linux.yml ├── src ├── include │ ├── limits.h │ ├── stdio.h │ ├── sys │ │ ├── time.h │ │ ├── statvfs.h │ │ ├── types.h │ │ └── stat.h │ ├── string.h │ ├── kernel │ │ ├── font.h │ │ ├── version.h │ │ ├── irq.h │ │ ├── tty.h │ │ ├── logger.h │ │ ├── dirent.h │ │ ├── modules.h │ │ ├── acpi.h │ │ ├── syscalls.h │ │ ├── io.h │ │ ├── boot.h │ │ ├── elf.h │ │ ├── socket.h │ │ ├── signal.h │ │ ├── file.h │ │ ├── memory.h │ │ └── sched.h │ ├── stdlib.h │ ├── platform │ │ └── platform.h │ └── errno.h ├── platform │ ├── x86_64 │ │ ├── cpu │ │ │ ├── halt.c │ │ │ ├── stack.asm │ │ │ ├── interrupt.c │ │ │ ├── excepstub.asm │ │ │ ├── tss.c │ │ │ ├── setup.c │ │ │ └── wrapper.asm │ │ ├── lux-x86_64.ld │ │ ├── include │ │ │ └── platform │ │ │ │ ├── lock.h │ │ │ │ ├── tss.h │ │ │ │ ├── cmos.h │ │ │ │ ├── idt.h │ │ │ │ ├── mmap.h │ │ │ │ ├── context.h │ │ │ │ ├── gdt.h │ │ │ │ ├── smp.h │ │ │ │ ├── exception.h │ │ │ │ └── x86_64.h │ │ ├── ipc │ │ │ ├── sigstub.asm │ │ │ └── signal.c │ │ ├── apic │ │ │ ├── timerstub.asm │ │ │ ├── override.c │ │ │ ├── nmi.c │ │ │ ├── smpstub.asm │ │ │ ├── irqstub.asm │ │ │ └── timer.c │ │ ├── stub.asm │ │ ├── sched │ │ │ ├── yield.c │ │ │ ├── switch.asm │ │ │ └── idle.asm │ │ ├── rand.c │ │ ├── io.c │ │ ├── lock.asm │ │ ├── main.c │ │ ├── syscalls.asm │ │ ├── string.asm │ │ └── cmos.c │ └── README ├── time.c ├── sched │ ├── cleanup.c │ ├── sleep.c │ ├── waitpid.c │ ├── exit.c │ ├── elf.c │ └── fork.c ├── servers │ ├── request.c │ ├── handle.c │ └── general.c ├── libc │ ├── string.c │ └── stdio.c ├── cwd.c ├── args.c ├── memory │ ├── brk.c │ └── mmio.c ├── logger.c ├── modules │ ├── modules.c │ └── ramdisk.c ├── irq.c ├── main.c ├── acpi │ └── tables.c ├── dirent.c ├── syscalls │ └── queue.c └── ipc │ ├── sockio.c │ └── connection.c ├── Makefile ├── LICENSE └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.bin 3 | lux -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: luxOS 2 | -------------------------------------------------------------------------------- /src/include/limits.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Kernel-Level Partial Implementation of the C Library 6 | */ 7 | 8 | #pragma once 9 | 10 | #define MAX_PATH 2048 11 | #define ARG_MAX 128 12 | -------------------------------------------------------------------------------- /src/platform/x86_64/cpu/halt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | void platformHalt() { 12 | enableIRQs(); 13 | halt(); 14 | } 15 | -------------------------------------------------------------------------------- /src/include/stdio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Kernel-Level Partial Implementation of the C Library 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | int putchar(int); 13 | int puts(const char *); 14 | int printf(const char *, ...); 15 | int vprintf(const char *, va_list); 16 | -------------------------------------------------------------------------------- /src/include/sys/time.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | struct timeval { 14 | time_t tv_sec; 15 | suseconds_t tv_usec; 16 | }; 17 | 18 | int gettimeofday(Thread *, struct timeval *, void *); 19 | -------------------------------------------------------------------------------- /src/time.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | int gettimeofday(Thread *t, struct timeval *tv, void *tzp) { 13 | tv->tv_sec = platformTimestamp(); 14 | tv->tv_usec = (platformUptime() % PLATFORM_TIMER_FREQUENCY) * 1000; 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /src/platform/README: -------------------------------------------------------------------------------- 1 | This directory is for platform-specific code. All assembly language code should go in here while still aiming to write as much as possible in a higher-level portable language. The aim is to keep as much as possible of the kernel portable and platform-independent. All the exposed functions implemented in this directory will be documented someday, as I also intend to write an ARM64 port someday, but for now x86_64 is just an easier starting point. -------------------------------------------------------------------------------- /src/platform/x86_64/lux-x86_64.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-x86-64) 2 | ENTRY(_start) 3 | 4 | SECTIONS 5 | { 6 | . = 0xFFFF800000200000; 7 | 8 | .text BLOCK(4096) : ALIGN(4096) 9 | { 10 | *(.text) 11 | } 12 | 13 | .rodata BLOCK(4096) : ALIGN(4096) 14 | { 15 | *(.rodata) 16 | } 17 | 18 | .data BLOCK(4096) : ALIGN(4096) 19 | { 20 | *(.data) 21 | } 22 | 23 | .bss BLOCK(4096) : ALIGN(4096) 24 | { 25 | *(.bss) 26 | } 27 | } -------------------------------------------------------------------------------- /src/include/string.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Kernel-Level Partial Implementation of the C Library 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | void *memcpy(void *, const void *, size_t); 13 | void *memmove(void *, const void *, size_t); 14 | size_t strlen(const char *); 15 | char *strcpy(char *, const char *); 16 | void *memset(void *, int, size_t); 17 | int strcmp(const char *, const char *); 18 | int memcmp(const void *, const void *, size_t); 19 | -------------------------------------------------------------------------------- /src/platform/x86_64/include/platform/lock.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | /* every platform must provide some implementation of locks */ 13 | #define LOCK_INITIAL 0 // initial free value 14 | 15 | typedef uint64_t lock_t; 16 | int lockStatus(lock_t *); 17 | int acquireLock(lock_t *); 18 | int acquireLockBlocking(lock_t *); 19 | int releaseLock(lock_t *); 20 | -------------------------------------------------------------------------------- /src/include/kernel/font.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | /* 9 | * This is the Modern DOS font from https://github.com/susam/pcface/, which is 10 | * used and redistributed under the Creative Commons License. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include 16 | 17 | #define FONT_WIDTH 8 18 | #define FONT_HEIGHT 16 19 | #define FONT_MIN_GLYPH 32 // ' ' 20 | #define FONT_MAX_GLYPH 126 // '~' 21 | 22 | extern const uint8_t font[]; 23 | -------------------------------------------------------------------------------- /src/platform/x86_64/ipc/sigstub.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | ; Signal Trampoline Code 8 | ; this simply invokes the sigreturn() system call to allow the kernel to 9 | ; restore the state of the thread before the signal was raised 10 | 11 | SYSCALL_SIGRETURN equ 49 12 | 13 | section .data 14 | 15 | global sigstub 16 | align 16 17 | sigstub: 18 | mov rax, SYSCALL_SIGRETURN 19 | syscall 20 | 21 | sigstubEnd: 22 | 23 | global sigstubSize 24 | align 16 25 | sigstubSize: dq sigstubEnd - sigstub 26 | -------------------------------------------------------------------------------- /src/platform/x86_64/apic/timerstub.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | section .text 8 | 9 | %include "cpu/stack.asm" 10 | 11 | ; assembly stub for the timer IRQ handler 12 | ; just to preserve the CPU state and then call the true handler 13 | 14 | global timerHandlerStub 15 | align 16 16 | timerHandlerStub: 17 | cli 18 | pushaq 19 | 20 | cld 21 | extern timerIRQ 22 | mov rdi, rsp ; pointer to the regs we just pushed 23 | call timerIRQ ; IRQ is acknowledged in here 24 | 25 | popaq 26 | iretq 27 | -------------------------------------------------------------------------------- /src/sched/cleanup.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /* threadCleanup(): frees all memory associated with a thread and removes it 14 | * from the run queues 15 | * params: t - thread structure 16 | * returns: nothing 17 | */ 18 | 19 | void threadCleanup(Thread *t) { 20 | platformCleanThread(t->context, t->highest); 21 | 22 | /* TODO: properly re-implement this after implementing per-CPU run queue */ 23 | } -------------------------------------------------------------------------------- /src/include/kernel/version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #define PLATFORM 13 | 14 | #ifdef PLATFORM_X86_64 15 | #undef PLATFORM 16 | #define PLATFORM "x86_64" 17 | #endif 18 | 19 | #ifdef PLATFORM_ARM64 20 | #undef PLATFORM 21 | #define PLATFORM "arm64" 22 | #endif 23 | 24 | #ifndef RELEASE_VERSION 25 | #define KERNEL_VERSION "lux microkernel " PLATFORM " (built " __DATE__ ")" 26 | #else 27 | #define KERNEL_VERSION "lux microkernel " RELEASE_VERSION " " PLATFORM " (built " __DATE__ ")" 28 | #endif 29 | -------------------------------------------------------------------------------- /src/platform/x86_64/include/platform/tss.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | 10 | #pragma once 11 | 12 | #define KENREL_STACK_SIZE 32768 13 | 14 | typedef struct { 15 | uint32_t reserved1; 16 | uint64_t rsp0; 17 | uint64_t rsp1; 18 | uint64_t rsp2; 19 | uint64_t reserved2; 20 | uint64_t ist[7]; 21 | uint64_t reserved3; 22 | uint16_t reserved4; 23 | uint16_t iomap; 24 | uint8_t ioports[8192]; // I/O port bitmap, 0 = allowed, 1 = deny 25 | uint8_t ones; // final byte must be all ones 26 | } __attribute__((packed)) TSS; 27 | 28 | void tssSetup(); 29 | -------------------------------------------------------------------------------- /src/platform/x86_64/stub.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | section .text 8 | global _start 9 | 10 | ; Kernel entry point stub from boot loader 11 | ; prototype: void _start(KernelBootInfo *k) 12 | align 8 13 | _start: 14 | mov rsp, initial_stack_top 15 | mov rbp, rsp 16 | 17 | ; now we call platform-specific initialization code 18 | ; this function will later call the true kernel main 19 | extern platformMain 20 | cld 21 | mov rax, platformMain 22 | call rax 23 | 24 | .hang: 25 | ; this should never return 26 | cli 27 | hlt 28 | jmp .hang 29 | 30 | section .bss 31 | 32 | align 32 33 | initial_stack: resb 16384 34 | initial_stack_top: 35 | -------------------------------------------------------------------------------- /src/platform/x86_64/cpu/stack.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | ; macros to replace pusha/popa instructions that are not available on x86_64 8 | 9 | %macro pushaq 0 10 | push rax 11 | push rbx 12 | push rcx 13 | push rdx 14 | push rsi 15 | push rdi 16 | push rbp 17 | push r8 18 | push r9 19 | push r10 20 | push r11 21 | push r12 22 | push r13 23 | push r14 24 | push r15 25 | %endmacro 26 | 27 | %macro popaq 0 28 | pop r15 29 | pop r14 30 | pop r13 31 | pop r12 32 | pop r11 33 | pop r10 34 | pop r9 35 | pop r8 36 | pop rbp 37 | pop rdi 38 | pop rsi 39 | pop rdx 40 | pop rcx 41 | pop rbx 42 | pop rax 43 | %endmacro 44 | -------------------------------------------------------------------------------- /src/include/stdlib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Kernel-Level Partial Implementation of the C Library 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #define OCTAL 8 13 | #define DECIMAL 10 14 | #define HEX 16 15 | 16 | #define RAND_MAX 0x1FFFFFFF // at least 32767 17 | 18 | char *itoa(int, char *, int); 19 | int atoi(const char *); 20 | char *ltoa(long, char *, int); 21 | long atol(const char *); 22 | void *malloc(size_t); 23 | void *calloc(size_t, size_t); 24 | void *realloc(void *, size_t); 25 | void *umalloc(size_t); 26 | void *uxmalloc(size_t); 27 | void *ucalloc(size_t, size_t); 28 | void *urealloc(void *, size_t); 29 | void free(void *); 30 | int rand(); 31 | void srand(unsigned int); 32 | -------------------------------------------------------------------------------- /src/include/sys/statvfs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #define ST_RDONLY 0x01 14 | #define ST_NOSUID 0x02 15 | 16 | struct statvfs { 17 | unsigned long f_bsize; 18 | unsigned long f_frsize; 19 | fsblkcnt_t f_blocks; 20 | fsblkcnt_t f_bfree; 21 | fsblkcnt_t f_bavail; 22 | fsfilcnt_t f_files; 23 | fsfilcnt_t f_ffree; 24 | fsfilcnt_t f_favail; 25 | unsigned long f_fsid; 26 | unsigned long f_flag; 27 | unsigned long f_namemax; 28 | }; 29 | 30 | int fstatvfs(Thread *, uint64_t, int, struct statvfs *); 31 | int statvfs(Thread *, uint64_t, const char *, struct statvfs *); 32 | -------------------------------------------------------------------------------- /src/include/sys/types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | // Unix system types 14 | 15 | typedef uint64_t dev_t; // device 16 | typedef uint64_t ino_t; // inode 17 | typedef uint32_t mode_t; 18 | typedef uint64_t nlink_t; 19 | typedef int32_t id_t; 20 | typedef id_t uid_t; 21 | typedef id_t gid_t; 22 | typedef id_t pid_t; 23 | typedef int64_t off_t; 24 | typedef int64_t time_t; 25 | typedef int64_t clock_t; 26 | typedef int64_t suseconds_t; 27 | typedef uint64_t useconds_t; 28 | typedef int16_t blksize_t; 29 | typedef int64_t blkcnt_t; 30 | typedef int64_t ssize_t; 31 | typedef int16_t clockid_t; 32 | typedef uint64_t fsblkcnt_t; 33 | typedef uint64_t fsfilcnt_t; 34 | typedef int64_t timer_t; 35 | -------------------------------------------------------------------------------- /src/include/kernel/irq.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | /* Generic platform-independent structure for IRQ handlers */ 14 | typedef struct { 15 | char name[256]; // device name 16 | char driver[256]; // name of server/driver prefixed with "lux:///ks" 17 | int kernel; // 1 for kernel-level IRQ handler, 0 for user space 18 | uintptr_t khandler; // entry point for kernel IRQ handlers 19 | int level, high; // trigger mode 20 | } IRQHandler; 21 | 22 | typedef struct { 23 | int pin; // irq number 24 | int devices; // number of devices sharing this IRQ 25 | IRQHandler *handlers; 26 | } IRQ; 27 | 28 | int installIRQ(Thread *, int, IRQHandler *); 29 | -------------------------------------------------------------------------------- /src/include/kernel/tty.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | typedef struct { 16 | uint16_t w, h; // in pixels 17 | uint16_t wc, hc; // in characters 18 | uint8_t bpp, bytesPerPixel; 19 | uint16_t posx, posy; 20 | uint32_t fg, bg; 21 | uint32_t *fb; 22 | uint32_t *fbhw; 23 | uint32_t pitch; 24 | char escape[256]; 25 | bool escaping; 26 | int escapeIndex; 27 | } KTTY; 28 | 29 | extern const uint32_t ttyColors[]; 30 | 31 | void ttyInit(KernelBootInfo *); 32 | void ttyPutc(char); 33 | void ttyPuts(const char *); 34 | void ttyRemapFramebuffer(); 35 | void getTtyStatus(KTTY *); 36 | void ttyCreateBackbuffer(); -------------------------------------------------------------------------------- /src/platform/x86_64/include/platform/cmos.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #define CMOS_INDEX 0x70 14 | #define CMOS_DATA 0x71 15 | 16 | #define CMOS_RTC_SECS 0x00 17 | #define CMOS_RTC_MINS 0x02 18 | #define CMOS_RTC_HOURS 0x04 19 | #define CMOS_RTC_DAY 0x07 20 | #define CMOS_RTC_MONTH 0x08 21 | #define CMOS_RTC_YEAR 0x09 22 | #define CMOS_RTC_STATUS_A 0x0A 23 | #define CMOS_RTC_STATUS_B 0x0B 24 | 25 | #define CMOS_STATUS_A_UPDATE 0x80 26 | #define CMOS_STATUS_B_24HR 0x02 27 | #define CMOS_STATUS_B_BINARY 0x04 28 | 29 | uint8_t cmosRead(uint8_t); 30 | void cmosWrite(uint8_t, uint8_t); 31 | -------------------------------------------------------------------------------- /src/platform/x86_64/sched/yield.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /* this is called when the kernel threads are idle 14 | * it essentially tries to queue another thread to do something more useful 15 | * with the CPU time, else it returns (and then idle.asm halts the CPU to save 16 | * power since there are no queued tasks) */ 17 | 18 | void kernelYield(void *stack) { 19 | // save the kernel thread's context 20 | if(!schedBusy()) return; 21 | 22 | KernelCPUInfo *info = getKernelCPUInfo(); 23 | if(info->thread && info->thread->context) { 24 | platformSaveContext(info->thread->context, stack); 25 | schedule(); 26 | } 27 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PLATFORM=x86_64 2 | PLATFORMDEF=PLATFORM_X86_64 3 | ASFLAGS=-f elf64 -i./src/platform/$(PLATFORM) 4 | CCFLAGS=-Wall -c -D$(PLATFORMDEF) -I./src/include -I./src/platform/$(PLATFORM)/include -mno-sse -ffreestanding -O3 -mcmodel=large -mno-red-zone 5 | LDFLAGS=-T./src/platform/$(PLATFORM)/lux-$(PLATFORM).ld -nostdlib 6 | AS=nasm 7 | CC=x86_64-lux-gcc 8 | LD=x86_64-lux-ld 9 | SRCC:=$(shell find ./src -type f -name "*.c") 10 | OBJC:=$(SRCC:.c=.o) 11 | SRCA:=$(shell find ./src -type f -name "*.asm") 12 | OBJA:=$(SRCA:.asm=.o) 13 | 14 | all: lux 15 | 16 | %.o: %.asm 17 | @echo "\x1B[0;1;36m as \x1B[0m $<" 18 | @$(AS) $(ASFLAGS) -o $@ $< 19 | 20 | %.o: %.c 21 | @echo "\x1B[0;1;32m cc \x1B[0m $<" 22 | @$(CC) $(CCFLAGS) -o $@ $< 23 | 24 | lux: $(OBJA) $(OBJC) 25 | @echo "\x1B[0;1;93m ld \x1B[0m lux" 26 | @$(LD) $(LDFLAGS) $(OBJA) $(OBJC) -o lux 27 | 28 | clean: 29 | @rm -f lux $(OBJC) $(OBJA) 30 | -------------------------------------------------------------------------------- /src/include/kernel/logger.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define KPRINTF_LEVEL_DEBUG 0 15 | #define KPRINTF_LEVEL_WARNING 1 16 | #define KPRINTF_LEVEL_ERROR 2 17 | #define KPRINTF_LEVEL_PANIC 3 18 | 19 | #define KDEBUG(...) kprintf(KPRINTF_LEVEL_DEBUG, __FILE__+4, __VA_ARGS__) 20 | #define KWARN(...) kprintf(KPRINTF_LEVEL_WARNING, __FILE__+4, __VA_ARGS__) 21 | #define KERROR(...) kprintf(KPRINTF_LEVEL_ERROR, __FILE__+4, __VA_ARGS__) 22 | #define KPANIC(...) kprintf(KPRINTF_LEVEL_PANIC, __FILE__+4, __VA_ARGS__) 23 | 24 | void loggerSetVerbose(bool); 25 | int kprintf(int, const char *, const char *, ...); 26 | int ksprint(int, const char *, const char *); 27 | -------------------------------------------------------------------------------- /src/platform/x86_64/include/platform/idt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | 10 | #pragma once 11 | 12 | #define IDT_FLAGS_VALID 0x8000 13 | 14 | #define IDT_FLAGS_TYPE_INTERRUPT 0x0E 15 | #define IDT_FLAGS_TYPE_TRAP 0x0F 16 | #define IDT_FLAGS_TYPE_SHIFT 8 17 | 18 | #define IDT_FLAGS_DPL_KERNEL 0 19 | #define IDT_FLAGS_DPL_USER 3 20 | #define IDT_FLAGS_DPL_SHIFT 13 21 | 22 | typedef struct { 23 | uint16_t offset_lo; 24 | uint16_t segment; 25 | uint16_t flags; 26 | uint16_t offset_mi; 27 | uint32_t offset_hi; 28 | uint32_t reserved; 29 | } __attribute__((packed)) IDTEntry; 30 | 31 | typedef struct { 32 | uint16_t limit; 33 | uint64_t base; 34 | } __attribute__((packed)) IDTR; 35 | -------------------------------------------------------------------------------- /src/servers/request.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | /* Helper functions for syscalls that depend on user space servers */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | extern int lumenSocket; 15 | 16 | /* requestServer(): sends a request message to lumen 17 | * params: t - requesting thread 18 | * params: sd - socket descriptor to request, zero for lumen routing 19 | * params: msg - pointer to message 20 | * returns: 0 on success 21 | */ 22 | 23 | int requestServer(Thread *t, int sd, void *msg) { 24 | SyscallHeader *hdr = (SyscallHeader *)msg; 25 | hdr->header.requester = t->tid; 26 | 27 | if(!sd) sd = lumenSocket; 28 | 29 | ssize_t s = send(NULL, sd, hdr, hdr->header.length, 0); 30 | if(s == hdr->header.length) return 0; 31 | else if(s >= 0) return -ENOBUFS; 32 | else return s; 33 | } 34 | -------------------------------------------------------------------------------- /src/include/kernel/dirent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | // directory descriptors will be OR'ed with this flag 15 | #define DIRECTORY_DESCRIPTOR_FLAG 0x8000 16 | 17 | typedef int DIR; 18 | 19 | struct dirent { 20 | ino_t d_ino; 21 | char d_name[]; 22 | }; 23 | 24 | /* directory-specific I/O descriptor (see io.h) */ 25 | typedef struct { 26 | Process *process; 27 | char path[MAX_FILE_PATH]; 28 | char device[MAX_FILE_PATH]; 29 | size_t position; 30 | int sd; 31 | } DirectoryDescriptor; 32 | 33 | int opendir(Thread *, uint64_t, const char *); 34 | int closedir(Thread *, DIR *); 35 | int readdir_r(Thread *, uint64_t, DIR *, struct dirent *, struct dirent **); 36 | void seekdir(Thread *, DIR *, long); 37 | long telldir(Thread *, DIR *); 38 | -------------------------------------------------------------------------------- /.github/workflows/build-mac.yml: -------------------------------------------------------------------------------- 1 | name: build - macOS 2 | 3 | on: 4 | [push, pull_request, workflow_dispatch] 5 | 6 | jobs: 7 | build: 8 | runs-on: macos-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Install dependencies 14 | run: brew install nasm curl make 15 | 16 | - name: Fetch and install toolchain 17 | run: | 18 | curl https://jewelcodes.io/lux/toolchain-macos-arm64.tar.xz -o toolchain-macos-arm64.tar.xz 19 | tar -xvJf toolchain-macos-arm64.tar.xz 20 | mv toolchain $HOME/work/toolchain 21 | mv host $HOME/work/host 22 | echo "$HOME/work/toolchain/bin" >> $GITHUB_PATH 23 | 24 | - name: Verify toolchain is executable 25 | run: x86_64-lux-gcc -v 26 | 27 | - name: Build kernel 28 | run: make 29 | 30 | - name: Clean up artifacts 31 | run: | 32 | rm -rf toolchain-macos-arm64.tar.xz $HOME/work/toolchain $HOME/work/host 33 | make clean 34 | -------------------------------------------------------------------------------- /.github/workflows/build-linux.yml: -------------------------------------------------------------------------------- 1 | name: build - linux 2 | 3 | on: 4 | [workflow_dispatch] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Install dependencies 14 | run: sudo apt install build-essential nasm curl make 15 | 16 | - name: Fetch and install toolchain 17 | run: | 18 | curl https://jewelcodes.io/lux/toolchain-linux-x86_64.tar.xz -o toolchain-linux-x86_64.tar.xz 19 | tar -xvJf toolchain-linux-x86_64.tar.xz 20 | mv toolchain $HOME/work/toolchain 21 | mv host $HOME/work/host 22 | echo "$HOME/work/toolchain/bin" >> $GITHUB_PATH 23 | 24 | - name: Verify toolchain is executable 25 | run: x86_64-lux-gcc -v 26 | 27 | - name: Build kernel 28 | run: make 29 | 30 | - name: Clean up artifacts 31 | run: | 32 | rm -rf toolchain-linux-x86_64.tar.xz $HOME/work/toolchain $HOME/work/host 33 | make clean 34 | -------------------------------------------------------------------------------- /src/libc/string.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Kernel-Level Partial Implementation of the C Library 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | size_t strlen(const char *s) { 13 | size_t i = 0; 14 | for(; *s; i++) { 15 | s++; 16 | } 17 | return i; 18 | } 19 | 20 | char *strcpy(char *dst, const char *src) { 21 | return (char *)memcpy(dst, src, strlen(src)+1); 22 | } 23 | 24 | int strcmp(const char *s1, const char *s2) { 25 | while(*s1 == *s2) { 26 | if(!*s1) return 0; 27 | 28 | s1++; 29 | s2++; 30 | } 31 | 32 | return *s1 - *s2; 33 | } 34 | 35 | int memcmp(const void *d1, const void *d2, size_t n) { 36 | uint8_t *d1c = (uint8_t *)d1; 37 | uint8_t *d2c = (uint8_t *)d2; 38 | 39 | for(size_t i = 0; i < n; i++) { 40 | if(*d1c != *d2c) { 41 | return *d1c - *d2c; 42 | } 43 | 44 | d1c++; 45 | d2c++; 46 | } 47 | 48 | return 0; 49 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 by the luxOS authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/include/kernel/modules.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define MAX_MODULES 16 15 | 16 | void modulesInit(KernelBootInfo *); 17 | int moduleCount(); 18 | size_t moduleQuery(const char *); 19 | void *moduleLoad(void *, const char *); 20 | 21 | struct USTARMetadata { 22 | char name[100]; 23 | char mode[8]; 24 | char owner[8]; 25 | char group[8]; 26 | char size[12]; 27 | char modified[12]; 28 | char checksum[8]; 29 | char type; 30 | char link[100]; 31 | char magic[6]; // "ustar" 32 | char version[2]; // '00' 33 | char ownerName[32]; 34 | char groupName[32]; 35 | char deviceMajorNumber[8]; 36 | char deviceMinorNumber[8]; 37 | char namePrefix[155]; 38 | } __attribute__((packed)); 39 | 40 | void ramdiskInit(KernelBootInfo *); 41 | struct USTARMetadata *ramdiskFind(const char *); 42 | int64_t ramdiskFileSize(const char *); 43 | size_t ramdiskRead(void *, const char *, size_t); 44 | -------------------------------------------------------------------------------- /src/platform/x86_64/rand.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | static uint64_t seed; 13 | 14 | /* platformInitialSeed(): sets the initial seed for a random number generator 15 | * this is platform-dependent because the source of the seed can differ */ 16 | 17 | void platformInitialSeed() { 18 | seed = (uint64_t)lapicRead(LAPIC_TIMER_CURRENT); 19 | seed *= platformCountCPU(); 20 | } 21 | 22 | /* platformRand(): generates a random number 23 | * params: none 24 | * returns: 64-bit random number 25 | */ 26 | 27 | uint64_t platformRand() { 28 | seed += (uint64_t)lapicRead(LAPIC_TIMER_CURRENT); 29 | 30 | uint8_t *bytes = (uint8_t *)&seed; 31 | for(int i = 0; i < 4; i++) { 32 | bytes[i] ^= bytes[7-i]; 33 | bytes[7-i] ^= bytes[i+4]; 34 | } 35 | 36 | return ~seed; 37 | } 38 | 39 | /* platformSeed(): seeds the random number generator 40 | * params: s - the seed to use 41 | * returns: nothing 42 | */ 43 | 44 | void platformSeed(uint64_t s) { 45 | seed = s; 46 | } 47 | -------------------------------------------------------------------------------- /src/include/kernel/acpi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | typedef struct { 14 | // revision == 0 15 | char signature[8]; // 'RSD PTR ' 16 | uint8_t checksum; 17 | char oem[6]; 18 | uint8_t revision; 19 | uint32_t rsdt; 20 | 21 | // revision >= 2 22 | uint32_t length; 23 | uint64_t xsdt; 24 | uint8_t extendedChecksum; 25 | uint8_t reserved[3]; 26 | } __attribute__((packed)) ACPIRSDP; 27 | 28 | typedef struct { 29 | char signature[4]; 30 | uint32_t length; 31 | uint8_t revision; 32 | uint8_t checksum; 33 | char oem[6]; 34 | char oemTable[8]; 35 | uint32_t oemRevision; 36 | uint32_t creator; 37 | uint32_t creatorRevision; 38 | } __attribute__((packed)) ACPIStandardHeader; 39 | 40 | typedef struct { 41 | ACPIStandardHeader header; 42 | uint32_t tables[]; 43 | } __attribute__((packed)) ACPIRSDT; 44 | 45 | typedef struct { 46 | ACPIStandardHeader header; 47 | uint64_t tables[]; 48 | } __attribute__((packed)) ACPIXSDT; 49 | 50 | int acpiInit(KernelBootInfo *); 51 | void *acpiFindTable(const char *, int); 52 | -------------------------------------------------------------------------------- /src/platform/x86_64/io.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | /* platformIoperm(): sets the I/O permissions for the current thread 13 | * params: t - calling thread 14 | * params: from - base I/O port 15 | * params: count - number of I/O ports to change permissions 16 | * params: enable - 0 to disable access, 1 to enable 17 | * returns: zero on success, negative error code on fail 18 | */ 19 | 20 | int platformIoperm(Thread *t, uintptr_t from, uintptr_t count, int enable) { 21 | // privilege checks were already performed in the generic ioperm() 22 | if((from+count-1) > 0xFFFF) return -EINVAL; // 65536 I/O ports on x86 23 | 24 | ThreadContext *ctx = (ThreadContext *) t->context; 25 | ctx->iopl = 1; 26 | 27 | for(int i = 0; i < count; i++) { 28 | int byte = (from + i) / 8; 29 | int bit = (from + i) % 8; 30 | 31 | if(enable) ctx->ioports[byte] &= ~(1 << bit); 32 | else ctx->ioports[byte] |= (1 << bit); 33 | } 34 | 35 | // new permissions will be enforced in the next context switch, so return 36 | return 0; 37 | } -------------------------------------------------------------------------------- /src/platform/x86_64/cpu/interrupt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | /* installInterrupt(): installs an interrupt handler 12 | * params: handler - pointer to the entry point of the handler 13 | * params: segment - segment within the GDT to be loaded on execution 14 | * params: privilege - user or kernel privilege 15 | * params: type - interrupt or trap 16 | * params: i - interrupt number 17 | * returns: nothing 18 | */ 19 | 20 | void installInterrupt(uint64_t handler, uint16_t segment, int privilege, int type, int i) { 21 | if(i > 0xFF) { 22 | KERROR("request to install interrupt handler 0x%02X, ignoring\n", i); 23 | return; 24 | } 25 | 26 | privilege &= 3; 27 | type &= 0x0F; 28 | 29 | // this is really just about creating an IDT entry 30 | idt[i].offset_lo = handler & 0xFFFF; 31 | idt[i].offset_mi = (handler >> 16) & 0xFFFF; 32 | idt[i].offset_hi = handler >> 32; 33 | idt[i].segment = (segment << 3) | privilege; 34 | idt[i].flags = (privilege << IDT_FLAGS_DPL_SHIFT) | (type << IDT_FLAGS_TYPE_SHIFT); 35 | idt[i].flags |= IDT_FLAGS_VALID; 36 | idt[i].reserved = 0; 37 | } 38 | -------------------------------------------------------------------------------- /src/platform/x86_64/include/platform/mmap.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | // these constants must be defined for every CPU architecture 13 | #define PAGE_SIZE 4096 // bytes 14 | #define KERNEL_BASE_ADDRESS (uintptr_t)0xFFFF800000000000 15 | #define KERNEL_MMIO_BASE KERNEL_BASE_ADDRESS 16 | #define KERNEL_BASE_MAPPED 16 // gigabytes to be mapped 17 | #define KERNEL_BASE_END (KERNEL_BASE_ADDRESS-KERNEL_MMIO_LIMIT-1) 18 | #define KERNEL_HEAP_BASE (uintptr_t)0xFFFF8F0000000000 19 | #define KERNEL_HEAP_LIMIT (uintptr_t)0xFFFF8FFFFFFFFFFF 20 | #define KERNEL_MMIO_LIMIT ((uint64_t)KERNEL_BASE_MAPPED << 30) 21 | #define USER_BASE_ADDRESS 0x400000 // 4 MB, user programs will be loaded here 22 | #define USER_HEAP_BASE (uintptr_t)0x00006FFF80000000 // for signal structures 23 | #define USER_HEAP_LIMIT (uintptr_t)0x00006FFFFFFFFFFF // 2 GB of space 24 | #define USER_MMIO_BASE (uintptr_t)0x0000700000000000 // for mmap() and similar syscalls 25 | #define USER_LIMIT_ADDRESS (KERNEL_BASE_ADDRESS-1) // maximum limit for the lower half 26 | -------------------------------------------------------------------------------- /src/platform/x86_64/apic/override.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | /* IRQ Overrides for the I/O APIC */ 9 | 10 | #include 11 | #include 12 | 13 | static IRQOverride *overrides = NULL; 14 | static int count = 0; 15 | 16 | /* overrideIRQRegister(): registers an IRQ override 17 | * params: override - pointer to the IRQ override structure 18 | * returns: number of IRQ overrides present 19 | */ 20 | 21 | int overrideIRQRegister(IRQOverride *override) { 22 | if(!overrides) overrides = override; 23 | else { 24 | IRQOverride *list = overrides; 25 | while(list->next) list = list->next; 26 | list->next = override; 27 | } 28 | return count++; 29 | } 30 | 31 | /* overrideIRQCount(): returns the number of IRQ overrides present 32 | * params: none 33 | * returns: number of IRQ overrides 34 | */ 35 | 36 | int overrideIRQCount() { 37 | return count; 38 | } 39 | 40 | /* findOverrideIRQ(): returns an IRQ override for an ISA IRQ 41 | * params: pin - ISA IRQ number 42 | * returns: pointer to the IRQ override structure, NULL if not overridden 43 | */ 44 | 45 | IRQOverride *findOverrideIRQ(uint64_t pin) { 46 | if(!count) return NULL; 47 | 48 | IRQOverride *override = overrides; 49 | while(override) { 50 | if(override->gsi == pin) return override; 51 | else override = override->next; 52 | } 53 | 54 | return NULL; 55 | } -------------------------------------------------------------------------------- /src/include/kernel/syscalls.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define MAX_SYSCALL 66 15 | 16 | /* IPC syscall indexes, this range will be used for immediate handling without 17 | * waiting for the kernel thread to dispatch the syscall */ 18 | #define SYSCALL_IPC_START 48 // bind() 19 | #define SYSCALL_IPC_END 52 // send() 20 | #define SYSCALL_RW_START 18 // read() 21 | #define SYSCALL_RW_END 19 // write() 22 | #define SYSCALL_LSEEK 22 // lseek() 23 | 24 | typedef struct SyscallRequest { 25 | bool busy, queued, unblock; 26 | bool external; // set for syscalls that are handled in user space 27 | bool retry; // for async syscalls 28 | 29 | uint16_t requestID; // unique random ID for user space syscalls 30 | uint64_t function; 31 | uint64_t params[4]; 32 | uint64_t ret; // return value from the kernel to the program 33 | 34 | struct Thread *thread; 35 | struct SyscallRequest *next; 36 | } SyscallRequest; 37 | 38 | void syscallHandle(); 39 | SyscallRequest *syscallEnqueue(SyscallRequest *); 40 | SyscallRequest *syscallDequeue(); 41 | SyscallRequest *getSyscall(pid_t); 42 | int syscallProcess(); 43 | 44 | /* dispatch table */ 45 | extern void (*syscallDispatchTable[])(SyscallRequest *); 46 | -------------------------------------------------------------------------------- /src/platform/x86_64/sched/switch.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | section .text 8 | 9 | %include "cpu/stack.asm" 10 | 11 | ; Context Switching for the Scheduler 12 | 13 | ; platformSaveContext(): saves the context of the current running thread 14 | ; platformSaveContext(ThreadContext *, ThreadGPR *) 15 | ; sizeof(ThreadGPR) = 160 16 | ; sizeof(ThreadContext) = 680 17 | ; SSE offset = 0 18 | ; CR3 offset = 512 19 | ; ThreadGPR offset = 520 20 | 21 | global platformSaveContext 22 | align 16 23 | platformSaveContext: 24 | cli ; SENSITIVE AREA 25 | 26 | fxsave64 [rdi] 27 | 28 | ;mov rax, cr3 29 | ;mov [rdi+512], rax 30 | 31 | add rdi, 520 32 | mov rcx, 160/8 33 | rep movsq 34 | 35 | ret 36 | 37 | ; platformLoadContext(): loads the new context 38 | ; platformLoadContext(ThreadContext *) 39 | 40 | global platformLoadContext: 41 | align 16 42 | platformLoadContext: 43 | cli ;; SENSITIVE!!! this code can NOT be interrupted 44 | 45 | fxrstor64 [rdi] 46 | 47 | ; save performance by only invalidating TLB if the context actually changed 48 | mov rax, cr3 49 | mov rbx, [rdi+512] ; pml4 50 | cmp rax, rbx 51 | jz .continue 52 | 53 | mov cr3, rbx 54 | 55 | .continue: 56 | mov rax, [rdi+672] ; stack segment 57 | mov ds, rax 58 | mov es, rax 59 | mov fs, rax 60 | mov gs, rax 61 | 62 | mov rsp, rdi 63 | add rsp, 520 64 | 65 | popaq 66 | iretq 67 | -------------------------------------------------------------------------------- /src/platform/x86_64/lock.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | section .text 8 | 9 | ; Implementation of Locks 10 | 11 | ; int lockStatus(lock_t *lock) 12 | ; returns zero if the lock is free, one if not 13 | global lockStatus 14 | align 16 15 | lockStatus: 16 | xor rax, rax 17 | mov eax, [rdi] 18 | and eax, 1 19 | ret 20 | 21 | ; int acquireLock(lock_t *lock) 22 | ; non-blocking function to acquire a lock 23 | ; returns zero if not acquired, one if acquired 24 | global acquireLock 25 | align 16 26 | acquireLock: 27 | pushfq 28 | cli 29 | 30 | test dword [rdi], 1 31 | jnz .fail 32 | 33 | lock bts dword [rdi], 0 34 | jc .fail 35 | 36 | popfq 37 | mov rax, 1 38 | ret 39 | 40 | .fail: 41 | popfq 42 | xor rax, rax 43 | ret 44 | 45 | ; int acquireLockBlocking(lock_t *lock) 46 | ; blocking function to acquire lock, always returns one 47 | global acquireLockBlocking 48 | align 16 49 | acquireLockBlocking: 50 | cli 51 | 52 | test dword [rdi], 1 53 | jnz .wait 54 | 55 | .try: 56 | lock bts dword [rdi], 0 57 | jc .wait 58 | 59 | mov rax, 1 60 | ret 61 | 62 | .wait: 63 | pause 64 | test dword [rdi], 1 65 | jnz .wait 66 | jmp .try 67 | 68 | ; int releaseLock(lock_t *lock) 69 | ; releases a lock, always returns zero 70 | global releaseLock 71 | align 16 72 | releaseLock: 73 | xor rax, rax 74 | test dword [rdi], 1 75 | jz .done 76 | 77 | mov [rdi], eax 78 | 79 | .done: 80 | ret 81 | -------------------------------------------------------------------------------- /src/platform/x86_64/include/platform/context.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | /* Thread Context for x86_64 */ 14 | 15 | typedef struct { 16 | uint64_t r15; 17 | uint64_t r14; 18 | uint64_t r13; 19 | uint64_t r12; 20 | uint64_t r11; 21 | uint64_t r10; 22 | uint64_t r9; 23 | uint64_t r8; 24 | uint64_t rbp; 25 | uint64_t rdi; 26 | uint64_t rsi; 27 | uint64_t rdx; 28 | uint64_t rcx; 29 | uint64_t rbx; 30 | uint64_t rax; 31 | uint64_t rip; 32 | uint64_t cs; 33 | uint64_t rflags; 34 | uint64_t rsp; 35 | uint64_t ss; 36 | } __attribute__((packed)) ThreadGPR; 37 | 38 | typedef struct { 39 | uint8_t sse[512]; // fxsave 40 | 41 | // paging base will only be switched between processes 42 | // threads under the same process will share the same address space 43 | uint64_t cr3; 44 | 45 | ThreadGPR regs; // register state 46 | 47 | int iopl; // set to 1 if I/O port privileges have been modified 48 | uint8_t ioports[8192]; // I/O port privileges 49 | } __attribute__((packed)) ThreadContext; 50 | 51 | void *platformCreateContext(void *, int, uintptr_t, uintptr_t); 52 | int platformSetContext(Thread *, uintptr_t, uintptr_t, const char **, const char **); 53 | int platformSignalSetup(Thread *); 54 | 55 | #define PLATFORM_CONTEXT_SIZE sizeof(ThreadContext) 56 | 57 | #define PLATFORM_CONTEXT_KERNEL 0 58 | #define PLATFORM_CONTEXT_USER 1 59 | #define PLATFORM_THREAD_STACK 65536 60 | -------------------------------------------------------------------------------- /src/platform/x86_64/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | extern int main(int, char **); 25 | KernelBootInfo boot; 26 | 27 | // x86_64-specific kernel entry point 28 | int platformMain(KernelBootInfo *k) { 29 | memcpy(&boot, k, sizeof(KernelBootInfo)); 30 | 31 | // check if the kernel is booting is quiet mode 32 | size_t len = strlen(boot.arguments); 33 | if(len >= 9) { 34 | for(size_t i = 0; i <= len-6; i++) { 35 | if(!memcmp(boot.arguments+i, " quiet", 6)) { 36 | loggerSetVerbose(false); 37 | break; 38 | } 39 | } 40 | } 41 | 42 | platformCPUSetup(); 43 | ttyInit(&boot); 44 | 45 | KDEBUG("%s\n", KERNEL_VERSION); 46 | KDEBUG("booting with command-line options: %s\n", boot.arguments); 47 | 48 | installExceptions(); 49 | pmmInit(&boot); 50 | vmmInit(); 51 | 52 | ttyCreateBackbuffer(); 53 | acpiInit(&boot); 54 | apicInit(); 55 | platformInitialSeed(); 56 | ramdiskInit(&boot); 57 | modulesInit(&boot); 58 | 59 | char **argv; 60 | int argc = parseBootArgs(&argv, boot.arguments); 61 | return main(argc, argv); 62 | } -------------------------------------------------------------------------------- /src/platform/x86_64/sched/idle.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | section .text 8 | 9 | %include "cpu/stack.asm" 10 | 11 | ; For the kernel idle thread 12 | ; void platformIdle() 13 | global platformIdle 14 | align 16 15 | platformIdle: 16 | cli ; sensitive area 17 | 18 | sub rsp, 40 ; reserve iret frame 19 | pushaq 20 | 21 | mov rbp, rsp ; rsp to be restored 22 | 23 | add rsp, 160 ; skip over the pushed reg state 24 | 25 | push qword 0x10 ; ss 26 | push rbp ; rsp 27 | pushfq 28 | pop r10 29 | or r10, 0x202 30 | push r10 ; rflags 31 | push qword 0x08 ; cs 32 | mov r10, .next 33 | push r10 ; rip 34 | 35 | ; save this stack frame on the kernel's stack 36 | rdgsbase r10 37 | and r10, r10 38 | jnz .stack 39 | 40 | swapgs 41 | rdgsbase r10 42 | 43 | .stack: 44 | swapgs 45 | 46 | mov r10, [r10+8] ; r10 = top of kernel switch stack 47 | mov rdi, r10 48 | sub rdi, 160 ; rdi = bottom of kernel stack frame 49 | mov rsi, rbp ; rsi = stack we just created 50 | mov rcx, 160/8 51 | rep movsq 52 | 53 | ; switch stacks 54 | sub rdi, 160 ; rdi = bottom of kernel stack frame 55 | mov rsp, rdi 56 | sub rsp, 256 57 | 58 | extern kernelYield 59 | call kernelYield 60 | 61 | mov rsp, rbp 62 | 63 | ; if we get here then there is no work in the scheduler's queue 64 | ; we need to restore the original stack 65 | ; so halt the CPU for one timer tick so it doesn't burn up 66 | sti 67 | hlt 68 | 69 | .next: 70 | popaq 71 | add rsp, 40 ; skip over iret frame 72 | ret 73 | -------------------------------------------------------------------------------- /src/platform/x86_64/syscalls.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | section .text 8 | 9 | %include "cpu/stack.asm" 10 | 11 | USER_CODE_SEGMENT equ 0x23 ; gdt.h, these are hard-coded for a reason 12 | USER_DATA_SEGMENT equ 0x1B 13 | KERNEL_DATA_SEGMENT equ 0x10 14 | 15 | ; entry point into the kernel by the SYSCALL instruction 16 | ; this saves many CPU cycles compared to implementing syscalls via interrupts 17 | 18 | global syscallEntry 19 | align 16 20 | syscallEntry: 21 | ; rcx = return address for user program 22 | ; r11 = rflags for user program 23 | 24 | ; switch to kernel stack 25 | mov r9, rsp ; r9 = user stack 26 | 27 | rdgsbase r10 ; a register we're allowed to trash 28 | and r10, r10 29 | jnz .stack 30 | 31 | swapgs 32 | rdgsbase r10 ; r10 = pointer to kernel cpu-specific info block 33 | 34 | .stack: 35 | swapgs ; restore base 0 segment 36 | 37 | mov r10, [r10] 38 | mov rsp, r10 39 | 40 | ; construct a context switch stack as if an interrupt just happened 41 | push qword USER_DATA_SEGMENT ; ss 42 | push r9 ; rsp 43 | push r11 ; rflags 44 | push qword USER_CODE_SEGMENT ; cs 45 | push rcx ; rip 46 | 47 | ;xor r9, r9 48 | ;xor r11, r11 49 | xor r10, r10 ; zero out pointer to kernel structures 50 | pushaq 51 | 52 | extern syscallHandle 53 | mov rdi, rsp ; pass the context we just created 54 | call syscallHandle 55 | 56 | ; this should never return as it will force a context switch 57 | 58 | .hang: 59 | cli 60 | hlt 61 | jmp .hang 62 | -------------------------------------------------------------------------------- /src/platform/x86_64/include/platform/gdt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | 10 | #pragma once 11 | 12 | // 0x00: null descriptor 13 | // 0x08: kernel code descriptor 14 | // 0x10: kernel data descriptor 15 | // 0x18: user code descriptor 16 | // 0x20: user data descriptor 17 | // 0x28: task segment descriptor 18 | 19 | #define GDT_ENTRIES 7 20 | 21 | #define GDT_NULL 0 22 | #define GDT_KERNEL_CODE 1 23 | #define GDT_KERNEL_DATA 2 24 | #define GDT_USER_DATA 3 25 | #define GDT_USER_CODE 4 26 | #define GDT_TSS_LOW 5 27 | #define GDT_TSS_HIGH 6 28 | 29 | #define GDT_LIMIT 0xFFFFF // constant for all entries 30 | #define GDT_BASE 0 31 | 32 | #define GDT_ACCESS_ACCESSED 0x01 33 | #define GDT_ACCESS_RW 0x02 34 | #define GDT_ACCESS_DC 0x04 // direction (data) or conforming (code) 35 | #define GDT_ACCESS_EXEC 0x08 36 | #define GDT_ACCESS_CODE_DATA 0x10 37 | #define GDT_ACCESS_PRESENT 0x80 38 | 39 | #define GDT_ACCESS_TSS 0x09 40 | 41 | #define GDT_ACCESS_DPL_SHIFT 5 // privilege level 42 | #define GDT_ACCESS_DPL_KERNEL 0 43 | #define GDT_ACCESS_DPL_USER 3 44 | 45 | #define GDT_FLAGS_AVAILABLE 0x10 46 | #define GDT_FLAGS_64_BIT 0x20 47 | #define GDT_FLAGS_32_BIT 0x40 48 | #define GDT_FLAGS_PAGE_GRAN 0x80 49 | 50 | typedef struct { 51 | uint16_t limit; 52 | uint16_t baseLo; 53 | uint8_t baseMi; 54 | uint8_t access; 55 | uint8_t flagsLimitHi; 56 | uint8_t baseHi; 57 | } __attribute__((packed)) GDTEntry; 58 | 59 | typedef struct { 60 | uint16_t limit; 61 | uint64_t base; 62 | } __attribute__((packed)) GDTR; 63 | -------------------------------------------------------------------------------- /src/cwd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /* chdir(): changes the working directory of the running process 15 | * params: t - running thread 16 | * params: id - syscall ID 17 | * params: path - new working directory 18 | * returns: zero on success 19 | */ 20 | 21 | int chdir(Thread *t, uint16_t id, const char *path) { 22 | if(!path) return -EINVAL; 23 | if(strlen(path) > MAX_FILE_PATH) return -ENAMETOOLONG; 24 | 25 | Process *p = getProcess(t->pid); 26 | if(!p) return -ESRCH; 27 | 28 | ChdirCommand cmd; 29 | memset(&cmd, 0, sizeof(ChdirCommand)); 30 | cmd.header.header.command = COMMAND_CHDIR; 31 | cmd.header.header.length = sizeof(ChdirCommand); 32 | cmd.header.id = id; 33 | cmd.uid = p->user; 34 | cmd.gid = p->group; 35 | 36 | if(path[0] == '/') { 37 | strcpy(cmd.path, path); 38 | } else { 39 | strcpy(cmd.path, p->cwd); 40 | if(strlen(p->cwd) > 1) cmd.path[strlen(cmd.path)] = '/'; 41 | strcpy(cmd.path + strlen(cmd.path), path); 42 | } 43 | 44 | return requestServer(t, 0, &cmd); 45 | } 46 | 47 | /* getcwd(): returns the current working directory of the running process 48 | * params: t - running thread 49 | * params: buf - buffer to store the path in 50 | * params: len - length of the buffer 51 | * returns: pointer to buffer on success, negative error code on fail 52 | */ 53 | 54 | char *getcwd(Thread *t, char *buf, size_t len) { 55 | Process *p = getProcess(t->pid); 56 | if(!p) return (char *) -ESRCH; 57 | if(!len) return (char *) -EINVAL; 58 | if(len < (strlen(p->cwd) + 1)) return (char *) -ERANGE; 59 | 60 | return strcpy(buf, p->cwd); 61 | } -------------------------------------------------------------------------------- /src/platform/x86_64/include/platform/smp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // every platform must define some kind of structure that allows it to identify 17 | // different CPUs in a multiprocessing system 18 | // the contents of the structure are platform-dependent 19 | 20 | typedef struct PlatformCPU { 21 | uint8_t procID, apicID; 22 | bool bootCPU; // true for the BSP 23 | bool running; 24 | struct PlatformCPU *next; 25 | } PlatformCPU; 26 | 27 | // per-CPU kernel information structure 28 | // this will be stored using the kernel GS segment 29 | 30 | typedef struct { 31 | void *kernelStack; 32 | void *kernelSwitchStack; 33 | PlatformCPU *cpu; 34 | uint64_t uptime; 35 | 36 | // currently running process and thread 37 | Process *process; 38 | Thread *thread; 39 | TSS *tss; 40 | 41 | // IRQ command structure 42 | IRQCommand *irqcmd; 43 | 44 | int cpuIndex; 45 | } KernelCPUInfo; 46 | 47 | void smpCPUInfoSetup(); 48 | int smpBoot(); 49 | KernelCPUInfo *getKernelCPUInfo(); 50 | 51 | PlatformCPU *findCPUACPI(uint8_t); 52 | PlatformCPU *findCPUAPIC(uint8_t); 53 | 54 | /* entry point for non-boot CPUs */ 55 | 56 | extern uint8_t apEntry[]; 57 | extern uint32_t apEntryVars[]; 58 | 59 | #define AP_ENTRY_SIZE 4096 60 | #define AP_ENTRY_GDTR 1 // index into apEntryVars[] 61 | #define AP_ENTRY_IDTR 2 62 | #define AP_ENTRY_CR3 3 63 | #define AP_ENTRY_STACK_LOW 4 64 | #define AP_ENTRY_STACK_HIGH 5 65 | #define AP_ENTRY_NEXT_LOW 6 66 | #define AP_ENTRY_NEXT_HIGH 7 67 | 68 | #define AP_STACK_SIZE 32768 -------------------------------------------------------------------------------- /src/platform/x86_64/cpu/excepstub.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | ; Stubs for exception handlers 8 | 9 | section .text 10 | 11 | %include "cpu/stack.asm" 12 | 13 | extern exception 14 | 15 | ; handler_no_code name, number 16 | %macro handler_no_code 2 17 | global %1 18 | align 16 19 | %1: 20 | cli 21 | push qword 0 ; fake code 22 | pushaq 23 | mov rdi, %2 24 | xor rsi, rsi ; fake code 25 | mov rdx, rsp ; registers we just pushed 26 | cld 27 | call exception 28 | popaq 29 | add rsp, 8 ; pop fake error code 30 | iretq 31 | %endmacro 32 | 33 | ; handler_code name, number 34 | %macro handler_code 2 35 | global %1 36 | align 16 37 | %1: 38 | cli 39 | pushaq 40 | mov rdi, %2 41 | mov rsi, [rsp+120] ; error code 42 | mov rdx, rsp ; registers we just pushed 43 | cld 44 | call exception 45 | popaq 46 | add rsp, 8 ; "pop" error code 47 | iretq 48 | %endmacro 49 | 50 | handler_no_code divideException, 0x00 51 | handler_no_code debugException, 0x01 52 | handler_no_code nmiException, 0x02 53 | handler_no_code breakpointException, 0x03 54 | handler_no_code overflowException, 0x04 55 | handler_no_code boundaryException, 0x05 56 | handler_no_code opcodeException, 0x06 57 | handler_no_code deviceException, 0x07 58 | handler_code doubleException, 0x08 59 | handler_code tssException, 0x0A 60 | handler_code segmentException, 0x0B 61 | handler_code stackException, 0x0C 62 | handler_code gpException, 0x0D 63 | handler_code pageException, 0x0E 64 | handler_no_code mathException, 0x10 65 | handler_no_code alignmentException, 0x11 66 | handler_no_code machineCheckException, 0x12 67 | handler_no_code xmathException, 0x13 68 | handler_no_code virtualException, 0x14 69 | handler_code controlException, 0x15 70 | -------------------------------------------------------------------------------- /src/include/kernel/io.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | /* Abstractions for file systems and sockets */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #define MAX_IO_DESCRIPTORS 1024 // max files/sockets open per process 17 | 18 | #define IO_WAITING 1 // only used during setup 19 | #define IO_FILE 2 20 | #define IO_SOCKET 3 21 | #define IO_DIRECTORY 4 22 | 23 | /* I/O descriptor flags */ 24 | #define O_NONBLOCK 0x0001 25 | #define O_NDELAY O_NONBLOCK 26 | #define O_CLOEXEC 0x0002 27 | #define O_RDONLY 0x0004 28 | #define O_WRONLY 0x0008 29 | #define O_RDWR (O_RDONLY | O_WRONLY) 30 | #define O_APPEND 0x0010 31 | #define O_CREAT 0x0020 32 | #define O_DSYNC 0x0040 33 | #define O_EXCL 0x0080 34 | #define O_NOCTTY 0x0100 35 | #define O_RSYNC 0x0200 36 | #define O_SYNC 0x0400 37 | #define O_TRUNC 0x0800 38 | #define O_CLOFORK 0x1000 39 | 40 | /* Reserved bits in ioctl() opcodes */ 41 | #define IOCTL_IN_PARAM 0x0001 /* third param is a value */ 42 | #define IOCTL_OUT_PARAM 0x0002 /* third param is a pointer */ 43 | #define IOCTL_RESERVED 0x000F /* reserve the entire lowest 8 bits */ 44 | 45 | // TODO: decide whether to implement named pipes as files or an independent type 46 | 47 | typedef struct IODescriptor { 48 | bool valid; 49 | int type; 50 | uint16_t flags; 51 | void *data; // file or socket-specific data 52 | } IODescriptor; 53 | 54 | int openIO(void *, void **); 55 | void closeIO(void *, void *); 56 | int ioperm(struct Thread *, uintptr_t, uintptr_t, int); 57 | int ioctl(struct Thread *, uint64_t, int, unsigned long, ...); -------------------------------------------------------------------------------- /src/args.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | static char *argPosition(char *args, int index) { 14 | int count = 0; 15 | int i; 16 | for(i = 0; i < strlen(args); i++) { 17 | if(count == index) break; 18 | if(args[i] == ' ') count++; 19 | } 20 | 21 | if(count != index) return NULL; 22 | return args+i; 23 | } 24 | 25 | static size_t argLength(char *arg) { 26 | size_t len; 27 | for(len = 0; arg[len] && arg[len] != ' '; len++); 28 | 29 | return len; 30 | } 31 | 32 | static char *copyArg(char *dst, char *arg) { 33 | size_t len = argLength(arg); 34 | memcpy(dst, arg, len); 35 | dst[len] = 0; 36 | return dst; 37 | } 38 | 39 | /* parseBootArgs(): parses boot arguments into a series of C-like argmuents * 40 | * params: argv - pointer to store argv in 41 | * params: args - string containing arguments 42 | * returns: number of arguments "argc" 43 | */ 44 | 45 | int parseBootArgs(char ***argv, char *args) { 46 | // first count how many arguments 47 | char **v; 48 | int argc = 1; 49 | for(int i = 0; i < strlen(args); i++) { 50 | if(args[i] == ' ') argc++; 51 | } 52 | 53 | v = calloc(argc, sizeof(char *)); 54 | if(!v) { 55 | KERROR("failed to allocate memory for kernel boot arguments\n"); 56 | while(1); 57 | } 58 | 59 | // split the string by spaces 60 | for(int i = 0; i < argc; i++) { 61 | char *arg = argPosition(args, i); 62 | if(!arg) { 63 | KERROR("unable to retrieve boot argument %d\n", i); 64 | while(1); 65 | } 66 | 67 | v[i] = malloc(argLength(arg) + 1); 68 | if(!v[i]) { 69 | KERROR("failed to allocate memory for kernel boot arguments\n"); 70 | while(1); 71 | } 72 | 73 | copyArg(v[i], arg); 74 | } 75 | 76 | argv[0] = v; 77 | return argc; 78 | } 79 | -------------------------------------------------------------------------------- /src/platform/x86_64/string.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | section .text 8 | 9 | ; void *memcpy(void *dst, const void *src, size_t n) 10 | global memcpy 11 | align 16 12 | memcpy: 13 | mov rax, rdi ; return value 14 | 15 | cmp rdx, 8 16 | jl .low 17 | 18 | mov rcx, rdx 19 | shr rcx, 3 ; div 8 20 | rep movsq 21 | 22 | and rdx, 7 ; mod 8 23 | 24 | .low: 25 | mov rcx, rdx 26 | rep movsb 27 | 28 | ret 29 | 30 | ; void *memmove(void *dst, const void *src, size_t n) 31 | global memmove 32 | align 16 33 | memmove: 34 | mov rax, rdi ; return value 35 | 36 | mov r8, rdi 37 | sub r8, rsi ; dst-src 38 | pushfq 39 | jns .check_overlap 40 | 41 | not r8 42 | inc r8 43 | 44 | .check_overlap: 45 | cmp r8, rdx 46 | jge .memcpy 47 | 48 | popfq 49 | jns .backward ; if dst>src copy backwards 50 | 51 | .forward: 52 | mov rcx, rdx 53 | rep movsb 54 | 55 | ret 56 | 57 | .backward: 58 | std 59 | mov rcx, rdx 60 | dec rdx 61 | add rdi, rdx 62 | add rsi, rdx 63 | rep movsb 64 | cld 65 | 66 | ret 67 | 68 | .memcpy: 69 | popfq 70 | jmp memcpy 71 | 72 | ; void *memset(void *dst, int val, size_t n) 73 | global memset 74 | align 16 75 | memset: 76 | mov r8, rdi ; r8 = dst 77 | 78 | and rsi, 0xFF 79 | mov rax, rsi ; low 8 bits 80 | 81 | cmp rdx, 8 82 | jl .low 83 | 84 | shr rax, 8 85 | or rax, rsi ; 16 bits 86 | mov si, ax 87 | shr rax, 16 88 | mov ax, si ; 32 bits 89 | mov esi, eax 90 | shr rax, 32 91 | or rax, rsi ; full 64 bits 92 | 93 | mov rcx, rdx 94 | shr rcx, 3 ; div 8 95 | rep stosq 96 | 97 | and rdx, 7 ; mod 8 98 | 99 | .low: 100 | mov rcx, rdx 101 | rep stosb 102 | 103 | mov rax, r8 ; return value 104 | ret 105 | -------------------------------------------------------------------------------- /src/platform/x86_64/include/platform/exception.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #pragma once 12 | 13 | /* generic exception handler */ 14 | void exception(uint64_t, uint64_t, InterruptRegisters *); 15 | 16 | void installExceptions(); 17 | 18 | /* handlers for each exception, these stubs must be written in assembly so we 19 | * can save register states and perform an IRETQ when the exception is handled */ 20 | 21 | extern void divideException(); // 0x00 - no error code 22 | extern void debugException(); // 0x01 - error code in DR6 23 | extern void nmiException(); // 0x02 - no error code 24 | extern void breakpointException(); // 0x03 - no error code 25 | extern void overflowException(); // 0x04 - no error code 26 | extern void boundaryException(); // 0x05 - no error code 27 | extern void opcodeException(); // 0x06 - no error code 28 | extern void deviceException(); // 0x07 - no error code 29 | extern void doubleException(); // 0x08 - error code (zero) in stack 30 | extern void tssException(); // 0x0A - error code in stack 31 | extern void segmentException(); // 0x0B - error code in stack 32 | extern void stackException(); // 0x0C - error code in stack 33 | extern void gpException(); // 0x0D - error code in stack 34 | extern void pageException(); // 0x0E - error code in stack; page fault address in CR2 35 | extern void mathException(); // 0x10 - no error code 36 | extern void alignmentException(); // 0x11 - no error code 37 | extern void machineCheckException(); // 0x12 - error code in MSR 38 | extern void xmathException(); // 0x13 - no error code 39 | extern void virtualException(); // 0x14 - no error code 40 | extern void controlException(); // 0x15 - error code in stack 41 | 42 | // there are technically two more exception, but those will not be handled 43 | // because we will not implement AMD SVM 44 | -------------------------------------------------------------------------------- /src/memory/brk.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | /* Program Break Memory Manager */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /* sbrk(): changes the size of the program's data segment by moving the program break 19 | * params: t - calling thread 20 | * params: delta - number of bytes to increment or decrement 21 | * returns: pointer to previous program break on success, negative errno on fail 22 | */ 23 | 24 | void *sbrk(Thread *t, intptr_t delta) { 25 | intptr_t brk = t->highest; 26 | if(!delta) return (void *) brk; 27 | 28 | size_t pages; 29 | if(delta < 0) pages = ((-delta)+PAGE_SIZE-1) / PAGE_SIZE; 30 | else pages = (delta+PAGE_SIZE-1) / PAGE_SIZE; 31 | 32 | Process *p = getProcess(t->pid); 33 | if(!p) return (void *) -ESRCH; 34 | 35 | if(delta > 0) { 36 | // thread is trying to allocate memory 37 | // we will be optimistic here and allocate virtual memory only, making 38 | // the physical mm work only upon access 39 | uintptr_t ptr = vmmAllocate(brk, USER_LIMIT_ADDRESS, pages, VMM_USER | VMM_WRITE); 40 | if(!ptr) { 41 | return (void *) -ENOMEM; 42 | } else if(ptr != brk) { 43 | vmmFree(ptr, pages); 44 | return (void *) -ENOMEM; 45 | } 46 | 47 | memset((void *)ptr, 0, pages*PAGE_SIZE); 48 | 49 | t->pages += pages; 50 | p->pages += pages; 51 | t->highest += (pages * PAGE_SIZE); 52 | } else { 53 | // thread is trying to free memory 54 | // this is slightly tricky because we cant assume the caller page-aligned everything 55 | uintptr_t ptr = brk - (pages * PAGE_SIZE); 56 | if(delta % PAGE_SIZE) { 57 | pages--; 58 | ptr += PAGE_SIZE; 59 | } 60 | 61 | vmmFree(ptr, pages); 62 | t->pages -= pages; 63 | p->pages -= pages; 64 | t->highest -= (pages * PAGE_SIZE); 65 | } 66 | 67 | return (void *) brk; 68 | } -------------------------------------------------------------------------------- /src/logger.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | static lock_t lock = LOCK_INITIAL; 15 | static bool verbose = true; 16 | 17 | void loggerSetVerbose(bool v) { 18 | verbose = v; 19 | } 20 | 21 | int kprintf(int level, const char *src, const char *f, ...) { 22 | if(!verbose && (level != KPRINTF_LEVEL_ERROR) && (level != KPRINTF_LEVEL_PANIC)) 23 | return 0; 24 | 25 | acquireLockBlocking(&lock); 26 | 27 | // [minutes.timer ticks] 28 | int len = printf("\e[37m[%3d.%08d] ", platformUptime()/PLATFORM_TIMER_FREQUENCY/60, platformUptime()); 29 | len += printf("\e[96mkernel "); 30 | 31 | switch(level) { 32 | case KPRINTF_LEVEL_DEBUG: 33 | len += printf("\e[92m"); 34 | break; 35 | case KPRINTF_LEVEL_WARNING: 36 | len += printf("\e[93m"); 37 | break; 38 | case KPRINTF_LEVEL_ERROR: 39 | case KPRINTF_LEVEL_PANIC: 40 | default: 41 | len += printf("\e[91m"); 42 | } 43 | 44 | len += printf("%s: \e[37m", src); 45 | 46 | va_list args; 47 | va_start(args, f); 48 | len += vprintf(f, args); 49 | va_end(args); 50 | 51 | releaseLock(&lock); 52 | return len; 53 | } 54 | 55 | int ksprint(int level, const char *name, const char *msg) { 56 | if(!verbose && (level != KPRINTF_LEVEL_ERROR) && (level != KPRINTF_LEVEL_PANIC)) 57 | return 0; 58 | 59 | acquireLockBlocking(&lock); 60 | int len = printf("\e[37m[%3d.%08d] ", platformUptime()/PLATFORM_TIMER_FREQUENCY/60, platformUptime()); 61 | len += printf("\e[95mserver "); 62 | 63 | switch(level) { 64 | case KPRINTF_LEVEL_DEBUG: 65 | len += printf("\e[92m"); 66 | break; 67 | case KPRINTF_LEVEL_WARNING: 68 | len += printf("\e[93m"); 69 | break; 70 | case KPRINTF_LEVEL_ERROR: 71 | case KPRINTF_LEVEL_PANIC: 72 | default: 73 | len += printf("\e[91m"); 74 | } 75 | 76 | len += printf("%s: \e[37m%s", name, msg); 77 | releaseLock(&lock); 78 | return len; 79 | } -------------------------------------------------------------------------------- /src/platform/x86_64/apic/nmi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | /* Non-Maskable Interrupt Implementation */ 9 | 10 | /* Intel Software Developer's Manual Volume 3A Part 1 Chapter 11.5.1: 11 | * 12 | * The trigger mode of local APIC NMIs is always set to be edge-sensitive, and 13 | * that of local APIC ExtINTs is always set to level-sensitive. LINT1 is always 14 | * hardwired to edge-sensitive, and only LINT0 can be configured to use either 15 | * edge or level according to the information from the ACPI MADT. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | static LocalNMI *lnmis = NULL; 23 | static BusNMI *bnmis = NULL; 24 | static int nlnmi = 0, nbnmi = 0; 25 | 26 | /* lnmiRegister(): registers a local APIC NMI 27 | * params: lnmi - local APIC NMI structure 28 | * returns: count of local APIC NMIs 29 | */ 30 | 31 | int lnmiRegister(LocalNMI *lnmi) { 32 | if(!lnmis) lnmis = lnmi; 33 | else { 34 | LocalNMI *list = lnmis; 35 | while(list->next) list = list->next; 36 | list->next = lnmi; 37 | } 38 | 39 | return nlnmi++; 40 | } 41 | 42 | /* lnmiCount(): returns the number of local APIC NMIs 43 | * params: none 44 | * returns: number of local APIC NMIs 45 | */ 46 | 47 | int lnmiCount() { 48 | return nlnmi; 49 | } 50 | 51 | /* lnmiConfigure(): configures local APIC NMIs on a given core 52 | * params: none 53 | * returns: number of NMIs configured on this core 54 | */ 55 | 56 | int lnmiConfigure() { 57 | if(!nlnmi || !lnmis) return 0; 58 | int count = 0; 59 | 60 | PlatformCPU *cpu = getKernelCPUInfo()->cpu; 61 | uint8_t acpiID = cpu->procID; 62 | 63 | // find local APIC NMIs with a matching ACPI ID 64 | LocalNMI *list = lnmis; 65 | while(list) { 66 | if((list->procID == acpiID) || (list->procID == 0xFF)) { 67 | uint32_t config = LAPIC_LVT_NMI; 68 | if(list->low) config |= LAPIC_LVT_LOW; 69 | 70 | if(list->lint & 1) lapicWrite(LAPIC_LVT_LINT1, config); 71 | else lapicWrite(LAPIC_LVT_LINT0, config); 72 | count++; 73 | } 74 | 75 | list = list->next; 76 | } 77 | 78 | return count; 79 | } -------------------------------------------------------------------------------- /src/include/kernel/boot.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | typedef struct { 13 | uint8_t flags; 14 | uint8_t chsStart[3]; 15 | uint8_t id; 16 | uint8_t chsEnd[3]; 17 | uint32_t start; 18 | uint32_t size; 19 | } __attribute__((packed)) MBRPartition; 20 | 21 | typedef struct { 22 | uint32_t magic; // 0x5346584C 23 | uint32_t version; // 1 24 | 25 | uint8_t flags; 26 | 27 | /* BIOS-specific info */ 28 | uint8_t biosBootDisk; 29 | uint8_t biosBootPartitionIndex; 30 | MBRPartition biosBootPartition; 31 | 32 | /* TODO: UEFI info */ 33 | uint8_t uefiReserved[32]; 34 | 35 | /* generic info */ 36 | uint64_t kernelHighestAddress; 37 | uint64_t kernelTotalSize; 38 | 39 | uint64_t acpiRSDP; 40 | uint64_t highestPhysicalAddress; 41 | uint64_t memoryMap; 42 | uint8_t memoryMapSize; 43 | 44 | uint16_t width; 45 | uint16_t height; 46 | uint8_t bpp; 47 | uint64_t framebuffer; 48 | uint32_t pitch; 49 | uint8_t redPosition; 50 | uint8_t redMask; 51 | uint8_t greenPosition; 52 | uint8_t greenMask; 53 | uint8_t bluePosition; 54 | uint8_t blueMask; 55 | 56 | uint64_t ramdisk; // pointer 57 | uint64_t ramdiskSize; 58 | 59 | uint8_t moduleCount; 60 | uint64_t modules[16]; // array of pointers 61 | uint64_t moduleSizes[16]; 62 | 63 | uint64_t lowestFreeMemory; // pointer to the end of highest ramdisk/module, aka lowest free memory 64 | 65 | char arguments[256]; // command-line arguments passed to the kernel 66 | } __attribute__((packed)) KernelBootInfo; 67 | 68 | #define BOOT_FLAGS_UEFI 0x01 69 | #define BOOT_FLAGS_GPT 0x02 70 | 71 | typedef struct { 72 | uint64_t base; 73 | uint64_t len; 74 | uint32_t type; 75 | uint32_t acpiAttributes; 76 | } __attribute__((packed)) MemoryMap; 77 | 78 | #define MEMORY_TYPE_USABLE 1 79 | #define MEMORY_TYPE_RESERVED 2 80 | #define MEMORY_TYPE_ACPI_RECLAIMABLE 3 81 | #define MEMORY_TYPE_ACPI_NVS 4 82 | #define MEMORY_TYPE_BAD 5 83 | 84 | #define MEMORY_ATTRIBUTES_VALID 0x01 85 | #define MEMORY_ATTRIBUTES_NV 0x02 86 | 87 | int parseBootArgs(char ***, char *); 88 | -------------------------------------------------------------------------------- /src/include/sys/stat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | struct stat { 13 | dev_t st_dev; 14 | ino_t st_ino; 15 | mode_t st_mode; 16 | nlink_t st_nlink; 17 | uid_t st_uid; 18 | gid_t st_gid; 19 | dev_t st_rdev; 20 | off_t st_size; 21 | time_t st_atime, st_mtime, st_ctime; 22 | blksize_t st_blksize; 23 | blkcnt_t st_blocks; 24 | }; 25 | 26 | #define S_IFBLK 0x00000001 // block special 27 | #define S_IFCHR 0x00000002 // character special 28 | #define S_IFIFO 0x00000003 // named pipe 29 | #define S_IFREG 0x00000004 // regular file 30 | #define S_IFDIR 0x00000005 // directory 31 | #define S_IFLNK 0x00000006 // symbolic link 32 | #define S_IFSOCK 0x00000007 // socket 33 | #define S_IFMT 0x00000007 // mask for file type 34 | 35 | #define S_IRUSR 0x00000008 // owner read perms 36 | #define S_IWUSR 0x00000010 // owner write perms 37 | #define S_IXUSR 0x00000020 // owner execute perms 38 | #define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) 39 | 40 | #define S_IRGRP 0x00000040 // group read perms 41 | #define S_IWGRP 0x00000080 // group write perms 42 | #define S_IXGRP 0x00000100 // group execute perms 43 | #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) 44 | 45 | #define S_IROTH 0x00000200 // others read perms 46 | #define S_IWOTH 0x00000400 // others write perms 47 | #define S_IXOTH 0x00000800 // others execute perms 48 | #define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) 49 | 50 | #define S_ISUID 0x00001000 // set UID when executed 51 | #define S_ISGID 0x00002000 // set GID when execute 52 | #define S_ISVTX 0x00004000 // prevent directory deletion 53 | 54 | #define S_ISBLK(m) ((m & S_IFMT) == S_IFBLK) 55 | #define S_ISCHR(m) ((m & S_IFMT) == S_IFCHR) 56 | #define S_ISDIR(m) ((m & S_IFMT) == S_IFDIR) 57 | #define S_ISFIFO(m) ((m & S_IFMT) == S_IFIFO) 58 | #define S_ISREG(m) ((m & S_IFMT) == S_IFREG) 59 | #define S_ISLNK(m) ((m & S_IFMT) == S_IFLNK) 60 | #define S_ISSOCK(m) ((m & S_IFMT) == S_IFSOCK) 61 | 62 | int lstat(Thread *, uint64_t, const char *, struct stat *); 63 | int fstat(Thread *t, uint64_t, int, struct stat *); 64 | -------------------------------------------------------------------------------- /src/sched/sleep.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | static Thread **sleepingThreads = NULL; // list of sleeping threads 15 | static int sleepingCount = 0; 16 | 17 | /* msleep(): pauses thread execution for a duration of time in milliseconds 18 | * params: t - thread to be paused 19 | * params: msec - minimum number of milliseconds to pause 20 | * returns: zero 21 | */ 22 | 23 | unsigned long msleep(Thread *t, unsigned long msec) { 24 | msec *= (PLATFORM_TIMER_FREQUENCY / 1000); // convert ms to timer ticks 25 | if(!msec) return 0; 26 | 27 | schedLock(); 28 | t->status = THREAD_SLEEP; 29 | t->time = msec; 30 | 31 | sleepingCount++; 32 | Thread **list = realloc(sleepingThreads, sizeof(Thread *) * sleepingCount); 33 | if(!list) { 34 | KERROR("failed to allocate memory to put thread %d to sleep\n", t->tid); 35 | t->status = THREAD_QUEUED; // failed sleep (?) 36 | t->time = schedTimeslice(t, t->priority); 37 | sleepingCount--; 38 | schedRelease(); 39 | return 0; 40 | } 41 | 42 | sleepingThreads = list; 43 | sleepingThreads[sleepingCount-1] = t; 44 | schedRelease(); 45 | return 0; 46 | } 47 | 48 | /* schedSleepTimer(): ticks sleeping threads and wakes them if the duration has elapsed 49 | * params: none 50 | * returns: nothing 51 | */ 52 | 53 | void schedSleepTimer() { 54 | // we don't need to lock the scheduler because it is already locked by 55 | // schedTimer() which calls this timer 56 | if(!sleepingCount || !sleepingThreads) return; 57 | 58 | for(int i = 0; i < sleepingCount; i++) { 59 | if(sleepingThreads[i] && sleepingThreads[i]->status == THREAD_SLEEP) { 60 | sleepingThreads[i]->time--; 61 | 62 | if(!sleepingThreads[i]->time) { 63 | // duration has elapsed, wake this thread 64 | sleepingThreads[i]->status = THREAD_QUEUED; 65 | sleepingThreads[i]->time = schedTimeslice(sleepingThreads[i], sleepingThreads[i]->priority); 66 | 67 | sleepingThreads[i] = NULL; 68 | 69 | if(i != (sleepingCount-1)) { 70 | memmove(&sleepingThreads[i], &sleepingThreads[i+1], (sleepingCount-i) * sizeof(Thread *)); 71 | } 72 | 73 | i--; 74 | sleepingCount--; 75 | } 76 | } 77 | } 78 | 79 | if(!sleepingCount) { 80 | free(sleepingThreads); 81 | sleepingThreads = NULL; 82 | } 83 | } -------------------------------------------------------------------------------- /src/modules/modules.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static uint8_t *modules[MAX_MODULES]; 16 | static uint64_t moduleSizes[MAX_MODULES]; 17 | 18 | /* modulesInit(): initializes the boot modules 19 | * params: boot - boot information structure 20 | * returns: nothing 21 | */ 22 | 23 | void modulesInit(KernelBootInfo *boot) { 24 | memset(modules, 0, MAX_MODULES * sizeof(uint8_t)); 25 | memset(moduleSizes, 0, MAX_MODULES * sizeof(uint64_t)); 26 | 27 | if(boot->moduleCount) { 28 | KDEBUG("enumerating boot modules...\n"); 29 | 30 | for(int i = 0; i < boot->moduleCount; i++) { 31 | modules[i] = (uint8_t *)vmmMMIO(boot->modules[i], true); 32 | moduleSizes[i] = boot->moduleSizes[i]; 33 | 34 | KDEBUG(" %d of %d: %s loaded at 0x%08X\n", i+1, boot->moduleCount, modules[i], modules[i]); 35 | } 36 | } 37 | } 38 | 39 | /* moduleCount(): returns the module count 40 | * params: none 41 | * returns: number of boot modules loaded 42 | */ 43 | 44 | int moduleCount() { 45 | int n = 0; 46 | for(int i = 0; i < MAX_MODULES; i++) { 47 | if(moduleSizes[i] && modules[i]) n++; 48 | } 49 | 50 | return n; 51 | } 52 | 53 | /* moduleFind(): finds a module by name 54 | * params: name - name of the module 55 | * returns: index to the model, -1 on fail 56 | */ 57 | 58 | int moduleFind(const char *name) { 59 | int c = moduleCount(); 60 | 61 | for(int i = 0; i < c; i++) { 62 | if(modules[i] && !strcmp((const char *)modules[i], name)) { 63 | return i; 64 | } 65 | } 66 | 67 | return -1; 68 | } 69 | 70 | /* moduleQuery(): queries the size and existence of a module 71 | * params: name - name of the module 72 | * returns: size of the module in bytes, zero if it doesn't exist 73 | */ 74 | 75 | size_t moduleQuery(const char *name) { 76 | int i = moduleFind(name); 77 | if(i < 0) return 0; 78 | 79 | return (size_t) moduleSizes[i]; 80 | } 81 | 82 | /* moduleLoad(): loads a module into memory 83 | * params: buffer - buffer to load the module, at least size moduleQuery() 84 | * params: name - name of the module 85 | * returns: pointer to the buffer, NULL on failure 86 | */ 87 | 88 | void *moduleLoad(void *buffer, const char *name) { 89 | int i = moduleFind(name); 90 | if(i < 0) return NULL; 91 | 92 | uint8_t *ptr = modules[i]; 93 | ptr += strlen((const char *)ptr) + 1; 94 | 95 | if(!moduleSizes[i]) return NULL; 96 | return memcpy(buffer, ptr, moduleSizes[i]); 97 | } 98 | -------------------------------------------------------------------------------- /src/include/kernel/elf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | /* this is identical to the structures used in the boot loader */ 11 | 12 | #include 13 | 14 | #define ELF_VERSION 1 15 | 16 | /* general header for ELF files */ 17 | typedef struct { 18 | // this structure follows the 64-bit ELF format because there's no need 19 | // to implement 32-bit ELF 20 | uint8_t magic[4]; // 0x7F, 'ELF' 21 | uint8_t isaWidth; // 2 = 64-bit 22 | uint8_t endianness; // 1 = little endian 23 | uint8_t headerVersion; 24 | uint8_t abi; // 0 = System V 25 | uint64_t reserved; 26 | 27 | uint16_t type; 28 | uint16_t isa; // CPU architecture 29 | uint32_t version; 30 | uint64_t entryPoint; // absolute address in virtual memory, not the ELF file structure 31 | uint64_t headerTable; 32 | uint64_t sectionTable; 33 | uint32_t flags; // architecture-dependent; undefined for x86/x86_64 34 | 35 | uint16_t headerSize; 36 | uint16_t headerEntrySize; 37 | uint16_t headerEntryCount; 38 | uint16_t sectionEntrySize; 39 | uint16_t sectionEntryCount; 40 | uint16_t sectionHeaderStrings; 41 | } __attribute__((packed)) ELFFileHeader; 42 | 43 | #define ELF_ISA_WIDTH_64 2 44 | #define ELF_LITTLE_ENDIAN 1 45 | #define ELF_SYSV_ABI 0 46 | 47 | #define ELF_TYPE_RELOC 1 48 | #define ELF_TYPE_EXEC 2 49 | #define ELF_TYPE_SHARED 3 50 | #define ELF_TYPE_CORE 4 51 | 52 | #define ELF_ARCHITECTURE_X86_64 0x3E 53 | #define ELF_ARCHITECTURE_RISCV 0xF3 54 | #define ELF_ARCHITECTURE_ARM64 0xB7 55 | 56 | /* these will be set by the compiler */ 57 | #ifdef PLATFORM_X86_64 58 | #define ELF_ARCHITECTURE ELF_ARCHITECTURE_X86_64 59 | #endif 60 | 61 | #ifdef PLATFORM_RISCV 62 | #define ELF_ARCHITECTURE ELF_ARCHITECTURE_RISCV 63 | #endif 64 | 65 | #ifdef PLATFORM_ARM64 66 | #define ELF_ARCHITECTURE ELF_ARCHITECTURE_ARM64 67 | #endif 68 | 69 | /* program header, pointed to by headerTable */ 70 | typedef struct { 71 | uint32_t segmentType; 72 | uint32_t flags; 73 | uint64_t fileOffset; 74 | uint64_t virtualAddress; 75 | uint64_t physicalAddress; 76 | uint64_t fileSize; 77 | uint64_t memorySize; 78 | uint64_t alignment; 79 | } __attribute__((packed)) ELFProgramHeader; 80 | 81 | #define ELF_SEGMENT_TYPE_NULL 0 82 | #define ELF_SEGMENT_TYPE_LOAD 1 83 | #define ELF_SEGMENT_TYPE_DYNAMIC 2 84 | #define ELF_SEGMENT_TYPE_INTERPRET 3 85 | #define ELF_SEGMENT_TYPE_NOTES 4 86 | 87 | #define ELF_SEGMENT_FLAGS_EXEC 0x01 88 | #define ELF_SEGMENT_FLAGS_WRITE 0x02 89 | #define ELF_SEGMENT_FLAGS_READ 0x04 90 | 91 | uint64_t loadELF(const void *, uint64_t *); 92 | -------------------------------------------------------------------------------- /src/platform/x86_64/cpu/tss.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | /* tssSetup(): sets up a task state segment in the GDT for each CPU */ 16 | 17 | void tssSetup() { 18 | GDTR gdtr; 19 | storeGDT(&gdtr); 20 | 21 | GDTEntry *gdt = malloc(gdtr.limit+1); 22 | if(!gdt) { 23 | KERROR("failed to allocate memory for GDT\n"); 24 | while(1); 25 | } 26 | 27 | memcpy(gdt, (void *)gdtr.base, gdtr.limit+1); 28 | gdtr.base = (uint64_t)gdt; 29 | 30 | // now create a TSS 31 | TSS *tss = calloc(1, sizeof(TSS)); 32 | if(!tss) { 33 | KERROR("failed to allocate memory for TSS\n"); 34 | while(1); 35 | } 36 | 37 | void *stack = calloc(1, KENREL_STACK_SIZE); 38 | if(!stack) { 39 | KERROR("failed to allocate memory for kernel stack\n"); 40 | while(1); 41 | } 42 | 43 | tss->rsp0 = (uint64_t)stack + KENREL_STACK_SIZE; 44 | tss->rsp1 = (uint64_t)stack + KENREL_STACK_SIZE; 45 | tss->rsp2 = (uint64_t)stack + KENREL_STACK_SIZE; 46 | 47 | for(int i = 0; i < 7; i++) { 48 | tss->ist[i] = (uint64_t)stack + KENREL_STACK_SIZE-16; 49 | } 50 | 51 | // I/O port privileges 52 | tss->iomap = 0x68; // offset from TSS start address 53 | memset(tss->ioports, 0xFF, 8192); // deny I/O port access by default 54 | tss->ones = 0xFF; 55 | 56 | // store the TSS pointer in the GDT so the CPU knows where to find it 57 | gdt[GDT_TSS_LOW].baseLo = (uintptr_t)tss; 58 | gdt[GDT_TSS_LOW].baseMi = (uintptr_t)tss >> 16; 59 | gdt[GDT_TSS_LOW].baseHi = (uintptr_t)tss >> 24; 60 | gdt[GDT_TSS_LOW].limit = sizeof(TSS) - 1; 61 | gdt[GDT_TSS_LOW].access = GDT_ACCESS_TSS | GDT_ACCESS_PRESENT; 62 | 63 | uint64_t *high = (uint64_t *)&gdt[GDT_TSS_HIGH]; 64 | *high = (uintptr_t)tss >> 32; 65 | 66 | loadGDT(&gdtr); 67 | resetSegments(GDT_KERNEL_CODE, PRIVILEGE_KERNEL); 68 | loadTSS(GDT_TSS_LOW << 3); 69 | 70 | // store the stack pointer in the per-CPU info structure as well 71 | KernelCPUInfo *info = getKernelCPUInfo(); 72 | stack = calloc(1, KENREL_STACK_SIZE); 73 | if(!stack) { 74 | KERROR("failed to allocate memory for kernel stack\n"); 75 | while(1); 76 | } 77 | 78 | info->kernelStack = (void *) ((uintptr_t) stack+KENREL_STACK_SIZE-16); 79 | info->tss = tss; 80 | 81 | // allocate a switching stack as well 82 | stack = calloc(1, KENREL_STACK_SIZE/2); 83 | if(!stack) { 84 | KERROR("failed to allocate memory for kernel switch stack\n"); 85 | while(1); 86 | } 87 | 88 | info->kernelSwitchStack = (void *)((uintptr_t) stack + (KENREL_STACK_SIZE/2) - 16); 89 | } -------------------------------------------------------------------------------- /src/sched/waitpid.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | /* processStatus(): helper function to return the status of a process 12 | * params: p - process to check 13 | * params: status - buffer to store the status 14 | * returns: PID on success, zero if no status is available 15 | */ 16 | 17 | static pid_t processStatus(Process *p, int *status) { 18 | if(!p) return -ESRCH; 19 | if(!p->threadCount || !p->threads) return 0; 20 | 21 | // iterate over the threads of the process and return the first one with a 22 | // valid exit status code 23 | for(int i = 0; i < p->threadCount; i++) { 24 | Thread *t = p->threads[i]; 25 | if(!t) continue; 26 | 27 | if((!t->clean) && (t->status == THREAD_ZOMBIE)) { 28 | t->clean = true; 29 | *status = t->exitStatus; 30 | pid_t pid = t->tid; 31 | 32 | // free the thread structure 33 | threadCleanup(t); 34 | return pid; 35 | } 36 | } 37 | 38 | return 0; // no data available 39 | } 40 | 41 | /* waitpid(): polls the status of a process (group) 42 | * params: t - calling thread 43 | * params: pid - pid of the process (group) to poll 44 | * pid == -1 refers to all children processes of the caller 45 | * pid == 0 refers to any process in the same process group of the caller 46 | * pid < -1 refers to any process in the process group referred to by abs(pid) 47 | * pid > 0 refers to the process whose PID is exactly pid 48 | * params: status - buffer to store the status at 49 | * params: options - modifying flags 50 | * returns: zero if no status is available 51 | * returns: PID of the process if status is available 52 | * returns: negative error code on error 53 | */ 54 | 55 | pid_t waitpid(Thread *t, pid_t pid, int *status, int options) { 56 | Process *p = getProcess(t->pid); 57 | if(!p) return -ESRCH; 58 | 59 | schedLock(); 60 | 61 | if(pid > 0) { 62 | // case for one specific process 63 | pid = processStatus(getProcess(pid), status); 64 | schedRelease(); 65 | return pid; 66 | } 67 | 68 | if(pid < -1) { 69 | pid *= -1; 70 | p = getProcess(pid); 71 | if(!p) { 72 | schedRelease(); 73 | return -ESRCH; 74 | } 75 | } 76 | 77 | // iterate over children processes and threads 78 | if(!p->childrenCount || !p->children) { 79 | schedRelease(); 80 | return -ECHILD; 81 | } 82 | 83 | for(int i = 0; i < p->childrenCount; i++) { 84 | Process *child = p->children[i]; 85 | if(!child) continue; 86 | 87 | pid = processStatus(child, status); 88 | if(pid) { 89 | // return pid here which will be valid for both error and success 90 | schedRelease(); 91 | return pid; 92 | } 93 | } 94 | 95 | // no data is available 96 | schedRelease(); 97 | return 0; 98 | } -------------------------------------------------------------------------------- /src/include/kernel/socket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | /* system-wide limits */ 18 | #define MAX_SOCKETS (1 << 18) // 262k 19 | #define SOCKET_DEFAULT_BACKLOG 1024 // default socket backlog size 20 | 21 | #define SOCKET_IO_BACKLOG 64 // default I/O backlog size 22 | 23 | /* socket family/domain - only Unix sockets will be implemented in the kernel */ 24 | #define AF_UNIX 1 25 | #define AF_LOCAL AF_UNIX 26 | 27 | /* socket type - these will be ignored for local Unix sockets */ 28 | /* the kernel will ensure packets are sent and received in the same order */ 29 | #define SOCK_STREAM 1 // stream-oriented 30 | #define SOCK_DGRAM 2 // datagram-oriented 31 | #define SOCK_SEQPACKET 3 // connection-oriented 32 | 33 | /* additional socket flags */ 34 | #define SOCK_NONBLOCK 0x100 35 | #define SOCK_CLOEXEC 0x200 36 | 37 | /* flags for socket I/O */ 38 | #define MSG_PEEK 0x01 39 | #define MSG_OOB 0x02 40 | #define MSG_WAITALL 0x04 41 | 42 | typedef uint16_t sa_family_t; 43 | typedef size_t socklen_t; 44 | 45 | /* generic socket */ 46 | struct sockaddr { 47 | sa_family_t sa_family; 48 | char sa_data[512]; 49 | }; 50 | 51 | /* Unix domain socket */ 52 | struct sockaddr_un { 53 | sa_family_t sun_family; // AF_UNIX 54 | char sun_path[512]; // filename 55 | }; 56 | 57 | /* socket-specific I/O descriptor (see io.h) */ 58 | typedef struct SocketDescriptor { 59 | Process *process; 60 | struct sockaddr address; 61 | socklen_t addressLength; 62 | lock_t lock; 63 | bool listener; 64 | int globalIndex; 65 | int type, protocol, backlogMax, backlogCount; 66 | int inboundMax, outboundMax; // buffer sizes 67 | int inboundCount, outboundCount; 68 | void **inbound, **outbound; 69 | size_t *inboundLen, *outboundLen; 70 | struct SocketDescriptor **backlog; // for incoming connections via connect() 71 | struct SocketDescriptor *peer; // for peer-to-peer connections 72 | int refCount; 73 | } SocketDescriptor; 74 | 75 | void socketInit(); 76 | SocketDescriptor *getLocalSocket(const struct sockaddr *, socklen_t); 77 | void socketLock(); 78 | void socketRelease(); 79 | int socketRegister(SocketDescriptor *); 80 | SocketDescriptor *socketUnregister(int); 81 | 82 | /* socket system calls */ 83 | int socket(Thread *, int, int, int); 84 | int connect(Thread *, int, const struct sockaddr *, socklen_t); 85 | int bind(Thread *, int, const struct sockaddr *, socklen_t); 86 | int listen(Thread *, int, int); 87 | int accept(Thread *, int, struct sockaddr *, socklen_t *); 88 | ssize_t recv(Thread *, int, void *, size_t, int); 89 | ssize_t send(Thread *, int, const void *, size_t, int); 90 | int closeSocket(Thread *, int); 91 | -------------------------------------------------------------------------------- /src/platform/x86_64/apic/smpstub.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | ; This is the entry point that runs when other CPUs are started up 6 | ; All x86 CPUs start in 16-bit mode, so this code has to set up and enable 7 | ; 64-bit long mode 8 | 9 | [bits 16] 10 | 11 | section .data 12 | global apEntry 13 | align 16 14 | apEntry: 15 | ; this code will actually start running at 0x1000; the kernel will copy it 16 | ; into low memory for accessibility from 16-bit mode 17 | cli 18 | cld 19 | xor ax, ax 20 | mov ds, ax 21 | mov es, ax 22 | 23 | mov esi, 0x1FE4 ; GDT 24 | mov esi, [esi] 25 | lgdt [esi] 26 | 27 | mov esi, 0x1FE8 ; IDT 28 | mov esi, [esi] 29 | lidt [esi] 30 | 31 | mov eax, [0x1FEC] ; pml4 32 | mov cr3, eax 33 | 34 | ; configure the CPU for long mode - this is the same sequence we followed 35 | ; in the boot loader 36 | mov eax, 0x620 ; enable SSE and PAE 37 | mov cr4, eax 38 | 39 | mov ecx, 0xC0000080 40 | rdmsr 41 | or eax, 0x100 ; enable 64-bit mode 42 | wrmsr 43 | 44 | mov eax, cr0 45 | and eax, 0x9FFFFFFF ; enable global caching 46 | or eax, 0x80000001 ; enable paging and protected mode 47 | mov cr0, eax 48 | 49 | jmp 0x08:0x1100 50 | 51 | times 0x100 - ($-$$) nop 52 | [bits 64] 53 | 54 | ; now we're in 64-bit long mode 55 | mov rax, 0x10 56 | mov ss, rax 57 | mov ds, rax 58 | mov es, rax 59 | mov fs, rax 60 | mov gs, rax 61 | 62 | ; enable NX - this is necessary here because the pages mapping the stack 63 | ; are marked as no-execute, so we cannot wait until the C code to do this 64 | ; else we get unhandled page faults :) 65 | mov ecx, 0xC0000080 66 | rdmsr 67 | or eax, 0x800 68 | wrmsr 69 | 70 | mov rax, 0x1FF0 ; top of stack pointer 71 | mov rsp, [rax] 72 | mov rbp, rsp 73 | 74 | mov rax, 2 75 | push rax 76 | popfq 77 | 78 | ; indicate to the kernel that the CPU started 79 | mov eax, 1 80 | mov [0x1FE0], eax 81 | 82 | mov rax, 0x1FF8 ; entry point 83 | mov rax, [rax] 84 | call rax 85 | 86 | ; this should never return 87 | 88 | times 0x200 - ($-$$) nop 89 | 90 | .hang: 91 | cli 92 | hlt 93 | jmp 0x1200 94 | 95 | times 0xFE0 - ($-$$) nop 96 | 97 | global apEntryVars 98 | apEntryVars: 99 | 100 | ; the kernel will also fill in these variables 101 | life: dd 0 ; 0x1FE0: sign of life 102 | gdtrPointer: dd 0 ; 0x1FE4: pointer to the GDTR 103 | idtrPointer: dd 0 ; 0x1FE8: pointer to the IDTR 104 | kernelCR3: dd 0 ; 0x1FEC: pointer to kernel paging 105 | stackTopLow: dd 0 ; 0x1FF0: newly allocated stack for this CPU 106 | stackTopHigh: dd 0 ; 0x1FF4: high bits 107 | nextLow: dd 0 ; 0x1FF8: true entry point to call next 108 | nextHigh: dd 0 ; 0x1FFC: high bits 109 | 110 | global apEntryEnd 111 | apEntryEnd: 112 | -------------------------------------------------------------------------------- /src/include/kernel/signal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | typedef volatile uint32_t sig_atomic_t; 13 | typedef uint64_t sigset_t; 14 | 15 | #include 16 | 17 | /* Signal Handler Macros */ 18 | #define SIG_DFL (void (*)(int))0 19 | #define SIG_ERR (void (*)(int))1 20 | #define SIG_HOLD (void (*)(int))2 21 | #define SIG_IGN (void (*)(int))3 22 | 23 | #define SIG_T 1 /* terminate */ 24 | #define SIG_A 2 /* abort */ 25 | #define SIG_C 3 /* continue */ 26 | #define SIG_S 4 /* stop */ 27 | #define SIG_I 5 /* ignore */ 28 | 29 | /* ISO C Signals */ 30 | #define SIGABRT 1 31 | #define SIGFPE 2 32 | #define SIGILL 3 33 | #define SIGINT 4 34 | #define SIGSEGV 5 35 | #define SIGTERM 6 36 | 37 | /* POSIX Extension Signals */ 38 | #define SIGALRM 7 39 | #define SIGBUS 8 40 | #define SIGCHLD 9 41 | #define SIGCONT 10 42 | #define SIGHUP 11 43 | #define SIGKILL 12 44 | #define SIGPIPE 13 45 | #define SIGQUIT 14 46 | #define SIGSTOP 15 47 | #define SIGTSTP 16 48 | #define SIGTTIN 17 49 | #define SIGTTOU 18 50 | #define SIGUSR1 19 51 | #define SIGUSR2 20 52 | #define SIGPOLL 21 53 | #define SIGSYS 22 54 | #define SIGTRAP 23 55 | #define SIGURG 24 56 | #define SIGVTALRM 25 57 | #define SIGXCPU 26 58 | #define SIGXFSZ 27 59 | 60 | #define MAX_SIGNAL 27 61 | 62 | /* Signal Flags */ 63 | #define SA_NOCLDSTOP 0x0001 64 | #define SA_ONSTACK 0x0002 65 | #define SA_RESETHAND 0x0004 66 | #define SA_RESTART 0x0008 67 | #define SA_SIGINFO 0x0010 68 | #define SA_NOCLDWAIT 0x0020 69 | #define SA_NODEFER 0x0040 70 | 71 | #define SIG_BLOCK 0x0001 72 | #define SIG_UNBLOCK 0x0002 73 | #define SIG_SETMASK 0x0003 74 | 75 | #define SS_ONSTACK 0x0001 76 | #define SS_DISABLE 0x0002 77 | 78 | #define MINSIGSTKSZ 4096 79 | #define SIGSTKSZ 16384 80 | 81 | typedef struct { 82 | int si_signo, si_code, si_errno, si_status; 83 | pid_t si_pid; 84 | uid_t si_uid; 85 | void *si_addr; 86 | long si_band; 87 | } siginfo_t; 88 | 89 | struct sigaction { 90 | union { 91 | void (*sa_handler)(int); 92 | void (*sa_sigaction)(int, siginfo_t *, void *); 93 | }; 94 | 95 | sigset_t sa_mask; 96 | int sa_flags; 97 | }; 98 | 99 | int sigemptyset(sigset_t *); 100 | int sigfillset(sigset_t *); 101 | int sigaddset(sigset_t *, int); 102 | int sigdelset(sigset_t *, int); 103 | int sigismember(sigset_t *, int); 104 | 105 | void *signalDefaults(); 106 | int signalDefaultHandler(int); 107 | void *signalClone(const void *); 108 | void signalHandle(Thread *); 109 | 110 | int kill(Thread *, pid_t, int); 111 | int sigaction(Thread *, int, const struct sigaction *, struct sigaction *); 112 | void sigreturn(Thread *); 113 | int sigprocmask(Thread *, int, const sigset_t *, sigset_t *); 114 | -------------------------------------------------------------------------------- /src/memory/mmio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | /* Memory-Mapped I/O for Device Drivers */ 9 | 10 | #define MMIO_R 0x01 // read perms 11 | #define MMIO_W 0x02 // write perms 12 | #define MMIO_X 0x04 // execute perms 13 | #define MMIO_CD 0x08 // cache disable 14 | #define MMIO_ENABLE 0x80 // create mapping; clear to unmap 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | /* mmio(): creates or deletes a memory mapping 24 | * params: t - calling thread 25 | * params: addr - physical address for creation, virtual address for deletion 26 | * params: count - number of bytes to be mapped/unmapped 27 | * params: flags - memory flags 28 | * returns: creation: virtual address on success, zero on fail 29 | * returns: deletion: zero on success, virtual address on fail 30 | */ 31 | 32 | uintptr_t mmio(Thread *t, uintptr_t addr, off_t count, int flags) { 33 | Process *p = getProcess(t->pid); 34 | if(!p) return 0; 35 | 36 | // only root can do this 37 | if(p->user) return 0; 38 | 39 | off_t offset = addr & (PAGE_SIZE-1); 40 | size_t pageCount = (count + PAGE_SIZE - 1) / PAGE_SIZE; 41 | if(addr & ~(PAGE_SIZE-1)) pageCount++; 42 | 43 | if(flags & MMIO_ENABLE) { 44 | // creating a memory mapping 45 | int pageFlags = PLATFORM_PAGE_PRESENT | PLATFORM_PAGE_USER; 46 | if(flags & MMIO_W) pageFlags |= PLATFORM_PAGE_WRITE; 47 | if(flags & MMIO_X) pageFlags |= PLATFORM_PAGE_EXEC; 48 | if(flags & MMIO_CD) pageFlags |= PLATFORM_PAGE_NO_CACHE; 49 | 50 | uintptr_t virt = vmmAllocate(USER_MMIO_BASE, USER_LIMIT_ADDRESS, pageCount, VMM_USER); 51 | if(!virt) return 0; 52 | 53 | for(int i = 0; i < pageCount; i++) 54 | platformMapPage(virt + (i*PAGE_SIZE), addr + (i*PAGE_SIZE), pageFlags); 55 | 56 | //KDEBUG("mapped %d pages at physical addr 0x%X for tid %d\n", pageCount, addr, t->tid); 57 | return virt | offset; 58 | } else { 59 | // deleting a memory mapping 60 | if(addr < USER_MMIO_BASE) return addr; 61 | 62 | for(int i = 0; i < pageCount; i++) 63 | platformMapPage(addr + (i * PAGE_SIZE), 0, 0); 64 | 65 | //KDEBUG("unmapped %d pages at virtual address 0x%X for tid %d\n", pageCount, addr, t->tid); 66 | return 0; 67 | } 68 | } 69 | 70 | /* vtop(): returns the physical address associated with a virtual address 71 | * params: t - calling thread 72 | * params: virt - virtual address 73 | * returns: physical address on success, zero on error 74 | */ 75 | 76 | uintptr_t vtop(Thread *t, uintptr_t virt) { 77 | Process *p = getProcess(t->pid); 78 | if(!p) return 0; 79 | 80 | // only root can do this 81 | if(p->user) return 0; 82 | 83 | off_t offset = virt & (PAGE_SIZE-1); 84 | uintptr_t phys = 0; 85 | int flags = vmmPageStatus(virt, &phys); 86 | if(flags & PLATFORM_PAGE_ERROR) return 0; 87 | 88 | // disallow using this function to gain access to kernel physical memory 89 | if(!(flags & PLATFORM_PAGE_USER)) return 0; 90 | 91 | return phys | offset; 92 | } -------------------------------------------------------------------------------- /src/irq.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | static lock_t lock = LOCK_INITIAL; 20 | static IRQ *irqs = NULL; 21 | 22 | /* installIRQ(): installs an IRQ handler 23 | * params: t - calling thread 24 | * params: pin - IRQ number, platform-dependent 25 | * params: h - IRQ handler structure 26 | * returns: interrupt pin on success, negative error code on fail 27 | */ 28 | 29 | int installIRQ(Thread *t, int pin, IRQHandler *h) { 30 | if(!t) t = getKernelThread(); 31 | Process *p = getProcess(t->pid); 32 | if(!p) return -ESRCH; 33 | if(p->user) return -EPERM; // only root can do this 34 | 35 | // only the kernel can install kernel-level IRQ handlers 36 | if(h->kernel && t != getKernelThread()) return -EPERM; 37 | 38 | if(pin < 0 || pin > platformGetMaxIRQ()) return -EIO; 39 | 40 | if(!irqs) { 41 | irqs = calloc(platformGetMaxIRQ() + 1, sizeof(IRQ)); 42 | if(!irqs) return -ENOMEM; 43 | 44 | for(int i = 0; i < platformGetMaxIRQ(); i++) irqs[i].pin = i; 45 | } 46 | 47 | // install the new handler 48 | acquireLockBlocking(&lock); 49 | 50 | int actual = platformConfigureIRQ(t, pin, h); // allow the platform to redirect IRQs 51 | if(actual < 0) { 52 | releaseLock(&lock); 53 | return actual; 54 | } 55 | 56 | IRQHandler *newHandlers = realloc(irqs[pin].handlers, (irqs[pin].devices+1)*sizeof(IRQHandler)); 57 | if(!newHandlers) { 58 | releaseLock(&lock); 59 | return -ENOMEM; 60 | } 61 | 62 | irqs[pin].handlers = newHandlers; 63 | memcpy(&newHandlers[irqs[pin].devices], h, sizeof(IRQHandler)); 64 | irqs[pin].devices++; 65 | 66 | if(actual == pin) { 67 | KDEBUG("device '%s' is using IRQ %d, currently used by %d device%s\n", 68 | h->name, actual, irqs[pin].devices, irqs[pin].devices != 1 ? "s" : ""); 69 | } else { 70 | KDEBUG("device '%s' is using IRQ %d (redirected from IRQ %d), currently used by %d device%s\n", 71 | h->name, actual, pin, irqs[pin].devices, irqs[pin].devices != 1 ? "s" : ""); 72 | } 73 | 74 | releaseLock(&lock); 75 | return actual; 76 | } 77 | 78 | /* dispatchIRQ(): dispatches an IRQ to its handler 79 | * params: pin - IRQ number, platform-dependent 80 | * returns: nothing 81 | */ 82 | 83 | void dispatchIRQ(uint64_t pin) { 84 | if(!irqs[pin].devices) { 85 | KWARN("IRQ %d fired but no devices are using it\n", pin); 86 | platformAcknowledgeIRQ(NULL); 87 | return; 88 | } 89 | 90 | // dispatch the interrupt to the appropriate servers (possible shared IRQs) 91 | Thread *k = getKernelThread(); // for socket context 92 | IRQCommand *irqcmd = platformGetIRQCommand(); 93 | irqcmd->pin = pin; 94 | 95 | for(int i = 0; i < irqs[pin].devices; i++) { 96 | // notify all driver sharing this IRQ 97 | int sd = serverSocket(irqs[pin].handlers[i].driver); 98 | if(sd > 0) send(k, sd, irqcmd, sizeof(IRQCommand), 0); 99 | } 100 | 101 | platformAcknowledgeIRQ(NULL); // end of interrupt 102 | } 103 | -------------------------------------------------------------------------------- /src/platform/x86_64/cmos.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | static lock_t lock = LOCK_INITIAL; 15 | static time_t initialTimestamp = 0; 16 | static uint64_t initialUptime = 0; 17 | static const int daysPerMonth[] = { 18 | 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 // common year 19 | }; 20 | 21 | /* cmosRead(): reads from a CMOS register 22 | * params: index - index to read from 23 | * returns: value from register 24 | */ 25 | 26 | uint8_t cmosRead(uint8_t index) { 27 | outb(CMOS_INDEX, index); 28 | return inb(CMOS_DATA); 29 | } 30 | 31 | /* cmosWrite(): writes to a CMOS register 32 | * params: index - index to write to 33 | * params: value - value to write 34 | * returns: nothing 35 | */ 36 | 37 | void cmosWrite(uint8_t index, uint8_t value) { 38 | outb(CMOS_INDEX, index); 39 | outb(CMOS_DATA, value); 40 | } 41 | 42 | /* platformTimestamp(): returns the Unix timestamp in seconds 43 | * params: none 44 | * returns: timestamp in seconds 45 | */ 46 | 47 | time_t platformTimestamp() { 48 | if(initialTimestamp) 49 | return initialTimestamp + ((platformUptime()-initialUptime) / PLATFORM_TIMER_FREQUENCY); 50 | 51 | acquireLockBlocking(&lock); 52 | 53 | uint8_t format = cmosRead(CMOS_RTC_STATUS_B); 54 | while(cmosRead(CMOS_RTC_STATUS_A) & CMOS_STATUS_A_UPDATE); 55 | 56 | uint16_t year = cmosRead(CMOS_RTC_YEAR); 57 | uint8_t month = cmosRead(CMOS_RTC_MONTH); 58 | uint8_t day = cmosRead(CMOS_RTC_DAY); 59 | uint8_t hour = cmosRead(CMOS_RTC_HOURS); 60 | uint8_t pm = hour & 0x80; 61 | hour &= 0x7F; 62 | uint8_t min = cmosRead(CMOS_RTC_MINS); 63 | uint8_t sec = cmosRead(CMOS_RTC_SECS); 64 | 65 | if(!(format & CMOS_STATUS_B_BINARY)) { 66 | year = (((year >> 4) & 0x0F) * 10) + (year & 0x0F); 67 | month = (((month >> 4) & 0x0F) * 10) + (month & 0x0F); 68 | day = (((day >> 4) & 0x0F) * 10) + (day & 0x0F); 69 | hour = (((hour >> 4) & 0x0F) * 10) + (hour & 0x0F); 70 | min = (((min >> 4) & 0x0F) * 10) + (min & 0x0F); 71 | sec = (((sec >> 4) & 0x0F) * 10) + (sec & 0x0F); 72 | } 73 | 74 | if(!(format & CMOS_STATUS_B_24HR)) { 75 | if(hour == 12) { 76 | if(!pm) hour = 0; 77 | } else if(pm) { 78 | hour += 12; 79 | } 80 | } 81 | 82 | year += 2000; 83 | int yearDay = 0; 84 | int leap = (!(year % 4) && (year % 100)) || (!(year % 400)); 85 | 86 | for(int i = 1; i < month; i++) { 87 | if(i == 2 && leap) yearDay += 29; // special case for february 88 | else yearDay += daysPerMonth[i-1]; 89 | } 90 | 91 | yearDay += day - 1; 92 | 93 | // calculation source: 94 | // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_15 95 | year -= 1900; // depends on years since 1900 96 | 97 | initialTimestamp = sec + (min*60) + (hour*3600) + (yearDay * 86400) 98 | + ((year-70) * 31536000) + (((year-69)/4) * 86400) 99 | - (((year-1)/100) * 86400) + (((year+299)/400) * 86400); 100 | initialUptime = platformUptime(); 101 | 102 | releaseLock(&lock); 103 | return initialTimestamp; 104 | } -------------------------------------------------------------------------------- /src/platform/x86_64/ipc/signal.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | extern size_t sigstubSize; 16 | extern uint8_t sigstub[]; 17 | 18 | /* platformSignalSetup(): sets up the signal trampoline code in a user process 19 | * params: t - thread structure 20 | * returns: zero on success 21 | */ 22 | 23 | int platformSignalSetup(Thread *t) { 24 | void *trampoline = uxmalloc(sigstubSize); 25 | if(!trampoline) return -1; 26 | 27 | void *siginfo = umalloc(sizeof(siginfo_t)); 28 | if(!siginfo) { 29 | free(trampoline); 30 | return -1; 31 | } 32 | 33 | void *sigctx = umalloc(PLATFORM_CONTEXT_SIZE); 34 | if(!sigctx) { 35 | free(trampoline); 36 | free(siginfo); 37 | return -1; 38 | } 39 | 40 | memcpy(trampoline, sigstub, sigstubSize); 41 | t->signalTrampoline = (uintptr_t) trampoline; 42 | t->siginfo = (uintptr_t) siginfo; 43 | t->signalUserContext = (uintptr_t) sigctx; 44 | return 0; 45 | } 46 | 47 | /* platformSendSignal(): dispatches a signal to a thread 48 | * params: sender - thread sending the signal 49 | * params: dest - thread receiving the signal 50 | * params: signum - signal number 51 | * params: handler - function to dispatch 52 | * returns: zero on success 53 | */ 54 | 55 | int platformSendSignal(Thread *sender, Thread *dest, int signum, uintptr_t handler) { 56 | memcpy(dest->signalContext, dest->context, PLATFORM_CONTEXT_SIZE); 57 | 58 | ThreadContext *ctx = (ThreadContext *) dest->context; 59 | platformUseContext(ctx); 60 | 61 | Process *p = NULL; 62 | if(sender) p = getProcess(sender->pid); 63 | 64 | siginfo_t *siginfo = (siginfo_t *) dest->siginfo; 65 | siginfo->si_signo = signum; 66 | if(sender) siginfo->si_pid = sender->tid; 67 | else siginfo->si_pid = 0; // we will use pid 0 for the kernel 68 | 69 | if(p) siginfo->si_uid = p->user; 70 | else siginfo->si_uid = 0; 71 | 72 | siginfo->si_code = 0; // TODO 73 | 74 | ThreadContext *uctx = (ThreadContext *) dest->signalUserContext; 75 | memcpy(uctx, dest->context, PLATFORM_CONTEXT_SIZE); 76 | 77 | // signal entry point 78 | // func(int sig, siginfo_t *info, void *ctx) 79 | // https://pubs.opengroup.org/onlinepubs/007904875/functions/sigaction.html 80 | 81 | ctx->regs.rip = handler; 82 | ctx->regs.rdi = signum; // int sig 83 | ctx->regs.rsi = (uint64_t) siginfo; // siginfo_t *info 84 | ctx->regs.rdx = (uint64_t) uctx; // void *ctx 85 | 86 | ctx->regs.rflags = 0x202; 87 | ctx->regs.rsp -= 128; // downwards of the red zone 88 | 89 | // ensure stack is 16-byte aligned on entry 90 | while(ctx->regs.rsp & 0x0F) 91 | ctx->regs.rsp--; 92 | ctx->regs.rbp = ctx->regs.rsp; 93 | 94 | // inject signal trampoline 95 | uint64_t *stack = (uint64_t *) ctx->regs.rsp; 96 | *stack = dest->signalTrampoline; 97 | 98 | return 0; 99 | } 100 | 101 | /* platformSigreturn(): restores context before a signal handler was invoked 102 | * params: t - thread to restore 103 | * returns: nothing 104 | */ 105 | 106 | void platformSigreturn(Thread *t) { 107 | memcpy(t->context, t->signalContext, PLATFORM_CONTEXT_SIZE); 108 | } -------------------------------------------------------------------------------- /src/platform/x86_64/apic/irqstub.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | section .text 8 | 9 | %include "cpu/stack.asm" 10 | 11 | ; Stub for IRQ Handlers 12 | extern dispatchIRQ 13 | 14 | %macro irq 2 15 | global %1 16 | align 16 17 | %1: 18 | cli 19 | pushaq 20 | mov rdi, %2 ; irq number 21 | cld 22 | call dispatchIRQ 23 | popaq 24 | iretq 25 | %endmacro 26 | 27 | irq dispatchIRQ0, 0 28 | irq dispatchIRQ1, 1 29 | irq dispatchIRQ2, 2 30 | irq dispatchIRQ3, 3 31 | irq dispatchIRQ4, 4 32 | irq dispatchIRQ5, 5 33 | irq dispatchIRQ6, 6 34 | irq dispatchIRQ7, 7 35 | irq dispatchIRQ8, 8 36 | irq dispatchIRQ9, 9 37 | irq dispatchIRQ10, 10 38 | irq dispatchIRQ11, 11 39 | irq dispatchIRQ12, 12 40 | irq dispatchIRQ13, 13 41 | irq dispatchIRQ14, 14 42 | irq dispatchIRQ15, 15 43 | irq dispatchIRQ16, 16 44 | irq dispatchIRQ17, 17 45 | irq dispatchIRQ18, 18 46 | irq dispatchIRQ19, 19 47 | irq dispatchIRQ20, 20 48 | irq dispatchIRQ21, 21 49 | irq dispatchIRQ22, 22 50 | irq dispatchIRQ23, 23 51 | irq dispatchIRQ24, 24 52 | irq dispatchIRQ25, 25 53 | irq dispatchIRQ26, 26 54 | irq dispatchIRQ27, 27 55 | irq dispatchIRQ28, 28 56 | irq dispatchIRQ29, 29 57 | irq dispatchIRQ30, 30 58 | irq dispatchIRQ31, 31 59 | irq dispatchIRQ32, 32 60 | irq dispatchIRQ33, 33 61 | irq dispatchIRQ34, 34 62 | irq dispatchIRQ35, 35 63 | irq dispatchIRQ36, 36 64 | irq dispatchIRQ37, 37 65 | irq dispatchIRQ38, 38 66 | irq dispatchIRQ39, 39 67 | irq dispatchIRQ40, 40 68 | irq dispatchIRQ41, 41 69 | irq dispatchIRQ42, 42 70 | irq dispatchIRQ43, 43 71 | irq dispatchIRQ44, 44 72 | irq dispatchIRQ45, 45 73 | irq dispatchIRQ46, 46 74 | irq dispatchIRQ47, 47 75 | irq dispatchIRQ48, 48 76 | irq dispatchIRQ49, 49 77 | irq dispatchIRQ50, 50 78 | irq dispatchIRQ51, 51 79 | irq dispatchIRQ52, 52 80 | irq dispatchIRQ53, 53 81 | irq dispatchIRQ54, 54 82 | irq dispatchIRQ55, 55 83 | irq dispatchIRQ56, 56 84 | irq dispatchIRQ57, 57 85 | irq dispatchIRQ58, 58 86 | irq dispatchIRQ59, 59 87 | irq dispatchIRQ60, 60 88 | irq dispatchIRQ61, 61 89 | irq dispatchIRQ62, 62 90 | irq dispatchIRQ63, 63 91 | 92 | section .rodata 93 | 94 | global dispatchIRQTable 95 | align 16 96 | dispatchIRQTable: 97 | dq dispatchIRQ0, dispatchIRQ1, dispatchIRQ2, dispatchIRQ3, dispatchIRQ4, dispatchIRQ5 98 | dq dispatchIRQ6, dispatchIRQ7, dispatchIRQ8, dispatchIRQ9, dispatchIRQ10, dispatchIRQ11 99 | dq dispatchIRQ12, dispatchIRQ13, dispatchIRQ14, dispatchIRQ15, dispatchIRQ16, dispatchIRQ17 100 | dq dispatchIRQ18, dispatchIRQ19, dispatchIRQ20, dispatchIRQ21, dispatchIRQ22, dispatchIRQ23 101 | dq dispatchIRQ24, dispatchIRQ25, dispatchIRQ26, dispatchIRQ27, dispatchIRQ28, dispatchIRQ29 102 | dq dispatchIRQ30, dispatchIRQ31, dispatchIRQ32, dispatchIRQ33, dispatchIRQ34, dispatchIRQ35 103 | dq dispatchIRQ36, dispatchIRQ37, dispatchIRQ38, dispatchIRQ39, dispatchIRQ40, dispatchIRQ41 104 | dq dispatchIRQ42, dispatchIRQ43, dispatchIRQ44, dispatchIRQ45, dispatchIRQ46, dispatchIRQ47 105 | dq dispatchIRQ48, dispatchIRQ49, dispatchIRQ50, dispatchIRQ51, dispatchIRQ52, dispatchIRQ53 106 | dq dispatchIRQ54, dispatchIRQ55, dispatchIRQ56, dispatchIRQ57, dispatchIRQ58, dispatchIRQ59 107 | dq dispatchIRQ60, dispatchIRQ61, dispatchIRQ62, dispatchIRQ63 108 | -------------------------------------------------------------------------------- /src/sched/exit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /* terminateThread(): helper function to terminate a thread 14 | * params: t - thread to exit 15 | * params: status - exit code 16 | * params: normal - normal vs abnormal termination 17 | * returns: nothing 18 | */ 19 | 20 | void terminateThread(Thread *t, int status, bool normal) { 21 | status &= 0xFF; // POSIX specifies signed 8-bit exit codes 22 | 23 | t->status = THREAD_ZOMBIE; // zombie status until the exit status is read 24 | t->normalExit = normal; 25 | t->exitStatus = status; // this should technically only be valid for normal exit 26 | 27 | if(normal) t->exitStatus |= EXIT_NORMAL; 28 | 29 | // lumen can never terminate 30 | if(t->pid == getLumenPID() || t->tid == getLumenPID()) { 31 | KPANIC("kernel panic: lumen (pid %d) terminated %snormally with exit status %d\n", getLumenPID(), normal ? "" : "ab", status); 32 | KPANIC("halting because there is nothing to do\n"); 33 | while(1); 34 | } 35 | 36 | // check if this was the last thread in its process 37 | Process *p = getProcess(t->pid); 38 | if(!p) { 39 | KWARN("pid %d from tid %d returned null pointer\n", t->pid, t->tid); 40 | return; 41 | } 42 | 43 | // if all threads are zombies, mark the process as a whole as a zombie 44 | // and then mark its children as orphans 45 | for(int i = 0; i < p->threadCount; i++) { 46 | if(p->threads[i]->status == THREAD_ZOMBIE) { 47 | p->zombie = true; 48 | } else { 49 | p->zombie = false; 50 | break; 51 | } 52 | } 53 | 54 | if(p->zombie && p->childrenCount && p->children) { 55 | // parent process is now a zombie, mark all children as orphans before 56 | // the parent status is read and it quits 57 | for(int i = 0; i < p->childrenCount; i++) { 58 | if(p->children[i]) { 59 | p->children[i]->orphan = true; 60 | p->children[i]->parent = getLumenPID(); // orphan processes are adopted by lumen 61 | } 62 | } 63 | } 64 | 65 | if(!normal) { 66 | KWARN("killed tid %d abnormally\n", t->tid); 67 | } 68 | } 69 | 70 | /* exit(): normally terminates the current running thread 71 | * params: t - thread to exit 72 | * params: status - exit code 73 | * returns: nothing 74 | */ 75 | 76 | void exit(Thread *t, int status) { 77 | // this is really a wrapper around a helper function to allow for normal 78 | // and abnormal termination in one place 79 | schedLock(); 80 | terminateThread(t, status, true); 81 | schedRelease(); 82 | } 83 | 84 | /* schedException(): exception handler for when a process causes a fault 85 | * params: pid - faulting process ID 86 | * params: tid - faulting thread ID 87 | * returns: 0 if the thread can be returned to, 1 if it was terminated 88 | */ 89 | 90 | int schedException(pid_t pid, pid_t tid) { 91 | // TODO: implement custom process exception handlers after signals and 92 | // allow them a chance at their own recovery 93 | Thread *t = getThread(tid); 94 | if(!t) { 95 | KERROR("faulting pid %d tid %d returned null pointer when trying to terminate\n"); 96 | return 1; 97 | } 98 | 99 | terminateThread(t, -1, false); 100 | return 1; 101 | } 102 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | static int idleThreshold = 0; 19 | 20 | void *idleThread(void *args) { 21 | int count = 0; 22 | for(;;) { 23 | if(!syscallProcess()) platformIdle(); 24 | count++; 25 | if(count >= idleThreshold) { 26 | count = 0; 27 | platformIdle(); 28 | } 29 | } 30 | } 31 | 32 | void *kernelThread(void *args) { 33 | setLocalSched(false); 34 | setScheduling(false); 35 | setKernelPID(getPid()); 36 | 37 | // open the kernel socket for server communication 38 | serverInit(); 39 | 40 | KDEBUG("attempt to load lumen from ramdisk...\n"); 41 | 42 | // spawn the router in user space 43 | int64_t size = ramdiskFileSize("lumen"); 44 | if(size <= 9) { 45 | KERROR("lumen not present on the ramdisk, halting because there's nothing to do\n"); 46 | while(1) platformHalt(); 47 | } 48 | 49 | void *lumen = malloc(size); 50 | if(!lumen) { 51 | KERROR("failed to allocate memory for lumen, halting because there's nothing to do\n"); 52 | while(1) platformHalt(); 53 | } 54 | 55 | if(ramdiskRead(lumen, "lumen", size) != size) { 56 | KERROR("failed to read lumen into memory, halting because there's nothing to do\n"); 57 | while(1) platformHalt(); 58 | } 59 | 60 | // TODO: maybe pass boot arguments to lumen? 61 | pid_t pid = execveMemory(lumen, NULL, NULL); 62 | free(lumen); 63 | 64 | if(!pid) { 65 | KERROR("failed to start lumen, halting because there's nothing to do\n"); 66 | while(1) platformHalt(); 67 | } 68 | 69 | setLumenPID(pid); 70 | 71 | PhysicalMemoryStatus ps; 72 | pmmStatus(&ps); 73 | KDEBUG("early boot complete, memory usage: %d MiB / %d MiB\n", ps.usedPages>>8, ps.usablePages>>8); 74 | 75 | setLocalSched(true); 76 | setScheduling(true); 77 | 78 | int count = 0; 79 | for(;;) { 80 | serverIdle(); 81 | if(!syscallProcess()) platformIdle(); 82 | count++; 83 | if(count >= idleThreshold) { 84 | count = 0; 85 | platformIdle(); 86 | } 87 | } 88 | } 89 | 90 | // the true kernel entry point is called after platform-specific initialization 91 | // platform-specific code is in platform/[PLATFORM]/main.c 92 | 93 | int main(int argc, char **argv) { 94 | /* the platform-specific main() function must initialize some basic form of 95 | * output for debugging, physical and virtual memory, and multiprocessing; the 96 | * boot process will continue here in a more platform-independent fashion */ 97 | 98 | socketInit(); // sockets 99 | schedInit(); // scheduler 100 | 101 | if(platformCountCPU() > 16) 102 | idleThreshold = 2; 103 | else if(platformCountCPU() > 8) 104 | idleThreshold = 4; 105 | else 106 | idleThreshold = 8; 107 | 108 | // number of kernel threads = number of CPU cores + 1 109 | kthreadCreate(&kernelThread, NULL); 110 | 111 | for(int i = 0; i < platformCountCPU(); i++) 112 | kthreadCreate(&idleThread, NULL); 113 | 114 | // now enable the scheduler 115 | setScheduling(true); 116 | 117 | while(1) platformHalt(); 118 | } -------------------------------------------------------------------------------- /src/acpi/tables.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static int acpiVersion = 0; 16 | static int tableCount = 0; 17 | static ACPIRSDT *rsdt = NULL; 18 | static ACPIXSDT *xsdt = NULL; 19 | 20 | void acpiDumpHeader(ACPIStandardHeader *h, uint64_t phys) { 21 | KDEBUG("'%c%c%c%c' revision 0x%02X OEM ID '%c%c%c%c%c%c' at 0x%08X len %d\n", 22 | h->signature[0], h->signature[1], h->signature[2], h->signature[3], 23 | h->revision, h->oem[0], h->oem[1], h->oem[2], h->oem[3], h->oem[4], h->oem[5], 24 | phys, h->length); 25 | } 26 | 27 | int acpiInit(KernelBootInfo *k) { 28 | if(!k->acpiRSDP) { 29 | KWARN("system is not ACPI-compliant; power management and multiprocessing will not be available\n"); 30 | return -1; 31 | } 32 | 33 | ACPIRSDP *rsdp = (ACPIRSDP *)vmmMMIO(k->acpiRSDP, true); 34 | KDEBUG("'RSD PTR ' revision 0x%02X OEM ID '%c%c%c%c%c%c' at 0x%08X\n", 35 | rsdp->revision, rsdp->oem[0], rsdp->oem[1], rsdp->oem[2], rsdp->oem[3], rsdp->oem[4], rsdp->oem[5], k->acpiRSDP); 36 | 37 | rsdt = (ACPIRSDT *)vmmMMIO(rsdp->rsdt, true); 38 | 39 | if(rsdp->revision >= 2) { 40 | xsdt = (ACPIXSDT *)vmmMMIO(rsdp->xsdt, true); 41 | acpiVersion = 2; // preliminary, we'll make sure of this from the FADT 42 | } else { 43 | acpiVersion = 1; // same as above 44 | } 45 | 46 | // now dump the ACPI tables starting with the RSDT/XSDT 47 | acpiDumpHeader(&rsdt->header, rsdp->rsdt); 48 | tableCount = (rsdt->header.length - sizeof(ACPIStandardHeader)) / 4; 49 | 50 | if(xsdt) { 51 | acpiDumpHeader(&xsdt->header, rsdp->xsdt); 52 | tableCount = (xsdt->header.length - sizeof(ACPIStandardHeader)) / 8; 53 | } 54 | 55 | ACPIStandardHeader *h; 56 | uint64_t phys; 57 | for(int i = 0; i < tableCount; i++) { 58 | if(xsdt) { 59 | phys = xsdt->tables[i]; 60 | h = (ACPIStandardHeader *)vmmMMIO(xsdt->tables[i], true); 61 | } else { 62 | phys = rsdt->tables[i]; 63 | h = (ACPIStandardHeader *)vmmMMIO(rsdt->tables[i], true); 64 | } 65 | 66 | if(!memcmp(h->signature, "FACP", 4)) { 67 | acpiVersion = h->revision; 68 | } 69 | 70 | acpiDumpHeader(h, phys); 71 | } 72 | 73 | KDEBUG("total of %d tables directly listed in the %s\n", tableCount, xsdt ? "XSDT" : "RSDT"); 74 | KDEBUG("system is compliant with ACPI revision %d\n", acpiVersion); 75 | return 0; 76 | } 77 | 78 | /* acpiFindTable(): finds an ACPI table by signature 79 | * params: sig - 4-character signature of the table 80 | * params: index - zero-based index of the table in cases of multiple tables 81 | * returns: pointer to the table header, NULL if not found 82 | */ 83 | 84 | void *acpiFindTable(const char *sig, int index) { 85 | int c = 0; 86 | ACPIStandardHeader *h; 87 | for(int i = 0; i < tableCount; i++) { 88 | if(xsdt) { 89 | h = (ACPIStandardHeader *)xsdt->tables[i]; 90 | } else { 91 | h = (ACPIStandardHeader *)(uintptr_t)rsdt->tables[i]; 92 | } 93 | 94 | h = (ACPIStandardHeader *)vmmMMIO((uintptr_t)h, true); 95 | 96 | if(!memcmp(h->signature, sig, 4)) { 97 | c++; 98 | if(c > index) return h; 99 | } 100 | } 101 | 102 | return NULL; 103 | } 104 | -------------------------------------------------------------------------------- /src/modules/ramdisk.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | /* the ramdisk is really a USTAR archive that'll be used to load early files 16 | * during early boot before the user space is set up */ 17 | 18 | static uint8_t *ramdisk; 19 | static uint64_t ramdiskSize; 20 | 21 | /* ramdiskInit(): initializes the ramdisk 22 | * params: boot - boot information structure 23 | * returns: nothing 24 | */ 25 | 26 | void ramdiskInit(KernelBootInfo *boot) { 27 | if(boot->ramdisk && boot->ramdiskSize) { 28 | KDEBUG("ramdisk is at 0x%08X\n", boot->ramdisk); 29 | KDEBUG("ramdisk size is %d KiB\n", boot->ramdiskSize/1024); 30 | 31 | ramdisk = (uint8_t *)vmmMMIO(boot->ramdisk, true); 32 | ramdiskSize = boot->ramdiskSize; 33 | } else { 34 | ramdisk = NULL; 35 | ramdiskSize = 0; 36 | } 37 | } 38 | 39 | /* parseOctal(): parses an octal number written in ASCII characters 40 | * params: s - string containing octal number 41 | * returns: integer 42 | */ 43 | 44 | static long parseOctal(const char *s) { 45 | long v = 0; 46 | int len = 0; 47 | 48 | while(s[len] >= '0' && s[len] <= '7') { 49 | len++; // didn't use strlen so we can only account for numerical characters 50 | } 51 | 52 | if(!len) return 0; 53 | 54 | long multiplier = 1; 55 | for(int i = 1; i < len; i++) { 56 | multiplier *= 8; 57 | } 58 | 59 | for(int i = 0; i < len; i++) { 60 | long digit = s[i] - '0'; 61 | v += (digit * multiplier); 62 | multiplier /= 8; 63 | } 64 | 65 | return v; 66 | } 67 | 68 | /* ramdiskFind(): returns pointer to a file on the ramdisk 69 | * params: name - file name 70 | * returns: pointer to the metadata of the file, NULL if non-existent 71 | */ 72 | 73 | struct USTARMetadata *ramdiskFind(const char *name) { 74 | if(!ramdisk) return NULL; 75 | 76 | struct USTARMetadata *ptr = (struct USTARMetadata *)ramdisk; 77 | size_t offset = 0; 78 | 79 | while(offset < ramdiskSize && !strcmp(ptr->magic, "ustar")) { 80 | //KDEBUG("%s\n", ptr->name); 81 | if(!strcmp(ptr->name, name)) return ptr; 82 | 83 | size_t size = ((parseOctal(ptr->size) + 511) / 512) + 1; 84 | size *= 512; 85 | 86 | offset += size; 87 | ptr = (struct USTARMetadata *)((uintptr_t)ptr + size); 88 | } 89 | 90 | return NULL; 91 | } 92 | 93 | /* ramdiskFileSize(): returns the size of a file on the ramdisk 94 | * params: name - file name 95 | * returns: file size in bytes, -1 if non-existent 96 | */ 97 | 98 | int64_t ramdiskFileSize(const char *name) { 99 | struct USTARMetadata *metadata = ramdiskFind(name); 100 | if(!metadata) return -1; 101 | 102 | else return parseOctal(metadata->size); 103 | } 104 | 105 | /* ramdiskRead(): reads a file from the ramdisk 106 | * params: buffer - buffer to read file into 107 | * params: name - file name 108 | * params: n - number of bytes to read 109 | * returns: number of bytes read 110 | */ 111 | 112 | size_t ramdiskRead(void *buffer, const char *name, size_t n) { 113 | struct USTARMetadata *metadata = ramdiskFind(name); 114 | if(!metadata) return 0; 115 | 116 | size_t size = parseOctal(metadata->size); 117 | if(n > size) n = size; 118 | 119 | uint8_t *data = (uint8_t *)((uintptr_t)metadata + 512); 120 | memcpy(buffer, data, n); 121 | return n; 122 | } 123 | -------------------------------------------------------------------------------- /src/include/platform/platform.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | /* routines that must be implemented and constants tha must be defined by any 16 | * platform-specific code, abstracting the difference between different platforms; 17 | * the goal is to keep the core kernel portable across CPU architectures */ 18 | 19 | #define PLATFORM_TIMER_FREQUENCY 100 // Hz, this is for the scheduler 20 | 21 | #define PLATFORM_PAGE_PRESENT 0x0001 // present in main memory 22 | #define PLATFORM_PAGE_SWAP 0x0002 // present in storage device 23 | #define PLATFORM_PAGE_USER 0x0004 // user-kernel toggle 24 | #define PLATFORM_PAGE_EXEC 0x0008 25 | #define PLATFORM_PAGE_WRITE 0x0010 26 | #define PLATFORM_PAGE_NO_CACHE 0x0020 27 | #define PLATFORM_PAGE_ERROR 0x8000 // all bits invalid if this bit is set 28 | 29 | extern char *platformCPUModel; 30 | 31 | int platformCPUSetup(); // very early setup for one CPU 32 | int platformPagingSetup(); // paging setup for virtual memory management 33 | uintptr_t platformGetPage(int *, uintptr_t); // get physical address and flags of a page 34 | uintptr_t platformMapPage(uintptr_t, uintptr_t, int); // map a physical address to a virtual address 35 | int platformUnmapPage(uintptr_t); // and vice versa 36 | 37 | int platformRegisterCPU(void *); // registers a CPU, relevant to multiprocessor systems 38 | int platformCountCPU(); 39 | void *platformGetCPU(int); // find CPU structure from index 40 | int platformWhichCPU(); // index of current CPU 41 | uint64_t platformUptime(); // total uptime of boot CPU 42 | void platformAcknowledgeIRQ(void *); // must be called at the end of interrupt handlers 43 | void platformInitialSeed(); 44 | uint64_t platformRand(); 45 | void platformSeed(uint64_t); 46 | void platformSaveContext(void *, void *); // save scheduler context 47 | void platformLoadContext(void *); // load scheduler context 48 | void platformSwitchContext(Thread *); // perform a context switch 49 | void platformHalt(); // halt the CPU until the next context switch 50 | void *platformGetPagingRoot(); 51 | void *platformCloneKernelSpace(); // clone kernel thread page tables 52 | void *platformCloneUserSpace(uintptr_t); // clone user thread page tables 53 | pid_t platformGetPid(); 54 | pid_t platformGetTid(); 55 | Process *platformGetProcess(); 56 | Thread *platformGetThread(); 57 | int platformUseContext(void *); // use the paging context of a thread without switching context 58 | SyscallRequest *platformCreateSyscallContext(Thread *); // create syscall context from register state 59 | void *platformCloneContext(void *, const void *); // for fork() 60 | void platformSetContextStatus(void *, uint64_t); // store syscall return value in the context 61 | int platformIoperm(Thread *, uintptr_t, uintptr_t, int); // request I/O port access 62 | int platformGetMaxIRQ(); // maximum interrupt implemented by hardware 63 | int platformConfigureIRQ(Thread *, int, IRQHandler *); // configure an IRQ pin 64 | IRQCommand *platformGetIRQCommand(); // per-CPU IRQ command structure 65 | void platformIdle(); // to be called when the CPU is idle 66 | void platformCleanThread(void *, uintptr_t); // garbage collector after thread is killed or replaced by exec() 67 | int platformSendSignal(Thread *, Thread *, int, uintptr_t); 68 | void platformSigreturn(Thread *); 69 | time_t platformTimestamp(); // unix timestamp 70 | -------------------------------------------------------------------------------- /src/platform/x86_64/cpu/setup.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | GDTEntry gdt[GDT_ENTRIES]; 15 | GDTR gdtr; 16 | IDTEntry idt[256]; 17 | IDTR idtr; 18 | 19 | static char _model[49]; 20 | char *platformCPUModel = _model; 21 | 22 | int platformCPUSetup() { 23 | // construct a global descriptor table 24 | memset(&gdt, 0, sizeof(GDTEntry) * GDT_ENTRIES); 25 | 26 | // kernel code descriptor 27 | gdt[GDT_KERNEL_CODE].baseLo = 0; 28 | gdt[GDT_KERNEL_CODE].baseMi = 0; 29 | gdt[GDT_KERNEL_CODE].baseHi = 0; 30 | gdt[GDT_KERNEL_CODE].limit = 0xFFFF; 31 | gdt[GDT_KERNEL_CODE].flagsLimitHi = GDT_FLAGS_64_BIT | GDT_FLAGS_PAGE_GRAN | 0x0F; // limit high 32 | gdt[GDT_KERNEL_CODE].access = GDT_ACCESS_PRESENT | GDT_ACCESS_CODE_DATA | GDT_ACCESS_EXEC | GDT_ACCESS_RW; 33 | 34 | // kernel data descriptor 35 | gdt[GDT_KERNEL_DATA].baseLo = 0; 36 | gdt[GDT_KERNEL_DATA].baseMi = 0; 37 | gdt[GDT_KERNEL_DATA].baseHi = 0; 38 | gdt[GDT_KERNEL_DATA].limit = 0xFFFF; 39 | gdt[GDT_KERNEL_DATA].flagsLimitHi = GDT_FLAGS_PAGE_GRAN | 0x0F; // limit high 40 | gdt[GDT_KERNEL_DATA].access = GDT_ACCESS_PRESENT | GDT_ACCESS_CODE_DATA | GDT_ACCESS_RW; 41 | 42 | // user code descriptor 43 | gdt[GDT_USER_CODE].baseLo = 0; 44 | gdt[GDT_USER_CODE].baseMi = 0; 45 | gdt[GDT_USER_CODE].baseHi = 0; 46 | gdt[GDT_USER_CODE].limit = 0xFFFF; 47 | gdt[GDT_USER_CODE].flagsLimitHi = GDT_FLAGS_64_BIT | GDT_FLAGS_PAGE_GRAN | 0x0F; // limit high 48 | gdt[GDT_USER_CODE].access = GDT_ACCESS_PRESENT | GDT_ACCESS_CODE_DATA | GDT_ACCESS_EXEC | GDT_ACCESS_RW; 49 | gdt[GDT_USER_CODE].access |= (GDT_ACCESS_DPL_USER << GDT_ACCESS_DPL_SHIFT); 50 | 51 | // user data descriptor 52 | gdt[GDT_USER_DATA].baseLo = 0; 53 | gdt[GDT_USER_DATA].baseMi = 0; 54 | gdt[GDT_USER_DATA].baseHi = 0; 55 | gdt[GDT_USER_DATA].limit = 0xFFFF; 56 | gdt[GDT_USER_DATA].flagsLimitHi = GDT_FLAGS_PAGE_GRAN | 0x0F; // limit high 57 | gdt[GDT_USER_DATA].access = GDT_ACCESS_PRESENT | GDT_ACCESS_CODE_DATA | GDT_ACCESS_RW; 58 | gdt[GDT_USER_DATA].access |= (GDT_ACCESS_DPL_USER << GDT_ACCESS_DPL_SHIFT); 59 | 60 | // load the GDT 61 | gdtr.base = (uint64_t)&gdt; 62 | gdtr.limit = (sizeof(GDTEntry) * GDT_ENTRIES) - 1; 63 | loadGDT(&gdtr); 64 | resetSegments(GDT_KERNEL_CODE, PRIVILEGE_KERNEL); 65 | 66 | // create and load an empty interrupt descriptor table 67 | memset(&idt, 0, sizeof(IDTEntry) * 256); 68 | idtr.base = (uint64_t)&idt; 69 | idtr.limit = (sizeof(IDTEntry) * 256) - 1; 70 | loadIDT(&idtr); 71 | 72 | writeCR0(readCR0() & ~CR0_NOT_WRITE_THROUGH); 73 | writeCR0(readCR0() & ~CR0_CACHE_DISABLE); 74 | writeCR0(readCR0() & ~CR0_WRITE_PROTECT); 75 | 76 | // read the CPU model 77 | memset(_model, 0, 49); 78 | uint32_t *ptr = (uint32_t *) _model; 79 | CPUIDRegisters regs; 80 | readCPUID(0x80000000, ®s); 81 | 82 | if(regs.eax < 0x80000004) { 83 | readCPUID(0, ®s); 84 | ptr[0] = regs.ebx; 85 | ptr[1] = regs.edx; 86 | ptr[2] = regs.ecx; 87 | } else { 88 | readCPUID(0x80000002, ®s); 89 | ptr[0] = regs.eax; 90 | ptr[1] = regs.ebx; 91 | ptr[2] = regs.ecx; 92 | ptr[3] = regs.edx; 93 | 94 | readCPUID(0x80000003, ®s); 95 | ptr[4] = regs.eax; 96 | ptr[5] = regs.ebx; 97 | ptr[6] = regs.ecx; 98 | ptr[7] = regs.edx; 99 | 100 | readCPUID(0x80000004, ®s); 101 | ptr[8] = regs.eax; 102 | ptr[9] = regs.ebx; 103 | ptr[10] = regs.ecx; 104 | ptr[11] = regs.edx; 105 | } 106 | 107 | enableIRQs(); 108 | return 0; 109 | } -------------------------------------------------------------------------------- /src/sched/elf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | /* 16 | * loadELF(): loads the sections of an ELF file 17 | * params: binary - pointer to the ELF header 18 | * params: highest - pointer to where to store the binary's highest address 19 | * returns: absolute address of the entry point, zero on fail 20 | */ 21 | 22 | uint64_t loadELF(const void *binary, uint64_t *highest) { 23 | uint8_t *ptr = (uint8_t *)binary; 24 | ELFFileHeader *header = (ELFFileHeader *)ptr; 25 | 26 | uint64_t addr = 0; 27 | int overlap = 0; 28 | 29 | if(header->magic[0] != 0x7F || header->magic[1] != 'E' || 30 | header->magic[2] != 'L' || header->magic[3] != 'F') { 31 | KWARN("ELF file does not contain valid signature\n"); 32 | return 0; 33 | } 34 | 35 | if(header->isaWidth != ELF_ISA_WIDTH_64) { 36 | KWARN("ELF file is not 64-bit\n"); 37 | return 0; 38 | } 39 | 40 | if(header->isa != ELF_ARCHITECTURE) { 41 | KWARN("ELF file is for an unsupported architecture\n"); 42 | return 0; 43 | } 44 | 45 | if(header->type != ELF_TYPE_EXEC) { 46 | return 0; 47 | } 48 | 49 | if(!header->headerEntryCount) { 50 | return 0; 51 | } 52 | 53 | // load the segments 54 | ELFProgramHeader *prhdr = (ELFProgramHeader *)(ptr + header->headerTable); 55 | for(int i = 0; i < header->headerEntryCount; i++) { 56 | if(prhdr->segmentType == ELF_SEGMENT_TYPE_NULL) { 57 | /* ignore null entries and do nothing */ 58 | } else if(prhdr->segmentType == ELF_SEGMENT_TYPE_LOAD) { 59 | // verify the segments are within the user space region of memory 60 | if((prhdr->virtualAddress < USER_BASE_ADDRESS) || 61 | ((prhdr->virtualAddress+prhdr->memorySize) > USER_LIMIT_ADDRESS)) { 62 | return 0; 63 | } 64 | 65 | uint64_t end = prhdr->virtualAddress + prhdr->memorySize; 66 | if(end > addr) { 67 | addr = end; // maintain the highest address 68 | } 69 | 70 | // allocate memory with permissions that match the program section 71 | size_t pages = (prhdr->memorySize + PAGE_SIZE - 1) / PAGE_SIZE; 72 | if(prhdr->virtualAddress % PAGE_SIZE) { 73 | pages++; 74 | } 75 | 76 | int flags = VMM_USER | VMM_WRITE; 77 | if(prhdr->flags & ELF_SEGMENT_FLAGS_EXEC) { 78 | flags |= VMM_EXEC; 79 | } 80 | 81 | uintptr_t vp = vmmAllocate(prhdr->virtualAddress, USER_LIMIT_ADDRESS, pages, flags); 82 | if(!vp) { 83 | return 0; 84 | } 85 | 86 | if(vp != (prhdr->virtualAddress & ~(PAGE_SIZE-1))) 87 | overlap = 1; 88 | 89 | memset((void *)prhdr->virtualAddress, 0, prhdr->memorySize); 90 | memcpy((void *)prhdr->virtualAddress, (const void *)((uintptr_t)binary + prhdr->fileOffset), prhdr->fileSize); 91 | 92 | // adjust the perms by removing the write perms if necessary 93 | if(!(prhdr->flags & ELF_SEGMENT_FLAGS_WRITE)) { 94 | flags &= ~VMM_WRITE; 95 | } 96 | 97 | if(!overlap) vmmSetFlags(prhdr->virtualAddress, pages, flags); 98 | } else { 99 | /* unimplemented header type */ 100 | KERROR("unimplemented ELF header type %d\n", prhdr->segmentType); 101 | return 0; 102 | } 103 | 104 | prhdr = (ELFProgramHeader *)((uintptr_t)prhdr + header->headerEntrySize); 105 | } 106 | 107 | *highest = addr; 108 | return header->entryPoint; 109 | } -------------------------------------------------------------------------------- /src/include/kernel/file.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /* system-wide limits */ 19 | #define MAX_FILE (1 << 18) // 262k 20 | #define MAX_FILE_PATH 2048 21 | 22 | #define SEEK_SET 1 23 | #define SEEK_CUR 2 24 | #define SEEK_END 3 25 | 26 | /* fcntl() commands */ 27 | #define F_DUPFD 1 28 | #define F_GETFD 2 29 | #define F_SETFD 3 30 | #define F_GETFL 4 31 | #define F_SETFL 5 32 | #define F_GETLK 6 33 | #define F_SETLK 7 34 | #define F_SETLKW 8 35 | #define F_GETOWN 9 36 | #define F_SETOWN 10 37 | #define F_DUPFD_CLOEXEC 11 38 | #define F_DUPFD_CLOFORK 12 39 | #define F_GETPATH 0x8000 40 | 41 | /* fcntl() flags */ 42 | #define FD_CLOEXEC (O_CLOEXEC) 43 | #define FD_CLOFORK (O_CLOFORK) 44 | 45 | /* file locks */ 46 | #define F_UNLOCK 1 47 | #define F_RDLCK 2 48 | #define F_WRLCK 4 49 | 50 | /* umount2() flags 51 | * these are not POSIX standard but are implemented by linux and honestly 52 | * they make a lot of sense to have 53 | */ 54 | #define MNT_FORCE 0x01 55 | #define MNT_DETACH 0x02 56 | #define MNT_EXPIRE 0x04 57 | #define UMOUNT_NOFOLLOW 0x08 58 | 59 | /* file-specific I/O descriptor (see io.h) */ 60 | typedef struct { 61 | Process *process; 62 | char abspath[MAX_FILE_PATH]; // absolute path 63 | char device[MAX_FILE_PATH]; // device 64 | char path[MAX_FILE_PATH]; // path relative to device mountpount 65 | off_t position; 66 | uint64_t id; // unique ID, this is for device files 67 | int charDev; // character device boolean 68 | int refCount; 69 | int sd; // socket descriptor of relevant driver 70 | } FileDescriptor; 71 | 72 | /* file lock structure */ 73 | struct flock { 74 | short l_type, l_whence; 75 | off_t l_start, l_len; 76 | pid_t l_pid; 77 | }; 78 | 79 | /* utime */ 80 | struct utimbuf { 81 | time_t actime; 82 | time_t modtime; 83 | }; 84 | 85 | /* file system syscalls */ 86 | int open(Thread *, uint64_t, const char *, int, mode_t); 87 | int close(Thread *, uint64_t, int); 88 | ssize_t read(Thread *, uint64_t, int, void *, size_t); 89 | ssize_t write(Thread *, uint64_t, int, const void *, size_t); 90 | off_t lseek(Thread *, int, off_t, int); 91 | int mount(Thread *, uint64_t, const char *, const char *, const char *, int); 92 | int umount2(Thread *, uint64_t, const char *, int); 93 | int fcntl(Thread *, int, int, uintptr_t); 94 | mode_t umask(Thread *, mode_t); 95 | int chown(Thread *, uint64_t, const char *, uid_t, gid_t); 96 | int chmod(Thread *, uint64_t, const char *, mode_t); 97 | int mkdir(Thread *, uint64_t, const char *, mode_t); 98 | int utime(Thread *, uint64_t, const char *, const struct utimbuf *); 99 | int fsync(Thread *, uint64_t, int); 100 | 101 | ssize_t readFile(Thread *, uint64_t, IODescriptor *, void *, size_t); 102 | ssize_t writeFile(Thread *, uint64_t, IODescriptor *, const void *, size_t); 103 | int closeFile(Thread *, uint64_t, int); 104 | 105 | int chdir(Thread *, uint16_t, const char *); 106 | char *getcwd(Thread *, char *, size_t); 107 | 108 | int link(Thread *, uint64_t, const char *, const char *); 109 | int unlink(Thread *, uint64_t, const char *); 110 | int symlink(Thread *, uint64_t, const char *, const char *); 111 | ssize_t readlink(Thread *, uint64_t, const char *, char *, size_t); 112 | -------------------------------------------------------------------------------- /src/dirent.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | /* Just as with the file system calls, these are just wrappers and do not 16 | * implement any actual functionality at the kernel level */ 17 | 18 | int opendir(Thread *t, uint64_t id, const char *path) { 19 | if(strlen(path) >= MAX_FILE_PATH) return -ENAMETOOLONG; 20 | Process *p = getProcess(t->pid); 21 | if(!p) return -ESRCH; 22 | 23 | OpendirCommand *cmd = calloc(1, sizeof(OpendirCommand)); 24 | if(!cmd) return -ENOMEM; 25 | 26 | cmd->header.header.command = COMMAND_OPENDIR; 27 | cmd->header.header.length = sizeof(OpendirCommand); 28 | cmd->header.id = id; 29 | cmd->uid = p->user; 30 | cmd->gid = p->group; 31 | 32 | if(path[0] == '/') { 33 | strcpy(cmd->abspath, path); 34 | } else { 35 | strcpy(cmd->abspath, p->cwd); 36 | if(strlen(p->cwd) > 1) cmd->abspath[strlen(cmd->abspath)] = '/'; 37 | strcpy(cmd->abspath + strlen(cmd->abspath), path); 38 | } 39 | 40 | int status = requestServer(t, 0, cmd); 41 | free(cmd); 42 | return status; 43 | } 44 | 45 | int readdir_r(Thread *t, uint64_t id, DIR *dir, struct dirent *entry, struct dirent **result) { 46 | Process *p = getProcess(t->pid); 47 | if(!p) return -ESRCH; 48 | 49 | // input validation 50 | int dd = (intptr_t) dir & ~(DIRECTORY_DESCRIPTOR_FLAG); 51 | if(dd < 0 || dd >= MAX_IO_DESCRIPTORS) return -EBADF; 52 | if(!p->io[dd].valid || p->io[dd].type != IO_DIRECTORY) return -EBADF; 53 | 54 | DirectoryDescriptor *descriptor = (DirectoryDescriptor *) p->io[dd].data; 55 | if(!descriptor) return -EBADF; 56 | 57 | ReaddirCommand *cmd = calloc(1, sizeof(ReaddirCommand)); 58 | if(!cmd) return -ENOMEM; 59 | 60 | cmd->header.header.command = COMMAND_READDIR; 61 | cmd->header.header.length = sizeof(ReaddirCommand); 62 | cmd->header.id = id; 63 | cmd->position = descriptor->position; 64 | strcpy(cmd->path, descriptor->path); 65 | strcpy(cmd->device, descriptor->device); 66 | 67 | int status = requestServer(t, descriptor->sd, cmd); 68 | free(cmd); 69 | return status; 70 | } 71 | 72 | void seekdir(Thread *t, DIR *dir, long position) { 73 | Process *p = getProcess(t->pid); 74 | if(!p) return; 75 | 76 | int dd = (intptr_t) dir & ~(DIRECTORY_DESCRIPTOR_FLAG); 77 | if(dd < 0 || dd >= MAX_IO_DESCRIPTORS) return; 78 | if(!p->io[dd].valid || p->io[dd].type != IO_DIRECTORY) return; 79 | 80 | DirectoryDescriptor *descriptor = (DirectoryDescriptor *) p->io[dd].data; 81 | if(!descriptor) return; 82 | 83 | descriptor->position = position; 84 | return; 85 | } 86 | 87 | long telldir(Thread *t, DIR *dir) { 88 | Process *p = getProcess(t->pid); 89 | if(!p) return -ESRCH; 90 | 91 | int dd = (intptr_t) dir & ~(DIRECTORY_DESCRIPTOR_FLAG); 92 | if(dd < 0 || dd >= MAX_IO_DESCRIPTORS) return -EBADF; 93 | if(!p->io[dd].valid || p->io[dd].type != IO_DIRECTORY) return -EBADF; 94 | 95 | DirectoryDescriptor *descriptor = (DirectoryDescriptor *) p->io[dd].data; 96 | if(!descriptor) return -EBADF; 97 | 98 | return descriptor->position; 99 | } 100 | 101 | int closedir(Thread *t, DIR *dir) { 102 | Process *p = getProcess(t->pid); 103 | if(!p) return -ESRCH; 104 | 105 | int dd = (intptr_t) dir & ~(DIRECTORY_DESCRIPTOR_FLAG); 106 | if(dd < 0 || dd >= MAX_IO_DESCRIPTORS) return -EBADF; 107 | if(!p->io[dd].valid || p->io[dd].type != IO_DIRECTORY) return -EBADF; 108 | 109 | DirectoryDescriptor *descriptor = (DirectoryDescriptor *) p->io[dd].data; 110 | if(!descriptor) return -EBADF; 111 | 112 | // destroy the descriptor 113 | p->io[dd].valid = false; 114 | p->io[dd].type = 0; 115 | p->io[dd].flags = 0; 116 | p->io[dd].data = NULL; 117 | p->iodCount--; 118 | 119 | return 0; 120 | } 121 | -------------------------------------------------------------------------------- /src/platform/x86_64/include/platform/x86_64.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #pragma once 13 | 14 | typedef struct { 15 | // these are in reverse order because of how the x86 stack works 16 | uint64_t r15; 17 | uint64_t r14; 18 | uint64_t r13; 19 | uint64_t r12; 20 | uint64_t r11; 21 | uint64_t r10; 22 | uint64_t r9; 23 | uint64_t r8; 24 | uint64_t rbp; 25 | uint64_t rdi; 26 | uint64_t rsi; 27 | uint64_t rdx; 28 | uint64_t rcx; 29 | uint64_t rbx; 30 | uint64_t rax; 31 | uint64_t code; // ignore 32 | uint64_t rip; 33 | uint64_t cs; 34 | uint64_t rflags; 35 | uint64_t rsp; 36 | uint64_t ss; 37 | } __attribute__((packed)) InterruptRegisters; 38 | 39 | typedef struct { 40 | uint32_t eax; 41 | uint32_t ebx; 42 | uint32_t ecx; 43 | uint32_t edx; 44 | } __attribute__((packed)) CPUIDRegisters; 45 | 46 | // wrappers for instructions that don't have an equivalent in C 47 | uint64_t readCR0(); 48 | void writeCR0(uint64_t); 49 | uint64_t readCR2(); 50 | uint64_t readCR3(); 51 | void writeCR3(uint64_t); 52 | uint64_t readCR4(); 53 | void writeCR4(uint64_t); 54 | void loadGDT(void *); 55 | void loadIDT(void *); 56 | void storeGDT(void *); 57 | void storeIDT(void *); 58 | void loadTSS(uint16_t); 59 | uint16_t storeTSS(); 60 | void outb(uint16_t, uint8_t); 61 | void outw(uint16_t, uint16_t); 62 | void outd(uint16_t, uint32_t); 63 | uint8_t inb(uint16_t); 64 | uint16_t inw(uint16_t); 65 | uint32_t ind(uint16_t); 66 | void resetSegments(uint64_t, uint8_t); 67 | uint32_t readCPUID(uint32_t, CPUIDRegisters *); 68 | uint64_t readMSR(uint32_t); 69 | void writeMSR(uint32_t, uint64_t); 70 | void enableIRQs(); 71 | void disableIRQs(); 72 | void halt(); 73 | 74 | #define CR0_NOT_WRITE_THROUGH 0x20000000 75 | #define CR0_CACHE_DISABLE 0x40000000 // caching 76 | #define CR0_WRITE_PROTECT 0x00010000 77 | 78 | #define CR4_FSGSBASE 0x00010000 // enable fs/gs segmentation 79 | 80 | // other x86_64-specific routines 81 | extern GDTEntry gdt[]; 82 | extern IDTEntry idt[]; 83 | extern GDTR gdtr; 84 | extern IDTR idtr; 85 | void installInterrupt(uint64_t, uint16_t, int, int, int); 86 | 87 | #define PRIVILEGE_KERNEL 0 88 | #define PRIVILEGE_USER 3 89 | 90 | #define INTERRUPT_TYPE_INT 0x0E 91 | #define INTERRUPT_TYPE_TRAP 0x0F 92 | 93 | #define MSR_EFER 0xC0000080 94 | #define MSR_FS_BASE 0xC0000100 95 | #define MSR_GS_BASE 0xC0000101 96 | #define MSR_GS_BASE_KERNEL 0xC0000102 // for swapgs 97 | #define MSR_STAR 0xC0000081 // CS and SS 98 | #define MSR_LSTAR 0xC0000082 // syscall 64-bit entry point 99 | #define MSR_CSTAR 0xC0000083 // syscall 32-bit entry point, probably never gonna use this 100 | #define MSR_SFMASK 0xC0000084 // 32-bit EFLAGS mask, upper 32 bits reserved 101 | 102 | #define MSR_EFER_SYSCALL 0x00000001 // syscall/sysret instructions 103 | #define MSR_EFER_NX_ENABLE 0x00000800 // PAE/NX 104 | #define MSR_EFER_FFXSR 0x00004000 // fast FXSAVE/FXRSTOR 105 | 106 | // paging 107 | #define PT_PAGE_PRESENT 0x0001 108 | #define PT_PAGE_RW 0x0002 109 | #define PT_PAGE_USER 0x0004 110 | #define PT_PAGE_WRITE_THROUGH 0x0008 111 | #define PT_PAGE_NO_CACHE 0x0010 112 | #define PT_PAGE_SIZE_EXTENSION 0x0080 113 | #define PT_PAGE_NXE ((uint64_t)0x8000000000000000) // SET to disable execution privilege 114 | #define PT_PAGE_LOW_FLAGS (PT_PAGE_PRESENT | PT_PAGE_RW | PT_PAGE_USER | PT_PAGE_NO_CACHE) 115 | 116 | // page fault status code 117 | #define PF_PRESENT 0x01 118 | #define PF_WRITE 0x02 119 | #define PF_USER 0x04 120 | #define PF_RESERVED_WRITE 0x08 121 | #define PF_FETCH 0x10 122 | // there are more causes of page faults, but these are the only implemented ones 123 | 124 | -------------------------------------------------------------------------------- /src/include/kernel/memory.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /* this must be defined on a platform-specific basis */ 19 | /* it defines the page size and other necessary attributes for paging */ 20 | #include 21 | 22 | #define PMM_CONTIGUOUS_LOW 0x01 23 | 24 | // these flags control allocated memory 25 | #define VMM_USER 0x01 // kernel-user toggle 26 | #define VMM_EXEC 0x02 27 | #define VMM_WRITE 0x04 28 | #define VMM_NO_CACHE 0x08 29 | 30 | // these flags are used as platform-independent status codes after page faults 31 | #define VMM_PAGE_FAULT_PRESENT 0x01 // caused by a present page 32 | #define VMM_PAGE_FAULT_USER 0x02 // caused by a user process 33 | #define VMM_PAGE_FAULT_WRITE 0x04 // caused by a write operation 34 | #define VMM_PAGE_FAULT_FETCH 0x08 // caused by instruction fetch 35 | 36 | // these values are used as "magic" addresses for pages marked as swap (i.e. not-present) 37 | // they are all 2 MiB-aligned for compatibility across architectures 38 | // they essentially indicate to the virtual memory manager how to handle a page fault 39 | #define VMM_PAGE_SWAP_MASK 0xE00000 40 | #define VMM_PAGE_SWAP 0x200000 // swap from disk 41 | #define VMM_PAGE_ALLOCATE 0x400000 // allocate physical memory 42 | 43 | // TODO: adjust bit masks and shifting here when implementing true swapping 44 | 45 | // protection and flags for memory-mapped files 46 | #define PROT_READ 0x01 47 | #define PROT_WRITE 0x02 48 | #define PROT_EXEC 0x04 49 | #define PROT_NONE 0x00 50 | 51 | #define MAP_SHARED 0x01 52 | #define MAP_PRIVATE 0x02 53 | #define MAP_FIXED 0x04 54 | #define MAP_ANONYMOUS 0x08 55 | #define MAP_ANON (MAP_ANONYMOUS) 56 | 57 | #define MS_ASYNC 0x01 58 | #define MS_SYNC 0x02 59 | #define MS_INVALIDATE 0x04 60 | 61 | typedef struct { 62 | uint64_t highestPhysicalAddress; 63 | uint64_t lowestUsableAddress; 64 | uint64_t highestUsableAddress; 65 | size_t highestPage; 66 | size_t usablePages, usedPages; 67 | size_t reservedPages; 68 | } PhysicalMemoryStatus; 69 | 70 | typedef struct { 71 | uint64_t usedPages, usedBytes; 72 | } KernelHeapStatus; 73 | 74 | typedef struct { 75 | int fd, prot, flags; 76 | pid_t pid, tid; // original owner 77 | bool device; 78 | size_t length; 79 | off_t offset; 80 | } MmapHeader; 81 | 82 | struct MmapSyscallParams { 83 | // probably the only syscall whose params will be passed in memory because 84 | // there's just too many 85 | void *addr; 86 | size_t len; 87 | int prot; 88 | int flags; 89 | int fd; 90 | off_t off; 91 | }; 92 | 93 | void pmmInit(KernelBootInfo *); 94 | void pmmStatus(PhysicalMemoryStatus *); 95 | uintptr_t pmmAllocate(void); 96 | uintptr_t pmmAllocateContiguous(size_t, int); 97 | int pmmFree(uintptr_t); 98 | int pmmFreeContiguous(uintptr_t, size_t); 99 | 100 | void vmmInit(); 101 | uintptr_t vmmAllocate(uintptr_t, uintptr_t, size_t, int); 102 | int vmmFree(uintptr_t, size_t); 103 | int vmmPageFault(uintptr_t, int); // the platform-specific page fault handler must call this 104 | uintptr_t vmmMMIO(uintptr_t, bool); 105 | int vmmPageStatus(uintptr_t, uintptr_t *); 106 | uintptr_t vmmSetFlags(uintptr_t, size_t, int); 107 | 108 | void *sbrk(Thread *, intptr_t); 109 | 110 | uintptr_t mmio(Thread *, uintptr_t, off_t, int); 111 | uintptr_t pcontig(Thread *, uintptr_t, off_t, int); 112 | uintptr_t vtop(Thread *, uintptr_t); 113 | 114 | void *mmap(Thread *, uint64_t, void *, size_t, int, int, int, off_t); 115 | int munmap(Thread *, void *, size_t); 116 | int msync(Thread *, uint64_t, void *, size_t, int); 117 | 118 | void mmapHandle(MmapCommand *, SyscallRequest *); 119 | -------------------------------------------------------------------------------- /src/include/errno.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | /* System Error Numbers */ 11 | 12 | #define E2BIG 1 // too many args 13 | #define EACCES 2 // access denied 14 | #define EADDRINUSE 3 // address in use 15 | #define EADDRNOTAVAIL 4 // address not available 16 | #define EAFNOSUPPORT 5 // address family not supported 17 | #define EAGAIN 6 // resource busy, try again 18 | #define EALREADY 7 // connection already in progress 19 | #define EBADF 8 // bad file descriptor 20 | #define EBADMSG 9 // bad message 21 | #define EBUSY 10 // resource busy 22 | #define ECANCELED 11 // canceled 23 | #define ECHILD 12 // no child processes 24 | #define ECONNABORTED 13 // connection aborted 25 | #define ECONNREFUSED 14 // connection refused 26 | #define ECONNRESET 15 // connection reset 27 | #define EDEADLK 16 // deadlock 28 | #define EDESTADDRREQ 17 // destination address required 29 | #define EDOM 18 // function argument not in domain 30 | #define EDQUOT 19 // reserved 31 | #define EEXIST 20 // file exists 32 | #define EFAULT 21 // bad address 33 | #define EFBIG 22 // file too big 34 | #define EHOSTUNREACH 23 // host unreachable 35 | #define EIDRM 24 // identifier removed 36 | #define EILSEQ 25 // illegal byte sequence 37 | #define EINPROGRESS 26 // operation in progress 38 | #define EINTR 27 // interrupted function 39 | #define EINVAL 28 // invalid argument 40 | #define EIO 29 // I/O error 41 | #define EISCONN 30 // socket already connected 42 | #define EISDIR 31 // directory 43 | #define ELOOP 32 // looping symlinks 44 | #define EMFILE 33 // file descriptor too big 45 | #define EMLINK 34 // too many links 46 | #define EMSGSIZE 35 // message too large 47 | #define EMULTIHOP 36 // reserved 48 | #define ENAMETOOLONG 37 // file name too long 49 | #define ENETDOWN 38 // network is down 50 | #define ENETRESET 39 // connection aborted by network 51 | #define ENETUNREACH 40 // network unreachable 52 | #define ENFILE 41 // too many files open 53 | #define ENOBUFS 42 // no buffers available 54 | #define ENODEV 43 // no such device 55 | #define ENOENT 44 // no such file/directory 56 | #define ENOEXEC 45 // executable file error 57 | #define ENOLCK 46 // no locks available 58 | #define ENOLINK 47 // reserved 59 | #define ENOMEM 48 // ran out of memory 60 | #define ENOMSG 49 // no message of desired type 61 | #define ENOPROTOOPT 50 // protocol not implemented 62 | #define ENOSPC 51 // ran out of storage 63 | #define ENOSYS 52 // function not supported 64 | #define ENOTCONN 53 // socket not connected 65 | #define ENOTDIR 54 // not a directory nor symlink to directory 66 | #define ENOTEMPTY 55 // directory not empty 67 | #define ENOTRECOVERABLE 56 // unrecoverable error 68 | #define ENOTSOCK 57 // not a socket 69 | #define ENOTSUP 58 // operation not supported 70 | #define EOPNOTSUPP ENOTSUP 71 | #define ENOTTY 59 // inappropriate I/O 72 | #define ENXIO 60 // no such device or address 73 | #define EOVERFLOW 61 // value larger than data type 74 | #define EOWNERDEAD 62 // previous owner died 75 | #define EPERM 63 // operation not permitted 76 | #define EPIPE 64 // broken pipe 77 | #define EPROTO 65 // protocol error 78 | #define EPROTONOSUPPORT 66 // protocol not supported 79 | #define EPROTOTYPE 67 // wrong protocol type 80 | #define ERANGE 68 // result too large 81 | #define EROFS 69 // read-only file system 82 | #define ESPIPE 70 // invalid seek 83 | #define ESRCH 71 // no such process 84 | #define ESTALE 72 // reserved 85 | #define ETIMEDOUT 73 // connection timeout 86 | #define ETXTBSY 74 // text file busy 87 | #define EWOULDBLOCK 75 // blocking operation 88 | #define EXDEV 76 // cross-device link 89 | -------------------------------------------------------------------------------- /src/platform/x86_64/cpu/wrapper.asm: -------------------------------------------------------------------------------- 1 | 2 | ; lux - a lightweight unix-like operating system 3 | ; Omar Elghoul, 2024 4 | 5 | [bits 64] 6 | 7 | ; Wrappers for instructions that are not directly usable in higher-level code 8 | 9 | section .text 10 | 11 | global readCR0 12 | align 16 13 | readCR0: 14 | mov rax, cr0 15 | ret 16 | 17 | global writeCR0 18 | align 16 19 | writeCR0: 20 | mov cr0, rdi 21 | ret 22 | 23 | global readCR2 24 | align 16 25 | readCR2: 26 | mov rax, cr2 27 | ret 28 | 29 | global readCR3 30 | align 16 31 | readCR3: 32 | mov rax, cr3 33 | ret 34 | 35 | global writeCR3 36 | align 16 37 | writeCR3: 38 | mov cr3, rdi 39 | ret 40 | 41 | global readCR4 42 | align 16 43 | readCR4: 44 | mov rax, cr4 45 | ret 46 | 47 | global writeCR4 48 | align 16 49 | writeCR4: 50 | mov cr4, rdi 51 | ret 52 | 53 | global loadGDT 54 | align 16 55 | loadGDT: 56 | lgdt [rdi] 57 | ret 58 | 59 | global loadIDT 60 | align 16 61 | loadIDT: 62 | lidt [rdi] 63 | ret 64 | 65 | global outb 66 | align 16 67 | outb: 68 | mov rdx, rdi 69 | mov rax, rsi 70 | out dx, al 71 | ret 72 | 73 | global outw 74 | align 16 75 | outw: 76 | mov rdx, rdi 77 | mov rax, rsi 78 | out dx, ax 79 | ret 80 | 81 | global outd 82 | align 16 83 | outd: 84 | mov rdx, rdi 85 | mov rax, rsi 86 | out dx, eax 87 | ret 88 | 89 | global inb 90 | align 16 91 | inb: 92 | mov rdx, rdi 93 | in al, dx 94 | ret 95 | 96 | global inw 97 | align 16 98 | inw: 99 | mov rdx, rdi 100 | in ax, dx 101 | ret 102 | 103 | global ind 104 | align 16 105 | ind: 106 | mov rdx, rdi 107 | in eax, dx 108 | ret 109 | 110 | global resetSegments 111 | align 16 112 | resetSegments: 113 | mov rax, rsp ; preserve original stack 114 | 115 | ; rdi = index into GDT for code segment 116 | ; rsi = privilege level 117 | inc rdi ; advance to data segment 118 | shl rdi, 3 ; mul 8 119 | or rdi, rsi 120 | 121 | mov ds, rdi 122 | mov es, rdi 123 | mov fs, rdi 124 | mov gs, rdi 125 | 126 | ; create interrupt frame 127 | push rdi ; ss 128 | push rax ; rsp 129 | pushfq ; rflags 130 | 131 | sub rdi, 8 132 | push rdi ; code segment 133 | mov rax, .next 134 | push rax ; rip 135 | iretq 136 | 137 | align 16 138 | .next: 139 | ret 140 | 141 | global readCPUID 142 | align 16 143 | readCPUID: 144 | ; readCPUID(uint32_t leaf, CPUIDRegisters *regs) 145 | ; typedef struct { 146 | ; uint32_t eax; 147 | ; uint32_t ebx; 148 | ; uint32_t ecx; 149 | ; uint32_t edx; 150 | ; } __attribute__((packed)) CPUIDRegisters; 151 | 152 | push rbx 153 | 154 | ; this will allow us to detect zeroes when a function isn't supported 155 | xor ebx, ebx 156 | xor ecx, ecx 157 | xor edx, edx 158 | 159 | mov eax, edi ; leaf 160 | push rsi ; regs 161 | mov ecx, [rsi+8] 162 | cpuid 163 | pop rsi 164 | mov [rsi], eax 165 | mov [rsi+4], ebx 166 | mov [rsi+8], ecx 167 | mov [rsi+12], edx 168 | 169 | pop rbx 170 | 171 | ret 172 | 173 | global readMSR 174 | align 16 175 | readMSR: 176 | mov ecx, edi 177 | rdmsr 178 | xor rcx, rcx 179 | not ecx ; rcx = all zeroes high, all ones low 180 | and rax, rcx 181 | shl rdx, 32 182 | or rax, rdx 183 | ret 184 | 185 | global writeMSR 186 | align 16 187 | writeMSR: 188 | mov ecx, edi 189 | mov rax, rsi 190 | mov rdx, rax 191 | shr rdx, 32 192 | wrmsr 193 | ret 194 | 195 | global enableIRQs 196 | align 16 197 | enableIRQs: 198 | sti 199 | ret 200 | 201 | global disableIRQs 202 | align 16 203 | disableIRQs: 204 | cli 205 | ret 206 | 207 | global getKernelCPUInfo 208 | align 16 209 | getKernelCPUInfo: 210 | rdgsbase rax 211 | and rax, rax 212 | jnz .done 213 | 214 | swapgs 215 | rdgsbase rax 216 | 217 | .done: 218 | swapgs ; gs base should always be zero 219 | ret 220 | 221 | global halt 222 | align 16 223 | halt: 224 | hlt 225 | ret 226 | 227 | global storeGDT 228 | align 16 229 | storeGDT: 230 | sgdt [rdi] 231 | ret 232 | 233 | global storeIDT 234 | align 16 235 | storeIDT: 236 | sidt [rdi] 237 | ret 238 | 239 | global loadTSS 240 | align 16 241 | loadTSS: 242 | pushfq 243 | cli 244 | ltr di 245 | nop 246 | popfq 247 | ret 248 | 249 | global storeTSS 250 | align 16 251 | storeTSS: 252 | xor rax, rax 253 | str ax 254 | ret 255 | -------------------------------------------------------------------------------- /src/include/kernel/sched.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | typedef struct Process Process; 20 | typedef struct Thread Thread; 21 | 22 | #include 23 | 24 | // still not sure of how to decide on this value so it'll probably change 25 | #define SCHED_TIME_SLICE 1 // ms 26 | 27 | #define MAX_PID 99999 28 | 29 | #define THREAD_QUEUED 0 30 | #define THREAD_RUNNING 1 31 | #define THREAD_BLOCKED 2 // waiting for I/O 32 | #define THREAD_ZOMBIE 3 33 | #define THREAD_SLEEP 4 34 | 35 | #define PRIORITY_NORMAL 1 36 | #define PRIORITY_HIGH 2 37 | #define PRIORITY_HIGHEST 3 38 | 39 | // exit status 40 | #define EXIT_NORMAL 0x100 41 | #define EXIT_SIGNALED 0x200 42 | 43 | // waitpid() flags 44 | #define WCONTINUED 0x01 45 | #define WNOHANG 0x02 46 | #define WUNTRACED 0x04 47 | 48 | typedef struct SignalQueue { 49 | struct SignalQueue *next; 50 | int signum; 51 | struct Thread *sender; 52 | } SignalQueue; 53 | 54 | struct Thread { 55 | int status, cpu, priority; 56 | pid_t pid, tid; // pid == tid for the main thread 57 | uint64_t time; // timeslice OR sleep time if sleeping thread 58 | lock_t lock; 59 | 60 | bool normalExit; // true when the thread ends by exit() and is not forcefully killed 61 | bool clean; // true when the exit status has been read by waitpid() 62 | bool handlingSignal; // true inside a signal handler 63 | 64 | void *signals; 65 | sigset_t signalMask; 66 | SignalQueue *signalQueue; 67 | uintptr_t signalTrampoline; 68 | uintptr_t siginfo; 69 | uintptr_t signalUserContext; 70 | 71 | SyscallRequest syscall; // for when the thread is blocked 72 | int exitStatus; // for zombie threads 73 | 74 | int pages; // memory pages used 75 | 76 | struct Thread *next; 77 | void *context; // platform-specific (page tables, registers, etc) 78 | void *signalContext; 79 | 80 | uintptr_t highest; 81 | }; 82 | 83 | struct Process { 84 | pid_t pid, parent, pgrp; 85 | uid_t user; 86 | gid_t group; 87 | mode_t umask; // file creation mask 88 | 89 | bool orphan; // true when the parent process exits or is killed 90 | bool zombie; // true when all threads are zombies 91 | 92 | char command[ARG_MAX*32]; // command line with arguments 93 | char name[MAX_PATH]; // file name 94 | 95 | struct IODescriptor io[MAX_IO_DESCRIPTORS]; 96 | int iodCount; 97 | 98 | char cwd[MAX_PATH]; 99 | 100 | int pages; // memory pages used 101 | 102 | size_t threadCount; 103 | size_t childrenCount; 104 | 105 | Thread **threads; 106 | struct Process **children; // array of pointers of size childrenCount 107 | struct Process *next; 108 | }; 109 | 110 | extern int processes, threads; 111 | void schedInit(); 112 | void schedLock(); 113 | void schedRelease(); 114 | uint64_t schedTimer(); 115 | pid_t getPid(); 116 | pid_t getTid(); 117 | void *schedGetState(pid_t); 118 | void schedule(); 119 | Process *getProcess(pid_t); 120 | Thread *getThread(pid_t); 121 | uint64_t schedTimeslice(Thread *, int); 122 | void schedAdjustTimeslice(); 123 | void setScheduling(bool); 124 | void blockThread(Thread *); 125 | void unblockThread(Thread *); 126 | Process *getProcessQueue(); 127 | void schedStatus(); 128 | bool schedBusy(); 129 | 130 | pid_t kthreadCreate(void *(*)(void *), void *); 131 | pid_t processCreate(); 132 | int threadUseContext(pid_t); 133 | void setLocalSched(bool); 134 | 135 | pid_t execveMemory(const void *, const char **argv, const char **envp); 136 | pid_t getLumenPID(); 137 | void setLumenPID(pid_t); 138 | void setKernelPID(pid_t); 139 | pid_t getKernelPID(); 140 | int schedException(pid_t, pid_t); 141 | void terminateThread(Thread *, int, bool); 142 | void schedSleepTimer(); 143 | Thread *getKernelThread(); 144 | void threadCleanup(Thread *); 145 | 146 | // these functions are exposed as system calls, but some will need to take 147 | // the thread as an argument from the system call handler - the actual user 148 | // application does not need to be aware of which thread is running for 149 | // Unix compatibility 150 | int yield(Thread *); 151 | pid_t fork(Thread *); 152 | void exit(Thread *, int); 153 | int execve(Thread *, uint16_t, const char *, const char **, const char **); 154 | int execveHandle(void *); 155 | int execrdv(Thread *, const char *, const char **); 156 | unsigned long msleep(Thread *, unsigned long); 157 | pid_t waitpid(Thread *, pid_t, int *, int); 158 | -------------------------------------------------------------------------------- /src/platform/x86_64/apic/timer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Platform-Specific Code for x86_64 6 | */ 7 | 8 | /* Implementation of the Local APIC Timer */ 9 | /* This will be the main timing source on x86_64 because it is on the same 10 | * physical circuit as the CPU, reducing latency compared to external timers 11 | * like the HPET or the legacy PIT (which I don't intend to support) */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | static uint64_t apicFrequency; 22 | 23 | /* apicTimerInit(): initializes the local APIC timer 24 | * this may depend on the CMOS to calibrate the timer */ 25 | 26 | int apicTimerInit() { 27 | // first check if we can calculate the APIC timer's frequency 28 | CPUIDRegisters regs; 29 | readCPUID(0, ®s); 30 | // TODO: use this to determine CPU core and bus frequency 31 | /*if(regs.eax >= 0x16) { // max function 32 | readCPUID(0x16, ®s); 33 | }*/ 34 | 35 | // enable the local APIC 36 | uint64_t apic = readMSR(MSR_LAPIC); 37 | if(!(apic & MSR_LAPIC_ENABLED)) { 38 | writeMSR(MSR_LAPIC, apic | MSR_LAPIC_ENABLED); 39 | } 40 | 41 | lapicWrite(LAPIC_TPR, 0); // enable all interrupts 42 | lapicWrite(LAPIC_DEST_FORMAT, lapicRead(LAPIC_DEST_FORMAT) | 0xF0000000); // flat mode 43 | lapicWrite(LAPIC_SPURIOUS_VECTOR, 0x1FF); 44 | 45 | /* TODO: use the HPET instead of the PIT to measure timer frequency */ 46 | // set up the PIT to wait for a fraction of a second 47 | uint16_t pitFrequency = 1193180 / 100; // PIT frequency divided by 100 Hz 48 | 49 | // set up the APIC timer in one-shot mode with no interrupts 50 | lapicWrite(LAPIC_TIMER_INITIAL, 0); // disable timer so we can set it up 51 | lapicWrite(LAPIC_LVT_TIMER, LAPIC_TIMER_ONE_SHOT | LAPIC_LVT_MASK); 52 | lapicWrite(LAPIC_TIMER_DIVIDE, LAPIC_TIMER_DIVIDER_1); 53 | lapicWrite(LAPIC_TIMER_INITIAL, 0xFFFFFFFF); // enable timer 54 | 55 | uint32_t apicInitial = lapicRead(LAPIC_TIMER_CURRENT); 56 | 57 | outb(0x43, 0x30); // channel 0, high and low in one transfer, mode 0 58 | outb(0x40, pitFrequency & 0xFF); 59 | outb(0x40, pitFrequency >> 8); 60 | 61 | uint16_t currentCounter = pitFrequency; 62 | uint16_t oldCurrentCounter = pitFrequency; 63 | 64 | // this way we correctly handle a zero counter, as well as an underflow 65 | while((currentCounter <= oldCurrentCounter) && currentCounter) { 66 | oldCurrentCounter = currentCounter; 67 | 68 | outb(0x43, 0x00); // channel 0, latch command 69 | currentCounter = (uint16_t) inb(0x40); 70 | currentCounter |= (uint16_t) inb(0x40) << 8; 71 | } 72 | 73 | uint32_t apicFinal = lapicRead(LAPIC_TIMER_CURRENT); 74 | 75 | // disable the APIC timer 76 | lapicWrite(LAPIC_TIMER_INITIAL, 0); 77 | uint64_t apicTicks = (uint64_t)apicInitial - (uint64_t)apicFinal; 78 | apicFrequency = apicTicks * 100; // the PIT was set up to 100 Hz 79 | 80 | KDEBUG("local APIC frequency is %d MHz\n", apicFrequency / 1000 / 1000); 81 | 82 | // ensure the hardware can go at least twice as fast as the software 83 | if(apicFrequency < (PLATFORM_TIMER_FREQUENCY*2)) { 84 | KERROR("local APIC frequency is not high enough to use as main timing source\n"); 85 | for(;;) platformHalt(); 86 | } 87 | 88 | //KDEBUG("setting up system timer at %d kHz\n", PLATFORM_TIMER_FREQUENCY / 1000); 89 | 90 | // set up the local APIC timer in periodic mode and allocate interrupt 0xFE for it 91 | lapicWrite(LAPIC_LVT_TIMER, LAPIC_TIMER_PERIODIC | LAPIC_LVT_MASK | LAPIC_TIMER_IRQ); 92 | lapicWrite(LAPIC_TIMER_DIVIDE, LAPIC_TIMER_DIVIDER_1); 93 | installInterrupt((uint64_t)timerHandlerStub, GDT_KERNEL_CODE, PRIVILEGE_KERNEL, INTERRUPT_TYPE_INT, LAPIC_TIMER_IRQ); 94 | 95 | // unmask the IRQ and enable the timer 96 | lapicWrite(LAPIC_LVT_TIMER, lapicRead(LAPIC_LVT_TIMER) & ~LAPIC_LVT_MASK); 97 | lapicWrite(LAPIC_TIMER_INITIAL, apicFrequency / PLATFORM_TIMER_FREQUENCY); 98 | 99 | return 0; 100 | } 101 | 102 | /* apicTimerFrequency(): returns the local APIC's frequency */ 103 | 104 | uint64_t apicTimerFrequency() { 105 | return apicFrequency; 106 | } 107 | 108 | /* timerIRQ(): timer IRQ handler 109 | * this is called PLATFORM_TIMER_FREQUENCY times per second */ 110 | 111 | void timerIRQ(void *stack) { 112 | setLocalSched(false); 113 | KernelCPUInfo *info = getKernelCPUInfo(); 114 | info->uptime++; 115 | 116 | // is it time for a context switch? 117 | if(!schedTimer()) { 118 | if(info->thread && info->thread->context) { 119 | platformSaveContext(info->thread->context, stack); 120 | } 121 | 122 | platformAcknowledgeIRQ(NULL); 123 | 124 | // now switch the context 125 | schedule(); 126 | } else { 127 | platformAcknowledgeIRQ(NULL); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/servers/handle.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | /* Kernel-Server Communication */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | int kernelSocket = 0, lumenSocket = 0; 19 | static int *connections; // connected socket descriptors 20 | static struct sockaddr *connaddr; // connected socket addresses 21 | static socklen_t *connlen; // length of connected socket addresses 22 | static void *in, *out; 23 | static int connectionCount = 0; 24 | static bool lumenConnected = false; 25 | 26 | /* serverInit(): initializes the server subsystem 27 | * params: none 28 | * returns: nothing 29 | */ 30 | 31 | void serverInit() { 32 | struct sockaddr_un addr; 33 | addr.sun_family = AF_UNIX; 34 | strcpy(addr.sun_path, SERVER_KERNEL_PATH); // this is a special path and not a true file 35 | 36 | kernelSocket = socket(NULL, AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0); // NEVER block the kernel 37 | if(kernelSocket < 0) { 38 | KERROR("failed to open kernel socket: error code %d\n", -1*kernelSocket); 39 | while(1) platformHalt(); 40 | } 41 | 42 | int status = bind(NULL, kernelSocket, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)); 43 | if(status) { 44 | KERROR("failed to bind kernel socket: error code %d\n", -1*status); 45 | while(1) platformHalt(); 46 | } 47 | 48 | status = listen(NULL, kernelSocket, SERVER_MAX_CONNECTIONS); 49 | if(status) { 50 | KERROR("failed to listen to kernel socket: error code %d\n", -1*status); 51 | while(1) platformHalt(); 52 | } 53 | 54 | connections = calloc(SERVER_MAX_CONNECTIONS, sizeof(int)); 55 | connaddr = calloc(SERVER_MAX_CONNECTIONS, sizeof(struct sockaddr)); 56 | connlen = calloc(SERVER_MAX_CONNECTIONS, sizeof(socklen_t)); 57 | in = malloc(SERVER_MAX_SIZE); 58 | out = malloc(SERVER_MAX_SIZE); 59 | 60 | if(!connections || !connaddr || !connlen || !in || !out) { 61 | KERROR("failed to allocate memory for incoming connections\n"); 62 | while(1) platformHalt(); 63 | } 64 | 65 | KDEBUG("kernel is listening on socket %d: %s\n", kernelSocket, addr.sun_path); 66 | } 67 | 68 | /* serverIdle(): handles incoming kernel connections when idle 69 | * params: none 70 | * returns: nothing 71 | */ 72 | 73 | void serverIdle() { 74 | // check for incoming connections 75 | setLocalSched(false); 76 | 77 | connlen[connectionCount] = sizeof(struct sockaddr); 78 | int sd = accept(NULL, kernelSocket, &connaddr[connectionCount], &connlen[connectionCount]); 79 | if(sd > 0 && sd < MAX_IO_DESCRIPTORS) { 80 | //KDEBUG("kernel accepted connection from %s\n", connaddr[connectionCount].sa_data); 81 | connections[connectionCount] = sd; 82 | connectionCount++; 83 | if(!lumenConnected) { 84 | // connect to lumen 85 | KDEBUG("connected to lumen at socket %d\n", sd); 86 | lumenConnected = true; 87 | lumenSocket = sd; 88 | } 89 | } 90 | 91 | // check if any of the incoming connections sent anything 92 | if(!connectionCount) { 93 | setLocalSched(true); 94 | return; 95 | } 96 | 97 | MessageHeader *h = (MessageHeader *) in; 98 | for(int i = 0; i < connectionCount; i++) { 99 | sd = connections[i]; 100 | ssize_t s = recv(NULL, sd, in, SERVER_MAX_SIZE, MSG_PEEK); // peek to check size 101 | while(s > 0 && s <= SERVER_MAX_SIZE) { 102 | if(h->length > SERVER_MAX_SIZE) { 103 | void *newptr = realloc(in, h->length); 104 | if(!newptr) KPANIC("ran out of physical memory while handling incoming requests\n"); 105 | 106 | in = newptr; 107 | h = (MessageHeader *) in; 108 | } 109 | 110 | recv(NULL, sd, in, h->length, 0); // read the actual message 111 | 112 | if(h->command <= MAX_GENERAL_COMMAND) handleGeneralRequest(sd, in, out); 113 | else if(h->command >= 0x8000 && h->command <= MAX_SYSCALL_COMMAND) handleSyscallResponse(sd, (SyscallHeader *)h); 114 | else { 115 | // TODO 116 | KWARN("unimplemented message command 0x%02X, dropping...\n", h->command); 117 | } 118 | 119 | s = recv(NULL, sd, in, SERVER_MAX_SIZE, MSG_PEEK); 120 | } 121 | } 122 | 123 | setLocalSched(true); 124 | } 125 | 126 | /* serverSocket(): returns the socket descriptor associated with a server 127 | * params: path - path to the server's socket 128 | * returns: socket descriptor, -1 if non-existent 129 | */ 130 | 131 | int serverSocket(const char *path) { 132 | if(!connectionCount) return -1; 133 | 134 | for(int i = 0; i < connectionCount; i++) { 135 | if(!strcmp(connaddr[i].sa_data, path)) return connections[i]; 136 | } 137 | 138 | return -1; 139 | } -------------------------------------------------------------------------------- /src/sched/fork.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /* fork(): forks the running thread 19 | * params: t - pointer to thread structure 20 | * returns: zero to child, PID of child to parent, negative on fail 21 | */ 22 | 23 | pid_t fork(Thread *t) { 24 | schedLock(); 25 | 26 | pid_t pid = processCreate(); 27 | if(!pid) { 28 | schedRelease(); 29 | return -EAGAIN; 30 | } 31 | 32 | // we now have a blank slate process, so we need to create a thread in it 33 | // and deep clone the parent into the child 34 | Process *p = getProcess(pid); 35 | p->parent = t->pid; // NOTICE: not sure if we should be using the PID or TID of the parent 36 | p->threadCount = 1; 37 | p->threads = calloc(p->threadCount, sizeof(Thread *)); 38 | if(!p->threads) { 39 | free(p); 40 | schedRelease(); 41 | return -ENOMEM; 42 | } 43 | 44 | p->threads[0] = calloc(1, sizeof(Thread)); 45 | if(!p->threads[0]) { 46 | free(p->threads); 47 | free(p); 48 | schedRelease(); 49 | return -ENOMEM; 50 | } 51 | 52 | // add the new thread to the queue 53 | p->threads[0]->status = THREAD_QUEUED; 54 | p->threads[0]->next = NULL; 55 | p->threads[0]->pid = pid; 56 | p->threads[0]->tid = pid; 57 | p->threads[0]->context = calloc(1, PLATFORM_CONTEXT_SIZE); 58 | p->threads[0]->signalContext = calloc(1, PLATFORM_CONTEXT_SIZE); 59 | p->threads[0]->highest = t->highest; 60 | p->threads[0]->pages = t->pages; 61 | p->threads[0]->signalMask = t->signalMask; 62 | 63 | // NOTE: fork() only clones one thread, which is why we're not cloning the 64 | // entire process memory, but just the calling thread 65 | p->pages = t->pages; 66 | 67 | if(!p->threads[0]->context || !p->threads[0]->signalContext) { 68 | free(p->threads[0]); 69 | free(p->threads); 70 | free(p); 71 | schedRelease(); 72 | return -ENOMEM; 73 | } 74 | 75 | // and clone the parent's context 76 | if(!platformCloneContext(p->threads[0]->context, t->context)) { 77 | free(p->threads[0]->context); 78 | free(p->threads[0]); 79 | free(p->threads); 80 | free(p); 81 | schedRelease(); 82 | return -ENOMEM; 83 | } 84 | 85 | // clone signal handlers 86 | p->threads[0]->signals = signalClone(t->signals); 87 | 88 | // clone I/O descriptors 89 | Process *parent = getProcess(t->pid); 90 | if(parent) { 91 | memcpy(p->io, parent->io, sizeof(IODescriptor) * MAX_IO_DESCRIPTORS); 92 | p->iodCount = parent->iodCount; 93 | p->umask = parent->umask; 94 | 95 | // increment reference counts for file and socket descriptors and close 96 | // those flagged with O_CLOFORK 97 | for(int i = 0; i < MAX_IO_DESCRIPTORS; i++) { 98 | if(p->io[i].valid) { 99 | if(p->io[i].flags & O_CLOFORK) { 100 | p->io[i].valid = 0; 101 | p->io[i].data = NULL; 102 | p->io[i].flags = 0; 103 | continue; 104 | } 105 | 106 | switch(p->io[i].type) { 107 | case IO_FILE: 108 | FileDescriptor *file = p->io[i].data; 109 | file->refCount++; 110 | break; 111 | case IO_SOCKET: 112 | SocketDescriptor *socket = p->io[i].data; 113 | socket->refCount++; 114 | break; 115 | } 116 | } 117 | } 118 | 119 | // clone working directory 120 | strcpy(p->cwd, parent->cwd); 121 | 122 | // clone command line and process name 123 | strcpy(p->name, parent->name); 124 | strcpy(p->command, parent->command); 125 | 126 | // and process group 127 | p->pgrp = parent->pgrp; 128 | 129 | // if we made this far then the creation was successful 130 | // list the child process as a child of the parent 131 | Process **newChildren = realloc(parent->children, parent->childrenCount+1); 132 | if(!newChildren) { 133 | // we can't add the child to the parent's list 134 | platformCleanThread(p->threads[0]->context, p->threads[0]->highest); 135 | free(p); 136 | return -ENOMEM; 137 | } 138 | 139 | parent->children = newChildren; 140 | parent->children[parent->childrenCount] = p; 141 | parent->childrenCount++; 142 | } 143 | 144 | processes++; 145 | threads++; 146 | schedAdjustTimeslice(); 147 | 148 | // and we're done - return zero to the child 149 | platformSetContextStatus(p->threads[0]->context, 0); 150 | schedRelease(); 151 | return pid; // and PID to the parent 152 | } 153 | -------------------------------------------------------------------------------- /src/libc/stdio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Kernel-Level Partial Implementation of the C Library 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | int putchar(int c) { 16 | ttyPutc(c); 17 | return c; 18 | } 19 | 20 | int print(const char *s) { 21 | ttyPuts(s); 22 | return strlen(s); 23 | } 24 | 25 | int puts(const char *s) { 26 | int len = print(s); 27 | print("\n"); 28 | return len+1; 29 | } 30 | 31 | int printf(const char *f, ...) { 32 | va_list args; 33 | va_start(args, f); 34 | int len = vprintf(f, args); 35 | va_end(args); 36 | return len; 37 | } 38 | 39 | int vprintf(const char *f, va_list args) { 40 | int l = 0; 41 | bool formatter = false; 42 | char format[16]; 43 | char buffer[40]; 44 | uint64_t number; 45 | int formatIndex; 46 | int numberLength, paddingLength; 47 | char paddingCharacter; 48 | char *str; 49 | 50 | while(*f) { 51 | if(!formatter && *f == '%') { 52 | formatter = true; 53 | formatIndex = 0; 54 | memset(format, 0, 16); 55 | f++; 56 | } 57 | 58 | if(formatter) { 59 | if(*f == '%') { 60 | formatter = false; 61 | putchar('%'); 62 | } else { 63 | format[formatIndex] = *f; 64 | formatIndex++; 65 | 66 | if(*f >= 'A' && *f <= 'z') { 67 | formatter = false; 68 | 69 | // parse the format 70 | memset(buffer, 0, 40); 71 | 72 | if(format[0] == '0') { 73 | paddingCharacter = '0'; 74 | } else { 75 | paddingCharacter = ' '; // default behavior for some reason 76 | } 77 | 78 | paddingLength = atoi(format); 79 | 80 | switch(format[formatIndex-1]) { 81 | case 'c': 82 | number = va_arg(args, int); 83 | putchar(number); 84 | break; 85 | case 's': 86 | str = va_arg(args, char *); 87 | print(str); 88 | l += strlen(str); 89 | break; 90 | case 'd': 91 | case 'i': 92 | case 'u': 93 | number = va_arg(args, uint64_t); 94 | ltoa(number, buffer, DECIMAL); 95 | 96 | numberLength = strlen(buffer); 97 | if(numberLength < paddingLength) { 98 | for(int i = 0; i < (paddingLength-numberLength); i++) { 99 | putchar(paddingCharacter); 100 | l++; 101 | } 102 | } 103 | 104 | print(buffer); 105 | l += numberLength; 106 | break; 107 | case 'x': 108 | number = va_arg(args, uint64_t); 109 | ltoa(number, buffer, HEX); 110 | 111 | numberLength = strlen(buffer); 112 | if(numberLength < paddingLength) { 113 | for(int i = 0; i < (paddingLength-numberLength); i++) { 114 | putchar(paddingCharacter); 115 | l++; 116 | } 117 | } 118 | 119 | print(buffer); 120 | l += numberLength; 121 | break; 122 | case 'X': 123 | number = va_arg(args, uint64_t); 124 | ltoa(number, buffer, HEX); 125 | 126 | numberLength = strlen(buffer); 127 | if(numberLength < paddingLength) { 128 | for(int i = 0; i < (paddingLength-numberLength); i++) { 129 | putchar(paddingCharacter); 130 | l++; 131 | } 132 | } 133 | 134 | for(int i = 0; i < numberLength; i++) { 135 | if(buffer[i] >= 'a' && buffer[i] <= 'f') { 136 | putchar(buffer[i] - 0x20); 137 | } else { 138 | putchar(buffer[i]); 139 | } 140 | } 141 | l += numberLength; 142 | break; 143 | default: 144 | print(format); 145 | } 146 | } 147 | } 148 | } else { 149 | putchar(*f); 150 | l++; 151 | } 152 | f++; 153 | } 154 | 155 | return l; 156 | } -------------------------------------------------------------------------------- /src/syscalls/queue.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static SyscallRequest *requests = NULL; // sort of a linked list in a sense 16 | 17 | /* syscallHandle(): generic handler for system calls 18 | * params: ctx - context of the current thread 19 | * returns: nothing 20 | */ 21 | 22 | void syscallHandle(void *ctx) { 23 | setLocalSched(false); 24 | Thread *t = getThread(getTid()); 25 | if(t) { 26 | platformSaveContext(t->context, ctx); 27 | 28 | SyscallRequest *req = platformCreateSyscallContext(t); 29 | 30 | // allow immediate handling of IPC syscalls without going through the 31 | // syscall queue for performance 32 | if((req->function >= SYSCALL_IPC_START && req->function <= SYSCALL_IPC_END) || 33 | (req->function >= SYSCALL_RW_START && req->function <= SYSCALL_RW_END) || 34 | (req->function == SYSCALL_LSEEK)) { 35 | syscallDispatchTable[req->function](req); 36 | 37 | if(req->unblock) { 38 | req->thread->status = THREAD_RUNNING; 39 | platformSetContextStatus(t->context, req->ret); 40 | platformLoadContext(t->context); 41 | } else { 42 | req->thread->status = THREAD_BLOCKED; 43 | } 44 | } else { 45 | syscallEnqueue(req); 46 | t->status = THREAD_BLOCKED; 47 | } 48 | } 49 | 50 | for(;;) schedule(); // force context switch! 51 | } 52 | 53 | /* syscallEnqueue(): enqueues a syscall request 54 | * params: request - pointer to the request 55 | * returns: pointer to the request 56 | */ 57 | 58 | SyscallRequest *syscallEnqueue(SyscallRequest *request) { 59 | schedLock(); 60 | 61 | request->queued = true; 62 | request->unblock = false; 63 | request->busy = false; 64 | 65 | if(!requests) { 66 | requests = request; 67 | } else { 68 | SyscallRequest *q = requests; 69 | while(q->next) { 70 | q = q->next; 71 | } 72 | 73 | q->next = request; 74 | } 75 | 76 | if(request->thread->status == THREAD_BLOCKED) 77 | request->retry = true; 78 | 79 | schedRelease(); 80 | return request; 81 | } 82 | 83 | /* syscallDequeue(): dequeues a syscall request 84 | * params: none 85 | * returns: pointer to the request, NULL if queue is empty 86 | */ 87 | 88 | SyscallRequest *syscallDequeue() { 89 | schedLock(); 90 | if(!requests) { 91 | schedRelease(); 92 | return NULL; 93 | } 94 | 95 | SyscallRequest *request = requests; 96 | requests = requests->next; 97 | request->busy = true; 98 | request->queued = false; 99 | 100 | schedRelease(); 101 | return request; 102 | } 103 | 104 | /* syscallProcess(): processes syscalls in the queue from the kernel threads 105 | * params: none 106 | * returns: zero if syscall queue is empty 107 | */ 108 | 109 | int syscallProcess() { 110 | if(!requests) return 0; 111 | SyscallRequest *syscall = syscallDequeue(); 112 | if(!syscall) return 0; 113 | if(syscall->thread->status != THREAD_BLOCKED) return 0; 114 | 115 | setLocalSched(false); 116 | 117 | // essentially just dispatch the syscall and store the return value 118 | // in the thread's context so it can get it back 119 | if(syscall->function > MAX_SYSCALL || !syscallDispatchTable[syscall->function]) { 120 | KWARN("undefined syscall request %d from tid %d, killing thread...\n", syscall->function, syscall->thread->tid); 121 | schedLock(); 122 | terminateThread(syscall->thread, -1, false); 123 | schedRelease(); 124 | } else { 125 | signalHandle(syscall->thread); 126 | if(syscall->thread->status == THREAD_ZOMBIE) { 127 | setLocalSched(true); 128 | return 1; 129 | } else if(syscall->thread->status == THREAD_QUEUED) { 130 | syscallEnqueue(syscall); 131 | } else if(syscall->thread->status == THREAD_BLOCKED) { 132 | threadUseContext(syscall->thread->tid); 133 | syscallDispatchTable[syscall->function](syscall); 134 | platformSetContextStatus(syscall->thread->context, syscall->ret); 135 | } 136 | } 137 | 138 | if((syscall->thread->status == THREAD_BLOCKED) && syscall->unblock) { 139 | // this way we prevent accidentally running threads that exit() 140 | syscall->thread->status = THREAD_QUEUED; 141 | syscall->thread->time = schedTimeslice(syscall->thread, syscall->thread->priority); 142 | syscall->busy = false; 143 | } 144 | 145 | setLocalSched(true); 146 | return 1; 147 | } 148 | 149 | /* getSyscall(): returns the syscall request structure of a thread 150 | * params: tid - thread ID 151 | * returns: pointer to syscall structure, NULL on fail 152 | */ 153 | 154 | SyscallRequest *getSyscall(pid_t tid) { 155 | Thread *t = getThread(tid); 156 | if(!t) return NULL; 157 | 158 | return &t->syscall; 159 | } 160 | -------------------------------------------------------------------------------- /src/servers/general.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | /* Kernel-Server Communication */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | static void (*generalRequests[])(Thread *, int, const MessageHeader *req, void *res); 22 | 23 | /* handleGeneralRequest(): handles a general server request 24 | * params: sd - socket descriptor to reply to 25 | * params: req - request buffer 26 | * params: res - response buffer 27 | * returns: nothing - reply is sent to socket 28 | */ 29 | 30 | void handleGeneralRequest(int sd, const MessageHeader *req, void *res) { 31 | if(req->response || !req->requester || req->length < sizeof(MessageHeader)) 32 | return; 33 | 34 | Thread *t = getThread(req->requester); 35 | if(!t) return; 36 | 37 | // only lumen and its immediate children can communicate with the kernel 38 | // using this kinda socket 39 | if(req->requester != getLumenPID()) { 40 | Process *p = getProcess(t->pid); 41 | if(!p || p->parent != getLumenPID()) return; 42 | } 43 | 44 | // and dispatch the call 45 | if(generalRequests[req->command]) 46 | return generalRequests[req->command](t, sd, req, res); 47 | else 48 | KWARN("unhandled general request 0x%02X, dropping\n", req->command); 49 | } 50 | 51 | /* serverLog(): prints to the kernel log */ 52 | 53 | void serverLog(Thread *t, int sd, const MessageHeader *req, void *res) { 54 | LogCommand *request = (LogCommand *) req; 55 | ksprint(request->level, request->server, request->message); 56 | } 57 | 58 | /* serverSysinfo(): returns system information */ 59 | 60 | void serverSysinfo(Thread *t, int sd, const MessageHeader *req, void *res) { 61 | SysInfoResponse *sysinfo = (SysInfoResponse *) res; 62 | memcpy(sysinfo, req, sizeof(MessageHeader)); 63 | sysinfo->header.response = 1; 64 | sysinfo->header.status = 0; 65 | sysinfo->header.length = sizeof(SysInfoResponse); 66 | sysinfo->maxFiles = MAX_IO_DESCRIPTORS; 67 | sysinfo->maxSockets = MAX_IO_DESCRIPTORS; 68 | sysinfo->maxPid = MAX_PID; 69 | sysinfo->pageSize = PAGE_SIZE; 70 | sysinfo->uptime = platformUptime(); 71 | 72 | PhysicalMemoryStatus pmm; 73 | pmmStatus(&pmm); 74 | sysinfo->memorySize = pmm.usablePages; 75 | sysinfo->memoryUsage = pmm.usedPages; 76 | sysinfo->processes = processes; 77 | sysinfo->threads = threads; 78 | strcpy(sysinfo->kernel, KERNEL_VERSION); 79 | strcpy(sysinfo->cpu, platformCPUModel); 80 | send(NULL, sd, sysinfo, sizeof(SysInfoResponse), 0); 81 | } 82 | 83 | /* serverRand(): random number generator */ 84 | void serverRand(Thread *t, int sd, const MessageHeader *req, void *res) { 85 | RandCommand *response = (RandCommand *) res; 86 | memcpy(response, req, sizeof(MessageHeader)); 87 | response->header.response = 1; 88 | response->header.length = sizeof(RandCommand); 89 | response->number = platformRand(); 90 | 91 | send(NULL, sd, response, sizeof(RandCommand), 0); 92 | } 93 | 94 | /* getFramebuffer(): provides frame buffer access to the requesting thread */ 95 | 96 | void getFramebuffer(Thread *t, int sd, const MessageHeader *req, void *res) { 97 | // set up response header 98 | FramebufferResponse *response = (FramebufferResponse *) res; 99 | memcpy(response, req, sizeof(MessageHeader)); 100 | response->header.response = 1; 101 | response->header.length = sizeof(FramebufferResponse); 102 | 103 | KTTY ttyStatus; 104 | getTtyStatus(&ttyStatus); 105 | 106 | // we will need to map the frame buffer into the thread's address space 107 | // so temporarily switch to it 108 | if(threadUseContext(t->tid)) return; 109 | 110 | uintptr_t phys = ((uintptr_t)ttyStatus.fbhw - KERNEL_MMIO_BASE); 111 | 112 | size_t pages = (ttyStatus.h * ttyStatus.pitch + PAGE_SIZE - 1) / PAGE_SIZE; 113 | uintptr_t base = vmmAllocate(USER_MMIO_BASE, USER_LIMIT_ADDRESS, pages, VMM_USER | VMM_WRITE); 114 | if(!base) return; 115 | 116 | // and finally map it 117 | for(int i = 0; i < pages; i++) { 118 | platformMapPage(base + (i * PAGE_SIZE), phys + (i * PAGE_SIZE), PLATFORM_PAGE_PRESENT | PLATFORM_PAGE_USER | PLATFORM_PAGE_WRITE); 119 | } 120 | 121 | response->buffer = base; 122 | response->bufferPhysical = phys; 123 | response->w = ttyStatus.w; 124 | response->h = ttyStatus.h; 125 | response->bpp = ttyStatus.bpp; 126 | response->pitch = ttyStatus.pitch; 127 | 128 | // and finally send the response 129 | send(NULL, sd, response, sizeof(FramebufferResponse), 0); 130 | } 131 | 132 | /* dispatch table, much like syscalls */ 133 | 134 | static void (*generalRequests[])(Thread *, int, const MessageHeader *req, void *res) = { 135 | serverLog, // 0 - log 136 | serverSysinfo, // 1 - sysinfo 137 | serverRand, // 2 - rand 138 | NULL, // 3 - request I/O access 139 | NULL, // 4 - get process I/O privileges 140 | NULL, // 5 - get list of processes/threads 141 | NULL, // 6 - get status of process/thread 142 | getFramebuffer, // 7 - request framebuffer access 143 | }; 144 | -------------------------------------------------------------------------------- /src/ipc/sockio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | /* Socket I/O Functions */ 9 | /* send() and recv() are implemented here */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | /* send(): sends a message to a socket connection 20 | * params: t - calling thread 21 | * params: sd - socket descriptor 22 | * params: buffer - buffer containing the message 23 | * params: len - size of the message 24 | * params: flags - optional flags for the request 25 | * returns: positive number of bytes sent, negative error code on fail 26 | */ 27 | 28 | ssize_t send(Thread *t, int sd, const void *buffer, size_t len, int flags) { 29 | if(sd < 0 || sd >= MAX_IO_DESCRIPTORS) return -EBADF; 30 | Process *p; 31 | if(t) p = getProcess(t->pid); 32 | else p = getProcess(getKernelPID()); 33 | if(!p) return -ESRCH; 34 | 35 | if(!p->io[sd].valid || !p->io[sd].data || (p->io[sd].type != IO_SOCKET)) 36 | return -ENOTSOCK; 37 | 38 | SocketDescriptor *self = (SocketDescriptor*) p->io[sd].data; 39 | SocketDescriptor *peer = self->peer; 40 | if(!peer) return -EDESTADDRREQ; // not in connection mode 41 | 42 | acquireLockBlocking(&peer->lock); 43 | 44 | sa_family_t family = self->address.sa_family; 45 | 46 | if(family == AF_UNIX || family == AF_LOCAL) { 47 | if(!peer->inbound || !peer->inboundLen || !peer->inboundMax) { 48 | // ensure that the peer's inbound list exists at all 49 | peer->inbound = calloc(SOCKET_IO_BACKLOG, sizeof(void *)); 50 | peer->inboundLen = calloc(SOCKET_IO_BACKLOG, sizeof(size_t)); 51 | 52 | if(!peer->inbound || !peer->inboundLen) { 53 | releaseLock(&peer->lock); 54 | return -ENOMEM; 55 | } 56 | 57 | peer->inboundMax = SOCKET_IO_BACKLOG; 58 | peer->inboundCount = 0; 59 | } 60 | 61 | if(peer->inboundCount >= peer->inboundMax) { 62 | // reallocate the backlog if necessary 63 | void **newlist = realloc(peer->inbound, peer->inboundMax * 2 * sizeof(void *)); 64 | if(!newlist) { 65 | releaseLock(&peer->lock); 66 | return -ENOMEM; 67 | } 68 | 69 | peer->inbound = newlist; 70 | 71 | size_t *newlen = realloc(peer->inboundLen, peer->inboundMax * 2 * sizeof(size_t)); 72 | if(!newlen) { 73 | releaseLock(&peer->lock); 74 | return -ENOMEM; 75 | } 76 | 77 | peer->inboundLen = newlen; 78 | peer->inboundMax *= 2; 79 | } 80 | 81 | void *message = malloc(len); 82 | if(!message) { 83 | releaseLock(&peer->lock); 84 | return -ENOBUFS; 85 | } 86 | 87 | // and send 88 | memcpy(message, buffer, len); 89 | peer->inbound[peer->inboundCount] = message; 90 | peer->inboundLen[peer->inboundCount] = len; 91 | peer->inboundCount++; 92 | 93 | releaseLock(&peer->lock); 94 | return len; 95 | } else { 96 | /* TODO: handle other protocols in user space */ 97 | releaseLock(&peer->lock); 98 | return -ENOTCONN; 99 | } 100 | } 101 | 102 | /* recv(): receives a message from a socket connection 103 | * params: t - calling thread 104 | * params: sd - socket descriptor 105 | * params: buffer - buffer to store message 106 | * params: len - maximum size of the buffer 107 | * params: flags - optional flags for the request 108 | * returns: positive number of bytes received, negative error code on fail 109 | */ 110 | 111 | ssize_t recv(Thread *t, int sd, void *buffer, size_t len, int flags) { 112 | if(sd < 0 || sd >= MAX_IO_DESCRIPTORS) return -EBADF; 113 | Process *p; 114 | if(t) p = getProcess(t->pid); 115 | else p = getProcess(getKernelPID()); 116 | if(!p) return -ESRCH; 117 | 118 | if(!p->io[sd].valid || !p->io[sd].data || (p->io[sd].type != IO_SOCKET)) 119 | return -ENOTSOCK; 120 | 121 | SocketDescriptor *self = (SocketDescriptor*) p->io[sd].data; 122 | if(!self->peer) return -EDESTADDRREQ; // not in connection mode 123 | 124 | acquireLockBlocking(&self->lock); 125 | 126 | sa_family_t family = self->address.sa_family; 127 | if(!self->inboundCount || !self->inbound || !self->inboundLen) { 128 | releaseLock(&self->lock); 129 | return -EWOULDBLOCK; // no messages available 130 | } 131 | 132 | if(family == AF_UNIX || family == AF_LOCAL) { 133 | // copy from the inbound list 134 | void *message = self->inbound[0]; // FIFO 135 | size_t truelen = self->inboundLen[0]; 136 | 137 | if(!message) { 138 | releaseLock(&self->lock); 139 | return -EWOULDBLOCK; 140 | } 141 | 142 | if(truelen > len) truelen = len; // truncate longer messages 143 | memcpy(buffer, message, truelen); 144 | 145 | // remove the received message from the queue if we're in non-peek mode 146 | if(!(flags & MSG_PEEK)) { 147 | free(message); 148 | 149 | self->inboundCount--; 150 | if(self->inboundCount) { 151 | memmove(&self->inbound[0], &self->inbound[1], self->inboundCount * sizeof(void *)); 152 | memmove(&self->inboundLen[0], &self->inboundLen[1], self->inboundCount * sizeof(size_t)); 153 | } 154 | } 155 | 156 | releaseLock(&self->lock); 157 | return truelen; 158 | } else { 159 | /* TODO: handle other protocols in user space */ 160 | releaseLock(&self->lock); 161 | return -ENOTCONN; 162 | } 163 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![luxOS logo](https://jewelcodes.io/lux/logo-small.png)](https://github.com/lux-operating-system) 4 | 5 | [![License: MIT](https://img.shields.io/github/license/lux-operating-system/kernel?color=red)](https://github.com/lux-operating-system/kernel/blob/main/LICENSE) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/lux-operating-system/kernel)](https://github.com/lux-operating-system/kernel/commits/main/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/01007e2804e34b4da5d164ea927443a5)](https://app.codacy.com/gh/lux-operating-system/kernel/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Build Status](https://github.com/lux-operating-system/kernel/actions/workflows/build-mac.yml/badge.svg)](https://github.com/lux-operating-system/kernel/actions) [![GitHub Issues](https://img.shields.io/github/issues/lux-operating-system/kernel)](https://github.com/lux-operating-system/kernel/issues) 6 | 7 | # 8 | 9 |
10 | 11 | **lux** (intentionally stylized in lowercase) is a portable work-in-progress microkernel written from scratch that currently runs on x86_64, with future plans for an ARM64 port. For the operating system built on the lux microkernel, visit [lux-operating-system/lux](https://github.com/lux-operating-system/lux). 12 | 13 | ![Screenshot of luxOS running on QEMU](https://jewelcodes.io/lux-01-14-24.png) 14 | 15 | # Overview 16 | 17 | In under 5,000 lines [1] of code, lux implements **memory management**, preemptive **multiprocessor priority scheduling**, **interprocess communication**, and basic **Unix-like system calls**. This elimination of bloat minimizes resource consumption compared to mainstream operating systems and increases stability and memory protection. lux is developed primarily as a one-person project, both as a learning and research tool as well as a criticism of the bloat that has become normalized in modern software engineering. 18 | 19 | # Features 20 | 21 | > ⚠️ For a more complete list of features, the full project roadmap, and progress checklist, visit [lux-operating-system/lux](https://github.com/lux-operating-system/lux#progress-checklist). The list below only concerns the microkernel and not luxOS as a whole. 22 | 23 | - [x] **Portability:** At the heart of lux is a platform abstraction layer with a set of functions and constants to be implemented and defined for each platform. This enables ease of porting lux to other CPU architectures. 24 | - [x] **Memory management:** lux implements a future-proof memory manager that can manage practically unlimited physical memory (as limited by hardware) and virtual address spaces of up to 256 TiB for each thread. 25 | - [x] **Multiprocessor priority scheduling:** The scheduler of lux was designed with multiprocessor support from the start. The microkernel itself is also multithreaded and can be preempted. 26 | - [x] **Interprocess communication:** Alongside POSIX signals, lux implements kernel-level support for Unix domain sockets to facilitate communication with the servers. 27 | - [x] **Unix-like system calls:** lux provides a minimal Unix-like API for the common system calls, namely those related to files, sockets, and scheduling. Most of the system calls wrap around external servers implementing the actual functionality. 28 | - [x] **Asynchronous I/O:** I/O system calls implemented by lux are fully asynchronous. The microkernel threads are never blocked, and user processes can explicitly request either blocking or asynchronous system calls in accordance with POSIX. 29 | 30 | # Software Architecture 31 | 32 | lux is a microkernel that provides minimal kernel-level functionality and behaves as a wrapper for a [variety of standalone servers](https://github.com/lux-operating-system/servers) running in user space, which provide the expected OS functionality. This design depends on a user space router ([lux-operating-system/lumen](https://github.com/lux-operating-system/lumen)) to forward or "route" messages between the kernel and the servers. The router additionally doubles as an [init program](https://en.wikipedia.org/wiki/Init). The servers implement driver functionality, such as device drivers, file system drivers, networking stacks, and other higher-level abstractions. lux, lumen, and the servers follow the client-server paradigm and communicate via standard Unix domain sockets. 33 | 34 | This diagram illustrates the architecture of the various components in an operating system built on lux and lumen. It is a work in progress and is subject to change as more components are developed. 35 | 36 | ![Diagram showing the software architecture of lux](https://jewelcodes.io/res/posts/postdata/7d0ff176d0a68f16603c5030937b325f66d8bd777193d.png) 37 | 38 | All of the described components, including the microkernel itself (with the exception of the scheduler), can be preempted and are fully multithreaded applications for a more responsive software foundation. 39 | 40 | # Building 41 | 42 | Visit [lux-operating-system/lux](https://github.com/lux-operating-system/lux) for the full build instructions, starting from the toolchain and ending at a disk image that can be booted on a virtual machine or real hardware. If you don't want to manually build lux, [nightly builds](https://github.com/lux-operating-system/lux/actions/workflows/nightly-mac.yml) also generate a bootable disk image that can be used on a virtual machine. 43 | 44 | # Contributing 45 | 46 | The lux microkernel and the luxOS Project are both personal educational/research projects and are not planned to be community-developed. However, if you like what you're seeing and/or you learned something, monetary contributions would be greatly appreciated and provide a direct incentive to allocate more time to the project. You can support my work on [Patreon](https://patreon.com/luxOS) if you're interested. 47 | 48 | # Contact 49 | Join the project's [Discord server](https://discord.gg/GEeekQEgaB) if you just wanna say hi or talk about OS development in general. 50 | 51 | # License 52 | 53 | The lux microkernel is free and open source software released under the terms of the MIT License. Unix is a registered trademark of The Open Group. 54 | 55 | # Notes 56 | 57 | 1. This figure excludes blank lines, comments, header files, and the platform abstraction layer, retaining only the core microkernel code that forms the basic logic behind lux. 58 | 59 | # 60 | 61 | Made with 💗 from Boston and Cairo 62 | -------------------------------------------------------------------------------- /src/ipc/connection.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lux - a lightweight unix-like operating system 3 | * Omar Elghoul, 2024 4 | * 5 | * Core Microkernel 6 | */ 7 | 8 | /* Socket Connection Functions */ 9 | /* connect(), listen(), and accept() are implemented here */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | /* connect(): creates a socket connection 21 | * params: t - calling thread, NULL for kernel threads 22 | * params: sd - socket descriptor 23 | * params: addr - peer address 24 | * params: len - length of peer address 25 | * returns: zero on success, negative error code on fail 26 | */ 27 | 28 | int connect(Thread *t, int sd, const struct sockaddr *addr, socklen_t len) { 29 | if(sd < 0 || sd >= MAX_IO_DESCRIPTORS) return -EBADF; 30 | Process *p; 31 | if(t) p = getProcess(t->pid); 32 | else p = getProcess(getKernelPID()); 33 | if(!p) return -ESRCH; 34 | 35 | if(!p->io[sd].valid || !p->io[sd].data || (p->io[sd].type != IO_SOCKET)) 36 | return -ENOTSOCK; 37 | 38 | socketLock(); 39 | 40 | SocketDescriptor *self = (SocketDescriptor *) p->io[sd].data; 41 | SocketDescriptor *peer = getLocalSocket(addr, len); 42 | 43 | if(!peer) { 44 | socketRelease(); 45 | return -EADDRNOTAVAIL; 46 | } 47 | 48 | if(self->peer) { 49 | if(!memcmp(self->peer->address.sa_data, addr->sa_data, len)) { 50 | socketRelease(); 51 | return 0; 52 | } 53 | 54 | socketRelease(); 55 | return -EISCONN; 56 | } 57 | 58 | if(self->address.sa_family != peer->address.sa_family) { 59 | socketRelease(); 60 | return -EAFNOSUPPORT; 61 | } 62 | 63 | if(!peer->listener || !peer->backlogMax || !peer->backlog) { 64 | socketRelease(); 65 | return -ECONNREFUSED; 66 | } 67 | 68 | if(peer->backlogCount >= peer->backlogMax) { 69 | socketRelease(); 70 | return -ECONNREFUSED; 71 | } 72 | 73 | // make sure the connection is not already in the backlog 74 | for(int i = 0; peer->backlogCount && (i < peer->backlogCount); i++) { 75 | SocketDescriptor *request = peer->backlog[i]; 76 | if(!memcmp(request->address.sa_data, self->address.sa_data, self->addressLength)) { 77 | socketRelease(); 78 | return -EINPROGRESS; 79 | } 80 | } 81 | 82 | // at this point we're sure it's safe to attempt a connection 83 | peer->backlog[peer->backlogCount] = self; 84 | peer->backlogCount++; 85 | socketRelease(); 86 | return -EWOULDBLOCK; 87 | } 88 | 89 | /* listen(): listens for incoming connections on a socket 90 | * params: t - calling thread, NULL for kernel threads 91 | * params: sd - socket descriptor 92 | * params: backlog - maximum number of queued connections, zero for default 93 | * returns: zero on success, negative error code on fail 94 | */ 95 | 96 | int listen(Thread *t, int sd, int backlog) { 97 | if(sd < 0 || sd >= MAX_IO_DESCRIPTORS) return -EBADF; 98 | Process *p; 99 | if(t) p = getProcess(t->pid); 100 | else p = getProcess(getKernelPID()); 101 | if(!p) return -ESRCH; 102 | 103 | if(!p->io[sd].valid || !p->io[sd].data || (p->io[sd].type != IO_SOCKET)) 104 | return -ENOTSOCK; 105 | 106 | socketLock(); 107 | SocketDescriptor *sock = (SocketDescriptor *) p->io[sd].data; 108 | sock->backlogCount = 0; 109 | 110 | if(backlog > 0) sock->backlogMax = backlog; 111 | else sock->backlogMax = SOCKET_DEFAULT_BACKLOG; 112 | 113 | sock->backlog = calloc(sock->backlogMax, sizeof(SocketDescriptor *)); 114 | if(!sock->backlog) { 115 | socketRelease(); 116 | return -ENOBUFS; 117 | } 118 | 119 | sock->listener = true; 120 | socketRelease(); 121 | return 0; 122 | } 123 | 124 | /* accept(): accepts an incoming socket connection 125 | * this function does NOT block at the kernel level - the syscall dispatcher 126 | * will take care of blocking if the socket is not set to be non-blocking 127 | * params: t - calling thread, NULL for kernel threads 128 | * params: sd - socket descriptor 129 | * params: addr - buffer to store peer's address 130 | * params: len - length of the buffer on input, length of data stored on output 131 | * returns: positive socket descriptor on success, negative error code on fail 132 | */ 133 | 134 | int accept(Thread *t, int sd, struct sockaddr *addr, socklen_t *len) { 135 | if(sd < 0 || sd >= MAX_IO_DESCRIPTORS) return -EBADF; 136 | Process *p; 137 | if(t) p = getProcess(t->pid); 138 | else p = getProcess(getKernelPID()); 139 | if(!p) return -ESRCH; 140 | 141 | if(!p->io[sd].valid || !p->io[sd].data || (p->io[sd].type != IO_SOCKET)) 142 | return -ENOTSOCK; 143 | 144 | SocketDescriptor *listener = (SocketDescriptor *)p->io[sd].data; 145 | if(!listener->listener || !listener->backlog || !listener->backlogMax) { 146 | return -EINVAL; // socket is not listening 147 | } 148 | 149 | if(!listener->backlogCount) { 150 | return -EWOULDBLOCK; // socket has no incoming queue 151 | } 152 | 153 | socketLock(); 154 | 155 | // create a new connected socket 156 | IODescriptor *iod = NULL; 157 | int connectedSocket = openIO(p, (void **) &iod); 158 | if((connectedSocket < 0) || !iod) { 159 | socketRelease(); 160 | return -EMFILE; 161 | } 162 | 163 | iod->type = IO_SOCKET; 164 | iod->flags = p->io[sd].flags; 165 | iod->data = calloc(1, sizeof(SocketDescriptor)); 166 | if(!iod->data) { 167 | socketRelease(); 168 | closeIO(p, iod); 169 | return -ENOMEM; 170 | } 171 | 172 | // copy the self address 173 | SocketDescriptor *self = (SocketDescriptor *)iod->data; 174 | self->refCount = 1; 175 | memcpy(&self->address, &listener->address, sizeof(struct sockaddr)); 176 | self->addressLength = listener->addressLength; 177 | self->type = listener->type; 178 | self->protocol = listener->protocol; 179 | self->process = listener->process; 180 | 181 | // and assign the peer address 182 | self->peer = listener->backlog[0]; // TODO: is this always FIFO? 183 | self->peer->peer = self; 184 | memmove(&listener->backlog[0], &listener->backlog[1], (listener->backlogMax - 1) * sizeof(SocketDescriptor *)); 185 | listener->backlogCount--; 186 | 187 | // save the peer address if requested 188 | if(addr && len) { 189 | if(*len > sizeof(struct sockaddr)) *len = sizeof(struct sockaddr); 190 | memcpy(addr, &self->peer->address, *len); 191 | } 192 | 193 | socketRelease(); 194 | return connectedSocket; 195 | } 196 | --------------------------------------------------------------------------------