Autor Thema: Software Multitasking  (Gelesen 42413 mal)

TheThing

  • Beiträge: 105
    • Profil anzeigen
Gespeichert
« am: 19. March 2009, 12:15 »
Hi,
hab gestern versucht (Hardware-)Multitasking in meinen Kernel einzubauen, was in einem GPF geendet hat. Dann hab ich gelesen, das im Long-Mode Hardware-Multitasking sowieso nicht mehr geht.
Mein Problem ist, das ich KEINE Ahnung hab, wie man software-multitasking implementiert.
Software-Multitasking funktioniert (wenn ich das richtig verstanden habe) so:
Task1 wird von Timer unterbrochen
Kernel pusht Daten (Register usw.) auf den Stack von Task1
Kernel läd Stack von Task2
Kernel holt Daten vom Stack

So, wie springt jetzt aber der Kernel zurück zum Task?
Habt ihr da Tutorials, code oder so was für mich?

rizor

  • Beiträge: 521
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 19. March 2009, 13:15 »
Das machst du dann durch iret.
Du unterbrichst den Prozess durch einen IRQ und springst damit durch iret zurück
Programmiertechnik:
Vermeide in Assembler zu programmieren wann immer es geht.

blitzmaster

  • Beiträge: 77
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 19. March 2009, 16:45 »
Du musst darauf achten, dass du beim Laden der Daten vom Stack des 2. Tasks zuletzt die Rücksprungadresse (+ segment glaub ich) auf den Stack legst, damit iret funktioniert
A / OS PM - THE operating system of the future

TheThing

  • Beiträge: 105
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 20. March 2009, 12:43 »
schonmal danke für die Antworten, aber ich hab noch en paar Fragen:
Was mache ich wenn der Task das erste Mal gestartet werden soll?
Könnt ihr mir evtl. sagen wo ich Beispielcode finde?

rizor

  • Beiträge: 521
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 20. March 2009, 18:00 »
Da gibt es mehrere Möglichkeiten.
Das einfachste wäre, wenn du elf-Dateien benutzt.
Am besten du schaust dir im Coding-Bereich meinen Thread an.
Da habe ich die selben Fragen gehabt, da ich auch gerade Multitasking einbaue.
Programmiertechnik:
Vermeide in Assembler zu programmieren wann immer es geht.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 21. March 2009, 11:10 »
ELF hat damit erstmal nichts zu tun, das ist ja nur ein Binärformat, in dem man Programme ablegen kann. Ein Task kann auch ein Kernelthread sein, also letztlich einfach eine Funktion im Kernel. Genau so wird Multitasking ja auch meistens zuerst getestet: Zwei Kernelthreads geben jeweils 'a' oder 'b' aus und in der Ausgabe sollte dann ein Wechsel sichtbar sein.

Was hier beim Multitasking an sich beim ersten Start gilt, ist, daß man den Stack genau so vorbereitet, wie er aussehen würde, wenn der laufende Task unterbrochen worden wäre. Statt den dann gesicherten Werten gibt man beim ersten Start eben den Anfangszustand rein.

Quellcode gibt es wie immer im tyndur-Repository. ;)
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

TheThing

  • Beiträge: 105
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 23. March 2009, 16:17 »
danke  :-)
hm, C <ironie>meine Lieblingssprache</ironie>
ich glaub das ich das irgendwie hinkrieg ;)

ehenkes

  • Gast
Gespeichert
« Antwort #7 am: 26. April 2009, 19:11 »
Ich habe die Funktion task switch (siehe Kap. 9, James Molloy) eingebaut. Klappt auch gut, wenn ich an der richtigen Stelle im Code umschalte. Erledige ich das via System Timer Handler, also an rein zufälliger Stelle, so erhalte ich einen General  Protection Fault. Wie muss der Scheduler arbeiten, um dies zu vermeiden? Gibt es da ein Vorbild?

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 26. April 2009, 19:47 »
Dann machst du bei der Interruptverarbeitung was falsch. Der Userspace-Prozeß sollte an jeder beliebigen Stelle unterbrochen werden können. Wenn du eine bestimmte Stelle zum Umschalten brauchst, deutet das darauf hin, daß du den Zustand des Prozesses in deinem Interrupthandler nicht richtig oder nicht vollständig speicherst.

Du kannst dir mal den Interrupthandler-Stub von tyndur anschauen. Der sichert den gesamten Zustand auf den Stack und ruft anschließend den eigentlichen Interrupthandler in C auf.
« Letzte Änderung: 26. April 2009, 19:48 von taljeth »
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #9 am: 26. April 2009, 20:07 »
Danke für den Hinweis. So sieht der task_switch aus:
void task_switch()
{
    if (!current_task) return;
    ULONG esp, ebp, eip;
    asm volatile("mov %%esp, %0" : "=r"(esp));
    asm volatile("mov %%ebp, %0" : "=r"(ebp));
    eip = read_eip();
    if (eip == 0x12345) return;
    current_task->eip = eip;
    current_task->esp = esp;
    current_task->ebp = ebp;
    current_task = current_task->next;
    if (!current_task) current_task = ready_queue;
    eip = current_task->eip;
    esp = current_task->esp;
    ebp = current_task->ebp;
    current_directory = current_task->page_directory;
    set_kernel_stack(current_task->kernel_stack+KERNEL_STACK_SIZE);
    asm volatile("         \
      cli;                 \
      mov %0, %%ecx;       \
      mov %1, %%esp;       \
      mov %2, %%ebp;       \
      mov %3, %%cr3;       \
      mov $0x12345, %%eax; \
      sti;                 \
      jmp *%%ecx           "
                 : : "r"(eip), "r"(esp), "r"(ebp), "r"(current_directory->physicalAddr));
}

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #10 am: 26. April 2009, 20:21 »
Den eigentlichen Taskswitch solltest du nicht in C machen, Assembler ist besser geeignet. Der richtige Ort dafür sind die Interrupthandler. Der Interrupthandler speichert den alten Zustand auf den Stack (bevor C-Funktionen die Register verändern), ruft Kernelcode für das Interrupthandling auf (der dann möglicherweise den aktuellen Task ändert), stellt den Zustand des jetzt aktuellen Tasks wieder her und springt zurück. Jeder Task hat seinen eigenen Kernelstack, so daß der Zustand immer auf diesen Stack liegen bleibt und erst wieder abgebaut und geladen ist, wenn tatsächlich in diesen Task gesprungen werden soll.

Hast du dir den verlinkten tyndur-Code angeschaut? Der macht genau das.

Edit: Was man bei deinem Code noch erwähnen sollte, ist vielleicht, daß der Zustand eines Tasks aus mehr als eip und esp besteht. Da gehören wirklich alle Register dazu. Wenn die nicht gesichert werden, passieren interessante Dinge, aber sicher nicht, was man möchte.
« Letzte Änderung: 26. April 2009, 20:23 von taljeth »
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #11 am: 26. April 2009, 20:36 »
Zitat
Hast du dir den verlinkten tyndur-Code angeschaut? Der macht genau das.
Ja, das klingt überzeugend. Ich habe mich an James Molloy's Code gehalten, da mir C leichter fällt, leider hört er dort auf, wo die Probleme  anfangen. Daher kann ich nicht beurteilen, inwieweit sein Code wirklich brauchbar ist. Wie gesagt bisher GPF im Timer Handler. Werde mir Tyndur nun mal genauer anschauen.

Zitat
Du kannst dir mal den Interrupthandler-Stub von tyndur anschauen. Der sichert den gesamten Zustand auf den Stack und ruft anschließend den eigentlichen Interrupthandler in C auf.
Ich habe auch einen Interrupthandler in asm, der C ISR aufruft. Einen Software Interrupt habe ich bisher nur für system calls eingesetzt. Welchen interrupt verwendet tyndur für das Schalten der Tasks, oder läuft das beim Timer Handler (IRQ0) mit? 
« Letzte Änderung: 26. April 2009, 20:40 von ehenkes »

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #12 am: 26. April 2009, 20:45 »
Das läuft normal über den Timer. Oder der Task kann natürlich vorzeitig über einen Syscall abgeben.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #13 am: 26. April 2009, 22:10 »
Ich habe den Code in task_switch mit putch('!');  gecheckt. Der Fehler liegt im hinteren Teil im asm-code. Dort tritt der GPF auf.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #14 am: 26. April 2009, 22:28 »
Mir ist dieser ganze Ansatz sehr suspekt. Ob das ganze möglicherweise funktionieren kann, hängt wohl auch davon ab, wo task_switch() aufgerufen wird. Es sollte wohl direkt von einem Interrupthandler sein. Kannst du ein bißchen mehr Code herzeigen?
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #15 am: 26. April 2009, 22:56 »
void timer_handler(struct regs* r)
{
    ++timer_ticks;
    if (eticks)
        --eticks;

    //TEST
    static ULONG c = 0;
    ++c;
    if(c>200)
    {
      task_switch();
      settextcolor(getpid(),0);
      c=0;
    }
    //TEST
}
das ist der Code im timer handler, der bei IRQ0 angestoßen wird. Ändert man den Wert von c:
Manchmal gelingt ein task_switch, manchmal keiner. Mal wird mit GPF beendet, mal mit Absturz ohne Fehlermeldung. Also echt chaotisches Verhalten.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #16 am: 26. April 2009, 23:25 »
Hm, gut, theoretisch könnte das tun. Voraussetzung ist, daß kein Register das asm() in task_switch() überlebt, sondern gcc alles auf den Stack packt. Du könntest das vielleicht erreichen, indem du die nötigen Register in die Clobber-Liste reinnimmt. Ich bin mir im Moment selbst nicht sicher, welche das alles sind, ebx dürfte eins davon sein.

Ich glaube aber wirklich, daß du dir einen Gefallen tust, wenn du es ähnlich wie in tyndur machst. Dort gibt es viel weniger Dinge, die man beim Sichern der Register beachten muß, weil kein C-Compiler reinspielt.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #17 am: 26. April 2009, 23:53 »
Zitat
indem du die nötigen Register in die Clobber-Liste reinnimmt.
Clobber-Liste, noch nie gehört. Hier kann man ja richtig was lernen.  :-D
Ich habe das gefunden:
asm volatile ( Anweisungen : Outputs : Inputs : Clobbers)

Wenn ich das richtig sehe, ist die Clobbers-Liste z.Z. leer:
asm volatile(" ..." :  : "r"(eip), "r"(esp), "r"(ebp), "r"(current_directory->physicalAddr));
Wie muss das genau aussehen hinter einem dritten Doppelpunkt? kenne mich da leider gar nicht gut aus mit der Syntax.

Ich habe mal vor dem Assemblercode in task_switch bochs abgefangen mit:
    /// TEST
      cli();
      asm volatile("hlt;");
      for(;;);
    /// TEST

    asm volatile("         \
01368000000p[WGUI ] >>PANIC<< POWER button turned off.
01368000000i[CPU0 ] CPU is in protected mode (halted)
01368000000i[CPU0 ] CS.d_b = 32 bit
01368000000i[CPU0 ] SS.d_b = 32 bit
01368000000i[CPU0 ] EFER   = 0x00000000
01368000000i[CPU0 ] | RAX=000000000000008f  RBX=000000000000d502
01368000000i[CPU0 ] | RCX=00000000000b8000  RDX=00000000000003d5
01368000000i[CPU0 ] | RSP=000000000018ff1c  RBP=000000000018ff44
01368000000i[CPU0 ] | RSI=000000000018ffd8  RDI=000000000018fff0
01368000000i[CPU0 ] |  R8=0000000000000000   R9=0000000000000000
01368000000i[CPU0 ] | R10=0000000000000000  R11=0000000000000000
01368000000i[CPU0 ] | R12=0000000000000000  R13=0000000000000000
01368000000i[CPU0 ] | R14=0000000000000000  R15=0000000000000000
01368000000i[CPU0 ] | IOPL=0 id vip vif ac vm rf nt of df if tf sf zf af pf cf
01368000000i[CPU0 ] | SEG selector     base    limit G D
01368000000i[CPU0 ] | SEG sltr(index|ti|rpl)     base    limit G D
01368000000i[CPU0 ] |  CS:0008( 0001| 0|  0) 00000000 000fffff 1 1
01368000000i[CPU0 ] |  DS:0010( 0002| 0|  0) 00000000 000fffff 1 1
01368000000i[CPU0 ] |  SS:0010( 0002| 0|  0) 00000000 000fffff 1 1
01368000000i[CPU0 ] |  ES:0010( 0002| 0|  0) 00000000 000fffff 1 1
01368000000i[CPU0 ] |  FS:0010( 0002| 0|  0) 00000000 000fffff 1 1
01368000000i[CPU0 ] |  GS:0010( 0002| 0|  0) 00000000 000fffff 1 1
01368000000i[CPU0 ] |  MSR_FS_BASE:0000000000000000
01368000000i[CPU0 ] |  MSR_GS_BASE:0000000000000000
01368000000i[CPU0 ] | RIP=000000000000d3f9 (000000000000d3f9)
01368000000i[CPU0 ] | CR0=0xe0000011 CR1=0x0 CR2=0x0000000000000000
01368000000i[CPU0 ] | CR3=0x00306000 CR4=0x00000000
01368000000i[CPU0 ] >> add esp, 0x00000010 : 83C410
01368000000i[CMOS ] Last time is 1240781579 (Sun Apr 26 23:32:59 2009)
01368000000i[     ] restoring default signal behavior
01368000000i[CTRL ] quit_sim called with exit code 1
Das stand dann in bochsout.txt.

 

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #18 am: 27. April 2009, 00:05 »
Clobber-Liste, noch nie gehört. Hier kann man ja richtig was lernen.  :-D
Das ist Sinn und Zweck des Forums. ;)

Zitat
Ich habe das gefunden:
asm volatile ( Anweisungen : Outputs : Inputs : Clobbers)

Wenn ich das richtig sehe, ist die Clobbers-Liste z.Z. leer:
asm volatile(" ..." :  : "r"(eip), "r"(esp), "r"(ebp), "r"(current_directory->physicalAddr));
Wie muss das genau aussehen hinter einem dritten Doppelpunkt? kenne mich da leider gar nicht gut aus mit der Syntax.
Genau, und dahinter die Registernamen, die geclobbert werden. Also ungefähr so: asm("" : : : "ebx", "esi", "edi", "ebp");

Zitat
01368000000p[WGUI ] >>PANIC<< POWER button turned off.
Ich benutze ja eher qemu als bochs und kenne daher die typischen Meldungen nicht. Aber für mich klingt das nach ganz normalem Ausschalten der VM und nicht nach Fehlermeldung.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

ehenkes

  • Gast
Gespeichert
« Antwort #19 am: 27. April 2009, 00:17 »
Zitat
Aber für mich klingt das nach ganz normalem Ausschalten der VM und nicht nach Fehlermeldung.
Ja, genau. Das ist vor dem Fehler.

Zitat
can't find a register in class `GENERAL_REGS' while reloading `asm'
    asm volatile("         \
      cli;                 \
      mov %0, %%ecx;       \
      mov %1, %%esp;       \
      mov %2, %%ebp;       \
      mov %3, %%cr3;       \
      mov $0x12345, %%eax; \
      sti;                 \
      jmp *%%ecx           "
      : : "r"(eip), "r"(esp), "r"(ebp), "r"(current_directory->physicalAddr) : "ebx", "edx", "esi", "edi" );
Ich verwende gcc.exe (Version 3.1) und ld.exe (Version 2.13) wegen Linkens des aout-Formats von NASM.

Mit : "ebx", "edx" ); compiliert es, gibt aber wieder Absturz.

esi oder edi mag er nicht an dieser Stelle.  :-D

« Letzte Änderung: 27. April 2009, 00:38 von ehenkes »

 

Einloggen