A work in progress time-sharing operating system for concurrency on the Raspberry Pi 3.
A project showcasing the capability of this project can be found here. This showcase project is Conway's Game of Life where each cell is a thread that runs concurrently.
This operating system is intended to still be used for very low-level programming somewhat similar to a real-time operating system. Additionally, it was also created to learn more about operating systems and how they work.
- Preemptive Round-Robin Scheduling: Ensures fair CPU time distribution among threads with configurable time quantums.
- System Calls: Provides essential system calls for memory management and thread control.
- Memory Management: No virtual memory with paging and heap allocation.
- Thread Management: All threads, including the kernel, run at EL1. All threads run on a single core.
- Utilities:
Mutex
: Class that provides a way to create mutual exclusion locks.MutexLockGuard
: Class that provides a way to create mutual exclusion blocks of code.AtomicGuard
: Class that provides a way to create atomic blocks of code. (Likely will be removed in future versions)
Threads are the fundamental unit of execution in this operating system. Each thread is allocated a single 4KB page of memory and is scheduled by the kernel. Threads are not tied to each other, that is, there is no concept of parent and child threads. All threads (except the main startup thread) are created by the user and are managed by the kernel.
It is worth noting that everytime a system call is made, the scheduler preempts the callee thread and it will have to wait throught the entire round-robin cycle before it can be scheduled again. This means system calls are blocking and expensive.
This call sets the debug output handler for the kernel. The output handler is a function that takes a const char*
and writes it to the output device. This is intended to be used for debugging purposes.
This call writes a message to the output handler. The output handler must have been initialized, or nothing will happen. This is intended as more of a test system call than anything else but can be used to safely transmit over UART if output handler is configured to do so.
This call allocates a block of memory on the heap and returns a pointer to the allocated block. If no memory is available, it returns nullptr
. This is analogous to malloc
in C.
This call frees a previously allocated block of memory on the heap.
This call reallocates a previously allocated block of memory on the heap.
This call yields the current thread, allowing the scheduler to switch to another thread.
This call exits the current thread and cleans up any resources it was using.
This call sleeps for at least the specified number of microseconds before resuming execution.
This call spawns a new thread and populates a ThreadHandle
that can be used to join the thread later.
The system call protocol is as follows:
- The system call code is passed into the
w8
register. - A single system call argument is passed into the
x0
register. (This can be a pointer to a structure to pass multiple arguments) - The system call is executed by calling the
svc
instruction.
The API provided abstracts this protocol away from developers.
When writing code that utilizes this operating system, it is intended that you only include header files from include/api
as these are the only files that are intended to be used by the user. The include/kernel
directory is for internal kernel use only.
The operating system is intended to be simple but useful. It is designed to be a time-sharing operating system that can run multiple threads concurrently. The system is designed to run on a single core and all threads run at EL1 (as of now). The system is designed to be preemptive and uses round-robin scheduling with configurable time quantums for each thread.
Each thread is allocated a single 4KB page of memory. The memory layout for each thread is described below.
The stack is located at the top of the memory page and grows downwards. The stack is intended to be half the size of the memory page, but this is not enforced and can certainly overflow into the heap.
The heap is located at the bottom of the memory page and grows upwards. The heap is constrained to be the other half of the memory page and cannot grow beyond that. The heap is implemented as a first-fit allocator with a simple free list. The heap is intended to be used for dynamic memory allocation but is prone to fragmentation.
Developed and tested by connellr023 in 2024/2025.
This project is licensed under the MIT License.