Lowlevel
Lowlevel => OS-Design => Thema gestartet von: TheThing am 14. May 2009, 18:27
-
@ehenkes: Hab deine Tuts gelesen. Baust du das mit dem Multitasking auch ein?
Weil, naja, also irgendwie verstehe ich das alles noch nicht.
Ich habe auch das Tutorial von James Molloy gelesen, aber ich habe da noch ein paar Fragen:
Wie funktioniert das genau, das er den richtigen Wert in eip hat?
Warum verlässt er die Routine mit "jmp *%%ecx" ? Müsste das nicht den Stack zerstören? Warum springt er nicht mir IRET zurück?
Wo sichert er die ganzen Register?
Warum überprüft er, ob in eip 0x12345 steht?
Bei meinem Kernel gibt es da auch noch ein Problem. Der Kernel läuft nach dem laden der Register des zweiten Tasks nicht mehr weiter. Falls jemand sich das mal anschauen will: http://rapidshare.com/files/232940400/FROST_V0.2.0a_unstable.zip
Sorry wenn ich dämliche Fehler gemacht habe, das ist mein erster Versuch. Ich hab noch kein TSS drin, da ich das ganze momentan sowieso nur innerhalb des Kernels teste.
Ehrlich gesagt hab ich so meine Probleme damit, fertigen Code zu verstehen. Und, naja, die OS-Projekte die ich mir bisher angesehen habe, sind nicht gerade ausführlich kommentiert. Kurz: Ich verstehe bei pedigree & co überhaupt nichts. Und das ganze ASM & C gemischte bringt mich dann komplett durcheinander.
Wenn Software-Multitasking bei meinem Kernel läuft, schreib ich ein Tutorial, mit dem jeder das zum laufen kriegt ;)
-
Hm, naja das mit dem Thema Trennen hat dann doch nicht so geklappt, wie ich wollte.
Also FreeBasic Code kann ich nur bedingt interpretieren. Was mir auffällt, ist dass du sehr viele "naked" Funktionen hast, die du als Interrupt Handler oder für andere spezifische Assembler Sachen nimmst. Ich würd mich nicht darauf verlassen, dass bloß weil du "naked" ranschreibst, auch der FreeBasic Code ohne Seiteneffekte ist.
Z.B. in Multitasking_switch sieht das mit der lokalen Variable auch sehr verdächtig nach Problemen aus. Wenn dir da der Compiler zum falschen Zeitpunkt eine lokale Variable auf dem Stack anlegt, dann geht was kaputt. Da solltest du dir mal den disassemblieren Code ansehen.
Das Erstellen eines Stacks in Multitasking_Init musst du für jeden einzelnen Task machen, weil natürlich jeder Task einen Stack braucht. In Task_Create gehört das vermutlich hin. Außerdem musst du natürlich auch bevor du [oldesp] lädst, erstmal das aktuelle esp in Tasks(i).esp speichern.
-
Weil, naja, also irgendwie verstehe ich das alles noch nicht.
Was genau verstehst Du nicht? :?
-
Hm, jetzt wo ich mir das ursprüngliche Thema so anschaue, denke ich, dass ich eigentlich wohl ehenkes Fragen hätte abspalten sollen. Bin wohl etwas verplant gewesen 8-)
Also mal um deine ursprünglichen Fragen etwas zu beantworten:
Ich würde die Erstellung von Tasks so betrachten, dass man versucht das nachzubauen, als wäre der Task an seinem Anfang durch einen Interrupt unterbrochen worden.
Also was dein Code tut (oder zumindest vielleicht tut, wenn FreeBasic mitspielt), ist ja schon mal nicht ganz verkehrt:
Dein Interrupt Handler sichert alle Register, scheduled (mittels Round Robin) und lädt alle Register vom Stack.
Das Erstellen des Tasks sollte praktisch so ablaufen, dass du nach dem scheduling mit deinem neuerstellten Task fortfahren kannst. Das ist übrigens etwas, dass du in einem Debugger wie Bochs überprüfen kannst, indem du auf den Stack schaust (Befehl war print-stack glaub ich). Wenn du das mit dem vergleichst, was rauskommt, wenn dein OS statt Scheduling einfach gar nichts macht (oder nur einen Task hat), dann solltest du Hinweise darauf bekommen, was an deinem Stack falsch ist.
-
Hm, ich hab das Multitasking-Zeug mal neu geschrieben, nach einem Tut das ich auf google code gefunden habe. Der Kernel sorgt dafür, das zwei Tasks vorbereitet werden, erlaubt IRQs und geht in eine Endlosschleife.
Das Problem ist: Sobald der erste Task aktiviert wird, kriege ich einen GPF.
Screenshot: http://darkinsanity.freehoster.ch/FROST_V0.2.0a.PNG
Den Code dazu gibts hier: http://darkinsanity.freehoster.ch/FROST_V0.2.0a_unstable.zip
Ein paar Auszüge:
type TaskType
ID as USHORT '// the ID of the task
stack as UINTEGER '// the stack of the task
eip as UINTEGER
status as BYTE '// the status of the task (blocked, ready, active...)
end type
dim shared Tasks(100) as TaskType
function Multitasking_scheduler (stack as UINTEGER) as UINTEGER
Tasks(currentPID).stack = stack
currentPID += 1
if currentPID > AllTasks then currentPID = 1
return Tasks(currentPID).stack
end function
sub Multitasking_Switch naked
dim tempstack as UINTEGER
asm
pusha
push ds
push es
push fs
push gs
mov eax, &h10
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov eax, esp
mov [tempstack], eax
end asm
tempstack = Multitasking_scheduler(tempstack)
asm
mov eax, [tempstack]
mov esp, eax
mov al, &h20
out &h20, al
pop gs
pop fs
pop es
pop ds
popa
iret
end asm
end sub
sub Task_Create (ID as USHORT, address as any ptr)
if Tasks(ID).status <> TASK_EMPTY then return '// place not free
dim stack as UINTEGER
dim kernelstack as UINTEGER
asm cli '// we donŽt want to be interrupted
stack = cast(UINTEGER, malloc(4096) + 4096) '// allocate space for 4kb stack
print "Allocated Stack: ";
print stack
asm
mov eax, esp '// get the stackpointer in eax
mov [kernelstack], eax '// save the kernelstack-pointer
mov eax, [stack] '// load the new stack into eax
mov esp, eax '// get the new stack to esp
mov eax, &h202
push eax
mov eax, &h08
push eax
push [address]
mov eax, &h0
push eax
push eax
push eax
push eax
push eax
push eax
push eax
push eax
mov eax, &h10
push eax
push eax
push eax
push eax
end asm
Tasks(ID).status = TASK_READY
Tasks(ID).stack = stack
Tasks(ID).eip = cast(UINTEGER, address)
AllTasks += 1
asm sti
end sub
-
Ich hab mir mal FreeBasic geholt (hätte mir mal wer gesagt, dass erst die SVN Version naked kann -.-), und rausgekommen ist folgendes:
.globl _MULTITASKING_SWITCH@0
_MULTITASKING_SWITCH@0:
.Lt_000A:
mov dword ptr [ebp-4], 0 ; <--- das ist die lokale variable. das darf natürlich nicht passieren
pusha
push ds
...
Also musst du auf die lokale Variable verzichten. Zum Beispiel indem du die Funktion Multitasking_scheduler aus dem Assembler-Code heraus aufrufst:
...
mov fs, eax
mov gs, eax
push esp
call _MULTITASKING_SCHEDULER@4
mov esp, eax
mov al, 32
out 32, al
pop gs
pop fs
...
Testen kann ich es leider nicht, weil dein Download nicht geht.
Task_Create ist auch noch nicht korrekt. Du setzt Tasks(ID).stack auf den Anfang des Stacks, nicht auf die Stelle, wo du kurz vorher noch Daten hingetan hast. Außerdem musst du den Kernelstack wiederherstellen:
...
mov eax, &h10
push eax
push eax
push eax
push eax
mov eax, esp ' esp sichern
mov [stack], eax
mov eax, [kernelstack] ' kernelstack wiederherstellen
mov esp, eax
end asm
Du solltest dir auch mal überlegen, ob du das vielleicht ohne Stackwechsel machen willst. Langfristig macht dieses Gefummel nur Probleme.
-
vielen Dank, jetzt funktioniert alles! :-) :-)
/edit: versprochenes Tutorial ist in Arbeit
-
@ehenkes: Hab deine Tuts gelesen. Baust du das mit dem Multitasking auch ein?
Hier findest Du das Thema Multitasking:
http://www.henkessoft.de/OS_Dev/OS_Dev2.htm#mozTocId114565
-
danke :)
Ich habe noch ein paar Fragen, weil ich ELF-Dateien nutzen möchte:
Brauch ich Paging, um ELF-Dateien zu nutzen?
Auf was muss ich beim Multitasking achten, wenn ich Paging benutze?
Bisher funktioniert das nämlich nicht :)
Ist mein ELF-header denn richtig?
Der header liegt hier: http://rapidshare.com/files/234717136/ELF.bi.html
-
ok, ähm, kann mir jemand erklären, welche Schritte ich tun muss damit ich eine ELF-Datei ausführen kann? Ich meine ich hätte sowas hier im Forum schon mal gesehen, aber ich finds nicht mehr...
Und wie oft pro Sekunde sollte ich denn den Task wechseln?
/edit: Hab meine Tutorials mal ins Wiki eingetragen:
http://lowlevel.brainsware.org/wiki/index.php/Software_Multitasking_Tutorial
http://lowlevel.brainsware.org/wiki/index.php/Paging_Tutorial
Bitte testlesen :)
-
Eigentlich musst du nur alle Program Headers (mit Typ LOAD) der ELF-Datei durchgehen und den Inhalt der Datei an die angegebene Stelle im Adressraum des Prozesses legen, und noch mit Nullen auffüllen wo mehr in den Adressraum soll als in der Datei ist.
In Code ausgedrückt (zwei unabhägnige Versionen):
- http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/lib/bin_loader/elf32.c
- http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/kernel/src/modules.c
Und zur Frequenz: Es kommt darauf an. ;-) Da probierst du am besten selbst aus, was für dich am besten tut.
-
Bitte testlesen :)
Also an dem Software Multitasking Tutorial stört mich ein wenig der Aufbau des Stacks mittels Inline Assembler und esp-Gebastel. Gibts in FreeBasic keine Möglichkeit, das mit Zeigern, Structs oder Arrays zu lösen? Du hattest selbst ja auch mit dieser uneleganten Lösung Probleme, und für ein Tutorial ist es sehr ungünstig, wenn so ein Stolperstein mehr oder weniger als Black Box präsentiert wird.
Wenn du das zum Beispiel mit einem Struct (wie auch immer diese type xxx/end type-Geschichte heißt) machst, dann sparst du dir eventuell sogar einiges an umständlichen Erklärungen mit stack-60 und so ;)
-
in Text ausgedrückt:
ELF Dateien ausführen (http://lowlevel.brainsware.org/forum/index.php?topic=1017.msg11966#msg11966)
Im lowlevel-wikiartikel zu ELF ist ein Link zu wikipedia. Dort sind wiederum alle ELF-Spezifikationen gelinkt, in denen man dann die Details nachschlagen kann.
-
danke für die Links :)
in dem Tutorial, das ich mir über Multitasking durchgelesen habe, ist das irgendwie mit "*--stack = 0;" gemacht worden. Unter FreeBASIC geht das leider (so weit ich weiß) noch nicht. Ich könnte die Werte in den Speicher schreiben und dann mit "stackpointer -= 1" den stackpointer niedriger machen, aber das wäre sehr lästig :)
Aber ich glaub ich setz mal einen Feature-Request dafür ab, wenns das noch nicht gibt.
-
Also ich dachte an sowas (mit Hilfe von Google zusammengeschnipselt):
Type StackFrame
GS As Integer
FS As Integer
ES As Integer
...
Eax As Integer
...
Eip As Integer
Cs As Integer
...
End Type
sub Task_Create (ID as USHORT, address as any ptr)
...
Dim StackFrame As StackFrame Ptr
' den Stack Frame an den Anfang des Stacks (Ende der Seite) legen
stack = cast(UINTEGER, malloc(4096))+4096 - sizeof(StackFrame)
StackFrame = stack ' StackFrame soll auch auf das Ende des Stacks zeigen
StackFrame.Gs = &H10
StackFrame.Fs = &H10
StackFrame.Es = &H10
StackFrame.Ds = &H10
...
StackFrame.Cs = &H08
StackFrame.Eip = address
Tasks(ID).stack = stack ' Den Wert haben wir oben ja schon berechnet
So klappt das vermutlich noch nicht, aber ich hoffe meine Idee wird deutlich. Ich glaube dass FreeBasis alle Sprachmittel (UDT, Zeiger dafür hat.
-
hm, ja könnte man machen :)
-
So, ich hab mal meinen Kernel und das Tutorial überarbeitet. Jetzt brauch ich keinen Stack-Wechsel mehr :)
btw könnte mal jemand über meinen floppy-treiber-source drüberlesen und mir sagen ob da alles so in Ordnung ist? Hier der Link zum source: http://darkinsanity.netne.net/nerdpole/source/floppy.bas
-
So, ich hätte da noch ein paar Fragen zu ELF-Dateien.
Ich hab ein kleines testprogramm in ASM geschrieben (ruft in einer Endlosschleife int 0x30 auf), assembliert und gelinkt. Rausgekommen ist eine ELF-Datei, die ich nun mit meinem Kernel ausführen möchte.
Daher unternimmt mein Kernel jetzt folgende Schritte:
ELF-Header prüfen
Program-Header-Einträge durchlaufen und alle PT_LOAD-Segmente in den virtuellen Speicher kopieren (der Bereich von p_offset bis p_offset+p_filesz wird kopiert)
Task erstellen, der e_entry als eip hat
Das ganze funktioniert aber noch nicht (eine ganze Weile tut sich einfach nix, irgendwann fliegt dann ein Page-Fault). Also hab ich ein bisschen herumprobiert und herausgefunden, das mein Programm gar keine PT_Load-Segmente hat. Alle anderen Segmente, die in der ELF-Datei existieren, haben als virtuelle Zieladresse 0. Wie soll jetzt aber der ausführbare Code im Speicher landen, wenn gar keine Segmente zum kopieren da sind?
Und für was ist eigentlich der Sektionsheader gut? Brauch ich den?
Btw Ich glaub ich schreib am besten gleich ein ganzes Buch über OS-dev. Ich werd nämlich auch zum Thema ELF-Dateien ein Tutorial schreiben, und wenn ich über jedes Thema ein Tutorial schreib, werden das ganz schön viele. Aber vorerst sind es nur ein paar, und da benutze ich erstmal das Lowlevel-Wiki um meine Tutorials anderen zugänglich zu machen.
An dieser Stelle muss ich jetzt einfach mal ein großes Lob an alle Leute von Lowlevel aussprechen. Ich bin erst seit August 2008 am OS-coden, und ohne eure Hilfe hätte ich schon längst aufgegeben. :) Weiter so.
-
Also hab ich ein bisschen herumprobiert und herausgefunden, das mein Programm gar keine PT_Load-Segmente hat. Alle anderen Segmente, die in der ELF-Datei existieren, haben als virtuelle Zieladresse 0. Wie soll jetzt aber der ausführbare Code im Speicher landen, wenn gar keine Segmente zum kopieren da sind?
Dann ist da wohl irgend etwas beim linken schiefgegangen.
Was sagt den "objdum -x <foo>" und wie rufst du den linker auf bzw. wie sieht dein link-script aus?
-
Und für was ist eigentlich der Sektionsheader gut? Brauch ich den?
Die Sectionheaders sind für die Objektdateien (also *.o) und die Segmentheader für die ausführbaren Dateien. Man hat in den ELF-Specs einen Standard für beide Arten von Dateien gemacht, weil viele Sachen eben relativ gleich sind.
-
Ok danke.
Also, ich erstelle meine ELF-Datei so:
as -o test.o test.asm
ld test.o -melf_i386 -o test.elf
Die test.asm hat folgenden Inhalt:
.intel_syntax noprefix
.global _start
_start:
x:
int 0x30
jmp x
Laut meinem Kernel hat das Programm dann 1 Program-Header-Entry und 5 Section-Header-Entries, aber das PH-Segment ist angeblich kein PT_Load-Segment. Als e_entry hab ich dann 0x8048054.
Ich glaube irgendwie, dass ich noch irgendeinen kleinen dummen Fehler in meinem ELF-Code habe. Mein ELF-Code liegt hier: http://darkinsanity.netne.net/nerdpole/source/ELF.bi
Der Code ist noch nicht ganz fertig, z.B. mappt er im Moment einfach die Segmente in den Speicher, anstatt sie zu kopieren.
Ich hab schon probiert an die "type"s ein "FIELD=1" anzuhängen (was gleichbedeutend mit einem "__attribute__ ((packed))" ist), und ich hab das schon weggelassen, aber es hilft alles nichts.
"objdump -x test.elf" gibt aus:
test.elf: file format elf32-i386
test.elf
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x08048054
Program Header:
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x00000058 memsz 0x00000058 flags r-x
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000004 08048054 08048054 00000054 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
SYMBOL TABLE:
08048054 l d .text 00000000 .text
08048054 l .text 00000000 x
08048054 g .text 00000000 _start
08049058 g *ABS* 00000000 __bss_start
08049058 g *ABS* 00000000 _edata
08049058 g *ABS* 00000000 _end
Anscheinend hat das Program doch ein PT_Load-Segment. Aber wieso behauptet mein Code das Gegenteil?
Mein Code sagt ja auch das vaddr 0 wäre. Das stimmt ja auch nicht. Ich bin echt ratlos...
-
Der Link ist tot.
-
Laut meinem Kernel hat das Programm dann 1 Program-Header-Entry und 5 Section-Header-Entries, aber das PH-Segment ist angeblich kein PT_Load-Segment. Als e_entry hab ich dann 0x8048054.
Sollte das nicht auch so sein? Man muss ja nur die Segmente laden, wo Code oder Daten drin sind, um ein Programm auszuführen. Der Rest ist ja nur Overhead in Form von Headern. Dass man die irgendwie in den Speicher laden muss, um nachzuschauen, was drin steht is klar, aber dafür kriegen die kein Flag.
-
Sorry, Link sollte jetzt gehen.
Ich verstehe nicht was du meinst.
Ich denke, ich muss nur die PT_load Segmente in den Speicher laden? Dort sind doch dann die wichtigen Sachen drin?
-
Damit meine ich, dass ein einfacher ELF-Lader, erstmal die komplette Datei in den Speicher lädt, bevor er schaut, welche Sektionen Code und Daten sind.
-
Achso.
Ich hab den Fehler durch Zufall gefunden. Ich hab an der Stelle, an der ich die Adresse der PH-Entries berechnet habe, mit Pointern anstatt mit Zahlen gerechnet, und das geht ganz übel aus. Ein paar "cast"s eingebaut und schon funktionierts. :mrgreen:
-
Da ich das mit den ELF-Dateien hingekriegt hab, hab ich auch ein Tutorial geschrieben: http://lowlevel.brainsware.org/wiki/index.php/ELF_Tutorial
Ich werde es vielleicht noch mal überarbeiten, aber ich denke es ist hilfreich für Leute die noch keine Ahnung davon haben.