Eigenes OS?



  • 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.
    🙂


  • Mod

    Ein lokaler Stack auf der Heap ist mithilfe eines speziellen 'Registers' einfach realisierbar

    Du findest Stacks auf dem (die?) Heap also auch nicht schlimm? Hat ja Vorteile wegen der einfachen Speicherfreigabe bei Task-Beendigung.



  • Erhard Henkes schrieb:

    Du findest Stacks auf dem (die?) Heap also auch nicht schlimm? Hat ja Vorteile wegen der einfachen Speicherfreigabe bei Task-Beendigung.

    spricht ja erstmal nix dagegen, alle speicherressourcen eines prozesses (stack, handle-tabellen, usw.) auf dem system-heap anzulegen. der heap, den der prozess selber verwendet (für seine malloc/free aufrufe) sollte natürlich ein anderer sein.
    🙂


  • Mod

    Inzwischen habe ich mich auch an die Idee des Stacks auf dem Heap gewöhnt. Das hat gegenüber dem Schieben der placement_address ins "Unendliche" eindeutig die Vorteile eines vernünftigen Speichermanagements. 😋

    Das Tutorial wurde um das Thema Virtuelles Filesystem und Initial RAM Disk erweitert. Hier habe ich den Code von JM übernommen, da er entsprechend POSIX klar strukturiert und für PrettyOS zunächst tauglich ist: http://www.henkessoft.de/OS_Dev/OS_Dev2.htm#mozTocId676802

    Ich habe direkt hinter die RAM Disk, die beispielhaft mit 3 Files bestückt wurde, noch das kleine Test-Programm gehängt.

    Vielleicht habt ihr bessere Ideen für einen veränderten RAM-Disk- und File-Header. JM hat hier im RAM-Disk-Header lediglich die Anzahl Files eingetragen und in den File-Headern den Namen, den Offset und die Größe angegeben. Da könnte man evtl. eine interessante Lösung umsetzen, wobei dies keine besondere bedeutung hat, außer das der Leser solche Formate vom Typus her generell kennen lernt. 🙂

    Interessant wäre es vielleicht, in die RAM Disk nicht nur langweilige Text-Files, sondern mehrere Programm-Files aufzunehmen und diese mit einer Auswahl-Routine Run(FileName) aus verschiedenen Tasks im Multitasking zu starten. Damit kämen wir dem Laden/Starten eines Programms/Moduls, in diesem Fall von einer virtuellen Disk auf einem abstrakten File-Node im VFS, doch schon etwas näher? Habt ihr Ideen für kleine Demo-Programme? Assembler-Programmierer nach vorne! Bisher habe ich nur dieses winzige Programm, dessen Ausgabe man nur mit dem Debugger wahr nehmen kann:

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

    Die Programme dürfen ja noch keine externen Ressourcen anfordern, weil dieses Prozess- und I/O-Management noch fehlt.

    EDIT: Syscalls und Usermode (z.Z. leider noch mit Supervisor-Privileg, weil es ansonsten mit PF oder GPF endet) ist in meinem Experimentierfeld bereits eingebaut. Ich frage mich gerade, wie eine API von innen her gesehen funktioniert. Von außen verwendet man diese typischerweise mit api.h/api.lib.


  • Mod

    Ich wollte an dieser Stelle darauf hinweisen, dass die deutsche Übersetzung der dritten Auflage von Tanenbaum's Meisterwerk verfügbar ist und kann dieses Grundlagen- und Nachschlagewerk nur empfehlen: Andrew S. Tanenbaum et.al., Moderne Betriebssysteme, 3. Auflage, Pearson Studium, April 2009 (Übersetzung von "Modern Operating Systems", Prentice Hall, Dec 2007). Der wesentliche Makel der deutschen Ausgabe ist, dass das IMHO gelungene Titelbild des Originals nicht enthalten ist.
    deutsch: http://www.pearson-studium.de/media_local/shop_u1bigs_3d/9783827373427.jpg (April 2009)
    amerikanisch: http://vig-fp.prenhall.com/bigcovers/0136006639.jpg (Dec 2007)
    Für mich neben den Intel Manuals und einem Assemblerbuch eine wesentliche Fundgrube für Fakten und Ideen. 👍
    Allerdings staune ich immer wieder, dass selbst so ein Standardwerk noch Fehler, didaktische Luecken und Ungereimtheiten enthaelt. 🙄

    Gibt es eigentlich schon so etwas wie "OS Development for Dummies" in engl. oder deutsch? Ist mir bisher nicht unter gekommen.


  • Mod

    Kurzer Zwischenstand: z.Z. kämpfe ich mit dem "echten" User Mode (Ring 3).
    Da gibt es noch GPF.

    EDIT: http://www.henkessoft.de/OS_Dev/Downloads/20090522_43_user_mode.zip
    PrettyOS arbeitet zum ersten Mal im "echten" User Mode (Ring 3). Wichtig ist, dass man im ring3 ss und esp vor die eflags "pusht" (Intel Manual 3A, Kap. 5.12).

    task_t* create_task(void* entry, UCHAR privilege)
    {
        cli();
        page_directory_t* directory = clone_directory(current_directory);
        task_t* new_task            = (task_t*)k_malloc(sizeof(task_t),0,0);
    
        new_task->id  = next_pid++;
        new_task->page_directory = directory;
        new_task->kernel_stack   = k_malloc(KERNEL_STACK_SIZE,1,0)+KERNEL_STACK_SIZE;
        new_task->next = 0;
    
        task_t* tmp_task = (task_t*)ready_queue;
        while (tmp_task->next)
            tmp_task = tmp_task->next;
        tmp_task->next = new_task; // ... and extend it
    
        ULONG* kernel_stack = (ULONG*) new_task->kernel_stack;
    
        ULONG code_segment=0x08, data_segment=0x10;
    
        if(privilege == 3)
        {
            //Intel 3A Chapter 5.12
            *(--kernel_stack) = new_task->ss = 0x23;    // ss
            *(--kernel_stack) = new_task->kernel_stack; // esp = esp0
            code_segment = 0x1B; // 0x18|0x3=0x1B
        }
    
        *(--kernel_stack) = 0x0202; // eflags = interrupts activated and iopl = 0
        *(--kernel_stack) = code_segment; // cs
        *(--kernel_stack) = (ULONG)entry; // eip
        *(--kernel_stack) = 0; // error code
    
        *(--kernel_stack) = 0; // interrupt nummer
    
        // general purpose registers w/o esp
        *(--kernel_stack) = 0;
        *(--kernel_stack) = 0;
        *(--kernel_stack) = 0;
        *(--kernel_stack) = 0;
        *(--kernel_stack) = 0;
        *(--kernel_stack) = 0;
        *(--kernel_stack) = 0;
    
        if(privilege == 3) data_segment = 0x23; // 0x20|0x3=0x23
    
        *(--kernel_stack) = data_segment;
        *(--kernel_stack) = data_segment;
        *(--kernel_stack) = data_segment;
        *(--kernel_stack) = data_segment;
    
        //setup TSS
        tss_entry.ss0   = 0x10;
        tss_entry.esp0  = new_task->kernel_stack;
        tss_entry.ss    = data_segment;
    
        //setup task_t
        new_task->ebp = 0xd00fc0de; // test value
        new_task->esp = (ULONG)kernel_stack;
        new_task->eip = (ULONG)irq_tail;
        new_task->ss  = data_segment;
    
        sti();
        return new_task;
    }
    
    void test5()
    {
      while(TRUE)
      {
          syscall_puts("5");
          //puts("5");
      }
    }
    
    //...
    
    task_t* task5 = create_task (test5,3);
    

    Ohne syscall ( Aufruf Kernel-Fkt. aus Ring 3 ) ==>

    44444444444444444444444444444444444444444444444444444444444444444444444444444444
    4444444444444444444
    Page Fault ( read-only - write operation user-mode) at 000B8C06h - EIP: 00008986h

    Für echte User-"Programme" benötigt man ja auch noch Stack und Heap im User-Privileg. Da denke ich gerade nach, wie man das einfach möglichst einfach in Ring3 mappen/allokieren kann. 🙂

    Bei obigem Programm gibt es noch Probleme, wenn man das while in test5 streicht. Da überlege ich gerade eine Umhüllung oder ein exit. Wenn da jemand eine Idee hätte? 😕



  • Ich hab noch eine kleine Anmerkung zum Stack.
    Zwar weiß ich nicht, was Du da jetzt alles drüber abwickeln willst, (sagst ja, lokale Speicheranforderung extra etc.), und ich weiß deshalb nicht, ob hier ein Problem entstehen kann. Solltest aber einen klitzekleinen Moment drüber verwenden, ob hier memory fragmentation reinspielen kann oder nicht. Wenn ja, unterschätze den Effekt nicht. Ich habe damals an die DJGPP-newsgroup ein sample geschickt (okay, das ist jetzt lokale Speicheranforderung), was den gcc abschiessen konnte, obwohl alles korrekt war. Hatte einfach zufällige Mengen von Speicheranforderungen und -freigaben in rauher Menge laufen lassen.
    Auf die Bemerkung, das wäre ja nun sehr willkürlich, habe ich einfach gekontert, dass bei einer OOP-Sprache wie C++ doch kaum noch absehbar ist, was in den vielen kleinen Blackboxes alles passiert. Das haben sie dann doch ernst genommen, und das ganze Kapitel so überarbeitet, dass sich mein Sample auf den Kopf stellen konnte - es blieb stabil! Vielleicht kann man durch debuggen/disassemblen des new-Befehls mal schauen, was für ein Grundprinzip dahinter steckt.
    Kann schlecht abschätzen, ob Du so etwas an der bewußten Stelle auch brauchst.
    Reine fifo-Dinge werden es wohl auch nicht mehr sein.


  • Mod

    @Bitsy: Danke für den Hinweis. Momentan kämpfe ich dermaßen mit Prozessen (Multitasking/Privilegien/Syscall/Interrupts), dass ich gar keine Zeit für das Speichermanagement habe.

    Du meinst, wenn ständig irgendein Prozess 4 KB Stack anfordert und anschließend wieder frei gibt? Wir betreiben das Spiel mit den Stacks auf dem Heap. Ich muss mal schauen, wie man diese geordenete Liste aller "freien Löcher" bzw. "belegten Blocks" (Heap Code von James Molloy, Kernel Tutorial, Kap. 7, übernommen) visualisieren kann. Dann könnten wir den Algo testweise stressen.
    Vielleicht hat jemand Zeit/Lust für so eine Sonderaufgabe?


  • Mod

    Zum Thema Multitasking/UserMode/Syscalls:

    Wenn man früher aus einer Task ausbrechen will, als die Timeslice dauert, lässt sich das leicht über einen Software-Interrupt realisieren:

    void exit_task() //exit to next task
    {
      asm volatile("int $0x21"); // <== welchen INT würdet ihr verwenden?
    }
    
    void f2()
    {
        while(TRUE)
        {
          settextcolor(getpid(),0);
          putch(getpid()+'@');
          exit_task();
        }
    }
    
    void f3() //user mode at ring 3 requires syscall_...
    {
        while(TRUE)
        {
          syscall_settextcolor(syscall_getpid(),0);
          syscall_putch(syscall_getpid()+'@');
          syscall_exit_task();
        }
    }
    
    int main()
    {
        //...
    
        // create two additional tasks
        task_t* task2 = create_task (f2,0); // kernel mode (ring 0)
        task_t* task3 = create_task (f3,3); // user mode   (ring 3)
    
        pODA->ts_flag = 1; // enable task_switching
    
        while(TRUE)
        {
            settextcolor(getpid(),0);
            putch(getpid()+'@');
            exit_task();
        }
        return 0;
    }
    

    ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCAB
    CABCABCABABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCAB
    CABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCACABCABCABCAB
    CABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCA
    BCABCACABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCA
    BCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABABCABCABCABCABCABCA
    BCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABABCABCABCABCABCABCA
    BCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABABCABCABCABCABCABCA
    BCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC
    ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCBCABCABCABCABCABCABCABC
    ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCAB

    Syscalls (Überbrückung Ring3 ==> Ring0):

    #define DECL_SYSCALL0(fn)   int syscall_##fn(); //etc.
    
    #define DEFN_SYSCALL0(fn, num) \
    int syscall_##fn() \
    { \
      int a; \
      asm volatile("int $0x7F" : "=a" (a) : "0" (num)); \
      return a; \
    }
    //etc.
    
    DECL_SYSCALL1(puts,  UCHAR*)
    DECL_SYSCALL1(putch, UCHAR)
    DECL_SYSCALL2(settextcolor, UCHAR, UCHAR)
    DECL_SYSCALL0(getpid)
    DECL_SYSCALL0(f3)
    DECL_SYSCALL0(nop)
    DECL_SYSCALL0(exit_task)
    
    DEFN_SYSCALL1( puts,         0, UCHAR*       )
    DEFN_SYSCALL1( putch,        1, UCHAR        )
    DEFN_SYSCALL2( settextcolor, 2, UCHAR, UCHAR )
    DEFN_SYSCALL0( getpid,       3               )
    DEFN_SYSCALL0( f3,           4               )
    DEFN_SYSCALL0( nop,          5               )
    DEFN_SYSCALL0( exit_task,    6               )
    
    #define NUM_SYSCALLS 7
    
    static void* syscalls[NUM_SYSCALLS] =
    {
        &puts,
        &putch,
        &settextcolor,
        &getpid,
        &f3,
        &nop,
        &exit_task
    };
    


  • Das ist wohl eher ein task_yield(), da der Prozess wieder auftaucht und nicht aus der Liste der lauffaehigen Prozesse ausgetragen wird. Prozesse geben normalerweise nicht freiwillig auf, und wenn, dann mit exit endgueltig. 🙂


  • Mod

    zosxc63Tr schrieb:

    Das ist wohl eher ein task_yield(), da der Prozess wieder auftaucht und nicht aus der Liste der lauffaehigen Prozesse ausgetragen wird. Prozesse geben normalerweise nicht freiwillig auf, und wenn, dann mit exit endgueltig. 🙂

    Ja, alles richtig, werde dies auf ein exit() umbauen. Obiger Mechanismus koennte evtl. fuer ein thread_yield verwendet werden. 🙂



  • Wie geht es weiter mit dem OS Skript? Wann wird nach GRUB und Linux Tools umgestellt wie sonst ueblich?


Anmelden zum Antworten