Extremely simple multitask
Posted: Tue Nov 05, 2024 8:05 am
Hi everybody
I've been working on an extremely simple multitasking kernel, mainly to understand, explain and play with basic concepts and I have a very specific question on how to improve multitasking upon blocking syscalls.
Extremely simple means:
To ask the question let me show you some code. This is the handler of the timer (18 Hz and switches processes every time it interrupts)
The handler of the syscalls is similar. It saves all registers (I know it's not necessary to save all of them), except rax.
Now, let's consider the simplest syscall that implies preemption: yield. This syscall essentially enable interrupts (sti) and waits (hlt) for the next timer interrupt. After this interrupt, the stack of this process looks like this:
Soon, this process will be chosen by the scheduler in _timerHandler. popState and iretq will restore process A to the point immediately after hlt:
Then returns from the syscall yield
And _syscallHandler does the trick
Nothing happened.
This works but I don't want to wait for an interrupt to continue with the next process, besides an interrupt is costly. Forcing the same interrupt by software (int 0x20) is not completely correct since it would send the EOI for a non HW generated interrupt. Instead, I'd like to switch tasks with a function, however the stack of this process, when it's running inside yield, does not look like the stack of a process preempted by the timer. We have return addresses and local variables.
I don't understand how a kernel stack for each process would help with this scenario. (If it really does)
Is there any alternative? Opinions?
Thanks!
I've been working on an extremely simple multitasking kernel, mainly to understand, explain and play with basic concepts and I have a very specific question on how to improve multitasking upon blocking syscalls.
Extremely simple means:
- 2 binaries: kernel and user. The only mechanism to interact is syscalls (int 0x80)
- No memory protection, flat memory model
- Everything runs on ring 0
- Just 1 core
- No special registers (-mgeneral-regs-only)
- Handles the timer, the keyboard and syscalls
- No file system
- Process creations takes just a pointer to a function in the user binary. They are more like threads since they share the address space :p
- No kernel stacks
- Scheduler preemptive RR, no priorities
- The same memory manager is used for kernel and user (for example, one that alloc fixed size chunks)
- The linker output format is binary
- Arch: x86_64 (I know... it's like using a Ferrari to go grocery shopping)
- Bootloader: Pure64
To ask the question let me show you some code. This is the handler of the timer (18 Hz and switches processes every time it interrupts)
Code: Select all
_timerHandler:
pushState ; pushes r8-r15 rsi rdi rbp rdx rcx rbx rax
mov rdi, rsp
call sched ; Just returns rsp of the next ready process
mov rsp, rax
; Send EOI - omitted for this post
popState
iretq
Code: Select all
_syscallHandler:
pushStateNoRax ; Same as before, except rax
call syscallDispatcher
popStateNoRax
iretq
Code: Select all
| ... |
| CS:IP RFLAGS SS:SP | <- int 0x80
| pushStateNoRax | <- _syscallHandler
| ret addr / vars | <- return addresses and local vars from syscallDispatcher, yield, etc
| CS:IP RFLAGS SS:SP | <- timer interrupt
| pushState | <- _timerHandler
Code: Select all
| ... |
| CS:IP RFLAGS SS:SP | <- int 0x80
| pushStateNoRax | <- _syscallHandler
| ret addr / vars | <- return addresses and local vars from syscallDispatcher, yield, etc
Code: Select all
| ... |
| CS:IP RFLAGS SS:SP | <- int 0x80
| pushStateNoRax | <- _syscallHandler
Code: Select all
| ... |
This works but I don't want to wait for an interrupt to continue with the next process, besides an interrupt is costly. Forcing the same interrupt by software (int 0x20) is not completely correct since it would send the EOI for a non HW generated interrupt. Instead, I'd like to switch tasks with a function, however the stack of this process, when it's running inside yield, does not look like the stack of a process preempted by the timer. We have return addresses and local variables.
I don't understand how a kernel stack for each process would help with this scenario. (If it really does)
Is there any alternative? Opinions?
Thanks!