Autor Thema: Multitasking - Frage zum Taskwechsel  (Gelesen 5676 mal)

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« am: 01. July 2009, 09:52 »
Hallo,
ich habe da noch eine Frage zum Taskwechsel.
Ich pushe den kompletten Stack und rufe den Interrupt-Handler auf. Dieser ruft dann die Funktion zum Taskwechsel auf und übergibt den gepushten Stack. Also wie folgt:
u32int_t new_esp = multitasking_schedule(context);Wenn ich vom einen zum nächsten Prozess wechsle, speichere ich den Stack wie folgt:
// context needs to be saved when Multitasking is going around
// not directly after initialising it
if(current != NULL)
{
printf("0x%x - 0x%x - ", ((struct regs*)context)->esp, context);
current->kernel_stack = ((struct regs*)context)->esp;
//current->kernel_stack = context;
}


Meine Frage ist nun, muss ich esp des contexts speichern oder komplett den context?
 
 
Gruß Christian

bluecode

  • Beiträge: 1 391
    • Profil anzeigen
    • lightOS
Gespeichert
« Antwort #1 am: 01. July 2009, 10:10 »
Meine Frage ist nun, muss ich esp des contexts speichern oder komplett den context?
Das kommt darauf an, wenn du den Stack nicht kaputtmachst (d.h. im Prinzip ein Kernelstack pro Thread), dann musst du nur esp speichern. Aber du musst natürlich beim Taskswitchen auch den Stack wechseln.
Wenn du aber nur einen Kernelstack (pro CPU) hast bzw. haben willst, dann musst du das was auf dem Stack liegt in die Prozess/Threadstruktur speichern. Erst dann kannst du den Stack weiterverwenden.
lightOS
"Überlegen sie mal 'nen Augenblick, dann lösen sich die ganzen Widersprüche auf. Die Wut wird noch größer, aber die intellektuelle Verwirrung lässt nach.", Georg Schramm

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #2 am: 01. July 2009, 10:29 »
Meine Frage ist nun, muss ich esp des contexts speichern oder komplett den context?
Das kommt darauf an, wenn du den Stack nicht kaputtmachst (d.h. im Prinzip ein Kernelstack pro Thread), dann musst du nur esp speichern. Aber du musst natürlich beim Taskswitchen auch den Stack wechseln.
Wenn du aber nur einen Kernelstack (pro CPU) hast bzw. haben willst, dann musst du das was auf dem Stack liegt in die Prozess/Threadstruktur speichern. Erst dann kannst du den Stack weiterverwenden.
Der Stack wird gewechselt, nur habe ich den Code nicht gepostet...
Momentan sieht es so aus, dass ein Stack pro Prozess erstellt wird. Beim wechsel des Stacks wird momentan esp in kernel_stack abgelegt. Das geht solange gut, bis ich vom letzten wieder zum ersten Stack wechsle. Dann kommt ein "General Protection Fault". Woran könnte dass den liegen? Im IRC wurde mir gesagt, dass ich da was falsches auf den Stack schiebe...

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 01. July 2009, 12:22 »
Hi,

wenn du deinen Code nicht zeigst, können wir dir wenig helfen.

Falls du nur einen Tipp suchst, wie man sowas debuggt: Breakpoints in den Interrupt-Handler nach dem push und vor den pops setzen (und gegebenenfalls an weiteren interessanten Stellen), und Stacks sehr, sehr genau anschauen.
Dieser Text wird unter jedem Beitrag angezeigt.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #4 am: 01. July 2009, 13:52 »
Gut,
dann poste ich mal etwas code:
 
Funktion zum wechseln des Prozesses:
u32int_t multitasking_schedule(u32int_t context)
{
printf("Schedule begin... ");
// Save current task in local variable
task_t *current = multitasking_management_unit.current_task, *next = NULL;

if(!multitasking_initialised)
return(context);

printf("%i - ", (current!=NULL)?current->id:0);

// is current task set, then we need to save page directory
if(current != NULL)
current->cr3 = (u32int_t*)multitasking_get_cr3();

// do we need to change page directory?
if(!vmm_kernel_directory_loaded())
vmm_load_kernel_directory();

// get the next task
next = multitasking_get_next_task(current);
if(next == NULL)
{
// Switch to tasks page directory
vmm_switch_page_directory(next->cr3);
return(context);
}
// switch to next when next state is blocked
while(next->state != MULTITASKING_READY_FOR_EXECUTION)
{
printf("\tcurrent task is blocked!\n");
next = multitasking_get_next_task(next);
}

// context needs to be saved when Multitasking is going around
// not directly after initialising it
if(current != NULL)
{
printf("0x%x - 0x%x - ", ((struct regs*)context)->esp, context);
current->kernel_stack = ((struct regs*)context)->esp;
//current->kernel_stack = context;
}

// Reset current_task to next
multitasking_management_unit.current_task = next;

printf("%i ", next->id);

// Switch to tasks page directory
vmm_switch_page_directory(next->cr3);

printf("...Schedule end\n");

// and at last return the new stack
return(next->kernel_stack);
}

IRQ-Handler (hier wird die Funktion zum wechseln des Prozesses aufgerufen):
u32int_t irq_handler(u32int_t context)
{
struct regs *r = (struct regs *)context;
u32int_t new_esp;

/* This is a blank function pointer */
void (*handler)(struct regs *r);

/* Find out if we have a custom handler to run this
* IRQ, and finally run it */
handler = irq_routines[r->int_no - 32];
if(handler)
handler(r);

/* Try to schedule to next task */
new_esp = multitasking_schedule(context);

/* If the IDT entry that was invoked was greater than 40
* (meaning IRQ8 - 15), then we need to send EOI to
* the slave controller */
if(r->int_no >= 40)
outportb(0xA0, 0x20);

/* In either case, we need to send an EOI to the master
* internet controller too */
outportb(0x20, 0x20);

/* at last return new/old esp */
return(new_esp);
}

Als letztes noch den ASM-Part, der den Interrupthandler aufruft:
; 32: irq0
irq0:
cli
push byte 0 ; Note that these don't push an error_code onto the stack:
; We need to push a dummy error code
push byte 32
jmp irq_common_stub

...

irq_common_stub:
; store registers
pusha
push ds
push es
push fs
push gs
; Load the Kernel Data Segment descriptor!
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; Call C-Handler
push esp
call irq_handler
add esp, 4
; change stack
mov esp, eax
; restore registers
pop gs
pop fs
pop es
pop ds
popa

add esp, 8 ; Cleans up the pushed error code and pushed ISR number
iret ; pops 5 things at once: CS, EIP, EFLAGS, SS and ESP

Das Ausführen der Prozesse, sofern diese noch nicht ausgeführt wurden, funktioniert einwandfrei. Doch sobald vom letzten zum ersten geschaltet wird, tritt ein Fehler auf. Also muss ja folglich die Adresse in ESP falsch sein, oder ich mache etwas falsch...
Bei Bedarf packe ich den kompletten code mal als zip und lade ihn hoch...
 
Gruß Christian

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 01. July 2009, 19:27 »
Also beim ersten Durchlesen sieht folgendes sehr verdächtig aus:
// get the next task
next = multitasking_get_next_task(current);
if(next == NULL)
{
// Switch to tasks page directory
vmm_switch_page_directory(next->cr3); // <-------- DITTE
return(context);
}
Dieser Text wird unter jedem Beitrag angezeigt.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #6 am: 01. July 2009, 21:13 »
Mmmhhh...
Das ist mir bisher noch nicht aufgefallen, da ja das kernel page directory geladen werden soll. Ich habe das nun korrigiert, doch des Rätsels Lösung ist es nicht, da der GPF immer noch auftritt...
Ich hatte aber bis jetzt noch nicht die Zeit ein wenig weiter das debugging zu betreiben. :)
 
Laut dem Log von Bochs ist CS nicht gesetzt:
00024281251e[CPU0 ] iret: return CS selector nullDas lässt mich auf einen kaputten oder falsch gepushten Stack schließen. Dann sollte der General Protection Fault doch aber eigentlich gleich kommen, sobald der erstellte Prozess ausgeführt wird und nicht erst nach dem Taskwechsel, wenn man esp zurückgibt oder irre ich mich da gerade?
 
Ich habe nun mal etwas weiter herum experimentiert und mal die aktuelle Version des Quellcodes hochgeladen. Dieser ist hier zu finden: Quellcode.
Mir kommt da allerdings noch eine kleine Frage. Muss ich denn den Stack für tss.esp0 auch mit Werten füllen (wie auch beim normalen Prozess), oder reicht das, wenn ich nur eine Speicherseite reserviere, mappe und dann in der TSS eintrage?
 
EDIT
Hat sich erledigt.
Nun bekomme ich einen anderen Fehler, den ich aber eigentlich recht schnell in den Griff bekommen sollte.
« Letzte Änderung: 07. July 2009, 14:59 von ChristianF »

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #7 am: 14. July 2009, 07:47 »
*push*
Es gibt noch ein paar Unklarheiten...  :roll:
 
Und zwar habe ich mir einfach mal bei jedem Taskwechsel die ID, den stack und den gepushten context ausgeben lassen. Dabei ist folgendes herausgekommen:
1: 0x218fbc 0x216fbc
2: 0x21ffbc 0x216fbc
Ist das normal, dass das anscheinend immer gleich ist?
 
 
Dann ist da noch etwas. Das Ausführen der Tasks funktioniert, solange man keine Funktionen aufruft. Rufe ich eine Funktion in einem Task auf, und wechsle danach wieder zu einem Task, der bereits ausgeführt wurde, so tritt der Page Fault auf.
Ich habe aber noch etwas weiter herumprobiert und herausgefunden, dass die Page 0x216000 für esp0 im TSS reserviert wurde (was bei jedem Task gleich ist (evtl. ein Fehler)). Irgend etwas muss also an der Funktion kaputt sein, die den context zusammenstellt...
Wie macht ihr das? Wie kommt ihr an den richtigen (zuvor erstellten) stack, wird der sonst noch irgendwo gespeichert?

 
Ich bin dem Fehler anscheinend auf die Schliche gekommen. Es liegt nicht am gepushten context und auch nicht am esp0.
Und zwar sucht eine Funktion immer den nächsten Task raus. Nun habe ich mir immer die jeweiligen werte (esp, eip, ...) ausgeben lassen (bei jedem Taskwechsel). Interessant hierbei ist, dass der Wert von eip im stack des nächsten Tasks sich verändert hat. Es steht also statt 0xa000001d 0xa0000015, was eine Ursache für den Zugriff auf die Adresse 0 und somit für den Page Fault sein könnte.

EDIT
Ich habe die Ursache gefunden, weiß allerdings nicht, wie ich Sie beheben soll.
Wenn die Funktion zum wechseln des Tasks aufgerufen wird, wird immer der context gepusht (die ganzen register). Nun wird aber beim Wechsel von Ring 3 nach Ring 0 der Stack in tss.esp0 geladen.Wo finde ich dann meinen alten Stack, den ich für den Prozess erstellen musste, wieder?
 
EDIT 2
Problem gelöst. Der Fehler war der, dass ich nur einen Stack für das esp0 des tss hatte. Dadurch wurde dies immer wieder überschrieben und hat zu dem Page Fault gefüht.  :roll:
Mal so nebenbei, kann es sein, dass die Forenuhr falsch eingestellt ist, oder ist das nur bei mir so?
« Letzte Änderung: 14. July 2009, 14:45 von ChristianF »

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 14. July 2009, 16:52 »
Mal so nebenbei, kann es sein, dass die Forenuhr falsch eingestellt ist, oder ist das nur bei mir so?
Jap. Der Server lebt auch nicht in meiner Zeitzone. (Geht 2Stdunden zurück). Die Uhrzeit kannst du dir aber hier anpassen.
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #9 am: 27. July 2009, 14:21 »
Ich habe den Fehler gefunden und behoben (ist schon ein paar Tage her). Derweil habe ich mal weiter geschraubt und bin nun soweit, dass ausführbare Programme (ELF-Format) aus meiner InitRD ausgelesen und ausgeführt werden, es wird also ein Task angelegt.
 
Wenn ich nun an den Grafiktreiber denke, dann muss dieser ja auf verschiedene Ports zugreifen.
Das System bekommt per System Call eine Anforderung mit einem bestimmten Port. Dieser wird geprüft, ob er nicht bereits von einem anderen Task genutzt wird und dann je nach dem freigegeben oder nicht freigegeben.
Hierzu habe ich eine globale Bitmap angelegt, damit nicht 2 Tasks gleichzeitig auf den gleichen Port zugreifen können. Nun muss ich aber noch in der TSS diesen Port als freigegeben setzen. Wo geschieht dies genau? Ich habe ein 16Bit-Feld "iomap_base". Muss hier eine Adresse rein, sprich ich muss für jeden Task diese Bitmap reservieren und dann immer den Wert in diesem Feld ändern, oder wie ist das?

Quasi so:
//....
// Irgendwo beim initialisieren des tasks
new_task->iobitmap = kmalloc(sizeof(u8int_t)*8192);
//....
// Irgendwo beim wechseln des Tasks
tss.iomap_base = task->iobitmap;
Oder habe ich da doch was falsch verstanden...
« Letzte Änderung: 27. July 2009, 14:26 von ChristianF »

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #10 am: 27. July 2009, 17:31 »
Hallo,


diese Bit-Map kommt direkt ins TSS.
"iomap_base" ist quasie die Basis der Bit-Map innerhalb des TSS.
Wie groß Deine Bit-Map ist weiß die CPU anhand der Segment-Größe aus dem zugehörigem TSS-Descriptor. Ich weiß allerdings nicht genau was die CPU mit Ports macht die außerhalb Deiner Bit-Map liegen, ich glaube die sind per default geblockt.


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #11 am: 27. July 2009, 19:55 »
Gut okay.
Das ist dann wohl geklärt. Ich werde jetzt erst einmal Intel Docs weiter wälzen. Vom Prinzip her habe ich es verstanden und auch das Umsetzen letzen Endes war ein Kinderspiel...

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #12 am: 27. July 2009, 21:36 »
@ChristianF: Wie hast du es denn umgesetzt? Hast du für jeden Task, dem irgendwelche IO-Ports gehören ein eigenes TSS? Oder kopierst du jedes Mal?
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #13 am: 28. July 2009, 07:45 »
Ich habe ein TSS für alle Prozesse und eine extra Bitmap für jeden Prozess. Bei jedem Taskwechsel, sowie der Anforderung eines neuen oder der Freigabe eines reservierten Ports wird die bitmap des TSSs mit der des Tasks überschrieben.
Das ist vielleicht nicht die feine Art, aber es funktioniert. *g*
Also bei einer Portanforderung wird z.B. folgende Funktion aufgerufen:
bool port_request(u32int_t port_num, task_t *current)
{
if(!current->tss_iobitmap)
{
current->tss_iobitmap = (u8int_t*)kmalloc(sizeof(u8int_t) * 8192);
memset((u8int_t*)current->tss_iobitmap, 0xFF, sizeof(u8int_t) * (MULTITASKING_IOBITMAP_SIZE / 8));
}
u32int_t idx = INDEX_FROM_BIT(port_num);
u32int_t off = OFFSET_FROM_BIT(port_num);
u32int_t tssidx = port_num / 8;
u32int_t tssoff = port_num % 8;

if(global_permission_bitmap[idx] & BIT(off))
return(false);

// set port within tasks permission bitmap
current->tss_iobitmap[tssidx] &= ~(BIT(tssoff));
// Set port within global permission bitmap
global_permission_bitmap[idx] |= BIT(off);

// Copy new port permission bitmap
memcpy((u8int_t*)multitasking_kernel_tss.iobitmap, (u8int_t*)current->tss_iobitmap, (sizeof(u8int_t) * (MULTITASKING_IOBITMAP_SIZE / 8)));

return(true);
}
Wie wird das denn in Tyndur gemacht?
 
@ehenkes:
Ich kann den Code hochladen. Was für Probleme hast du denn? Ich hatte zuvor auch die InitRD aus dem Tutorial von JamesMollloy, allerdings wurden aus irgendeinem nicht geklärten Grund die Dateien nicht richtig in das Image kopiert, weswegen ich dann zum TAR-Format gewechselt habe.
Irgendwie war immer der Header der ELF-Datei kapputt, was glaube ich am Programm lag, dass die Dateien zusammenkopiert hat...
 
*EDIT*
Wenn du dir den Code anschaust, könntest du das kalte Grausen bekommen. *g* Er ist voller Workarounds, die ich noch ausmerzen muss und besitzt einen sehr unsicheren System Call, mit dem man eine Adresse und eine Anzahl darauf folgender Pages in den User Mode mappen kann. Hier werden nur die Flags umgesetzt, da die Adresse 0xa0000 ja schon vom Kernel gemappt wurde, es wird auch nicht geprüft, ob die Adresse gültig ist und ob diese überhaupt zuvor gemappt wurde *g*. Dies deswegen, da ich für den Test des Port Managers unbedingt ein Programm haben wollte, was in den Mode13h, aber nicht mehr zurück wechselt... :roll:
« Letzte Änderung: 28. July 2009, 14:25 von ChristianF »

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #14 am: 28. July 2009, 22:41 »
Ich habe ein TSS für alle Prozesse und eine extra Bitmap für jeden Prozess. Bei jedem Taskwechsel, sowie der Anforderung eines neuen oder der Freigabe eines reservierten Ports wird die bitmap des TSSs mit der des Tasks überschrieben.
Das ist vielleicht nicht die feine Art, aber es funktioniert. *g*
Ja, es funktioniert, aber es ist langsam. Und damit meine ich: Wirklich spürbar langsam.

Wir machen im Moment beim Taskwechsel nur im TSS den Verweis auf die IO-Bitmap kaputt (auf irgendwas > Limit setzen) und kopieren erst, wenn ein GPF fliegt. Damit spart man sich das Kopieren in vielen Fällen. Aber es ist immer noch messbar, IOPL=3 bringt doch nochmal mehr Performance. ;)
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #15 am: 28. July 2009, 23:11 »
Das wäre natürlich auch ne Idee.
 
Ich könnte aber auch anstatt immer alles zu kopieren, den Port einmal in der bitmap des Tasks und in der Bitmap der TSS setzen. Quasi nach anstatt dem Kopieren folgendes:
multitasking_kernel_tss.iobitmap[tssidx] &= ~(BIT(tssoff));
Aber ich muss so oder so nochmal drüber nachdenken. Dann muss ich auch noch schauen, wie ich am sinnvollsten die System Calls erweitere...

 

Einloggen