thxbb12 wrote:
This works well, each task gets scheduled properly in a round-robin fashion as expected.
However, this implementation suffers from two issues:
- Executing iret in a task to go back to the scheduler triggers a general protection fault (GPF)
If your scheduler is a separate task, why do you iret to get to the scheduler? Shouldn't you call into the scheduler via a TASK gate? That will set NT and the LINK flag, which your scheduler will overwrite to select the next task.
thxbb12 wrote:
- Once in the exception handler which is also a task, I'm not able to go back to the scheduler more than once without triggering a double fault exception.
Is the scheduler already locked? If your exception is invoked while the scheduler is in use, you can't then switch back to the scheduler except by returning to it using the nested task mechanism. So if your scheduler is already in the back link chain, and you try to switch to the scheduler again via a task gate, its TSS will already be marked as in use and trigger your exception, which could well result in a double fault if the resulting GPF also cannot be handled.
thxbb12 wrote:
Does anyone have an idea what I do wrong above?
- Why does executing iret in a task to go back to the scheduler triggers a general protection fault?
- Why is the way I "kill" a task doesn't seem to work?
From my understanding of how the CPU works, what I do should work, but clearly there is something I don't understand.
Thanks a lot for your help
There is a reason most people [citation required] avoid hardware task switching on the x86 - it's just a nasty mess.
Unless you're particularly interested in the x86 hardware task mechanism itself, I'd recommend dumping it and switching to a software mechanism, for the following benefits:
- It is simpler.
- It will be easier to debug as a result.
- It will probably perform better as a result (less state to save).
- It is more portable conceptually to other architectures, if and when you move beyond x86 (that includes amd64, which doesn't have TSS based task switching in long mode.)
- It is more scalable, you're not limited in the number of tasks by hardware imposed GDT limits.
Using software task switching, you just need a single TSS per CPU, which is required because the TSS contains the ESP for the kernel stack to use when entering the kernel via an interrupt from user mode.
When you switch tasks in a software manner, all you have to do is:
- Switch callee saved registers as defined by your C ABI. On x86, that'd be ebx, edi, esi, ebp and esp. All other registers are scratch registers in the default x86 ABI.
- Put the new kernel stack into the TSS.esp0, for use when switching from user mode to kernel mode in the incoming thread.
- Switch to your new thread's page table by updating cr3.
That's it!
If your incoming thread has user level state, it'll be reachable via the incoming kernel stack, so user state other than the page table doesn't have to be explicitly switched.
Contrast that to your current hardware task switching, where you read/write all of:
- General purpose registers - eax, ebx, ecx, edx, edi, esi, ebp, esp
- Segment registers - cs, ds, ss, es, fs, gs - with corresponding segment loads for the new values
- eip and eflags
In my kernel, my software task switch is simply a setjmp/longjmp, plus an update of TSS.esp0 and cr3, and even those latter two can be optionally deferred until returning to user mode (or user memory access is required from the kernel.)
My entire task switch is thus:
Code:
void arch_thread_switch(thread_t * thread)
{
thread_t * old = arch_get_thread();
if (old == thread) {
thread->state = THREAD_RUNNING;
return;
}
if (old->state == THREAD_RUNNING) {
old->state = THREAD_RUNNABLE;
}
if (old->process != thread->process) {
if (thread->process) {
vmap_set_asid(thread->process->as);
} else {
vmap_set_asid(0);
}
}
if (0 == setjmp(old->context.state)) {
if (thread->state == THREAD_RUNNABLE) {
thread->state = THREAD_RUNNING;
}
tss[1] = (uint32_t)thread->context.stack + ARCH_PAGE_SIZE;
current = thread;
longjmp(thread->context.state, 1);
}
}