Beiträge anzeigen

Diese Sektion erlaubt es dir alle Beiträge dieses Mitglieds zu sehen. Beachte, dass du nur solche Beiträge sehen kannst, zu denen du auch Zugriffsrechte hast.


Themen - FlashBurn

Seiten: [1] 2
1
Lowlevel-Coding / Eigener mehrstufiger Bootloader
« am: 09. September 2012, 11:24 »
Als erstes möchte ich sagen, ja ich will einen eigenen Bootloader schreiben (bzw. habe dies ohnehin schon gemacht) und nicht GRUB und Co nutzen.

Da ich jetzt endlich wieder etwas mehr Zeit habe und mich mal wieder mit OSDev beschäftigen kann, wollte ich meinen Kernel (und dann auch das gesamte OS) auf andere Architekturen portieren. Da mit dem Raspberry Pi auch eine sehr gute Möglichkeit dafür zur Verfügung steht, habe ich mit der ARM-Archiktektur angefangen.

Bevor ich den Kernel portieren kann, muss dieser erstmal geladen werden können (und bei mir auch noch zusätzlich Programme). Nein ich will die Programme nicht in eine initrd packen (warum das eher unschön ist, sollte sich weiter unten ergeben).

Problem, vorallem auch auf der ARM Architektur, ist es, dass es keine einheitliche Platform, für die man programmieren kann, gibt. Jedes Board ist im Endeffekt wieder eine andere Platform, selbst wenn mir ein flexibler Bootloader zur Verfügung stehen sollte. Es ist von uboot, über Openfirmware (mein momentaner Favorit), fastboot bis bald EFI alles mögliche vorhanden.

Dann kommt noch hinzu, dass es keine, mir bekannte, Möglichkeit gibt, zur Laufzeit die vorhandene Hardware herauszufinden und vorallem auch wo im Adressbereich sie sich befindet. Also muss es auf jeden Fall auf der ARM-Architektur immer was Board-spezifisches geben.

Dazu kam mir dann, mal wieder (wie einige jetzt denken werden ;) ), hoch komplizierte Idee.

Ich möchte meinen Bootloader in mehrere Stages aufteilen (bisher nix neues):
  • Stage 1 (optional, wenn wir auf dem PC, die Firmware keine Dateien laden kann) -> Bootsektor, lädt Stage 2 und 3
  • Stage 1.5 (optinal, nur wenn benötigt) -> lädt Stage 2 und 3
  • Stage 2 -> hardware-dependent Teil, stellt ein HAL für die jeweilige Platform zur Verfügung
  • Stage 3 -> hardware-independent Teil, nutzt HAL und lädt Kernel und Programme
Soweit nix neues, aber ich möchte es nun so umsetzen, dass Stage 2 und 3, 2 voneinander unabhängige Dateien sind.

Der "boot"-Ordner von meinem System könnte für die x86-Architektur z.B. so aussehen:

/boot
    osloader
    /stage2
        bios
        efi
        openfirmware

Mit EFI habe ich mich noch nicht weiter beschäftigt, aber Openfirmware z.B. kann Dateien von unterstützten Dateisystemen laden. Problem ist hier das BIOS, denn dort habe ich nur 512byte (bzw. genug wenn ich direkt von CD boote) zur Verfügung. Der Bootsector müsste nun aber die beiden Dateien "/boot/stage2/bios" und "/boot/osloader" laden, dass das nicht geht ist mir klar. Also war mein erster (und bisher leider einziger) Gedanke, ich brauche eine Stage 1.5. Der Bootsektor würde also entweder ein paar mehr Sektoren laden, keine Ahnung wo ich die abzwacken soll oder (was zwar gehen würde, aber unschön wäre) ich müsste noch eine Datei z.B. "stage_1_5_bios" laden und die lädt dann die anderen beiden Dateien.

Problem bei dieser zusätzlichen Datei wäre, wo soll diese gespeichert werden. Um so flexibel wie möglich zu sein, müsste sie in "/", was aber nicht so schön in eine aufgeräumte Ordnerstruktur passen würde. Eine andere Möglichkeit wäre, im Bootsektor einen direkten Verweis auf z.B. den FAT-Dateieintrag zu speichern. Da sehe ich aber das Problem, was passiert beim Defragmentieren? Der FAT-Dateieintrag könnte ja an eine andere Position verschoben werden und dann findet der Bootsektor den Eintrag nicht mehr. Ich weiß auch nicht wie es mit anderen Dateisystemen ist, könnte es da ein ähnliches Problem geben?

Meine Frage also, wie könnte man obiges Problem sinnvoll lösen?

[Jetzt sollte man nur weiterlesen wenn man das Konzept interessant findet oder darüber diskutieren möchte, hat nämlich nix mehr mit dem obigen Problem zu tun]

Warum eigentlich so kompliziert? Mein Problem ist einfach, dass ich nicht meinen kompletten Bootloader für jedes ARM-Board anpassen wollte. Als ich mich dann aber mal näher mit der Doku zum Raspberry Pi auseinander gesetzt habe, viel mir auf, dass sich das mit dem bisherigen Konzept nicht machen lässt. Deswegen jetzt mehrere Stufen.

Um einfach mal zu verdeutlichen wie das ganze dann für die ARM-Architektur aussehen könnte:

/boot
    osloader
    /stage2
        bcm2835
        tegra2
        openfirmware

Bei openfirmware bin ich mir auch nicht ganz 100%ig sicher, denn ich weiß dass es möglich ist, so einiges über die Hardware herauszufinden und auch bestimmte Funktionen (ähnlich wie beim BIOS) nutzen zu können, aber ob ich auch direkt herausfinde welcher UART verwendet wird und wo dieser hingemappt ist, keine Ahnung. Kann also gut sein, dass ich auch für Boards mit openfirmware eine spezialisierte Stage2 haben muss.

Die Idee ist es dann, dass Stage2 die Hardware initialisiert und einen HAL für Stage3 zur Verfügung stellt. Dabei kann Stage2 im einfachen binär-Format vorliegen (z.B. beim BIOS nötig) oder als ELF-Datei (für Bootloader wie uboot oder auch GRUB). Stage3 ist dann auf jeden Fall im ELF-Format (es sei denn mir kann noch jemand, ein einfacheres, weniger komplexes, Dateiformat nennen, wo ich die Datei an einer beliebigen Stelle ausführen kann und nur die Symbole auflösen muss, welches auch noch von aktuellen GCC-Versionen unterstützt wird?), damit es an einer beliebigen, von Stage2 festgelegten, Stelle ausgeführt werden kann.

Von Stage2 werden dann auch ein paar nötige hardware-dependent Sachen an Stage3 weitergegeben (z.B. Memory-Map und eindeutiger Name des Boards/derPlatform). Denn Stage3 muss dann auch eine Board-/Platformspezifische Konfigurationsdatei laden, in dem die zu ladenden Programme (was auch Treiber sein können bzw. müssen) stehen. Bei dieser Sache bin ich mir auch noch nicht ganz sicher ob ich das über eine Konfigurationsdatei mache oder ob die zu ladenden Programme nicht direkt in Stage2 gespeichert werden und Stage3 dann zur Verfügung gestellt werden. So wäre ich nicht ganz so flexibel, aber es wäre weniger aufwendig. Das einzige was nämlich in so einer Datei stehen würde, wäre der zu ladende Kernel (für den Fall es könnte mehrere Versionen geben, was ich im Moment nicht plane) und viel wichtige, die zu ladenden Programme und Treiber, damit das OS von sich aus die restlichen Treiber und Programme laden kann.

Am Beispiel des x86er, bräuchte ich z.B. einen IDE-, einen AHCI, einen USB1-3, einen ATAPI usw. -Treiber und noch mind. der Device- und Storage-Server. Für die x86er Architektur würde sich wahrscheinlich eine Datei anbieten, damit man die Treiber auswechseln bzw erweitern kann. Auf den ARM-Boards ist das eher unnötigt, da es sich um SOCs handelt, wo es keine austauschbare Hardware gibt.

Was also machen?
2
Softwareentwicklung / Makefile Magie ;)
« am: 15. February 2012, 21:12 »
Ich baue meine Sources gerade so um, dass ich meinen Kernel (und dann das ganze OS) auf andere Architekturen portieren kann.

In meinem Makefile habe ich eine Variable "SRCS" wo meine ganzen Source-Dateien drin stehen. Aus dieser Liste habe ich vorher einfach durch ersetzen der Endung eine Liste von Object-Dateien gemacht. Nun müssen aber einige Einträge der Liste um die Verzeichnisse bereinigt werden, was auch kein Problem war (wird über die Hilfsvariable "TEMP" gemacht). Nur kompiliert er jetzt genau diese Dateien nicht. Obwohl die entsprechenden Regeln dafür vorhanden sind.

Mein Makefile:
CXXFLAGS = -O2 -Wall -Wextra -fno-use-cxa-atexit -fno-stack-protector -fno-rtti -fno-exceptions -fno-asynchronous-unwind-tables -fno-builtin -march=i586 -I$(PREFIX)include -I../../../../../include -fno-common
CXXFLAGS += -DX86_32
LDFLAGS = -nostdlib -T$(LINKERSCRIPT) -i
PREFIX = ../../../../src/
PLATFORM = platform/x86_32/
SRCS = $(PLATFORM)kernel.cpp string.cpp $(PLATFORM)exceptions.cpp $(PLATFORM)idt.cpp $(PLATFORM)video.cpp debug.cpp $(PLATFORM)vmm.cpp
SRCS += $(PLATFORM)vmmAddrSpace.cpp slab.cpp map.cpp avl.cpp $(PLATFORM)pmm.cpp list.cpp $(PLATFORM)cpu.cpp $(PLATFORM)gdt.cpp
SRCS += $(PLATFORM)tss.cpp patriciatree.cpp array.cpp $(PLATFORM)syscall.cpp semaphore.cpp $(PLATFORM)scheduler.cpp
SRCS += $(PLATFORM)task.cpp $(PLATFORM)thread.cpp port.cpp
TEMP = $(subst $(PLATFORM),,$(SRCS))
OBJS = $(TEMP:%.cpp=%.o) exceptions-asm.o scheduler-asm.o
DEPENDFILE = .depend
LINKERSCRIPT = ../kernel.ld
VPATH = $(PREFIX)

.PHONY: all clean

all: $(DEPENDFILE) kernel.ko

$(DEPENDFILE): $(SRCS:%=$(PREFIX)%)
$(CXX) -M $(SRCS:%=$(PREFIX)%) $(CXXFLAGS) > $(DEPENDFILE)

-include $(DEPENDFILE)

exceptions-asm.o: $(PREFIX)$(PLATFORM)exceptions.asm
nasm -O3 $(PREFIX)$(PLATFORM)exceptions.asm -f elf -o exceptions-asm.o
scheduler-asm.o: $(PREFIX)$(PLATFORM)scheduler.asm
nasm -O3 $(PREFIX)$(PLATFORM)scheduler.asm -f elf -o scheduler-asm.o
   
kernel.ko: $(OBJS) $(LINKERSCRIPT)
$(LD) $(OBJS) -o kernel.ko $(LDFLAGS)
cp kernel.ko ../

clean:
-rm -f kernel.ko
-rm -f *.o
-rm -f *.s
-rm -f $(DEPENDFILE)

Folgender Ausschnitt aus meiner ".depend"-Datei sieht auch ok aus (die ganze passte nicht in den Beitrag):
kernel.o: ../../../../src/platform/x86_32/kernel.cpp \
 ../../../../src/include/panic.hpp ../../../../../include/typedef.h \
 ../../../../../include/compiler.h \
 ../../../../src/include/loader-strucs.hpp \
 ../../../../src/include/platform/x86_32/loader-strucs.hpp \
 ../../../../src/include/platform/x86_32/irq/ioapic.hpp \
 ../../../../src/include/video.hpp \
 ../../../../src/include/platform/x86_32/video.hpp \
 ../../../../src/include/exceptions.hpp \
 ../../../../src/include/platform/x86_32/exceptions.hpp \
 ../../../../src/include/debug.hpp ../../../../src/include/pmm.hpp \
 ../../../../src/include/platform/x86_32/pmm.hpp \
 ../../../../src/include/vmm.hpp \
 ../../../../src/include/platform/x86_32/vmm.hpp \
 ../../../../src/include/spinlock.hpp \
 ../../../../src/include/platform/x86_32/spinlock.hpp \
 ../../../../src/include/map.hpp ../../../../src/include/avl.hpp \
 ../../../../src/include/list.hpp ../../../../src/include/slab.hpp \
 ../../../../src/include/memory.hpp \
 ../../../../src/include/platform/x86_32/memory.hpp \
 ../../../../src/include/kernel.hpp \
 ../../../../src/include/platform/x86_32/kernel.hpp \
 ../../../../src/include/cpu.hpp \
 ../../../../src/include/platform/x86_32/cpu.hpp \
 ../../../../src/include/asm.hpp \
 ../../../../src/include/platform/x86_32/asm.hpp \
 ../../../../src/include/irq.hpp \
 ../../../../src/include/platform/x86_32/irq.hpp \
 ../../../../src/include/fpu.hpp \
 ../../../../src/include/platform/x86_32/fpu.hpp \
 ../../../../src/include/timer.hpp \
 ../../../../src/include/patriciatree.hpp \
 ../../../../src/include/rwlock.hpp \
 ../../../../src/include/platform/x86_32/rwlock.hpp \
 ../../../../src/include/syscall.hpp \
 ../../../../src/include/platform/x86_32/syscall.hpp \
 ../../../../src/include/semaphore.hpp ../../../../src/include/os.hpp \
 ../../../../src/include/array.hpp ../../../../src/include/task.hpp \
 ../../../../src/include/platform/x86_32/task.hpp \
 ../../../../src/include/thread.hpp \
 ../../../../src/include/platform/x86_32/thread.hpp \
 ../../../../src/include/atomic.hpp \
 ../../../../src/include/platform/x86_32/atomic.hpp \
 ../../../../src/include/scheduler.hpp \
 ../../../../src/include/platform/x86_32/tss.hpp \
 ../../../../src/include/port.hpp \
 ../../../../src/include/platform/x86_32/cpu/smp.hpp \
 ../../../../src/include/platform/x86_32/timer/rtc.hpp \
 ../../../../src/include/platform/x86_32/idt.hpp
string.o: ../../../../src/string.cpp ../../../../src/include/string.hpp \
 ../../../../../include/typedef.h ../../../../../include/compiler.h
Die "string.cpp" wird kompiliert, aber nicht die "kernel.cpp". Kann sich jemand denken wieso? Ich bekomme auch keine Fehlermeldung oder so (erst wenn ld versucht alle Object-Dateien zusammen zu linken).
3
Lowlevel-Coding / Code zum Verzweifeln
« am: 18. December 2011, 14:38 »
Bin heute beim Debugging mal wieder auf ein Problem zum Verzweifeln gestoßen.

Im Prinzip geht es darum das eine Variable auf den Wert 1024 überprüft wird und dann muss halt anderer Code ausgeführt werden. Mit dem originalen Code funktioniert das (aus irgendeinem mir nicht bekannten Grund) nicht und mit Code der nur eine Ausgabe macht (genau zw. dem ändern der Variable und dem Vergleich des neuen Wertes), funktioniert es.

Also hier mal die entsprechende Stelle des Code´s welcher nicht funktioniert:
movl %esi, %ebp
movl _ZL14g_sStack4kbPtr(,%esi,4), %edi
sall $12, %ebp
sall %cl, %edx
movl %ebx, -1069547520(%ebp,%edi,4)
leal 1(%edi), %ebx
movl %ebx, _ZL14g_sStack4kbPtr(,%esi,4)
cmpl $1024, %ebx
je .L25

Und hier der Code der funktioniert:
movl %edi, %ecx
movl _ZL14g_sStack4kbPtr(,%edi,4), %edx
sall $12, %ecx
movl %eax, -1069547520(%ecx,%edx,4)
leal 1(%edx), %eax
movl %eax, _ZL14g_sStack4kbPtr(,%edi,4)
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call _ZN5Video6printfEPKcz
movl %ebp, %ecx
sall %cl, %esi
cmpl $1024, _ZL14g_sStack4kbPtr(,%edi,4)
je .L25

Der einzige Unterschied der mir aufgefallen ist, ist der das einmal direkt der Inhalt des Registers überprüft wird und einmal der Inhalt der Speicheradresse. Fällt euch noch was auf oder ein warum der erste Code nicht funktioniert?

Für alle die die ganze Funktion haben wollen, einmal der nicht funktionierende Code:
_ZN3Pmm10dealloc4kbEPv:
pushl %ebp
pushl %edi
pushl %esi
pushl %ebx
movl 20(%esp), %ebx
testl $4095, %ebx
jne .L24
movl %ebx, %esi
movl %ebx, %eax
shrl $22, %esi
movl $1, %edx
shrl $27, %eax
movl %esi, %ecx
andl $31, %ecx
#APP
# 39 "../../src/include/spinlock.hpp" 1
lock xaddl %edx,_ZL10g_sPmmLock
cmpl %edx,_ZL10g_sPmmLock+4
je 2f
1: pause
cmpl %edx,_ZL10g_sPmmLock+4
jne 1b
2:

# 0 "" 2
#NO_APP
movl %esi, %ebp
movl _ZL14g_sStack4kbPtr(,%esi,4), %edi
sall $12, %ebp
sall %cl, %edx
movl %ebx, -1069547520(%ebp,%edi,4)
leal 1(%edi), %ebx
movl %ebx, _ZL14g_sStack4kbPtr(,%esi,4)
cmpl $1024, %ebx
je .L25
orl %edx, _ZL15g_sBitmap4mb4kb(,%eax,4)
movl _ZL10g_sFree4kb, %eax
incl %eax
movl %eax, _ZL10g_sFree4kb
.L23:
movl _ZL15g_sFreeTotal4kb, %eax
incl %eax
movl %eax, _ZL15g_sFreeTotal4kb
movl _ZL10g_sPmmLock+4, %eax
incl %eax
movl %eax, _ZL10g_sPmmLock+4
movb $1, %al
.L21:
popl %ebx
popl %esi
popl %edi
popl %ebp
ret
.L24:
xorl %eax, %eax
jmp .L21
.L25:
orl %edx, _ZL12g_sBitmap4mb(,%eax,4)
xorl %edx, _ZL15g_sBitmap4mb4kb(,%eax,4)
subl $1023, _ZL10g_sFree4kb
incl _ZL10g_sFree4mb
jmp .L23

Und einmal die vollständige funktionierende Funktion:
_ZN3Pmm10dealloc4kbEPv:
pushl %ebp
pushl %edi
pushl %esi
pushl %ebx
subl $28, %esp
movl 48(%esp), %eax
testl $4095, %eax
jne .L24
movl %eax, %edi
movl %eax, %ebx
shrl $22, %edi
movl $1, %esi
movl %edi, %ebp
movl %esi, %edx
shrl $27, %ebx
andl $31, %ebp
#APP
# 39 "../../src/include/spinlock.hpp" 1
lock xaddl %edx,_ZL10g_sPmmLock
cmpl %edx,_ZL10g_sPmmLock+4
je 2f
1: pause
cmpl %edx,_ZL10g_sPmmLock+4
jne 1b
2:

# 0 "" 2
#NO_APP
movl %edi, %ecx
movl _ZL14g_sStack4kbPtr(,%edi,4), %edx
sall $12, %ecx
movl %eax, -1069547520(%ecx,%edx,4)
leal 1(%edx), %eax
movl %eax, _ZL14g_sStack4kbPtr(,%edi,4)
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call _ZN5Video6printfEPKcz
movl %ebp, %ecx
sall %cl, %esi
cmpl $1024, _ZL14g_sStack4kbPtr(,%edi,4)
je .L25
movl _ZL10g_sFree4kb, %eax
orl %esi, _ZL15g_sBitmap4mb4kb(,%ebx,4)
incl %eax
movl %eax, _ZL10g_sFree4kb
.L23:
movl _ZL15g_sFreeTotal4kb, %eax
incl %eax
movl %eax, _ZL15g_sFreeTotal4kb
movl _ZL10g_sPmmLock+4, %eax
incl %eax
movl %eax, _ZL10g_sPmmLock+4
movb $1, %al
.L21:
addl $28, %esp
popl %ebx
popl %esi
popl %edi
popl %ebp
ret
.L24:
xorl %eax, %eax
jmp .L21
.L25:
orl %esi, _ZL12g_sBitmap4mb(,%ebx,4)
xorl %esi, _ZL15g_sBitmap4mb4kb(,%ebx,4)
subl $1023, _ZL10g_sFree4kb
incl _ZL10g_sFree4mb
jmp .L23

Mal ganz davon abgesehen, dass GCC da mMn ein wenig komischen Code für -O2 erzeugt.
4
OS-Design / IPC - Popup-Threads
« am: 23. November 2011, 16:29 »
Ich will ja die Idee von erik benutzen, wo der IPC-Handler in einem eigenen Thread läuft. Dieser Thread wird auch wirklich erst erstellt wenn eine Nachricht vorhanden ist/empfangen wird.

Dabei ist halt ein Problem bei meinem Kernel-Design aufgetreten. Dort ist es nicht möglich auf den VMM eines anderen Prozesses zu zugreifen, was ja nötig wäre um einen Stack für den Thread erstellen zu können.

Der Grund warum das nicht möglich ist, ist folgender. Auch der VMM braucht ja Speicher um die freien Bereiche verwalten zu können. Allerdings kann er ja schlecht die normalen malloc()/free() Funktionen nutzen, weil die wiederrum auf den VMM aufbauen.
Deswegen habe ich im KernelSpace einen Prozess-lokalen Bereich (der ist immer an der gleichen virtuellen Adresse) wo nur ein "kleiner" Speicher-Manager läuft und auch genau auf den VMM zugeschnitten ist.
Es ist halt einfacher wenn ich nur einen festen Bereich verwalten muss, als wenn ich das ganze von der Größe her dynamisch machen müsste.

Ich habe auch schon eine Idee für eine Lösung.

Wenn ein Client eine RPC-Anfrage macht, muss er in den Kernel. Dort wird dann der fixed-size Anteil der Message in einen Kernel-Buffer kopiert (sind im Mom 32bytes, die man als Metadaten nutzen könnte).
Es wird dann geguckt ob ein neuer Thread beim Empfänger erstellt werden darf (man kann bei der Erstellung der Ports festlegen wieviele Threads max gestartet werden dürfen), ist das der Fall so wird einfach in das PD des Empfängers gewechselt, ein neuer Thread erstellt und dieser wird gleich gestartet ohne das der Scheduler davon wissen muss.

Ist der Handler fertig (die Funktion macht also nen "ret" was wiederrum bewirkt das zurück in den Kernel gesprungen wird) wird überprüft ob eine weitere Nachricht vorliegt oder ob der Thread beendet werden kann.
Liegt eine weitere Nachricht vor, wird diese bearbeitet und der Sender-Thread wird in die Ready-Queue des Schedulers gepackt. Kann der Thread beendet werden, wird danach gleich in das PD des Senders gewechselt und der Sender Thread wird laufen gelassen.

Wird nur eine Nachricht versendet ohne das gewartet werden soll (oder es keine Antwort gibt), so muss trotzdem in das PD des Empfängers gewechselt werden und es wird ein neuer Thread erstellt. Dieser wird dann in die Ready-Queue des Schedulers gepackt und es wird wieder zurück in das PD des Senders gewechselt.

Falls jemand eine gute Idee hat, wie ich das Wechseln der PDs vermeiden kann, also im Endeffekt wie ich den Speicher für den VMM verwalten kann, dann immer her damit.

Fallen euch daran irgendwelche groben Fehler auf oder habt ihr eine bessere Idee (die Popup-Threads bleiben ;))?
5
OS-Design / Threads blockieren und wieder aufwecken
« am: 28. October 2011, 16:33 »
Also erstmal was gibt es alles, wo ein Thread blockiert?

Mir fallen spontan ein, sleep(), semaphoreAcquire() und wait(). Die ersten beiden machen mir keine Probleme, aber letzterer. ICh weiß gar nicht ob es normalerweise unterstützt wird, das ein Thread ein warten kann bis er wieder geweckt wird? Ich wollte das für eine UserSpace-Sachen nutzen.

Um mal ein ganz einfaches Bsp. für mein eigentliches Problem zu bringen.

CPU A:
Ein Thread will die Semaphore bekommen. Also holt er sich den Lock, dekrementiert den Counter und stellt fest das er warten muss. Der Lock wird wieder freigegeben und der Thread wird blockiert.

CPU B:
Ein Thread will die Semaphore freigeben. Also holt er sich den Lock (der auf CPU A gerade freigeben wurde), inkrementiert den Counter und stellt fest das er einen Thread aufwecken muss. Der Lock wird wieder freigegeben und der Thread wird aufgeweckt (genau der von CPU A).

Problem ist nun, dass es ja passieren könnte, dass CPU B den Thread schneller aufweckt als CPU A den Thread blockieren konnte. Lösung dafür wäre auch ne Art Semaphore, nämlich das Aufwecken wäre ein Freigeben und das Blockieren ein Anfordern.

Mit meinem wait() Syscall (und dem entsprechendem resume()) könnte es nun aber passieren, dass die Aktion resume() mehrmals aufgerufen wurde und der Thread also bei mehreren wait()´s nicht blockiert.

Daher meine Frage, was wäre besser, ne Semaphore zu nutzen wo der Counter beliebig groß werden kann oder ne Semaphore zu nutzen wo der Counter max 1 werden kann?

Oder ist das totaler Schmarrn?
6
OS-Design / Flexibles und einfaches Prozess erzeugen
« am: 14. October 2011, 21:11 »
Ich wollte keine ewig alten Threads aus der Versenkung holen, deswegen mache ich mal nen neuen auf, auch wenn das Thema (Prozesserstellung) schon ein paar Mal diskutiert wurde.

Da ich bisher immer noch nicht so richtig weiß/wusste wie ich einen Runtime-Loader haben kann, der als Programm vorliegt, ist mir jetzt eine Idee für die Prozesserstellung gekommen, mit der ich vllt sogar fork() anbieten kann, ohne das auf Kernel-Ebene machen zu müssen.

Mein createTask() soll nur einen komplett leeren Task erstellen, nicht mehr und nicht weniger.

Der VFS-Server kann dann über eine private Kernel-API (die soll so geschützt sein, dass nur der VFS-Server sie aufrufen kann) Speicher in einen Prozess "injezieren" (im Endeffekt mappen) können und zwar an Adressen wo er das gerne hätte (soweit es da keine Überschneidungen gibt).

Damit ist es möglich einen Runtime-Loader, der als Programm vorliegt, in den leeren Prozess zu injezieren, einen neuen Thread zu erstellen und dieser Thread führt dann den Runtime-Loader aus, welcher sich die Daten (die ausführbare Datei und die nötigen Libs) vom VFS-Server holt und parst.
Ein weiterer Vorteil ist, dass ich dann auch einfach mehrere Runtime-Loader, für verschiedene Dateiformate, haben kann und vorallem bleiben diese im Prozess gemappt und der Prozess kann dann später noch Libs nachladen (Plug-Ins) und die Symbole auflösen lassen.
Der Runtime-Loader würde dann aus 2 Dateien bestehen, einmal aus dem eigentlichen Runtime-Loader (als Programm oder ne Art Programm, da bin ich mir noch nicht sicher) und einmal aus nem Add-On für den VFS-Server. Das Add-On wäre dafür da, dass der VFS-Server weiß welchen Runtime-Loader er für welche Datei nehmen muss (ne einfache Funktion, die nen Pointer auf z.B. die ersten 8kb von der Datei bekommt und den Header überprüft).
Ganz nebenbei könnte man so auch das Starten jeglicher Dateien, wo Programme für installiert sind, lösen. Sprich jedes Programm bringt für jeden Dateityp den sie unterstützt ein Add-On für den VFS-Server mit (z.B. ein Media-Player ein Add-on für jedes Container-Format oder so). Ist das Overkill? Ich meine man muss ja nicht, man könnte aber, die Funktionalität wäre gegeben.

Genauso kann ich es so ermöglichen das ein fork() über den VFS-Server stattfindet. In dem einfach der schon vorhanden Prozess per COW in den neuen Prozess injeziert wird.

Eine Frage zum fork(), wird da wirklich der komplette Adressraum geclont oder werden z.B. die Stacks weggelassen? Weil letzteres wäre dann schon wieder schwieriger umzusetzen (und wahrscheinlich nicht ohne die Hilfe des Kernels).
7
OS-Design / Nicht unterbrechbarer Kernel
« am: 09. October 2011, 23:35 »
Jetzt habe ich mich gerade damit angefreundet (und angefangen entsprechenden Code zu schreiben) das ich einen nicht unterbrechbaren MikroKernel schreibe und schon fallen mir die ersten Probleme auf (Designphase mal wieder übersprungen -> erik ;)).

Also erstmal sollte man sich darüber im Klaren sein, das es auf x86 keinen 100%ig nicht unterbrechbaren Kernel gibt. Zumindest nicht wenn man nicht auf "hlt" oder andere Stromsparmechanismen verzichten möchte.

Ein anderes Problem ist mir erst jetzt bewusst geworden. Die Zeit die im Kernel verbracht wird, kann schonmal verdammt lange werden, nämlich dann wenn der User Speicher haben möchte.
Stellen wir uns einfach vor, wir sind auf einem Single-Core System (funktioniert auch auf nem Dual-Core System, wo alle CPUs gleichzeitig in den Kernel gehen und Speicher wollen, desto mehr CPUs desto unwahrscheinlicher wird es) und der User will z.B. 1GB Speicher haben.
Das Nachgucken und Besorgen eines Bereiches vom UserSpace der groß genug ist, sollte relativ schnell gehen (kann also vernachlässigt werden), aber das Besorgen der nötigen Pages ist ein Teil des Problems. Wir reden also von 1048576 4kb Pages und weil es mir um den worst-case geht, kann ich auch keine 4mb Pages nehmen. Da für jede Page der PMM aufgerufen werden muss und dieser mit einem Lock geschützt ist, rechnen wir mal so über den Daum gepeilt mit 20 Instruktionen (das ist schon wenig) pro Page, macht schonmal 20971520 Instruktionen und ich bin großzügig und rechne mal mit nur 2 Takten pro Instruktion, macht 41943040 Takte.

Jetzt gehe ich mal von 100MHz aus, ein Takt entspricht also 10ns und für obige Takte entspricht das dann 419430400ns was 419,4304ms macht und das nur um die Pages zu holen, da sind die noch nicht mal gemappt. Auch bei 1000MHz wird es nicht besser mit 41,94304ms, die dafür gebraucht werden.

Wie soll man sowas also lösen?

Ja man könnte für sowas (bzw. ist es der einzige Fall der mir einfällt, der so lange dauern könnte und natürlich der entgegengesetzte Fall, das Freigeben) Punkte einrichten, wo der Kernel dann unterbrechbar ist, aber wo würde man diese Punkte hinpacken? Weil ich will den Kernel dann z.B. nicht bei 4 Pages unterbrechen.

Ein weiteres Problem (was relativ einfach zu lösen ist) ist, dass ein Thread ja seine Zeitscheibe abgeben kann und dazu geht er per Syscall in den Kernel. Dort wird dann der Scheduler aufgerufen und der wählt einen neuen Thread aus. Problem ist hier nun, das der Scheduler immer aus dem Kernel springt, als wenn es ein IRQ wäre, aber in diesem Fall darf kein EOI gesendet werden. Das lässt sich lösen, dass man einfach jedes Mal wenn man in den Kernel springt, ein Flag setzt ob der Grund ein IRQ war oder nicht, aber das müsste man auch noch für jeden Thread speichern (wie er in den Kernel gekommen ist). Denn wenn der Thread per Syscall reingekommen ist, will man natürlich auch wieder auf dem Weg heraus (Sysret oder Sysexit).

Kleine Idee am Rande, ist es möglich, wenn ich weiß das der Thread zurück in den UserSpace geht, dass ich jedes Mal mit Sysret/Sysexit aus dem Kernel gehe (ich kann ja vorher nen EOI senden und das Page Directory wechseln, sowie andere nötige Sachen machen)? Damit könnte ich sogar ISRs ein wenig beschleunigen. Denn die sollten schneller sein als ein iret.

Auch müssen jedes Mal die Register, die beim Eintritt in den Kernel auf dem per CPU Kernel-Stack gespeichert werden, in der Thread-Struktur gespeichert werden, was auch nochmal ein paar Takte kostet.

Ein anderes Problem, welches aber auch für einen unterbrechbaren Kernel zutrifft, ist halt das Abgeben der Zeitscheibe. Denn dazu wird in den Kernel gesprungen und der Scheduler wird aufgerufen, dort (im Scheduler) müssen die Ints aus sein und genau in der Zeit kann es passieren das der Timer-IRQ feuert und dann gequeuet wird. Es wird ein neuer Thread ausgesucht und der Scheduler springt zurück in den UserSpace und was wird als erstes gemacht? Es wird wieder in den KernelSpace gesprungen, weil ja der IRQ abgearbeitet werden muss.

Kann man dagegen irgendetwas machen oder muss man damit leben? Auch das Abschalten oder maskieren hilft ja nicht, weil ja Zeit bis dahin vergeht wo ja ein IRQ auftreten kann.
8
OS-Design / EOI, wann senden?
« am: 07. October 2011, 16:59 »
Ich arbeite gerade an meinem Irq Code und da stellt sich mir mal wieder die Frage, wann sendet man einen EOI?

Wenn man alle Handler angetriggert hat oder wenn ein Handler bestätigt hat, dass es sein IRQ war? (Ich weiß das meistens nur die letzte Variante bei den meisten Systemen implementiert ist)

Interessant wäre noch zu wissen was für Vor- und Nachteile beide Varianten haben.
9
OS-Design / GUI LowLevel
« am: 27. September 2011, 14:32 »
Ich wollte mich gedanklich mal mit den LowLevel Konzepten einer GUI vertraut machen.

Erstmal was ich unter LowLevel verstehe. Damit meine ich wie was wann gezeichnet wird.

Hat wer dazu ein paar interessante Links/Papers die man lesen kann/sollte?

Ich "kenne" bisher folgende Konzepte:

Es wird direkt ein Bild im Grafikkartenspeicher erzeugt, sprich die einzelnen Programme haben keinen eigenen Buffer (sofern sie sich selbst keinen Anlegen), sondern bekommen von der GUI gesagt das sie doch bitte neuzeichnen sollen und welchen Bereich. Dort sollte doch der Buffer in den sie schreiben, auch mit übergeben werden und nicht so dass das Programm in einen eigenen Buffer schreibt und den dann an die GUI schickt, oder?
Das sollte so die "alte" Variante sein und vorallem die am wenigsten Speicher verbraucht, weil ja immer nur direkt in den Framebuffer geschrieben wird. Allerdings verbraucht dies auch mehr CPU Zeit.

Zu dieser Variante hätte ich gleich mal Fragen.

Ist das die Variante wie sie unter alten GUIs verwendet wurde (z.B. Windows 3.11)? Wenn ja, wie haben die Auflösungen wie 1024x768x32bit hinbekommen? Damit meine ich das wenn man davon ausgeht, dass das gesamte Bild neugezeichnet werden muss und von einem PCI-Bus ausgeht (was ja nicht unbedingt sein musste), wie konnten die 60Hz (FPS) und mehr erreichen? Das geht doch schon aus Bandbreitengründen nicht. Bei einem ISA-Bus und 640x480x32bit sollte das ja eigentlich auch nicht gehen.
Oder wurde früher einfach nur 16bit oder sogar nur 256 Farben verwendet?

Die andere Variante die ich dann kenne, nennt sich wohl Compositing (??).

Dort hat jedes Programm einen Buffer von seinem Fenster(n) und wenn die GUI ein neues Bild zeichnen möchte (und sich etwas in diesem Fenster geändert hat), erstellt das Programm nen neuen Buffer, zeichnet dort rein und schickt den Buffer an die GUI.
Vorteil ist hier das weniger CPU Zeit, aber dafür wesentlich mehr Speicher verbraucht wird.
Soweit richtig?

Was mich jetzt, insbesondere wenn ich dann irgendwann mal ne GUI designen möchte, interessiert, wie kann man dort Theming umsetzen bzw. wie wird es gemacht das jedes Fenster und die Komponenten gleich aussehen?

Ich stelle mir das so vor, dass man OS Libs nutzt um die Fenster und Elemente zu zeichnen und nicht nur in das eigentliche Fenster (ohne Rahmen, Menüleiste und sowas), sondern immer das gesamte Fenster (halt mir Rahmen, Menüleiste und sowas) zeichnet.
Theming würde dann so funktionieren, dass sich die Libs halt die Daten aus irgendwelchen Dateien holen, wie nun die Titelleiste aussehen soll, wie der Rahmen aussehen soll usw.
Ist das auch soweit richtig?

Dann, auch wenn es eigentlich außerhalb dessen liegt was ich je erreichen werde, wie läuft das mit 2D Hardwarebeschleunigung? Soweit ich es verstanden habe, gibt es die nur soweit das man Blitting in irgendeiner Weise hardwarebeschleunigt zur Verfügung hat.
Allerdings entsinne ich mich mal was gelesen zu haben, dass früher auch primitive Objekte (ala Rechteck und sowas) per Grafikkartenbefehl gezeichnet wurden?! Kann es sein, dass so höhere Auflösungen und Bittiefen möglich waren?

10
OS-Design / "Interface" der Pagemapping Funktion
« am: 21. August 2011, 22:00 »
Mir geht es bei diesem Thema eigentlich um eine ganz einfache Frage, wie oft kommt es vor das man wirklich mehrere/viele physikalische Pages, die nicht zusammenhängend sind, mappen will?

Ich frage deswegen, weil ich gerade (oder besser immernoch :( ) dabei bin meinen Kernel nach C++ zu portieren und dabei auch gleich mal nen kleineren rewrite starte.

Gerade bin ich halt dabei den VMM zu portieren und da ist mir halt der Gedanke gekommen, das es (zumindest bei mir) sehr von Vorteil wäre, wenn man mit einem Funktionsaufruf mehrere Pages mappen könnte (ich kann das jetzt auch schon, allerdings nur zusammenhängende Pages). Aber da stellt sich mir dann die Frage ob man das eigentlich wirklich braucht? Denn wenn ich die Funktion darauf optimieren würde, das nur eine Page gemappt wird, kann sie auch ein Stückchen schneller werden und meiner Meinung nach ist das auch fast immer der Fall (außer beim Booten des Kernels) und würde so mehr Sinn machen und wenn man dann doch mal mehrere Pages mappen will, muss halt die Funktion mehrmals aufgerufen werden.
11
Ich wollte mal eure Meinung zu einer Idee eines Sicherheitskonzeptes was das Dateisystem betrifft hören. Das ist allerdings noch nicht vollständig und vollkommen durchdacht bzw. hat noch Probleme.

Also es geht mir darum den Zugriff der Programme zu beschränken. Die erste Frage die aufkommt, darf ein Programm alle Dateien lesen? Oder ist es schlimm wenn es alle Dateien lesen kann?

Ich dachte mir es so, das ein Programm nur schreibzugriff auf einen Ordner /home/user/appdata/app/ hat. Somit hätte man Sachen wie eventuelle Konfigurationsdateien oder meinet wegen Savegames und sowas immer an einem genau bekannten Ort und das auch noch pro User.
Allerdings muss ich ja auch davon ausgehen, das es Programme gibt wo man diese Daten zentral und nicht pro User haben möchte oder sogar beides.
Dafür dachte ich an einen Ordner /home/shared/app/.
Will ein Programm in irgendeinen anderen Ordner schreiben, muss es die Rechte anfordern, was wie sudo oder UAC wäre (je nachdem ob GUI oder TUI).
Soweit so gut. Jetzt will man aber nicht für jeden Ordner diese Rechte anfordern müssen. Also dachte ich mir das ich noch einen Order habe /home/user/data/ was den "Eigene Dateien" Ordner unter Windows entsprechen würde und dort kann sich der User bzw. die Programme nach herzenslust austoben.
Ich will den User/die Programme nicht direkt in den /home/user/ Ordner schreiben lassen, weil ja dann der appdata Ordner nicht mehr geschützt wäre, bzw. für diesen Ordner (und eventuell andere Ordner) extra Regeln gelten müssten. So halte ich mir auch offen, noch andere Ordner wie appdata einzuführen.

Dadurch wäre erstmal sichergestellt das irgendwelche Programmdateien nicht ausversehen oder mit absicht (ohne Wissen des Users) geändert werden können.

Allerdings muss bei der Anforderung von Rechten auch unterschieden werden ob z.B. Dateien geschrieben (kopieren/ändern/löschen) werden oder ob ein Programm installiert wird. Denn bei letzterem wäre es eher unschön wenn man jede einzelne Datei als User genehmigen müsste.

Dazu dann auch gleich eine Frage. Wie macht man das am dümmsten mit den Libraries? Ich meine ich würde die schon irgendwie zentral haben wollen. Was ich so lösen würde, das man sich für jede Library registrieren müsste und das OS einen Zähler pro Library mitführt und wenn keine Anwendung die Library mehr benötigt (der Zähler also 0 ist) würde die Library gelöscht werden.
Allerdings kommt dann noch das Problem wie man das mit einer neueren Version der Library macht? Einfach überschreiben und davon ausgehen, wenn der User dem Installationsprogramm vertraut, dann wird das schon eine Library sein, die in Ordnung ist?
Speziell mit systemkritischen Libraries stelle ich mir das schwer vor. Wie will man da vor gehen?
Auch würde damit nur eine Möglichkeit geschaffen werden, Libraries zentral zu organisieren, aber die Apps könnten ihre Libraries ja trotzdem in ihrem Installationverzeichnis packen (womit man dann das Windowsproblem hätte). Das könnte man auch verhindern, indem man es explizit unterbindet und Libraries nur in dem zentralen Verzeichnis gesucht werden. Das wiederrum wäre auch nicht so toll, wenn ich da an Spiele denke, die bestimmte Sachen in Libraries auslagern. Was also machen?

Wie sicher auffällt, ist das ein Mix aus Unix- und Windowskonzepten. Ich mag das mit der zentralen Verwaltung der Anwendungsdaten unter Windows und finde den relativ starren (keine Ahnung ob das wirklich so ist, aber so würde ich es machen) Aufbau vom Wurzelverzeichnis ("/") unter Unix gut.
Mir ist klar dass das mit den Laufwerksbuchstaben nicht so das wahre ist und werde deshalb sowas wie unter Unix machen.

Vllt kennt ja jemand mal einen Link wo die Ordnerstruktur für Unix gut beschrieben wird, damit ich mir mal angucken kann wie die aussieht um mir ne Inspiration zu holen.
12
Lowlevel-Coding / PCI BARs
« am: 22. June 2011, 21:09 »
In einem anderem Forum geht es gerade darum ob es reicht wenn man die Adresse für Memory Mapped I/O in den BARs ändert oder ob man auch noch was in der Northbridge bzw. bei neueren CPUs in den MSRs ändern muss.

Ich dachte bisher eigentlich immer, dass das alles nur über die PCI BARs läuft? Wenn dem nicht so ist, kann ich mir den Gedanken, die PCI Geräte eventuell selbst zu initialiseren (und damit praktisch die BARs zu ändern) gleich sparen und mache mir da keine zusätzliche Arbeit.
13
Softwareentwicklung / GCC 4.6.0 ELF Segmente und Symbole
« am: 09. April 2011, 15:29 »
Da wollte ich mich heute mal ransetzen und gucken was man alles an Funktionen/Code für Exceptions braucht und dann musste ich feststellen das der GCC ganz schön viel "Müll" in die Objektdateien packt :(

Erstmal sind da einige Segmente (bei meinem kleinen Testprogramm von 10 Zeilen, immerhin 11) die ".group" heißen und wo ich keine Ahnung habe was die machen.
Dann erstellt mir der GCC für allenmöglichen "Müll" ein neues Segment, sprich wenn ich mehrere Konstanten habe, dann macht er jedes Mal nen neues Segment ".rodata." und dann der Name der Konstanten. Das gleiche macht er auch mit dem ".text" Segment.

Wie bekomme ich es hin das er gleich alles in ein Segment packt, warum macht das der GCC und wozu sind diese ".group" Segmente da?

Die Dateien zusammen zu linken ist keine Option für mich (ich weiß das die meisten komischen Segmente dann verschwinden, Thema Link-Time-Optimization), da ich keine ausführbare Datei brauche, sondern die Objektdateien!

Edit::

Es handelt sich um C++ Code, falls das nicht richtig rüber gekommen ist (kann natürlich sein, dass das dort normal ist?).
14
Lowlevel-Coding / C++ Kernel
« am: 30. January 2011, 22:10 »
Ich habe mich mal in das Abenteuer C++ Kernel gestürzt (macht sich unter anderem ganz gut für meine C++ Prüfung ;) ) und bin natürlich auf einige Probleme gestoßen.

Ich bin gerade auch erst dabei C++ richtig zu lernen, also kann es passieren das ich hier Probleme schildere für die es eine simple Lösung gibt.

Erstes Problem weiß jemand wo ich Infos herbekomme wie ich Exceptions im Kernel zum Laufen bekomme?

Dann habe ich oft das Problem, das ein Konstruktor ja ein Objekt initialiseren soll, aber bei einigen Objekten müsste mein Konstruktor ne Exception schmeißen. Dafür bräuchte ich die aber erstmal und selbst dann habe ich noch ein Problem, weil das meistens auch noch Objekte betrifft die global definiert sind und wo der Konstruktor ja eigentlich schon beim Starten des Kernels aufgerufen wird (ist bei eigentlich allen Tutorials dazu so).

Jetzt könnte man sagen gut, in dem Fall kann man auch mal auf das OO-Konzept Konstruktor verzichten und einfach ne Friend-Funktion zum initialisieren nutzen, aber vllt habt ihr ja noch ne Idee wie man das lösen könnte.
15
Lowlevel-Coding / erik´s IPC Idee ;)
« am: 23. January 2011, 10:10 »
Ich will mein IPC System nun umbauen und einige Ideen von erik nutzen. Im Endeffekt geht es darum Ports einzusparen.

Im Moment ist es noch so das man einen Port braucht um eine Nachricht versenden zu können und auch wenn man eigentlich nur RPC, also send+receive macht, benötigt man den Port.

Ich will das dahingehend ändern, das man ein send+receive auch als Thread machen kann, ohne dass das über einen Port läuft. Das wäre dann auch immer synchron, also blockierend. Damit kann ich auf Clientseite schonmal viele Ports einsparen.

Erik´s Idee ist es ja für jede Nachricht die ankommt einen neuen Thread zu starten und den die Nachricht verarbeiten zu lassen.

Ich möchte dabei dem Programmierer die Wahl lassen, ob er selber die Nachrichten abholt (es werden also keine Threads für eine Nachricht gestartet) oder ob für jede Nachricht ein Thread gestartet wird.

Soweit ist das noch kein Problem. Das Problem sehe ich darin, was ist wenn entweder keine Threads mehr vorhanden sind oder nicht mehr genügend Speicher um einen neuen Thread zu starten? Im Endeffekt geht es um die Frage, was passiert wenn die Nachricht in die Queue gepackt werden kann, aber es kann kein neuer Thread mehr gestartet werden.

Eine Lösung wäre, das jeder Thread der vom IPC System gestartet wird, beim Beenden vorher nochmal in die Queue guckt ob noch eine Nachricht vorhanden ist und diese dann auch noch bearbeitet, weil sonst bleiben die ja da drin.

Dann wollte ich es auch so machen, das die Nachricht erst gar nicht in die Queue kommt, sondern gleich dem neuen Thread zugeteilt wird.
16
Lowlevel-Coding / runtime loader - Programme laden/ausführen
« am: 27. December 2010, 22:00 »
Ich wollte mich mal wieder mit meinem OS beschäftigen und das Programme laden/ausführen besser implementieren, da es mir so wie ich es im Moment noch habe nicht gefällt.

Im Moment muss ich dem Kernel nen Pointer auf nen Speicherbereich, mit zugehöriger Größe, übergeben in dem Sich die auszuführenden Datei befindet.

Dieser Speicher wird dann in einen neuen Prozess gemappt und erst dann wird der Speicher geparst und das Programm-Image erstellt. Erst jetzt würden auch (wenn ich es denn schon implementiert hätte) die erforderlichen Libraries geladen werden.

Eigentlich finde ich dieses Vorgehen ganz gut, aber eine Art Programm, den Runtime-Loader, zu haben hätte auch so seine Vorteile (ich kann leichter mehrere Dateiformate unterstützen und noch andere nette Dinge leichter machen).

Aber wie funktioniert so ein Runtime-Loader eigentlich und welche Variante ist eurere Meinung nach besser und warum?
17
Lowlevel-Coding / Allgemeiner Ressourcen-Allocator
« am: 04. November 2010, 21:32 »
Ich arbeite gerade an einem allgemeinen Ressourcen-Allocator (inspiriert durch Bonwick und seinen SlabAllocator) und stecke ein wenig fest.

Es geht darum das man viele ja auf Integerwerte und Bereich zurück führen kann (z.B. IDs und virtuelle Speicherbereiche).

Man initialisert eine sogenannte Map mit einem Startwert und einer Größe (z.B. beim UserSpace mit 0 als Startwert und 0xC0000000 als Größe). Desweiteren gibt man noch die Größe einer Einheit an (bei virtuellen Adressen 0x1000 und bei IDs 1).

Eine Map-Datenstruktur könnte also so aussehen:
struct map_t {
 uint32t start;
 uint32t size;
 uint32t sizeItem;
};

Probleme habe ich jetzt bei der Organisation der einzelnen bzw. des gesamten Bereichs einer Map. Ich würde pro Bereich folgende Datenstruktur haben:
struct mapSegment_t {
 uint32t start;
 uint32t size;
};

Wenn man ein Segment/Bereich aus einer Map haben möchte ist es hilfreich wenn die freien Segmente/Bereiche de größe nach geordnet sind. Damit es im extrem Fall auch schnell geht würde ich nen Baum (AVL/Rot-Schwarz) verwenden.

Wenn man ein Segment/Bereich wieder freigibt und 2 oder 3 Segmente/Bereiche zu einem zusammengefügt werden sollen, wäre es nicht schlecht wenn sie auch nach dem Start-Wert sortiert sind, also noch nen Baum.
Auch macht es eine Sortierung nach dem Start-Wert einfacher beim zurückgeben, eines Segments/Bereichs, zu überprüfen ob das Segement/der Bereich überhaupt benutzt war.

Das ist ja alles gut und schön, aber wenn ich z.b. an IDs denke dann finde ich den Speicherverbrauch ziemlich hoch. Wenn es um virtuelle Adressbereiche geht ist der Speicherverbrauch in Ordnung (meine momentane Variante verbraucht genauso viel).

Hat jemand eine Idee wie man das mit dem Sortieren noch anders lösen könnte, so dass der Speicherverbrauch geringer wird, aber die Geschwindigkeit erhalten bleibt?
18
Lowlevel-Coding / Mein VMM Design
« am: 26. October 2010, 12:50 »
So da es ja Stimmen gibt die mein VMM Design als kaputt oder fehlerhaft bezeichnen ;) wollen wir dem mal auf den Grund gehen.

Im Endeffekt geht es ja "nur" darum wie und in welcher Reihenfolge ich locke.

Ich werde das Design erstmal nur grob umreißen, da der Code inzwischen fast 2000 Zeilen groß ist (ohne Kommentare).

Wichtigste Datenstruktur ist ein "struct vmmAddrSpace_t". Darin sind 2 AVL-Bäume mit freien virtuellen Bereichen, einmal nach Adresse sortiert und einmal nach der Größe, ansonsten zeigen die Nodes in beiden Bäumen auf die selbe Datenstruktur.
Dann sind noch 2 Locks mit dabei, einmal um die "Zonen" (also im Endeffekt die beiden Bäume) zu schützen und einmal um die PagingTables des Prozesses zu schützen.

Interessant ist noch, das der KernelSpace seinen eigenen AdressSpace hat.

An Funktionen wird nach außen hin folgendes bereitgestellt:
  • vmmAllocMemory(uint32t pages, uint32t flags)
  • vmmDeallocMemory(void *base, uint32t pages)
  • vmmAllocRange(uint32t pages)
  • vmmDeallocRange(void *base, uint32t pages)
  • vmmMap(void *virt, void *phys, uint32t count, uint32t flags)
  • vmmUnmap(void *virt, uint32t count, uint32t flags

vmmAllocMemory macht nichts anderes als eine Zone/Range zu allokieren (im Endeffekt könnte man sagen das vmmAllocRange aufgerufen wird) und in diese Zone dann Pages zu mappen (Aufruf von vmmMap).
Locks werden nicht direkt genutzt, sondern nur durch das Aufrufen von anderen Funktionen (die noch kommen). Einzige Ausnahme ist wenn ein Fehler auftritt und die bisherige Aktion rückgängig gemacht werden muss, aber da ist mir aufgefallen, dass das auch durch einen Aufruf von vmmDeallocMemory passieren kann.

vmmDeallocMemory unmappt erstmal alle Pages (Aufruf von vmmUnmap), holt sich dann den PageTableLock um die eventuell vorhandenen Pages freizugeben. Danach wird der Lock wieder freigegeben.
Zum Abschluss wird dann die Zone freigegeben (Aufruf von vmmDeallocRange).

vmmAllocRange holt sich den Lock für die Bäume. Dann wird halt nach einer Range gesucht und wird eine gefunden, wird die Node unter Umständen (Node entspricht der benötigten Range) aus dem Baum gelöscht und der Speicher der Node wird freigegeben.
Genau hier lag ein Problem meines Designs, wenn ich nämlich den SlabAllocator aufrufe, während ich den Lock für den KernelSpace habe (was ja passieren kann, wenn eine Range im KernelSpace allokiert werden soll), kann es passieren das es zu einem Deadlock kommt. Denn der SlabAllocator wiederrum könnte ja Speicherfreigeben wollen, aber da der Lock für die Bäume schon belegt ist, kann das nicht funktionieren -> DeadLock.

vmmDeallocRange holt sich den Lock für die Bäume. Dann wird geguckt ob die freizugebende Range mit einer oder zwei vorhanden Ranges zusammengefasst werden kann (dies kann wieder zu mehreren NodeAlloc´s und NodeDealloc´s führen).
Hier war mal genau das selbe Problem wie bei vmmAllocRange, nämlich das der VMM Speicher vom VMM braucht um seine Aufgabe zu erfüllen (klingt lustig war es aber nicht ;) ).

vmmMap holt sich den Lock für die PagingTables und versucht halt die Pages zu mappen, das gleiche macht vmmUnmap, nur das es die Pages unmappt.
Beide rufen noch eine Funktion auf, die sich darum kümmert wie oft jede physikalische Page gemappt ist. Hierfür wird ein Lock pro Page verwendet und dort kann kein Deadlock auftreten, da kein anderes Lock benötigt wird (es wird das Lock geholt, nen Counter inkrementiert/dekrementiert) und das wars schon.

So viele Locks sind es gar nicht, die meinen VMM ausmachen (genau 2 bzw. 3).

Problem war halt mal, das ich für das Allokieren der VMM Datenstrukturen meinen allgemeinen SlabAllocator benutzt habe und der wiederrum braucht den VMM um arbeiten zu können was ja nicht funktionieren kann.

Gelöst habe ich es dadurch, das ich mir extra das Prinzip des SlabAllocators zu nutze gemacht habe und in einem festgelegten Bereich (steht schon zur Compilezeit fest) nur noch Pages gemappt oder geunmappt werden müssen.
Diese Funktion (vmmSlabAllocNode/vmmSlabAllocZone usw.) holt sich nen Lock um ein Element aus dem Cache (wo viele oder auch gar kein Objekt drin ist) holen zu können.
Ist der Cache leer, muss eine neue Page gemappt werden (Aufruf von vmmMap), dafür wird kein neuer Lock benötigt, da eh immer nur einer auf diesen Cache zugreifen kann.

So ich weiß ganz schön viel, aber jetzt dürft ihr mein Konzept niedermachen und könnt Kritik direkt am Konzept (oder zur Not am Code) bringen!
19
Lowlevel-Coding / App-/GUI-Server
« am: 25. October 2010, 11:55 »
Um endlich mal wieder voran zu kommen wollte ich mein OS jetzt so gestalten das entweder nur eine Konsole (im Textmodus) oder ne richtige GUI geladen wird.

Ansich war es map geplant, das mein App-Server sich um die ganze GUI Geschichte kümmert, aber so würde ich jetzt nen App-Server und nen GUI-Server machen.

Problem ist mir will kein Grund einfallen wozu ich noch nen App-Server brauche ;)

Das einzige wäre vielleicht, wenn ich den App-Server die Konsolen verwalten lasse, so dass die Programme dann später (in 10 Jahren ;) ) auch problemlos auf der GUI laufen.

Was würde euch noch einfallen, was der App-Server machen müsste/sollte?
20
Lowlevel-Coding / SMP und die damit verbundenen Probleme
« am: 17. October 2010, 20:16 »
Ich mache diesmal ein seht weitreichendes Thema auf, da ich genau deswegen gerade Probleme habe, meinen Kernel wieder (vorher ist das Problem einfach nur noch nicht aufgetreten) zum Laufen zu bekommen.

Im speziellen geht es darum, wie man einen sogenannten TLB-Shutdown macht. Also der TLB auf allen CPUs synchronisiert werden muss.

Ich habe das bei mir so gelöst, das man Nachrichten an andere CPUs verschicken kann (per IPI) und man selbst dann wartet bis alle anderen CPUs die Nachricht abgearbeitet haben.

Mein Problem ist nun, das eine CPU die Ints aus hat, weil sie sich in einem kritischen Bereich befindet und versucht einen Lock zu bekommen. Diesen Lock hat nun aber die CPU die gerade eine Nachricht an alle anderen CPUs schickt. Der Deadlock entsteht dadurch das die eine CPU darauf wartet einen Lock zu bekommen und die Ints aus sind und die andere wartet darauf das die gesendete Nachricht bearbeitet wird und auch bei ihr sind die Ints aus.

Wenn ihr jetzt meint "dann mach doch einfach die Ints an", das ist leider nicht so einfach. Dieses spezielle Lock schützt die PagingTables eines Prozesses vor dem gleichzeitigen Zugriff (und außerdem ist es einfacher wenn ich davon ausgehen kann dass das Ändern alles auf ein und der selben CPU passiert) und ich muss auch von genau dieser CPU aus die IPI versenden.

Eine andere (vermeintliche) Lösung wäre einfach das Lock freizugeben, die Ints auszulassen und die andere CPU bekommt den Lock.
Dann habe ich trotzdem nen Deadlock, weil dann beide (immernoch) die Ints aus haben und beide jetzt versuchen ne Msg zu senden.

Einzige Lösung, die mir eingefallen ist, wäre das diese spezielle Funktion nur mit angeschalteten Ints aufgerufen werden darf, aber das ist leider nicht wirklich möglich.

Hat wer schon seinen Kernel so weit und SMP Unterstützung und hat das Problem gelöst?

@erik

Wie willst du solch ein Problem lösen? Denn dein Kernel ist ja grundsätzlich nicht unterbrechbar.
Seiten: [1] 2

Einloggen