So, ich versuche mich jetzt auch mal daran, das zu erklären. Ich bin zwar nicht PorkChicken, aber vielleicht hilfts ja trotzdem.
Also gehen wir das Ganze mal Schritt für Schritt durch, ausgegangen vom folgenden einfachen Usermode-Programm:
int main() {
puts("Hallo Welt!");
return 0;
}
Nun wie schon von taljeth erwähnt ist die main()-Funktion nicht der direkte Einsprungspunkt in den Task. Dafür hast du in der libc eine Funktion _start:
void start() {
int result;
// Hier würde man normalerweise noch sowas machen wie Parameter vom Kernel holen und parsen, aber das lassen wir der Einfachheit halber mal weg.
result = main();
syscall_exit(result);
}
Dabei ist es wichtig, dass die Funktion syscall_exit() nie zurückkehrt von einem Aufruf. Es handelt sich dabei ja, wie der Name sagt, um einen Syscall, der als Interrupt umgesetzt wird.
Wenn nun der Kernel diesen Syscall erhält, entfernt er den aktuellen Task aus dem Scheduler, und zestört (Speicher des Tasks und interne Strukturen werden freigegeben) ihn. Danach muss er logischerweise einen neuen Task schedulen, da der bisherige nicht mehr existiert.
In tyndur geht das so, dass der Interrupt-Handler eh immer den nächsten zu benutzenden Stackpointer zurueckgibt, und auch cr3 für den neuen Task lädt, wenn er gewechselt hat. Damit muss der Syscall-Handler im Wesentlichen nur current_task neu setzen und der Interrupthandler übernimmt dann den Rest.
So, ich hoffe, dass ich dich damit nicht noch mehr verwirrt habe.
Edit: Bäh, der PorkChicken war schneller. Aber ich lasse das jetzt trotzdem mal stehen.