Eigenes OS?
-
Ja, das ist ein Argument.
..
Was mir am meisten fehlt, ist eine standardisierte Diagnose-Funktion für die Memory-Darstellung. Vielleicht muss ich da meine "ODA" noch mit ein paar Daten auf Stand halten, die ich dann bei Bedarf loggen kann. Hauptproblem sind momentan page faults.
Das mit dem FIFO (20 Keys) bei den Tasten-Infos hat ja sehr gut geklappt. Danke für den Vorschlag. Die exakte Auswertung der Scans kann man später noch ausgefeilt aufbauen, wenn die Anwendungen (z.B. eine Shell) diese wirklich brauchen.
-
..
-
Das hier ist z.B. die "niedliche" Umschaltung auf User Mode. Da hinten dran wühle ich gerade herum. Jetzt fehlen noch brauchbare syscalls usw. Kämpfe noch mit page faults, weil der user nicht einfach auf kernel code zugreifen darf. PM schlägt voll zu (schön!). Endlich passt alles zusammen.
..
Danach hat man allerdings keinen direkten Zugriff mehr auf den eigenen Code in ckernel.c! Wenn man nach dem Umschalten in den user mode nicht per "page fault" ausgehebelt werden will, muss man dem user sozusagen Supervisor-Rechte geben, funktioniert voll!
..
-
Fragen:
- Welche Privilegstufen sollte man einrichten? Nur 0 und 3, oder auch 1 und 2?
--- Kernel (0) und User (3) ist klar, wozu sollte man den Rest nutzen?
- Wie würdet ihr die "sys calls" (call to the kernel) praktisch einrichten?
--- software interrupt (linux: 0x80), ich würde 0x7F bevorzugen wegen der unsigned char Begrenzung in unserem OS?
-
Erhard Henkes schrieb:
Frage: Wie würdet ihr nun die Syscalls praktisch einrichten?
x86'er haben spezielle befehle dafür: http://www.ews.uiuc.edu/~cjiang/reference/vc311.htm
-
Danke für den Link! Was hältst Du von der Software-Interrupt-Methode (software interrupt bei Linux: 0x80), ich würde allerdings 0x7F bevorzugen wegen der aktuellen Begrenzung auf 0..127 in unserem OS?
-
Erhard Henkes schrieb:
Was hältst Du von der Software-Interrupt-Methode ...
geht im prinzip auch, aber sysenter ist schneller. diese software-interrupt methode funzt ausserdem sogar auf 368'ern. 'sysenter' gibt's ja erst ab pentium-ähnlichen und späteren intel- und AMD-prozessoren. aber ich bin nicht so der x86-freak, warten wir doch mal ab, was nobuo dazu zu erzählen hat.
btw: bei syscalls musste extrem drauf achten, dass du keine backdoors einbaust. das problemchen hatten z.b. die ersten versionen von win-nt 4, die man mit zufallsparametern in syscalls crashen, oder sehr einfach code in den kernel einschleusen konnte. daher auch der imageverlust von windoofs, an dem mickrigsoft bis heute zu knabbern hat.
-
Na, klasse! Bereits die erste Sicherheitsdiskussion. ..
'sysenter' gibt's ja erst ab pentium-ähnlichen und späteren intel- und AMD-prozessoren.
Eines meiner Ziele ist, dass PrettyOS ab 80386 lauffähig ist, denn die späteren CPU haben an der prinzipiellen inneren Technik (GDT, IDT, ...) fest gehalten. Daher auch die komplizierte Umschaltung von einem Gate auf das andere, die bei modernen Pentiums hoffentlich leichter geht.
-
also ich würde euch empfehlen, das ganze über interrupts zu machen. da gibts dann später bei multitasking keine probleme, da bei einem interrupt autamtisch das i-flag in den eflags gelöscht wird, sodass der kernel nicht unterbrochen werden wird, bis er zumindest einen stack gewechselt hat bzw. bis ihr das halt wieder zulasst... Aber nachdem das ja ein Sprung in den Kernel ist, könnte es sein, dass dann zwei Tasks gleichzeitig springen und im Kernel dann Datenstrukturen zerstört werden, weil das nicht synchron geschieht.
Aber das ist nur meine bescheidene Meinung
-
Die Interrupt-Technik habe ich bereits vorbereitet (0x7F = 127).
..
Danke an alle, die mich bisher soweit getragen haben.
Jetzt habe ich soviel OS-Blut geleckt, dass ich keinesfalls aufhöre.
Diesem Assembler-Forum möchte ich für seine konstruktive und offene Haltung ein ganz großes Lob aussprechen. Das findet man sehr selten!
-
blitzmaster schrieb:
also ich würde euch empfehlen, das ganze über interrupts zu machen. da gibts dann später bei multitasking keine probleme, da bei einem interrupt autamtisch das i-flag in den eflags gelöscht wird, sodass der kernel nicht unterbrochen werden wird...
interrupts global zu sperren (macht man das mit dem i-flag?) ist, wenn nicht unbedingt erforderlich, eigentlich keine gute idee, weil dann interrupt-anforderungen verloren gehen können, wenn die sperre zu häufig eingesetzt wird oder lange dauert. und ich weiss nicht, ob die pc-architektur 'nested' interrupts und interrupt-prioritäten kennt (ich glaub' die hardware unterstützt es nicht, aber es geht mit software-tricks).
blitzmaster schrieb:
Aber nachdem das ja ein Sprung in den Kernel ist, könnte es sein, dass dann zwei Tasks gleichzeitig springen und im Kernel dann Datenstrukturen zerstört werden, weil das nicht synchron geschieht.
in einem multitasking-os sollten systemfunktionen sowieso immer reentrant-fähig sein. dass mehrere task gleichzeitig im kernel rumschwirren, oder gar die selbe systemfunktion nutzen, ist keine seltenheit.
-
in einem multitasking-os sollten systemfunktionen sowieso immer reentrant-fähig sein. dass mehrere task gleichzeitig im kernel rumschwirren, oder gar die selbe systemfunktion nutzen, ist keine seltenheit.
Klingt wirklich herausfordernd, erinnert mich an multithreading. Da lauern neben "malicious codes" bestimmt schon die "deadlocks".
interrupt-prioritäten
Je kleiner die Zahl, je höher die Priorität, zumindest habe ich es so gelesen. Also Timer > Keyboard > ...
Hat eigentlich jemand einen Tipp, wie ich "Files" - ohne GRUB - möglichst einfach in diese RAM disk tranferieren kann, so dass man den VFS-Mechanismus mal vorführen könnte?
-
+fricky schrieb:
blitzmaster schrieb:
also ich würde euch empfehlen, das ganze über interrupts zu machen. da gibts dann später bei multitasking keine probleme, da bei einem interrupt autamtisch das i-flag in den eflags gelöscht wird, sodass der kernel nicht unterbrochen werden wird...
interrupts global zu sperren (macht man das mit dem i-flag?) ist, wenn nicht unbedingt erforderlich, eigentlich keine gute idee, weil dann interrupt-anforderungen verloren gehen können, wenn die sperre zu häufig eingesetzt wird oder lange dauert. und ich weiss nicht, ob die pc-architektur 'nested' interrupts und interrupt-prioritäten kennt (ich glaub' die hardware unterstützt es nicht, aber es geht mit software-tricks).
Jo, das IF ignoriert praktisch die IRQ-Line der CPU (nicht aber NMIs, exceptions, usw.).
AFAIR: Selbst der PIC (programmable Interrupt Controller) in den ersten PCs kannte schon eine Prioritaetsbehandlung von IRQs. Das Ding wurde dann zunaechst in typischer x86-manier erweitert, indem man quasi den gleichen Controller nochmal hinten dran geklatscht hat (toll: jetzt mit 16 IRQs!!) und inzwischen gibt es den in die CPU integrierten APIC.
IAR geht also nichts verloren, wenn das IF geloescht wird ... es sei denn, das bleibt ueber Sekunden so (daher auch meine Ermahnung, schnelle IRQ-handler zu schreiben).
Dennoch hast du recht: IRQs sollte man nur ausschalten, wenn unbedingt noetig, was bei x86ern mit ihren ganzen atomaren spezial-Befehlen im Prinzip relativ selten vorkommen duerfte.+fricky schrieb:
blitzmaster schrieb:
Aber nachdem das ja ein Sprung in den Kernel ist, könnte es sein, dass dann zwei Tasks gleichzeitig springen und im Kernel dann Datenstrukturen zerstört werden, weil das nicht synchron geschieht.
in einem multitasking-os sollten systemfunktionen sowieso immer reentrant-fähig sein. dass mehrere task gleichzeitig im kernel rumschwirren, oder gar die selbe systemfunktion nutzen, ist keine seltenheit.
Richtig. Da kann man entweder alle moeglichen Teile des Kerns einzeln in kritische Sektionen packen, oder man tuetet den ganzen Kern einfach in eben eine solche. Zu Demonstrationszwecken im Rahmen eines Tutorials waere das IMHO uebersichtlicher. Dabei kann man dann auch problemlos IRQs zulassen.
Int loescht uebrigens nicht das IF. AFAIR sysenter genau so wenig.
Die Entscheidung gegen sysenter und fuer int befuerworte ich uebrigens (wie ich schon sagte) : Kein neuer Spezial-Befehl, Gebastel mit AMD vs. Intel, einfache Handhabung, abwaertskompatibel. KISS halt.BTW ist die Benutzung eines eigenen Stacks fuer Kern-Aufrufe (auch IRQ- und Exception-Handler) wie gesagt dringend zu empfehlen. Praktisch fast wie ein TASK-Switch.
-
Nobuo T schrieb:
Int loescht uebrigens nicht das IF. AFAIR sysenter genau so wenig.
So weit ich weiß, wird das IF bei einem Sprung zu einem Interrupt Gate gelöscht.
-
Erhard Henkes schrieb:
in einem multitasking-os sollten systemfunktionen sowieso immer reentrant-fähig sein. dass mehrere task gleichzeitig im kernel rumschwirren, oder gar die selbe systemfunktion nutzen, ist keine seltenheit.
Klingt wirklich herausfordernd, erinnert mich an multithreading.
isses auch. multithreading und -tasking bedeutet, je nach kontext, mal was anderes, mal das selbe. windows-user verstehen unter multitasking, dass gleichzeitig mehrere prozesse möglich sind, von denen jeder mehrere threads (==parallel ausführbare einheiten) haben kann. in der RTOS-terminologie wird das, was unter win 'thread' heisst, oft 'task' oder 'prozess' genannt.
Nobuo T schrieb:
BTW ist die Benutzung eines eigenen Stacks fuer Kern-Aufrufe (auch IRQ- und Exception-Handler) wie gesagt dringend zu empfehlen. Praktisch fast wie ein TASK-Switch.
dann haste aber ein ziemlich schwergewichtiges interface zwischen kernel und userland, das syscalls relativ unperformant macht. eigentlich braucht man doch nur einen privilegwechsel beim sprung in den kernel und bei der rückkehr. natürlich musste noch checken, ob die parameter gültig sind, genug platz auf dem stack ist, pointer sinnvollen inhalt haben und ähnliche kleinigkeiten. einen kompletten, task-switch ähnlichen mechanismus, halte ich für übertrieben. aber vielleicht geht's mit den x86 software-int befehlen nur so (weshalb später 'sysenter' eingeführt wurde).
-
Die system calls laufen. Man kann also nun z.B. die Funktion puts(...) als syscall_puts(...) aufrufen. Ich benötige mal eure Hilfe, da ich momentan etwas nicht verstehe und nicht sicher bin, ob das genau so richtig ist oder ob im OS etwas nicht stimmt (was mir wahrscheinlicher erscheint).
Ich habe hier zwei Varianten:
http://www.henkessoft.de/OS_Dev/Downloads/31&32.zip
31) nach dem switch_to_user_mode() hat der user privileg 0
32) nach dem switch_to_user_mode() hat der user privileg 3Im ersten Fall:
switch to user mode
After set_kernel_stack. tss_entry.esp0: 0000083Ch
Right before user modeIRQ 127<<<Hello, user world!
behind switch to user mode with ring 0 supervisor privilege!
SS: 00000010h, ESP: 0018FFE0h, EBP: 0018FFF0h, CS: 00000018h, DS: 00000020hDas "Hello, user world!" wird mittels syscall_puts(...) ausgegeben. Daher weiß ich, dass die system calls nun funktionieren. Baut man auf diese Weise ein API auf? (habe das bisher noch nie gemacht )
switch_to_user_mode(); syscall_puts("Hello, user world!\n");
Im zweiten Fall:
switch to user mode
After set_kernel_stack. tss_entry.esp0: 0000083Ch
Right before user mode
Page FaultException. System Halted! <<<
Ich bin etwas irritiert darüber, dass im echten User Mode der Syscall (ring 3 ruft ring 0) vor dem Page Fault (wegen unerlaubtem Zugriff auf Kernel code) nicht durchgegangen ist. Vielleicht liegt es daran, dass auf dem Weg zur Ausführung des Syscalls (... -> IRQ127 -> syscall_handler -> ...) bereits ein "Page Fault" erfolgte.
Sorry, dass der Code im IRQ-Bereich momentan etwas "gestöpselt" ist. Das muss ich noch aufräumen, wollte jetzt nur erst mal das Tasking und die Syscalls zum Laufen bekommen, denn die Schnittstelle zwischen Kernel und Anwendungen im User Mode sind ja genau das Interessante.
Wenn ich das so nebeneinander sehe, wundere ich mich gerade über die Leerzeile zwischen Right before user mode und >>>IRQ 127<<<Hello, user world!.
Die nächste Aufgabe ist jetzt, den vorhandenen Code vernünftig zu überarbeiten, damit er eine wirklich brauchbare Basis für ein OS ergibt, möchte nicht völlig chaotisch weiter hetzen, weil dann die Didaktik und vor allem das Tutorial auf der Strecke bleibt. Schlechten Code erklärt und zeigt man nicht gerne, sondern ist froh, wenn er läuft. Damit Experimentieren ist zumeist auch nicht gerade lustig. Vielleicht, wenn man die Module noch ordentlich sortiert und hier und da entkoppelt, kann das noch etwas Sinnvolles werden?
Vielleicht kann sich ein Assembler-Spezialist die IRQ/ISR/exception-Story mal anschauen (z.Z. eine wilde Mischung aus ASM und C). Da blicke ich kaum noch selbst durch. Den int127 (128 ging nicht wegen Überlauf, muss ja auch nicht sein, wäre nur wegen Linux 0x80) habe ich schon an den fault_handler, der eigentlich nur für exceptions gedacht war, "provisorisch angeschraubt", ging irgendwie nicht anders. Aber so ist wahrscheinlich auch Windows und Linux entstanden.
Inzwischen habe ich gehörig Respekt vor der OS-Entwicklung.
... und das Schwierigste liegt ja eher noch vor mir.
-
..
Gut finde ich bisher,
- dass nicht der GRUB Loader verwendet wird
- den sauberen Übergang von Assembler zum C-Kernel (thanks to Nobuo T)
- die Bitset-Verwaltung beim Paging (effizient)
- das VFS analog Linux (leider noch nicht richtig in Aktion)Schlecht finde ich
- das Interrupt-Gerödel (richtig zusammen geschustert, da verwende ich sogar noch den alten Aufbau, weil James das auch nicht wirklich auf die Reihe bekam, ist jedoch im Wesentlichen Fleißarbeit, das zu ordnen)
- die AT&T Syntax innerhalb C (ziemlich unverständlich, das gehört wirklich alles als Intel Syntax ausgelagert, da hat abc.w Recht)Unsicher bin ich mir bei
- der Heap-Erzeugung/-Verwaltung (schwieriges aber langweiliges Thema, Hauptsache läuft erstmal)
- der Tasking-SteuerungDas wird aber alles noch richtig gut, da bin ich ganz sicher.
Ich musste für mich schnell Zusammenhänge praktisch durchspielen, daher der Rückgriff auf Vorhandenes, um zu sehen, was wirklich voneinander abhängt. Übrigens geht es anderen auch so. Selbst, wenn ich den Sourcecode alleine erfinde, bringt das niemand weiter. Für andere ist der Code immer "fremd". Was zählt, sind gute Erklärungen. Da sieht es in James Molloy's Tutorial allerdings düster aus.
Was mir bisher noch weitgehend fehlt, ist die Darstellung der Arbeitsweise des OS. Ich möchte kein "unsichtbares" OS, sondern einen "offenen Wecker". Da habe ich bisher überhaupt noch keinen Ansatz gefunden, das ist bei vielen "Educational OS" lauter in sich geschlossenes unverständliches "Gefrickel".
..
-
..
-
So sieht momentan ein typischer Bildschirmausdruck zur Überprüfung aus:
..Bezüglich Visualisierung des Pagings habe ich mal Folgendes probiert:
Um das Bitset mit seinen Nullen und Einsen und den Bezug zu den Speicheradressen darzustellen, habe ich die unten stehende Funktion entworfen. Sie bleibt 'sec' Sekunden stehen, wenn eine oder mehrere Einsen (allokierte Pages) auftauchen, bei Nullen flitzt sie durch. Damit kann man recht praktisch den Speicher scannen und sich in Bochs mit copy von der jeweils interessanten Region screenshots machen:void analyze_frames_bitset(ULONG sec) { ULONG index, offset, counter1=0, counter2=0; for(index=0; index<(NFRAMES/32); ++index) { settextcolor(15,0); printformat("\n%x ",index*32*0x1000); ++counter1; for(offset=0; offset<32; ++offset) { if( !(frames[index] & 1<<offset) ) { settextcolor(4,0); putch('0'); } else { settextcolor(2,0); putch('1'); ++counter2; } } if(counter1==24) { counter1=0; if(counter2) sleepSeconds(sec); counter2=0; } } }
Diese Funktion geht sämtliche Pages durch und stellt jeweils 32 in einer Zeile je nach Allokation als Null oder Eins dar. Vorne steht die erste Hex-Speicheradresse der ersten Page (also jeweils 4KB * 32 = 128KB in einer Reihe).
Bsp.: Erstes Ende der kompakten Allokierung oberhalb 18 MB (1200000h) :
011C0000h 11111111111111111111111111111111 011E0000h 11111111111111111111111111111111 01200000h 11111111111111111110000000000000 01220000h 00000000000000000000000000000000
Screenshot: http://www.henkessoft.de/OS_Dev/Bilder/Paging_Scan.PNG
Bsp.: #define KHEAP_START 0x40000000 // 1GB
#define KHEAP_INITIAL_SIZE 0x100000
#define KHEAP_MAX 0x4FFFF000
Dort befindet sich auch die RAM Disk:3FFC0000h 00000000000000000000000000000000 3FFE0000h 00000000000000000000000000000000 40000000h 10100110000011001000100010000000 40020000h 10100000000000101000100010000000 40040000h 10100000000010101000100010000000 40060000h 10100000000001101000100010000000 40080000h 10100000000011101000100010000000 400A0000h 10100000000000011000100010000000 400C0000h 10100000000010011000100010000000 400E0000h 10100000000001011000100010000000 40100000h 10100000000011011000100010000000 40120000h 10100000000000111000100010000000 40140000h 10100000000010111000100010000000 40160000h 10100000000001111000100010000000 40180000h 10100000000011111000100010000000 401A0000h 10100000000000000100100010000000 401C0000h 10100000000010000100100010000000 401E0000h 10100000000001000100100010000000
usw.
Zum Verständnis die Bitset-Funktionen aus paging.c:
..Nun "sieht" man endlich, was man macht.
Solche Dinge sind mir wie bereits ausgeführt wichtig.EDIT: Ebenfalls hilfreich ist eine Analyse der physikalischen Adressen der Startadressen der Pages:
ULONG show_physical_address(ULONG virtual_address) { page_t* page = get_page(virtual_address, 0, kernel_directory); return( (page->frame_addr)*PAGESIZE + (virtual_address&0xFFF) ); } void analyze_physical_addresses() { int i,j,k, k_old; for(i=0;i<(PHYSICAL_MEMORY/0x18000+1);++i){ for(j=i*0x18000; j<i*0x18000+0x18000; j+=0x1000){ if(show_physical_address(j)==0){ settextcolor(4,0); k_old=k; k=1; } else{ if(show_physical_address(j)-j){ settextcolor(3,0); k_old=k; k=2; } else{ settextcolor(2,0); k_old=k; k=3; } } if(k!=k_old) printformat("%x %x\n", j, show_physical_address(j)); } } }
00000000h 00000000h
00001000h 00001000h
01113000h 00000000h
01400000h 12389000h
40100000h 00000000h
40400000h 12389000h12389... ist eine "Magic Number" (#define HEAP_MAGIC 0x123890AB in kheap.h)
-
Um den Page Fault näher zu beschreiben, wurde jetzt folgende Auswertung eingebaut:
if (r->int_no == 14) //Page Fault { ULONG faulting_address; asm volatile("mov %%cr2, %0" : "=r" (faulting_address)); // faulting address => CR2 register // The error code gives us details of what happened. int present = !(r->err_code & 0x1); // Page not present int rw = r->err_code & 0x2; // Write operation? int us = r->err_code & 0x4; // Processor was in user-mode? int reserved = r->err_code & 0x8; // Overwritten CPU-reserved bits of page entry? int id = r->err_code & 0x10; // Caused by an instruction fetch? // Output an error message. printformat("Page Fault ("); if (present) printformat("page not present"); if (rw) printformat("read-only - write operation"); if (us) printformat("user-mode"); if (reserved) printformat("overwritten CPU-reserved bits of page entry"); if (id) printformat("caused by an instruction fetch"); printformat(") at %x - EIP: %x\n", faulting_address, r->eip); }
Test mit Page Fault: http://www.henkessoft.de/OS_Dev/Downloads/34.zip
Der Page Fault wurde provoziert durch Streichen des Extra-Speichers oberhalb der placement_address:
// Allocate a little bit extra so the kernel heap can be initialised properly. // map (phys addr <---> virt addr) from 0x0 to the end of used memory ULONG counter=0; i=0; while( i < placement_address /*+ 0x100000*/ ) //important to add more! { alloc_frame( get_page(i, 1, kernel_directory), 0, 0); i += PAGESIZE; ++counter; }
Dadurch kann man heraus finden, wieviel Speicher allokiert werden muss, um den Kernel Heap zu initialisieren. Endlich mal eine Aufgabe für die Leser.
Antwort: Bei 0x0 0der 0x1000 erfolgt ein Page Fault. Die Info lautet:
Page Fault (page not present) at 01014004h - EIP: 0000AD36h
Über die Info 01014000h 00000000h weiß man, dass nur bis 01013000h frames allokiert wurden. Daher muss man mindestens 0x1001 addieren, welches dann auf 0x2000 "aligned" wird. Dann klappt das.
So langsam bekomme ich das Thema Paging didaktisch in den Griff. Jetzt fehlt eigentlich nur noch die Darstellung des nicht-linearen Mappings.