Experimenting with TilekarOS¶
TilekarOS is designed to be a playground for learning OS internals. This guide shows you how to "mess" with the system, create tasks, and intentionally trigger failures to see how the kernel responds.
1. Creating Kernel Tasks (Ring 0)¶
Kernel tasks run with full privileges. They are easy to create and can access any part of the hardware.
How to add a task:¶
- Define a function in
kernel/kernel.c: - Register it in
kernel_main():
Voluntary vs. Preemptive:¶
task_yield(NULL): Manually tells the scheduler to pick the next task.- Preemption: If you don't call yield, the Timer Interrupt (IRQ 0) will eventually force a context switch after its time slice.
2. Creating User Tasks (Ring 3)¶
User tasks are isolated and run in their own virtual address space. They cannot access kernel memory or execute privileged instructions.
The Lifecycle of a User Task¶
In TilekarOS, a user task is typically defined as a block of code (assembly or C) that is copied into a new address space.
Step 1: Define the User Code¶
User tasks use System Calls to interact with the kernel. See user_process.asm for a complete example of a user task using syscalls to list directories.
Code Preview: user_process.asm
[BITS 32]
section .text
global _start_user_task
global _end_user_task
_start_user_task:
; 1. Create directory /TESTDIR
mov eax, 6 ; SYS_MKDIR
call .get_dir_path
db "/TESTDIR", 0
.get_dir_path:
pop ebx
int 0x80
; 2. Open root directory /
mov eax, 3 ; SYS_OPEN
call .get_root_path
db "/", 0
.get_root_path:
pop ebx
mov ecx, 0
int 0x80
push eax ; Save root FD
; 3. List root directory
mov eax, 1 ; SYS_WRITE
mov ebx, 1
call .get_msg_list_root
db "Root Directory Listing:", 10, 0
.get_msg_list_root:
pop ecx
mov edx, 24
int 0x80
sub esp, 264 ; vfs_dirent_t buffer
mov edi, 0 ; index
.loop_root:
mov eax, 9 ; SYS_READDIR
mov ebx, [esp + 264] ; root FD
mov ecx, edi ; index
mov edx, esp ; out buffer
int 0x80
cmp eax, 0
jne .done_root
; Print entry name
mov eax, 1 ; SYS_WRITE
mov ebx, 1
mov ecx, esp ; dirent->name
mov edx, 256 ; max name len
int 0x80
mov eax, 1 ; SYS_WRITE
mov ebx, 1
call .get_newline
db 10
.get_newline:
pop ecx
mov edx, 1
int 0x80
inc edi
jmp .loop_root
.done_root:
add esp, 264
; 4. Close root FD
mov eax, 5 ; SYS_CLOSE
pop ebx
int 0x80
; 5. Delete directory /TESTDIR (using unlink for now as rmdir is alias)
; Actually, let's just exit
mov eax, 0 ; SYS_EXIT
int 0x80
_end_user_task:
Step 2: Launch the Task¶
In kernel_main, use the extern symbols to pass the code block to task_create_user:
extern char _start_user_task;
extern char _end_user_task;
task_create_user(&_start_user_task, &_end_user_task);
What happens under the hood?¶
- Memory Mapping: The code block is copied to a new page directory starting at
0x08048000. - Stack Setup: A user-mode stack is automatically mapped at
0xB0000000. - Ring Transition: The kernel uses the
iretinstruction to drop the CPU's privilege level to Ring 3. - TSS Update: The Task State Segment (TSS) is updated with the task's kernel stack pointer (
esp0) so the CPU can transition back to Ring 0 during interrupts or syscalls.
3. How to Make Things Fail¶
Testing the kernel's robustness is part of the fun. Here is how you can trigger common CPU exceptions.
A. Division by Zero (ISR 0)¶
Add this to any task:
B. Page Fault (ISR 14)¶
- From Kernel:
*(int*)0x0 = 123;(Accessing NULL). - From User: Trying to read kernel memory (addresses above
0xC0000000).
C. General Protection Fault (ISR 13)¶
Triggered in User Mode if you try to execute privileged instructions:
4. Debugging Tips¶
Headless Logging (Serial COM1)¶
TilekarOS mirrors all printf output to the serial port 0x3F8. Add -serial stdio to QEMU to see these logs in your terminal.