Eigenes OS?


  • Mod

    darum brauchste dich am anfang nicht zu kümmern, machste einfach primitivstes round-robin scheduling, d.h. einfach mit 'ner festen frequenz immer weiterswitchen und nachdem die letzte task dran war, kommt wieder die erste dran.

    Der Mechanismus funktioniert bereits, über mit vier Tasks.

    das grundprinzip ist doch klar, oder?

    Ja, mir macht die Abstimmung zwischen TSS entry (nur eine), CR3 (Physikalische Adresse der Page Directory des jeweiligen virtuellen Adressraums), Struktur task_t (für jede Task eine) und eigener Kernel_Stack pro Task noch etwas Probleme. Da stecke ich noch etwas fest. Ich möchte den Task Switch prinzipiell auch außerhalb eines Interrupts durchführen können, um ihn zu studieren. Denn im timer_handler macht es schnell bumm, dann ist es schwer zu analysieren, was eigentlich los ist (GPF, OpcodeF, DebugF, Reset, doing nothing, ...). Erst mit vollständigen log-Funktionen - passend zu den Strukturen task_t und tss_entry_t erkennt man, was im Hintergrund abgeht:

    void task_log(task_t* t)
    {
        printformat("id: %d ", t->id);               // Process ID
        printformat("ebp: %x ",t->ebp);              // Base pointer
        printformat("esp: %x ",t->esp);              // Stack pointer
        printformat("eip: %x ",t->eip);              // Instruction pointer
        printformat("PD: %x ", t->page_directory);   // Page directory.
        printformat("k_stack: %x ",t->kernel_stack); // Kernel stack location.
        printformat("next: %x\n",  t->next);         // The next task in a linked list.
    }
    
    void TSS_log(tss_entry_t* tss)
    {
        printformat("prev_tss: %x ", tss->prev_tss);
        printformat("esp0: %x ", tss->esp0);
        printformat("ss0: %x ", tss->ss0);
        printformat("esp1: %x ", tss->esp1);
        printformat("ss1: %x ", tss->ss1);
        printformat("esp2: %x ", tss->esp2);
        printformat("ss2: %x ", tss->ss2);
        printformat("cr3: %x ", tss->cr3);
        printformat("eip: %x ", tss->eip);
        printformat("eflags: %x ", tss->eflags);
        printformat("eax: %x ", tss->eax);
        printformat("ecx: %x ", tss->ecx);
        printformat("edx: %x ", tss->edx);
        printformat("ebx: %x ", tss->ebx);
        printformat("esp: %x ", tss->esp);
        printformat("ebp: %x ", tss->ebp);
        printformat("esi: %x ", tss->esi);
        printformat("edi: %x ", tss->edi);
        printformat("es: %x ", tss->es);
        printformat("cs: %x ", tss->cs);
        printformat("ss: %x ", tss->ss);
        printformat("ds: %x ", tss->ds);
        printformat("fs: %x ", tss->fs);
        printformat("gs: %x ", tss->gs);
        printformat("ldt: %x ", tss->ldt);
        printformat("trap: %x ", tss->trap); //only 0 or 1
        printformat("iomap_base: %x ", tss->iomap_base);
    }
    

    Siehe Intel Manual 3A, "Figure 6-2. 32-Bit Task-State Segment (TSS)"

    Die Struktur task_t muss sicher noch erweitert werden. ich möchte diese für das Tutorial aber auch nicht überfrachten, damit man den Überblick nicht verliert.


  • Mod

    Da es im timer_handler mit einer exception ausgestoppt wird, könnte das Problem im asm stub liegen. Ich verwende dort folgenden Code:
    ..
    Der task_switch erfolgt im timer_handler.

    ..

    Was ist die beste Lösung und warum?

    ..



  • Ein wesentlicher Punkt, auf den Du unbedingt achten mußt:
    Du musst unbedingt etwas installieren, was das Taskswitching kurzfristig stoppt, denn sonst kommt in der kritischen Phase des Anhängens einer neuen Task just in dem Moment ein switch und alles geht daneben. Bei dem 16-bit-taskswitching, was ich Dir in der Mail geschickt habe, war das ein entscheidender Faktor.
    Da ist übrigens auch eine Funktion mit drin, die einen Taskswitch erzwingt.
    Hab mich aber nie getraut, das alles auf 32-bit zu erweitern.


  • Mod

    Du musst unbedingt etwas installieren, was das Taskswitching kurzfristig stoppt, denn sonst kommt in der kritischen Phase des Anhängens einer neuen Task just in dem Moment ein switch

    Danke für den Hinweis! Ich werde da noch ein ts-flag in meiner ODA (OS Data Area) Struktur einbauen, das zuvor abgefragt wird. Die nutze ich sowieso viel zu wenig.

    typedef struct oda
    {
        //...    
        //tasking
        UCHAR  ts_flag; // 0: taskswitch off  1: taskswitch on
    }oda_t;
    
    //...
    pODA->ts_flag = 1;
    
    //...
    if( pODA->ts_flag == 1 )
        task_switch();
    


  • Besser einen counter.

    int task_freeze()
    {
      if (!taskstop)
      { ...
      }
      return ++taskstop;
    }
    
    void task_unfreeze()
    {
       if (taskstop>1)   
         --taskstop;     
       else             
       { taskstop=0;
         ...
       }
    }
    

    und dann überleg mal, wie sich die Timerfunktion eventuell verhalten kann.
    Bei mir wurde der Originalvektor auf eine Routine umgelenkt, der erst den Originalvektor bedient hat, und dann noch einen Ticker inkrementiert hat.

    Was ich auch noch Wichtiges gefunden habe - Turbo C hatte da eine Semaphore namens INDOS. Solange die gesetzt war, durfte der Switcher nicht umschalten.
    So ein Festplattenkopf kann da genervt reagieren 😉


  • Mod

    Besser einen counter.

    Kannst Du den Mechanismus bitte erklären?

    Momentan habe ich auf die task_switch Technik von tyndur (OS einer Community) umgestellt, irgendwo ist aber noch ein müder Fehler versteckt, der zu GPF führt.

    Ich benutze nur ein TSS. Was muss alles in diese TSS geschrieben werden beim task_switch?



  • Der Counter ermöglicht es Paare von freeze und unfreeze auf verschiedenen Ebenen zu bilden. Erst wenn das 'oberste' unfreeze wieder gerufen wurde, gibt er das switching wirklich wieder frei. Wenn Du nur ein einfaches Bit nimmst, verlierst Du früher oder später den Überblick.
    Nimm mal an, Du sperrst per Bit vor einem Diskzugriff und gibst es später wieder frei. Was ist, wenn der User auch einen Grund hatte das Switching zu sperren, und unmittelbar nach dem Diskzugriff wird es aber wieder freigegeben?
    Mit dem Counter erschlägst Du alle Probleme. (Außer dem, daß Du Fehler bei der Paarbildung machst ;))

    Habt ihr in anderen Fällen nicht auch schon solche Counter drin? Ich dachte, ich hätte da schon was gelesen.
    Was in die TSS muß - tja, für 32-bit-mode weiß ich's eben auch nicht!


  • Mod

    Danke für diese Information.

    Hier ist ein Link zum ersten erfolgreichen Multitasking-Beispiel:
    http://www.henkessoft.de/OS_Dev/Downloads/36p.zip 🙂

    Damit ist eine für mich nicht einfache Hürde genommen.



  • glückwunsch *daumen hoch*
    🙂



  • Jetzt hast Du Dir aber mal ein freies Wochenende verdient - das Wetter soll gut werden (jedenfalls Samstag und Sonntag). Ab in den Biergarten und das Projekt feiern 🙂


  • Mod

    Tut zum Multitasking: http://www.henkessoft.de/OS_Dev/OS_Dev2.htm#mozTocId114565

    Jetzt geht es doch erst richtig los mit PrettyOS. Ich denke gerade über die Darstellung von interessanten Möglichkeiten für Dispatcher (short-term scheduler) und long/middle-term Scheduler nach. 🙂

    Wo bekommt man ein lauffähiges kleines Programm (32 bit ohne BIOS-INT) her? Das könnte man doch über den incbin-Trick und evtl memcpy an eine bestimmte Position laden und anstelle moo() oder baa() ausführen, oder? Man könnte doch z.B. das alte COM-Format als flat Binaries verwenden. Man müsste dort doch nur noch einen Task erstellen (create_task ist ja bereits eingebaut), die Segmentregister sauber setzen und eip auf das erste Byte der Datei einstellen. Das Reinspringen in den Code könnte wieder der Interrupthandler übernehmen, der das auch schon bei den Kerneltasks gemacht hat. 🙂



  • Erhard Henkes schrieb:

    (create_task ist ja bereits eingebaut)

    vorschlag: create_task() sollte was zurückgeben, z.b. einen pointer auf die task_t oder die task-ID bzw. im fehlerfall 0 oder einen ungültigen ID-wert, damit der aufrufer die task kontrolliern kann und weiss, ob das erzeugen der task überhaupt geklappt hat. ausserdem wär's gut, der task-entry funktion noch einen void* mitzugeben, damit der aufrufer startparameter an die neue task übergeben kann.
    🙂


  • Mod

    create_task() sollte was zurückgeben, z.b. einen pointer auf die task_t

    Klar, das ist notwendig, damit man auf die Task-Struktur (noch sehr rudimentär) zugreifen kann, wurde daher sofort erledigt. 🙂

    task-entry funktion noch einen void* mitzugeben, damit der aufrufer startparameter an die neue task übergeben kann.

    Ich experimentiere gerade mit Varianten von create_task, die verschiedene Anzahlen Parameter übernehmen. Ich habe z.B. settextcolor(schriftfarbe,hintergrundfarbe) so aufgerufen, dass ich der Task diese beiden Argumente neben einer Dummy-Rücksprungadresse auf den Stack übergebe und dann vom Task aus den Code von "settextcolor" als Konstante mit call aufrufe.

    *(--kernel_stack) = arg2; // arg2
        *(--kernel_stack) = arg1; // arg1
        *(--kernel_stack) = 0x0;  // return address dummy
    
        *(--kernel_stack) = 0x0202; // eflags = interrupts activated and iopl = 0
    
    void surprise(ULONG a, ULONG b)
    {
      asm volatile ("push %0" :/* no output registers */: "r" (b));
      asm volatile ("push %0" :/* no output registers */: "r" (a));
      while(TRUE)
      {
          asm volatile ("call 0x00008484"); // address from kernel.map
          printformat("%d", getpid());      
      }
    }
    

    Der Aufruf läuft dann über einen neuen Task:

    create_task2(surprise,4,2);
    

    Das funktioniert gut. Damit taste ich mich experimentell an Programmaufrufe in PrettyOS heran.


  • Mod

    Eine Designfrage zum Thema Speicherreservierung für PDs, tasks, kernel stacks etc. mit

    1. Verschieben der placement_address bei bereits gemappten frames
    2. k_malloc (auf dem Heap)
      http://lowlevel.brainsware.org/forum/index.php?topic=2178.msg24534#msg24534

    Irgendwie kommt mir ein "Stack auf dem Heap" nicht gerade elegant vor.


  • Mod

    PrettyOS hat gerade das erste Programm von außen ausgeführt.
    Das ist ein richtig guter Moment für PrettyOS. Nun ist es "air borne".

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Test Program for PrettyOS      ;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    [BITS 32]
    start:
       Mov [0x000b8000], byte 'T'
       Mov [0x000b8002], byte 'e'
       Mov [0x000b8004], byte 's'
       Mov [0x000b8006], byte 't'
       Ret
    

    Dieses externe Programm wurde binär assembliert und über den Trick mit 'incbin' eingeschleust:
    nasmw -f bin file_data.asm -o file_data.dat

    process.asm:

    _file_data_start:
    incbin "file_data.dat"
    _file_data_end:
    

    ckernel.c:

    void test()
    {
      while(TRUE)
      {
          asm volatile ("call _file_data_start");
          printformat("%d", getpid());
          settextcolor(15,0);
      }
    }
    
    //...
    
    task_t* task5 = create_task0(test);
    

    Das von außen eingeschleuste ("geladene") Programm wurde als Task5 ausgeführt. 🙂

    http://www.henkessoft.de/OS_Dev/OS_Dev2.htm#mozTocId188270
    http://www.henkessoft.de/OS_Dev/Downloads/20090517_36v.zip

    Das Schlimme und Gute zugleich bei der OS-Entwicklung ist, dass alle Verständnislücken, die man so hat, gleichzeitig und hinterrücks zuschlagen. 😃
    Ich kann nur hoffen, dass das Tutorial anderen bei dem Finden des richtigen Weges etwas helfen kann.

    Kennt jemand einen Link bezüglich Laden/Ausführen von Programmen.
    Selbst hier - in dieser genialen Zusammenfassung - steht nichts genaues hierzu: http://ocw.mit.edu/NR/rdonlyres/Electrical-Engineering-and-Computer-Science/6-828Fall-2006/9B92FFBC-BA35-468E-971D-F7D6D0BB34DC/0/l2.pdf



  • Erhard Henkes schrieb:

    Das Schlimme und Gute zugleich bei der OS-Entwicklung ist, dass alle Verständnislücken, die man so hat, gleichzeitig und hinterrücks zuschlagen.

    das liegt nur an der mit, der heissen nadel gestrickten, x86-architektur. ich behaupte mal, jeder andere prozessor würde einem weniger kopfzerbrechen bereiten.
    🙂



  • +fricky schrieb:

    das liegt nur an der mit, der heissen nadel gestrickten, x86-architektur. ich behaupte mal, jeder andere prozessor würde einem weniger kopfzerbrechen bereiten.

    Vorsicht, vorsicht, das Eis auf dem Du Dich bewegst, ist sehr sehr dünn... 😃



  • abc.w schrieb:

    +fricky schrieb:

    das liegt nur an der mit, der heissen nadel gestrickten, x86-architektur. ich behaupte mal, jeder andere prozessor würde einem weniger kopfzerbrechen bereiten.

    Vorsicht, vorsicht, das Eis auf dem Du Dich bewegst, ist sehr sehr dünn...

    welcher ist schlimmer (von totalen exoten mal abgesehen)?
    🙂



  • Ein lokaler Stack auf der Heap ist mithilfe eines speziellen 'Registers' einfach realisierbar - so funktionierte der 68000er Prozessor.
    A7 war das Heapregister, A6 der Userstack.

    Die Kommandos link und unlink wurden in etwa so realisiert:
    (A7 und A6 beide long).

    void link(const int n)
    {
      A7 -= 4;
      *(long*)A7 = A6;
      A6 = A7;
      A7 -= n;
    }
    
    void unlk(void)
    {
      A7 = A6;
      A6 = *(long*)A7;
      A7 += 4;
    }
    

    Die lokalen Daten werden jetzt im Bereich A6-n ... A6 hinterlegt.
    Und A7 verwaltet weiterhin die Rücksprungadressen, sowie alle pushs und pops.

    btw: bei 68000er-Simulationen haut die Endianess fürchterlich rein - ich hoffe, das bleibt PrettyOS erspart 😉



  • Bitsy schrieb:

    btw: bei 68000er-Simulationen haut die Endianess fürchterlich rein

    der machts wenigsten richtig rum.
    🙂


Anmelden zum Antworten