Eigenes OS?
-
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.
-
Für das Laden von vorgefertigten File-Daten in den Speicher für die Ram Disk des VFS hat mir jemand einen möglichen Weg gewiesen, nämlich via NASM-Anweisung incbin und Linken mit ld. Diesen Befehl kannte ich bisher noch nicht.
-
Hier ein prinzipielles Beispiel mit Erzeugung mehrerer Tasks und Taskswitch:
http://www.henkessoft.de/OS_Dev/Downloads/35.zipAfter set_kernel_stack ==> tss_entry.esp0: 40101800h
> IRQ 127 <<<
Hello, user world!create a new process in a new address space
fork() returned: 00000002h and getpid() returned: 00000001hcreate a new process in a new address space
fork() returned: 00000003h and getpid() returned: 00000001hcreate a new process in a new address space
fork() returned: 00000004h and getpid() returned: 00000001hgetpid() 00000002h
getpid() 00000003h
getpid() 00000004h
getpid() 00000001h
getpid() 00000002hint retValFork1 = fork(); //create a new process in a new address space which is a clone of this int retValGetPid1 = getpid(); printformat("fork() returned: %x and getpid() returned: %x\n", retValFork1, retValGetPid1); //... task_switch(); printformat("getpid() %x\n", getpid()); sleepSeconds(3); task_switch(); //...
Den Scheduler kann man dann über den Timer aufrufen.
Mit Task Switches kann man schon auf einfachste Weise recht nette Sachen machen, hier ein Beispiel mit Farbwechsel (Farbe = Process ID) beim Tippen:
http://www.henkessoft.de/OS_Dev/Downloads/35a.zip (Programm)
http://www.henkessoft.de/OS_Dev/Downloads/task_switch_test.png (Test in Bochs)UCHAR c=0; while(TRUE) { if( k_checkKQ_and_print_char() ) { ++c; if(c>5) { c=0; settextcolor(4,0); printformat("\nT: %x H: %x WRITE: %i Read: %i ", pODA->pTailKQ, pODA->pHeadKQ, pODA->KQ_count_write, pODA->KQ_count_read); printformat("*T: %c %i *H: %c %i\n", *(pODA->pTailKQ),*(pODA->pTailKQ),*(pODA->pHeadKQ),*(pODA->pHeadKQ)); settextcolor(2,0); task_switch(); settextcolor(getpid(),0); } } }
Interessant ist, wenn man die Zeile task_switch(); settextcolor(getpid(),0); an eine andere Stelle in der while-Schleife setzt, dann gibt es einen eindrucksvollen "General protection fault":
http://en.wikipedia.org/wiki/General_protection_fault#Switching
..Nun muss ich mich noch mit dem Fileformat, dem Transfer in die RAM Disk und dem Zugriff im Programm via Virtual File System beschäftigen. Sobald das alles klappt und die Didaktik passt, werde ich stepwise am Tutorial weiter schreiben (verflixt viel Material und Background) und ein Basis Modell für die Weiterentwicklung von PrettyOS stabilisieren.
-
Erhard Henkes schrieb:
Nun muss ich mich noch mit dem Fileformat, dem Transfer in die RAM Disk und dem Zugriff im Programm via Virtual File System beschäftigen. Sobald das alles klappt und die Didaktik passt, werde ich stepwise am Tutorial weiter schreiben (verflixt viel Material und Background) und ein Basis Modell für die Weiterentwicklung von PrettyOS stabilisieren.
nun wird es auch an der zeit sein, dir grundsätzliche überlegungen zu machen, ob du dabeibleiben oder abweichen solltest.
nur ein kleines beispiel: ich bekomme als spielstandslader oder compilerbauer vom bs (win oder linux hier echt gleich) per filemapping daten hereingeschubst. Shade nannnte es dieser datenhaltung diesbezüglich afair NonCopyStrings, ich splitte bei zum beispiel immer bei \n und habe "zeilen", die aus struct{char *begin,*end} bestehen. das bs gibt mir einen bei read() z.B. 8K großen sektor un ich gebe ihn zurück, wenn ich ihn nicht mehr brauche. natürlich kann ich als old-style-lib ein kopierendes read() im userspace anbieten. vielleicht wäre ein no-copy-read verflixt viel schneller, wenn die anwendung das neue paradigma verwendet. in dieser richtung isses nicht nervig. andersrum schon: ich habe viele strings, ich habe ausschließlich stings mit begin/end in meiner anwendung (naja, c++ halt) und muß bei jedem syscall erstmal speicher für nullterminierten string anlegen, würg.
vielleicht wäre das an sich gar kein problem, wenn man strings von vorn herein mit begin/end auch im kernel schon angedacht hätte.
vielleicht wäre das ganze startup/shuutdown-geschmuse gar nicht notwendig, wenn ein prozess==programm leben würde, sobald es installiert ist und runterfahren/hochfahren nur betriebssystem-kacke wäre, die die anwendungen gar nicht sehen.
nur mal so als meinen kleinen einwurf. es könnte möglich sein, daß anscheinend voll verrückte konzepte aus versehen zugleich dir voll arbeit sparen, das BS schneller machen und für den anwendungsprogrammierer auch noch netter sind.hihi, ich möchte nicht in deinen schuhen stecken.
-
volkard schrieb:
nur ein kleines beispiel: ich bekomme als spielstandslader oder compilerbauer vom bs (win oder linux hier echt gleich) per filemapping daten hereingeschubst. Shade nannnte es dieser datenhaltung diesbezüglich afair NonCopyStrings, ich splitte bei zum beispiel immer bei \n und habe "zeilen", die aus struct{char *begin,*end} bestehen. das bs gibt mir einen bei read() z.B. 8K großen sektor un ich gebe ihn zurück, wenn ich ihn nicht mehr brauche.
hört sich gut an. eine proprietäre high-speed datenschnittstelle zwischen OS und user-mode hat vorteile, z.b. wenn irgendwann netzwerkprotokolle, filesystems, etc. dran sind, die dann bequem im userspace laufen könnten, was der stabilität des system zugute kommen würde. knackpunkt ist aber das zurückgeben der buffer ans system, es wäre ja doof, wenn die anwendung das vergisst und der kernel irgendwann 'out of memory' ist.
volkard schrieb:
hihi, ich möchte nicht in deinen schuhen stecken.
ich auch nicht, aber erhard hat durchhaltevermögen und auf den kopf gefallen ist er auch nicht.
-
Danke für den interessanten Einwurf.
hihi, ich möchte nicht in deinen schuhen stecken.
Das Problem ist, dass alles miteinander vernetzt ist, aber so ist es nun einmal.
-
Erhard Henkes schrieb:
Das Problem ist, dass alles miteinander vernetzt ist, aber so ist es nun einmal.
deswegen sollteste frühzeitig das ganze in überschaubare teilkomponenten zerlegen, KISS-prinzip, teile und herrsche und so.
-
Erhard Henkes schrieb:
hihi, ich möchte nicht in deinen schuhen stecken.
Das Problem ist, dass alles miteinander vernetzt ist, aber so ist es nun einmal.
hihi, ich möchte nicht in deinen schuhen stecken.
-
deswegen sollteste frühzeitig das ganze in überschaubare teilkomponenten zerlegen
Genau dies werde ich versuchen, soweit das möglich ist. Paging, Heap, Task Switching, User Mode vs Kernel Mode, Syscalls, VFS sind solche Einheiten, die allerdings zum einfachen Begreifen teilweise zu groß sind. Da versuche ich gerade, Unterelemente als elementare Module weiter heraus zu schneiden.
Noch wichtiger ist die Visualisierung / Verständlichmachung dessen, was passiert, wenn man dies oder jenes ändert, weil viele Leute beim Verstehen und Begreifen eher praktisch an die Dinge heran gehen.
..
Der Schwachpunkt von PrettyOS besteht momentan noch darin, dass noch kein Fileformat festgelegt wurde (jedoch kein Problem) und noch kein Mechanismus zum Übertragen von "Files" (Laden/Lesen Schreiben/Speichern), z.B. aus der RAM Disk in den Speicher und umgekehrt existiert. Sobald diese Mechanismen funktionieren, komme ich auf dieses Thema "proprietäre high-speed Datenschnittstelle zwischen OS und User Mode" zurück. Es geht nichts verloren. Wie sehen die entsprechenden Pendants bei Linux und Windows genau aus, damit ich das im Detail studieren kann?
-
Erhard Henkes schrieb:
Wie sehen die entsprechenden Pendants bei Linux und Windows genau aus, damit ich das im Detail studieren kann?
win haste ja schon gefunden. bei linux heißt die schlüsselfunktion mmap http://linux.die.net/man/3/mmap
-
Danke!
-
Erhard Henkes schrieb:
Wie sehen die entsprechenden Pendants bei Linux und Windows genau aus, damit ich das im Detail studieren kann?
http://www.i.u-tokyo.ac.jp/edu/training/ss/lecture/new-documents/Lectures/
unter: 10-LPC. auch der rest, z.b. memory management, dürfte dich auch interessieren.
-
@+fricky: Danke für den Link!
Momentan interessiert mich dies hier am meisten, da ich davon bisher nur einen Bruchteil habe, und mir genau der Rest fehlt:
http://www.i.u-tokyo.ac.jp/edu/training/ss/lecture/new-documents/Lectures/02-VirtualMemory/VirtualMemory.pdf
PrettyOS muss aber nicht wirklich performant sein, sondern vor allem verständlich, eben als Experimentier-Plattform geeignet. Ich bin mir noch nicht sicher, ob mir das gelingt, hängt davon ab, inwieweit mir geeignete Visualisierungs-Funktionen für die einzelnen Module und Exceptions gelingen.Gibt es eine Übersicht, wie man weitere Infos aus Registern erhält, um Exceptions möglichst vollständig zu analysieren? Dann könnte man diese Funktion, die bisher nur Page Faults genauer unter die Lupe nimmt, weiter ausbauen:
..
r->err_code und das Register cr2 dürften wohl der Schlüssel dazu sein. Wenn man beim "Spielen" mit dem Code in ckernel.c auf eine Exception trifft, sollte man heraus finden können, was man eigentlich falsch gemacht hat. Das ist momentan noch nicht ausreichend gegeben. Das ist aus meiner Sicht noch das Hauptproblem.
-
English, my German is not very well.
Try to create some code for installing fault handlers, so each fault handler does have its own function. Check if a custom handler is installed, and execute it. If not, write down a message on the screen and halt CPU (cli, hlt, for(;;);)
Keeping them apart this way like, is very handy for dynamically installing and removing handlers. Also, when having a Virtual86Mode implemented, you will need #GP (general protection fault) very often, and write some big routines for it.for the Debug and Break exception you can write some nice simple handler. Just dump all registers given as argument in function call.
The Page Fault handler:
Receive the CR3 AND CR2. CR2 contains faulting address. CR3 the PHYSICAL address of the page directory. Then, split the faulting address in some parts.
PageDirectoryIndex (cr2 >> 22, when using 4kb pages on a 32-bit system)
PageTableIndex ((cr2 >> 12) & 0x3ff)
PageOffset ((cr2 & 0xfff), not necessary for now)Then, when page was not present, check the PDE for present bit, then the existence of the PageTable (PDE->frame << 12 and then to virtual), then present of PTE, and then existence of the page mapped to the PTE. (PTE->frame != 0x0)
Solve any of these things, but keep patience: be sure caller should be able to write or read something at the address in cr2.
When having r/w (read/write) page fault and is present, kill the process. It is trying to access kernel space. Same way with u/s (user/supervisor).
When looking at the Intel Manual, we see that there is NO ID bit in the error code of a #PF (page fault).
Reserved, if problems with that, kill process. (or maybe just continuing is fine too)greetings, and success.
// PHPnerd
-
@PHPnerd: thank you very much.
-
Keine Probleme.
I will look at this topic often to help here
I am busy with kmalloc.The memory management part is the most boring, tricky and frustrating part of OS development.Don't quit
Also: be sure you always print out messages of exceptions, especially at the page fault exception. There shouldn't be ANY exceptions occur when programming kernel itself.
// PHPnerd
-
PHPnerd schrieb:
There shouldn't be ANY exceptions occur when programming kernel itself.
even if the kernel is 100% bug free, third-party modules (device drivers and similar stuff running in kernel space) can crash the system. erhard should implement an exception handling mechanism, so that common errors (like wild pointers, divison by zero, etc.) are handled properly.
-
Yeah, but kernel itself (main routine, setting up paging) should
Writing div by zero handler. I think that should be setting return value to zero.
so
int a = 5/0;
a will be zero, instead of killing process.// PHPnerd
-
PHPnerd schrieb:
so
int a = 5/0;
a will be zero, instead of killing process.no, that keeps the error. the error creeps forward and will cause weird behaviour of the whole thing. instead, exceptions should be 'handled' in a controlled manner (like this):
... begin_guarded_section(); { ... int a = x/0; ... } if (get_exception_code() == DIV_BY_ZERO) // enter exception handler { // deal with division by 0 } end_guarded_section(); // stop guarding ... // process will be killed at this point, if another exceptions occur
^^all can be done using c-functions. there's no need for a modfied compiler (like that is the case with structured exception handling under m$-windoofs).