Eigenes OS?



  • http://heim.ifi.uio.no/~stanisls/helppc/bios_data_area.html
    ab offset 449h. Mir scheint diese Struktur eigentlich im Grossen und Ganzen recht praktisch.

    Erhard Henkes schrieb:

    dafuer die grosse Endlosschleife noch immer.

    Das ist ja keine "echte" Endlosschleife, da ich dort mit continue oder break in die jeweils richtige Richtung ein-/aussteigen kann. Habe bisher noch keine perfekte Lösung für die passende Kontrollstruktur gefunden, weil man die while-Schleife verschieden durchlaufen muss. Ich hatte erst while (loopflag) geschrieben und das flag entsprechend gesetzt, dann aber festgestellt, dass man das auch durch ein einfaches break und Tschüss ersetzen kann.

    Ok, mal anders gefragt:
    Was fuer Faelle siehst du konkret, bei denen es noetig oder sinnvoll waere, in dieser Schleife mehrere Male hintereinander Scancodes von Port 60h zu lesen?
    Ich sehe hoechstens 2, naemlich erweiterte 2-Byte codes (Pfeiltasten, etc.) oder andere, laengere Status codes, die du aber saemtlichst noch nicht beruecksichtigst.
    Dagegen springst du so immer noch beim Druecken der Shift-Taste in eine Endlosschleife ohne Sinn und Wiederkehr.
    Deshalb mein Tipp: Weg mit der Schleife. Versuche moeglichst pro IRQ nur einmal Port 60h auszulesen, das gelesene direkt zu verarbeiten und gleich wieder zurueckzukehren.
    Dazu kannst du zB. in dieser Fetch-Funktion einen speziellen Rueckgabewert fuer Codes einfuehren, die sich nicht direkt in ASCII-Codes umsetzen lassen und zur Bearbeitung der Codes globale Variablen und Flags benutzen (zB. fuer shift, strg, alt down, usw. siehe auch meinen Link zur BDA oben als groben Denkansatz).

    Erhard Henkes schrieb:

    Kurz aus- und wieder einschalten ueber Port 61h, Bit7.

    Ich gehe davon aus, dass für das MSB 0->1 u. 1->0 der korrekte Weg ist.

    AFAIR: Ja.

    Erhard Henkes schrieb:

    Wann muss man dieses ACK(nowledgement) abgeben?

    Wenn du das Gelesene nicht mehr brauchst. In dem KB-Treiber, den ich vor Jahren mal geschrieben habe, hatte das direkt vor dem EOI stehen.

    Erhard Henkes schrieb:

    Würde das so passen?

    unsigned int FetchAndAnalyzeScancode()
    {
      unsigned int scancode; // For getting the keyboard scancode
      while(TRUE) // Loop until a key (w/o shift key) has been pressed
      {
        scancode = inportb(0x60);   // 0x60: get scan code from the keyboard
        // ACK: toggle bit 7 at port 0x61
        outportb(0x61,inportb(0x61) |  0x80); // 0->1
        outportb(0x61,inportb(0x61) &~ 0x80); // 1->0
    
        if ( scancode & 0x80 ) // Key released? ...
        { //...
    

    oder ist es so besser? (wie oben bei osdev.org)

    // ACK: toggle bit 7 at port 0x61
        unsigned char port_value = inportb(0x61);
        outportb(0x61,port_value |  0x80); // 0->1
        outportb(0x61,port_value &~ 0x80); // 1->0
    

    Die 2. Version ist eindeutig besser. Es schadet uebrigens sicher nicht, vor dem Lesen von Port 60h, den Port 64h, Bit 0 zu pruefen, wie du es urspruenglich auch gemacht hast (das meinte ich eigentlich gar nicht mit Endlosschleife, sondern deine aeussere while (true)-Schleife :D).
    Vielleicht den Port 64h statt in einer while-Schleife in einer for-Schleife mit Zaehler als zusaetzliche Abbruchbedingung pruefen. Im Fehlerfall springst du dann halt sofort zurueck.

    Erhard Henkes schrieb:

    Ich sehe allerdings noch keinen Effekt mit/ohne ACK? 😕
    Es heisst ja auch: "without waiting for another IRQ".
    Wir reagieren doch jetzt immer auf einen IRQ1. Also brauchen wir das nun nicht?
    Wir hätten es wohl eher beim Polling benötigt??? Da ist es mir aber nicht negativ aufgefallen.

    Theoretisch richtig, aber praktisch macht dieses x86-Gefrickel eben doch wieder alles anders. 😃
    Wenn du mit einer Runde Keyboard-Auslesen sicher fertig bist, kann es ganz bestimmt nicht verkehrt sein, mit diesem ACK sicher zu gehen.

    Erhard Henkes schrieb:

    Da war doch noch die Rede von einem IF? Ist dies das IF bei der CPU im Flag-Register?
    [...]
    isr.asm: dort findet sich bei jedem IRQ ein cli

    Richtig. Und genau deshalb ist eine [laengere/endlose] Schleife in einem IRQ-Handler ziemlich fatal. Damit blockierst du das komplette System. Ein IRQ-Handler muss moeglichst schnell sein: Rein, Hardware auslesen, das absolut Wichtigste kurz aufbereiten und im RAM abladen, ACK und wieder raus.
    Im Extremfall kannst du bei einem Micro-Kernel so weit gehen, dass du nur den Scancode in eine FIFO-queue im RAM schiebst und die Interpretation einem eigenen Treiber-Prozess ueberlaesst (wobei ich meine, dass man es nicht so uebertreiben braucht und sich der Keyboard-Input auch noch sehr gut weitgehend komplett im IRQ-Handler interpretieren/in ASCII-Codes umwandeln laesst).


  • Mod

    @Nobuo T: Vielen Dank für diese umfassenden Ausführungen. Du hast mich überzeugt. 🙂 Ich werde dies Schritt für Schritt umsetzen.

    In utils.c habe ich auch noch eine Änderung bei k_i2hex (h und NUL anghängt). Nobuo T hat dankenswerterweise k_itoa weiter optimiert:
    ..

    video.c sieht momentan wie folgt aus (noch ohne brauchbare printf(...), wie von Nobuo T vorgeschlagen):
    ..


  • Mod

    Leider noch nicht ganz perfekt, aber läuft prinzipiell schon wie es soll:
    ..


  • Mod

    Das ist - denke ich - noch nicht o.k.:

    case 'u':
                    u = va_arg (ap, UINT);
                    k_itoa(u, buffer);
                    puts(buffer);
                    break;
    

    Kennt einer ein utoa(...)?

    Bei %d fehlt noch das %i, das auch verwendet wird.
    Kleines %x barauche ich auch noch. Da muss ich wohl ein zweites k_i2hex schreiben.



  • Erhard Henkes schrieb:

    Kennt einer ein utoa(...)?

    bau doch dein 'itoa' um, aber statt 'int' mit 'nem 'unsigned' input.

    Erhard Henkes schrieb:

    Kleines %x barauche ich auch noch. Da muss ich wohl ein zweites k_i2hex schreiben.

    unnötiger luxus. wer hexziffern als gross/kleinbuchstaben braucht, kann doch toupper() oder tolower() bemühen.
    🙂



  • +fricky schrieb:

    Erhard Henkes schrieb:

    Kennt einer ein utoa(...)?

    bau doch dein 'itoa' um, aber statt 'int' mit 'nem 'unsigned' input.

    👍 Keine Ahnung, wie die entsprechende Standard-Funktion in C heisst, aber vom Prinzip her ist der Unterschied zu dem itoa minimal.

    Ansonsten ist da erstmal wohl alles Wichtige gut dabei. Nur einige Kleinigkeiten noch:

    video.c:
    k_clear_screen:
    Warum nicht gleich

    k_memsetw (vidmem, blank, 80 * 25);
    

    statt die Funktion 25* aufzurufen?

    putch:
    Grosse if/else-Schlacht... na egal 😃
    Ist es wirklich normales C-Verhalten, nur chars >= 0x20 auszugeben...?
    DOS ist da zB. recht kompromisslos und gibt alles aus, was nicht als Steuerzeichen gewertet wird. Wuerde IMHO auch einen Vergleich sparen. :p

    puts:
    Wozu extra mit strlen zaehlen, wenn du das Array danach eh nochmal Zeichen fuer Zeichen abwanderst?
    Mach's doch gleich so:

    void puts(UCHAR* text)
    {
        for (; *text; text++) putchar (*text);
    }
    

    (ich liebe es, for-Schleifen derart zu missbrauchen 🕶 )

    printf:
    Ist die Behandlung von \n, \t, usw. hier nicht doppelt? Das wird doch in putch auch schon geprueft...
    In dem Zusammenhang nehme ich auch mal an, dass du als default-Fall nicht wirklich "puts" nehmen wolltest?



  • Nobuo T schrieb:

    void puts(UCHAR* text)
    {
        for (; *text; text++) putchar (*text);
    }
    

    (ich liebe es, for-Schleifen derart zu missbrauchen 🕶 )

    Wenn missbrauchen, dann richtig:

    void puts(UCHAR* text)
    {
        for (; *text; putchar (*text++));
    }
    

    Weiss nicht obs kompiliert, wenn ja, dann 🕶



  • Ich bin skeptisch, aber: 😃 👍

    edit:
    Bei genauerer Betrachtung: Wuerde das nicht einfach den referenzierten Wert erhoehen? Das waere ja eher kontraproduktiv... 😉

    Wie waere es damit?

    for (; *text; putchar (*text), text++);
    

    🤡

    edit2:
    ok, einen habe ich noch!

    for (; *text; putchar (*(text++));
    

    Keine Ahnung, ob das kompiliert.


  • Mod

    gefällt mir wirklich gut! 🙂

    void puts(UCHAR* text)
    {
        for(; *text; putch(*text), ++text);
    }
    

    auch sehr schön:

    void k_clear_screen()
    {
        k_memsetw (vidmem, 0x20 | (attrib << 8), 80 * 25);
        csr_x = 0; csr_y = 0; update_cursor();
    }
    

    👍

    Ist die Behandlung von \n, \t, usw. hier nicht doppelt? Das wird doch in putch auch schon geprueft...
    In dem Zusammenhang nehme ich auch mal an, dass du als default-Fall nicht wirklich "puts" nehmen wolltest?

    Ja, ich habe es mal auskommentiert. Habe putch(...) anstelle puts(...) gesetzt.



  • Habt ihr an das Backspace gedacht?
    Für die ersten minimalen Editierdinge vielleicht doch wichtig.



  • Ist drin (siehe putch()). Was noch fehlt ist "bell". :p


  • Mod

    Bell

    Was noch fehlt ist "bell". :p

    '\a': Was soll denn da passieren?
    Ich habe es gerade mal ausprobiert:

    #include <iostream>
    
    int main()
    {
     for(int i=0;i<100;++i)
         std::cout << '\a' << "bell ";
    }
    

    Da kam nichts aus meinem PC! 😃
    Ich habe einen speaker code (timer, channel 2) probiert, aber da ist alles abgestürzt, hier ist der Code, ging aber nicht, alles abgestürzt. 🙄
    ..


  • Mod

    Als Belohnung für die, die bisher dabei geblieben sind:
    http://www.youtube.com/watch?v=_RyodnisVvU 😉
    http://www.youtube.com/watch?v=o8VqCqpfWrk



  • Erhard Henkes schrieb:

    http://www.youtube.com/watch?v=o8VqCqpfWrk

    *gähn*
    das sind coole roboter: http://www.bostondynamics.com/content/sec.php?section=robotics
    (mit verbrennungsmotor und so)
    🙂


  • Mod

    OK, will niemanden langweilen, also "back to the grindstone":
    http://www.henkessoft.de/OS_Dev/Downloads/PrettyOS_last_version.zip

    Ich habe die GDT/IDT-Infos und Timer-Späßchen auf dem Screen lahm gelegt und begonnen, den Keyboard Treiber zu überarbeiten, damit man endlich Texte mit automatischem Zeilenumbruch und Scroll eintippen kann, so eine Art Primitiv-Editor eben. Nobuo T dürfte sich freuen, die while(TRUE) Story ist vorbei, nun läuft es nur einmal den "Scanner" durch, wie es logischer ist. BACKSPACE, TAB und ENTER funktionieren, aber man hat noch keine Steuerung mit den Pfeiltasten (da bin ich noch vorsichtig, ein move_cursor_right() ist allerdings schon eingebaut; Orientierung an schwarzer DOS-Box (cmd)?), diesbezüglich muss man also noch "übertippen" bzw. sich mit der Kombination aus TAB und BACKSPACE korrekt positionieren. Pos1, Ende, Entf ... sind auch noch nicht aktiviert. Das läuft ja alles via putc(...) als default im printformat(...), siehe keyboard_handler(...).
    ..

    Was fehlt eurer Meinung nach noch bei dem EDIT-Modus des OS?
    "Befehle" würde man dann wohl per ENTER entgegen nehmen?
    In einem strcmp (analog zur Methode im Real Mode, jetzt nur in C) vergleichen und entsprechend über eine switch/case-Kontrollstruktur reagieren?
    Welche Befehle würdet ihr denn als ersten Schritt implementieren?
    - Info/info
    - Help/help/?
    - Reboot/reboot
    - ...


  • Mod

    Damit wir nicht den Gesamtüberblick verlieren, hier eine rohe Tasklist / Sammlung aus den bisherigen Posts:

    - PIC (die beiden PICs wurden bereits beim remapping von IRQ 0-15 verarbeitet)
    - PIT (programmable interval timer)
    - Speaker (ja, BEEP (frequence)! Seit Win NT kaputt wegen Behinderung.)
    - (video.c vernünftig ausbauen, bisher nur rudimentär)
    - OS-Thematik (generell)
    - Dynamische RAM Verwaltung:
    - Unterteilung des Speichers in Blöcke statischer Groesse (4KB)
    - Verwaltung (stat. Arrays, Bitmaps, dynam. Free-Lists)
    - Reservierungsstrategie next fit
    - Wiedereingliederungsproblematik
    - Paging
    - Programme & Prozesse
    - Multitasking vs Singletasking
    - Prozesserzeugung, Kontextwechsel, Scheduling, Verdrängung
    - Privilegien / Schutzmechanismen / Adressraumtrennung
    - Micro- vs Macro-Kernel
    - inter-process communication

    - Kernel-API (und einem HW-Abstraktions/Treiber-Prinzip).
    Alle Kernel-Funktionen, Treiber etc. einfach nur ueber C-Funktionen und header zur Verfuegung zu stellen,
    ist sicher nicht das Wahre. Ich wuerde die Einrichtung eines kleinen Sets der wichtigsten Funktionen zum
    I/O und spaeter dann Speicher- und Prozessverwaltung ueber Interrupts aehnlich DOS oder Linux vorschlagen.
    Je nachdem, was fuer einen Kern du basteln willst (Micro oder Monolith), waere es evtl. sinnvoll,
    das schon ganz am Anfang beim Aufbauen der ersten abstrakteren OS-Funktionen wie RAM-Verwaltung zu diskutieren,
    statt erst bei Adressraumtrennung, Privilegien und IPC. Evtl. mit Verweis auf diese spaeteren Themen zur Begruendung.

    - Was du da weiter anfuehrst, sind doch eher Spezialfaelle und Teilgebiete, des Themenbereichs Scheduling
    (Echtzeit-Prozesse). Solche Ansaetze wie Prioritaetensteuerung oder verschiedene Ansaetze fairer Ressourcenverteilung
    mit verschiedensten abgefahrenen queue-Konstrukten (da gibt es wirklich eine Menge weit komplizierteres
    als einfache Prioritaetensteuerung) kann man da vielleicht in einem Ueberblick kurz anschneiden,
    aber um den Rahmen nicht zu sprengen, waere mein Vorschlag, sich schliesslich einfach auf Round Robin zu beschraenken.

    - DMA-Gefrickel ist ja nun voellig abgehoben. Immer im Hinterkopf behalten, dass das ein Internet-Tutorial
    und kein Kompendium zur OS-Entwicklung werden soll - das wird wie ich das sehe schon so eine ordentliche Portion Stoff.

    Timer:
    - für frei laufende countdown-timer machste dir z.b. ein paar funktionen:
    // erzeugt einen neuen timer und meldet ihn beim OS an. gibt 0 zurück, wenn kein timer alloziert werden konnte
    timer_t *timer_create(void);

    // startet den timer, der ab jetzt vom OS von 'timeout' bis 0 runtergezählt wird
    void timer_start (timer_t *timer, unsigned long timeout);

    // prüft ob der timer abgelaufen ist, gibt 0 zurück, wenn der timer noch läuft, sonst 1
    int timer_expired (timer *t);

    // beseitigt den timer (trägt ihn z.b. aus der liste des OS aus und gibt seinen speicher frei)
    void timer_free (timer_t *timer);

    die hardware-isr schaut bei jedem tick in eine verkettete liste, ob timer drin sind, die sie runterzählen muss.

    Keyboard:
    - zu den I/O-daten: am besten du benutzt FIFO-buffer für sowas.
    die ISR (z.b. von der tastatur) trägt empfangene daten in den FIFO ein und die anwendung holt sie sich raus.
    ein tastatur-FIFO kann recht klein sein und darf ältere daten überschreiben, weil uralte tastendrücke ja nicht mehr interessieren.
    ein benutzer-prozess kann dann einfach zeichen aus dem FIFO holen, z.b. so:
    // hole ein zeichen aus dem tastatur-buffer. wenn c -1 (oder EOF) ist, war der FIFO leer
    int c = getchar();



  • Erhard Henkes schrieb:

    Was fehlt eurer Meinung nach noch bei dem EDIT-Modus des OS?

    vi-Kompatibilitätsmodus? 😉


  • Mod

    vi-Kompatibilitätsmodus?

    Interessanter Punkt. Ich kenne bisher nur dieses gvim und kann mich nur noch dunkel an den DOS editor (EDIT) erinnern. Arbeitet vi wie gvim?



  • Erhard Henkes schrieb:

    vi-Kompatibilitätsmodus?

    Interessanter Punkt. Ich kenne bisher nur dieses gvim und kann mich nur noch dunkel an den DOS editor (EDIT) erinnern. Arbeitet vi wie gvim?

    vim ist ein aufgemotzter vi, also voll kompatibel, aber genau so besch... in der bedienung. btw, ein editor ist doch kein bestandteil eines OS, sondern nur eine gewöhnliche anwendnung. mach besser mit'm OS weiter und nicht noch 'ne baustelle auf.
    🙂


  • Mod

    ein editor ist doch kein bestandteil eines OS, sondern nur eine gewöhnliche anwendnung. mach besser mit'm OS weiter und nicht noch 'ne baustelle auf.

    "Festina lente" sagt der Lateiner (Lieblingsspruch des Kaisers Augustus). Mir geht es aktuell darum, keyboard.c und video.c vernünftig zu stabilisieren. Diese beiden Module mit ihren Funktionen sind wichtig. Das wird dann auch das Ende von Teil 1 (Booten, RM, A20 Gate, RM -> PM, asm <-> C, GDT, IDT u. IRQ, video, timer und keyboard). Bei Teil 2 geht es mit dem wirklichen OS (Speicher, Multitasking, Prozesse, API, ...) weiter. Aber zuerst muss Teil 1 so abgerundet sein, dass man dort niemand verliert.

    Ein echter Editor ist in der Tat ein User-Programm, gehört also nicht zwingend zum OS. Daher werde ich mich bezüglich der Bedienung an den DOS- und Linux-Konsolen orientieren.

    Noch eine Detail-Frage: Über den PIT bin ich ja etwas weg gegangen, akzeptiere bisher die Standardeinstellung. Sollte man so etwas in der Art noch einbauen:

    void systemTimer_setFrequency( ULONG freq ) 
    {
       ULONG divisor = 1193180 / freq; //divisor must fit into 16 bits
    
       //TODO: save frequency globally
    
       // Send the command byte.
       outportb(0x43, 0x36);
    
       // Send divisor.
       outportb(0x40, (UCHAR)(  divisor     & 0xFF )); // low  byte
       outportb(0x40, (UCHAR)( (divisor>>8) & 0xFF )); // high byte
    }
    

    Ich habe es mal mit 100 Hz (Standard: ca. 18 Hz), also alle 10 ms ein Tick, probiert:

    void timer_install()
    {
        /* Installs 'timer_handler' to IRQ0 */
        irq_install_handler(0, timer_handler);
        systemTimer_setFrequency( 100 );
    }
    
    void sleepSeconds (ULONG seconds)
    {
        // timer_wait((ULONG)18.2065*seconds); // standard
        timer_wait((ULONG)100*seconds);
    }
    

    Macht das mit 100 Hz Sinn? Sollte man hier eine Auswahl bieten oder eine Festeinstellung vornehmen? (wird für IRQ-gesteuertes Multitasking interessant)

    Beim Keyboard verwende ich nun auch gespeicherte Cursors, falls man z.B. Infos in eine Statuszeile schreibt:

    void keyboard_handler(struct regs* r)
    {
       UCHAR KEY;
       KEY = k_getch();
       restore_cursor();
       printformat("%c",KEY); // the ASCII character
       save_cursor();
    }
    

    ckernel.c:

    #include "os.h"
    
    int main()
    {
        k_clear_screen();
    
        // GDT, IDT, ISRS, IRQ, timer, keyboard
        gdt_install();
        idt_install();
        isrs_install();
        irq_install();
        timer_install(); //jetzt incl. Frequenz
        keyboard_install();
        sti();
    
        set_cursor(0,0);
        printformat("   ************************************************\n");
        printformat("   *                                              *\n");
        printformat("   *          Welcome to PrettyOS 0.05            *\n");
        printformat("   *                                              *\n");
        printformat("   *        The C kernel has been loaded.         *\n");
        printformat("   *                                              *\n");
        printformat("   ************************************************\n");
    
        settextcolor(4,1);
    
        int y=10;
        while(TRUE)
        {
    	sleepSeconds(20);
    	if(y>24) y=24;
    	set_cursor(0,++y);
    	printformat("10 sec have passed");
        };
        return 0;
    };
    

    In Bochs klappt dies ganz gut mit den 100 Hz.

    Allgemein:
    Bezüglich des OS überdenke ich momentan die Reihenfolge. Wäre nun das "Paging" als nächster Schritt angesagt? Man kann diesem Mechanismus ja doch nicht wirklich entfliehen, und ich möchte wegen des interessanten inneren Aufbaus ein Multitasking-OS aufbauen.


Anmelden zum Antworten