Lowlevel
Lowlevel => Lowlevel-Coding => Thema gestartet von: ChristianF am 04. September 2009, 11:06
-
Moin,
ich möchte nun endlich mal einige Dinge geklärt haben, die bisher ungeklärt waren. Ich hatte die ganze Zeit ein Skript für "ld" aus einem Tutorial genutzt. Nun möchte ich dieses aber auch verstehen und nach dem Lesen des Manuals sind noch ein paar Dinge offen.
Die einzelnen Sektionen sind mir soweit klar. Nur frage ich mich, warum die Sektionen nach .text (.data und .bss) bei mir und in vielen Tutorials ein ALIGN(0x1000) haben.
Laut Manual heißt dass, das diese Sektionen dann an Page-Aligned werden. Das heißt also, dass .bss an 0x00101000 liegt (laut objdump). Aber was bringt das für Vorteile?
.data ALIGN(0x1000) : { /*...*/ }
Des Weiteren gibt es laut Manual die bss-Sektion für uninitialisierte Daten. Hier gibt es *(.bss) und *(COMMON). Das Manual sagt folgendes:
A special notation is needed for common symbols, because in many object file formats common symbols do not have a particular input section.
...
Was sind denn "common symbols"? Ein kleines Beispiel würde ausreichen...
Weiterhin habe ich in verschiedenen Tutorials immer wieder _sbss und _ebss in der Sektion .bss gesehen. Geben diese start und ende der uninitialisierten Daten an, oder wozu werden diese Angaben benötigt?
Gruß Christian
PS: Ich hoffe meine Sätze sind nicht ganz so wirr, wie sie mir gerade erscheinen, und meine Fragen verständlich :P
EDIT
Noch eine kleine Frage. Allerdings diesmal nicht zu ld...
Und zwar habe ich im Assemblerstub für GRUB folgenden Abschnitt:
[SECTION .bss]
ALIGN 32
stack:
resb OS_STACK_SIZE
Dieser Abschnitt reserviert Daten für den Stack (16 KiB). Die Sektion BSS deswegen, weil es sich ja um uninitialisierte Daten handelt. Nun verstehe ich allerdings nicht, warum darüber ein "ALIGN 32" steht.
-
Die einzelnen Sektionen sind mir soweit klar. Nur frage ich mich, warum die Sektionen nach .text (.data und .bss) bei mir und in vielen Tutorials ein ALIGN(0x1000) haben.
Laut Manual heißt dass, das diese Sektionen dann an Page-Aligned werden. Das heißt also, dass .bss an 0x00101000 liegt (laut objdump). Aber was bringt das für Vorteile?
.data ALIGN(0x1000) : { /*...*/ }
Page-alignment ist immer dann von Vorteil, wenn Paging im Spiel ist. Falls du deinen Kernel in einen anderen Speicherbereich mappen willst, und nicht mit "halben Seiten" hantieren willst, kann das eventuell deinen Algorithmus vereinfachen.
Wenn du allerdings Programme laden willst, dann ist es von noch größerem Vorteil, dass deren Sektionen page-aligned sind, weil du dann deinen eigenen Lade-Algorithmus ein gutes Stück einfacher gestalten kannst.
Außerdem ist so eine Trennung der Sektionen auf Seitengrenzen Voraussetzung, dass du das No-Execute-Bit effektiv benutzen kannst. (Ich glaube die ersten beiden Gründe sind wichtiger als dieser. Sowas macht eh keiner^^)
Des Weiteren gibt es laut Manual die bss-Sektion für uninitialisierte Daten. Hier gibt es *(.bss) und *(COMMON). Das Manual sagt folgendes:
A special notation is needed for common symbols, because in many object file formats common symbols do not have a particular input section.
...
Was sind denn "common symbols"? Ein kleines Beispiel würde ausreichen...
Das sind auch nur globale uninitalisierte Variablen. Ich glaube dadurch dass sie in verschiedenen Dateien jeweils definiert werden (z.B. wenn jemand das "extern" "vergessen" hat) landen sie in der COMMON-Sektion statt in der .bss-Sektion. Aber ich bin mir nicht ganz sicher.
Weiterhin habe ich in verschiedenen Tutorials immer wieder _sbss und _ebss in der Sektion .bss gesehen. Geben diese start und ende der uninitialisierten Daten an, oder wozu werden diese Angaben benötigt?
Ja, die geben Anfang und Ende an. Diese Variablen werden dazu benutzt um mit memset die .bss-Sektion direkt am Anfang des Programms komplett mit Nullen zu füllen.
Und zwar habe ich im Assemblerstub für GRUB folgenden Abschnitt:
[SECTION .bss]
ALIGN 32
stack:
resb OS_STACK_SIZE
Dieser Abschnitt reserviert Daten für den Stack (16 KiB). Die Sektion BSS deswegen, weil es sich ja um uninitialisierte Daten handelt. Nun verstehe ich allerdings nicht, warum darüber ein "ALIGN 32" steht.
Ein gewisses Alignment für den Stack (und für alle Daten im allgemeinen) ist für die Performance von Vorteil. Man hätte das auch mit anderen Mitteln erreichen können, z.B. die Stackvariable nachträglich anpassen können.
Und ich glaube zur Zeit gibt es keine Register in aktuellen CPUs, die größer als 16 Bytes sind bzw. von mehr als 16 Byte Alignment profitieren. Also ist die 32 vielleicht etwas übertrieben. (Ich glaube der GCC sorgt in der 64 Bit Version auch nur für 16 Byte Alignment.)
-
Und zwar habe ich im Assemblerstub für GRUB folgenden Abschnitt:
[SECTION .bss]
ALIGN 32
stack:
resb OS_STACK_SIZE
Dieser Abschnitt reserviert Daten für den Stack (16 KiB). Die Sektion BSS deswegen, weil es sich ja um uninitialisierte Daten handelt. Nun verstehe ich allerdings nicht, warum darüber ein "ALIGN 32" steht.
Ein gewisses Alignment für den Stack (und für alle Daten im allgemeinen) ist für die Performance von Vorteil. Man hätte das auch mit anderen Mitteln erreichen können, z.B. die Stackvariable nachträglich anpassen können.
Und ich glaube zur Zeit gibt es keine Register in aktuellen CPUs, die größer als 16 Bytes sind bzw. von mehr als 16 Byte Alignment profitieren. Also ist die 32 vielleicht etwas übertrieben. (Ich glaube der GCC sorgt in der 64 Bit Version auch nur für 16 Byte Alignment.)
Das würde also heißen, das ein "ALIGN 16" auch ausreichen sollte und man dieses, wenn man die performance nicht betrachtet, auch weglassen könnte.
Die einzelnen Sektionen sind mir soweit klar. Nur frage ich mich, warum die Sektionen nach .text (.data und .bss) bei mir und in vielen Tutorials ein ALIGN(0x1000) haben.
Laut Manual heißt dass, das diese Sektionen dann an Page-Aligned werden. Das heißt also, dass .bss an 0x00101000 liegt (laut objdump). Aber was bringt das für Vorteile?
.data ALIGN(0x1000) : { /*...*/ }
Page-alignment ist immer dann von Vorteil, wenn Paging im Spiel ist. Falls du deinen Kernel in einen anderen Speicherbereich mappen willst, und nicht mit "halben Seiten" hantieren willst, kann das eventuell deinen Algorithmus vereinfachen.
Wenn du allerdings Programme laden willst, dann ist es von noch größerem Vorteil, dass deren Sektionen page-aligned sind, weil du dann deinen eigenen Lade-Algorithmus ein gutes Stück einfacher gestalten kannst.
Außerdem ist so eine Trennung der Sektionen auf Seitengrenzen Voraussetzung, dass du das No-Execute-Bit effektiv benutzen kannst. (Ich glaube die ersten beiden Gründe sind wichtiger als dieser. Sowas macht eh keiner^^)
Nun gut, dann ist verständlich, warum die Daten Page-aligned sind. Nun ist es in dem Linkerscript von mir so, dass nur die Daten Page-aligned sind. Sollten da nicht alle Sektionen ein "ALIGN(0x1000)" bekommen?
Ich habe mir mal die Arbeit gemacht, ein eigenes Linkerscript zu schreiben. Folgendes ist dabei zustande gekommen:
ENTRY(_start)
OUTPUT_FORMAT(elf32-i386)
/* some variables */
KERNEL_VMA = 0x00100000; /* VMA = virtual memory address */
KERNEL_LMA = 0x00100000; /* LMA = load memory address */
KERNEL_VMA_TO_LMA = KERNEL_VMA - KERNEL_LMA; /* address offset for e.g. higher half */
SECTIONS
{
/* Load kernel at virtual memory address */
. = KERNEL_VMA;
/* define variable for kernels start address */
kernel_start_address = .;
/* code section and read only data (e.g. strings within c-part)*/
.text : AT(ADDR(.text) - KERNEL_VMA_TO_LMA)
{
*(.text)
*(.rodata)
}
/* Initialised data - should be page aligned */
.data ALIGN(0x1000) : AT(ADDR(.data) - KERNEL_VMA_TO_LMA)
{
*(.data)
}
/* Uninitialised data */
.bss : AT(ADDR(.bss) - KERNEL_VMA_TO_LMA)
{
_sbss = .;
*(.bss)
*(COMMON)
_ebss = .;
}
/* define variable for kernels end address */
kernel_end_address = .;
}
Gibt es da noch etwaige Unstimmigkeiten oder Fehler? Irgendwelche Dinge, die ich nicht berücksichtigt habe?
Eines habe ich jetzt aber noch...
In manchen Tutorials ist rodata als extra Sektion angegeben. Dient dies einfach nur der Übersicht? Im OSDev-Wiki steht dann, dass rodata nach .text kommt... Was ist da jetzt richtig, bzw. besser, da ja beides richtig zu sein scheint?
Gruß Christian
-
Das würde also heißen, das ein "ALIGN 16" auch ausreichen sollte und man dieses, wenn man die performance nicht betrachtet, auch weglassen könnte.
Jop. Es gibt natürlich spezielle Instruktionen wie zum Beispiel CMPXCHG16B oder SSE-Instruktionen, die trotzdem ein Alignment fordern. Wenn du die aber nicht benutzt, sollte das kein Problem sein.
Nun gut, dann ist verständlich, warum die Daten Page-aligned sind. Nun ist es in dem Linkerscript von mir so, dass nur die Daten Page-aligned sind. Sollten da nicht alle Sektionen ein "ALIGN(0x1000)" bekommen?
Ja, könnte man machen. Der Grund, dass nur die .data-Sektion page aligned ist, konnte noch ein anderer sein: Wenn im Kernel Page-Directories/-Tables als unsigned char pgdir[4096]; oder so definiert werden. (Die müssen ja genau auf einer Page liegen.) Wenn das der Fall ist, sollte man das aber mit einer eigens für ganze Pages eingerichtete Sektion lösen.
Ich habe mir mal die Arbeit gemacht, ein eigenes Linkerscript zu schreiben. Folgendes ist dabei zustande gekommen:
...
Gibt es da noch etwaige Unstimmigkeiten oder Fehler? Irgendwelche Dinge, die ich nicht berücksichtigt habe?
Ich finde an denen jetzt nichts auffälliges. Wenn du dir mit objdump den Kernel und die Objekt-Dateien anschaust, kannst du manchmal hinweise bekommen, ob du Sektionen vergessen hast. Zum Beispiel legt der Compiler unter Umständen sachen in Untersektionen wie ".rodata.1" und so weiter ab.
Eines habe ich jetzt aber noch...
In manchen Tutorials ist rodata als extra Sektion angegeben. Dient dies einfach nur der Übersicht? Im OSDev-Wiki steht dann, dass rodata nach .text kommt... Was ist da jetzt richtig, bzw. besser, da ja beides richtig zu sein scheint?
Ich denke mal, dass es im Allgemeinen egal ist.
-
Gut...
Ich bin da auf noch etwas anderes gestoßen. Wenn ich unter der Sektion ".bss" kernel_end_address mit einem Wert befülle, so kann ich innerhalb des Kernels nur darauf zugreifen, in dem ich dieses per Funktionsdeklaration einbinde:
extern void kernel_end_address(void);
Bei einem anderen Tutorial stand am Ende des Linkerscripts folgendes:
end = .; _end = .; __end = .;
Schreibt man das so, kann man anstatt einer externen Funktionsdeklaration eine Variablendeklaration nutzen:
extern unsigned int end;
Aber warum steht da insgesamt dreimal end? Und welches würde dann im Endeffekt genutzt werden?
-
Du kannst es immer als Variable deklarieren. Aber du definierst damit nur ein Symbol, du reservierst keinen Platz. Was also in der Variablen drinsteht, ist nicht aussagekräftig. Du müsstest auf die Adresse der Variablen zugreifen, also &kernel_end. Der Trick beim Benutzen einer Funktion ist dass &func == func ist und man deswegen das & weglassen kann.
-
Mmmhhh...
Okay, das muss ich wohl nochmal ausprobieren....
Ich habe es mal ausprobiert und die letzten beiden weggelassen (_end, sowie __end). Als ich dann den Kernel kompilieren und linken wollte, kam eine Nachricht der Art "end nicht definiert". Deswegen denke ich, dass eines der letzten beiden ends genutzt wird, wenn man diese Adresse per Variable abfragen will... (myend = &end).
Damit ich bei einer späteren Portierung nichts mehr umändern muss und ich auch die komplette Makefile verstehe, habe ich beschlossen, diese neuzuschreiben.
Nun habe ich beim linken mittels ld (64Bit System) immer die Option "-melf_i386" genutzt, da das sonst nicht funktioniert hat. Gibt es auch ein äquivalent dazu, wie z.B. "-melf_x86-64"? Wenn nicht, muss ich eben eine Makefile mit den jeweiligen Parametern anlegen, die je nach gewünschter Architektur eingebunden wird...
Dann ist da noch eine Frage. Ich suche meine C- und Asm-Dateien per find zusammen. Nun würde ich allerdings, dass ein bestimmtes Verzeichnis nicht durchsucht wird. Kann man das irgendwie per Parameter mitgeben? Hier noch die betreffenden Zeilen der Makefile:
KERNEL_SRC_FOLDER := src/kernel/src
KERNEL_SRC_ARCH_FOLDER := src/kernel/src/arch/$(ARCH)
KERNEL_ARCH_CFILES := $(shell find $(KERNEL_SRC_ARCH_FOLDER) -mindepth 1 -maxdepth 10 -name "*.c")
KERNEL_NONARCH_CFILES := $(shell find $(KERNEL_SRC_FOLDER) -mindepth 1 -maxdepth 10 -name "*.c")
Konkret möchte ich, dass beim Suchen nach nicht Architekturabhängigen Dateien (C wie Assembler) das Verzeichnis "arch" nicht berücksichtigt wird, nur weiß ich nicht wie...
-
Nun habe ich beim linken mittels ld (64Bit System) immer die Option "-melf_i386" genutzt, da das sonst nicht funktioniert hat. Gibt es auch ein äquivalent dazu, wie z.B. "-melf_x86-64"?
-melf_x86_64 :-D (eine vollständige Liste kannst du dir mit ld -V ausgeben lassen)
Konkret möchte ich, dass beim Suchen nach nicht Architekturabhängigen Dateien (C wie Assembler) das Verzeichnis "arch" nicht berücksichtigt wird, nur weiß ich nicht wie...
$ man find:
find … -name "*.c" -and -not -path "*/arch/*"
Welches der drei end genutzt wird, hängt davon ab, ob du mit oder ohne leading-underscore compilieren lässt(gcc option: -f{no-}leading-underscore), und ob du end oder _end in deinem Code verwendest.
-
Nun ja, unter dem Debian und mit dem Crosscompiler immer ohne den Untersstrich :D
Ich habe auch mal etwas weiter gesucht und schließlich das ganze wie folgt gelöst:
$(shell find $(KERNEL_SRC_FOLDER) -mindepth 1 -maxdepth 10 -name "*.c" -type f | grep -v "^$(KERNEL_SRC_FOLDER)/arch/")
-
So so, da bin ich mal wieder.
Dieses mal hat es allerdings nicht direkt mit dem zu tun, was ich zuvor gepostet habe...
Und zwar bin ich am Überlegen, was vom Kernel Architekturabhängig ist und was nicht. Ich bin nun zu dem Schluss gekommen, dass eigentlich fast alles Abhängig von der gewählten Architektur ist, wenn man einen I386, einen amd64 und nen alten PowerPC (ich habe keine Ahnung von diesem) Prozessor betrachtet.
Was übrig bleibt sind in meinen Augen kleine Funktionen im Kernel wie memset oder memcpy, die man ja eigentlich nicht ändern müsste...
Ist das wirklich so, oder habe ich da eventuell etwas übersehen oder falsch gedacht?
-
Ich würde mal mit einem klaren "kommt drauf an" antworten.
Bei einem Mikrokernel wirst du relativ viele prozessorspezifische Sachen haben, während bei einem Monolithen der allergrößte Teil unabhängig von der Prozessorarchitektur sein wird. Unabhängige Sachen, die du in beiden Fällen haben wirst, sind zum Beispiel der Scheduler und allgemein recht viel von der Taskverwaltung, evtl. die physische Speicherverwaltung, die Verarbeitung von Syscalls (auch wenn der Aufrufmechanismus wieder plattformabhängig ist), sofern vorhanden wahrscheinlich auch noch die IPC.
Reicht das für den Anfang? ;)
-
Was übrig bleibt sind in meinen Augen kleine Funktionen im Kernel wie memset oder memcpy, die man ja eigentlich nicht ändern müsste...
Wenn du davon wirklich optimierte Implementierungen haben willst, ist das nicht nur von der Architektur, sondern eventuell sogar von der Prozessorgeneration abhängig.
-
Was übrig bleibt sind in meinen Augen kleine Funktionen im Kernel wie memset oder memcpy, die man ja eigentlich nicht ändern müsste...
Wenn du davon wirklich optimierte Implementierungen haben willst, ist das nicht nur von der Architektur, sondern eventuell sogar von der Prozessorgeneration abhängig.
Nun, ich habe die Funktionen jetzt so unoptimiert geschrieben, dass diese Plattformunabhängig sein sollten.
Ich würde mal mit einem klaren "kommt drauf an" antworten.
Bei einem Mikrokernel wirst du relativ viele prozessorspezifische Sachen haben, während bei einem Monolithen der allergrößte Teil unabhängig von der Prozessorarchitektur sein wird. Unabhängige Sachen, die du in beiden Fällen haben wirst, sind zum Beispiel der Scheduler und allgemein recht viel von der Taskverwaltung, evtl. die physische Speicherverwaltung, die Verarbeitung von Syscalls (auch wenn der Aufrufmechanismus wieder plattformabhängig ist), sofern vorhanden wahrscheinlich auch noch die IPC.
Reicht das für den Anfang? ;)
Fürs erste ja. :-D
Ich bin nun soweit, dass mein neugeschriebener Kernel bereits eine GDT lädt...
Diese, sowie auch der IDT-Code und der IRQ-Code sind in meinen Augen Plattformabhängig. Wenn man jetzt nur i386 und x86_64 betrachtet, könnte man hier auch nochmal einen geteilten Ordner erstellen, da diese teilweise ähnliche, bzw. gleiche Funktionen haben. Allerdings werde ich das lassen, weil ich nicht wirklich Lust habe dafür das Makefile nochmals anzupassen...
Was mir noch eingefallen ist, was eigentlich auch Größtenteils Plattformunabhängig ist, ist die Heapverwaltung. Abhängig von der Plattform ist da nur der Code, der den Heap erweitert, verkleinert und initialisiert, da das auf dem 386er ja gemappt werden muss, sobald das Paging aktiv ist.
Wie das bei anderen Prozessoren ist, weiß ich nicht genau, aber ich glaube kaum, dass jeder Prozessor Paging unterstützt. ;)
-
Jeder Prozessor, der es wert ist, unterstützt zu werden, hat eine MMU. Ohne macht es nicht viel Spaß. Allerdings gibt es da ein paar lustige andere Konzepte, wie so eine MMU funktioniert - das mit PD und PT ist nicht universell.
-
Genau das meinte ich ja. Es besteht ja schon ein Unterschied beim Paging zwischen 32 und 64 Bit...
Die folgende Frage wird nur gestellt, um sicher zu gehen:
Ich habe den kompletten Code für die Textausgabe mal im Ordner für plattformabhängige Parts abgelegt. Ist das so okay, oder gibt es den CGA-Krempel überall und wenn ja, ist der dann überall gleich?
-
Das eigentliche Ausgeben eines Zeichens ist natürlich geräteabhängig (nicht unbedingt plattformabhängig). Du wirst vermutlich schon für PCs zwei verschiedene Ausgabefunktionen für VGA und serielle Schnittstelle haben wollen.
Aber ein printf ist ein printf, egal womit es sein Zeichen am Ende ausgibt. Interpretieren des Formatstrings, Ausgabe von Zahlen usw. ist alles generisch.
-
Es wird mal wieder Zeit eine Frage zu posten... :roll:
Ich bin nun dabei, "Interrupt Service Routines" ISRs einzubauen. Es gibt 256 Interrupts und so weiter. Ich habe mir 2 Makros geschrieben, die mir die Tipparbeit im Assemblercode abnehmen...
Nun zur Frage:
Der Prozessor schiebt ja die folgenden Register auf den Stack:
SS, ESP, EFLAGS, CS, EIP, Error Code. Zusätzlich dazu schiebe ich noch die Interruptnummer auf den Stack.
Das ganze sieht dann wie folgt aus:
; Abschnitt
; Macro for isr stub without an already pushed error code
%macro INT_STUB 1
global intr_stub%1
intr_stub%1:
cli
push byte %1 ; ISR-Number
push byte 0 ; Dummy error code for a uniform stack frame
jmp interrupt_handler_stub
%endmacro
; Macro for isr stub with an already pushed error code
%macro INT_STUB_ERROR_CODE 1
global intr_stub%1
intr_stub%1:
cli
push byte %1 ; ISR-Number
jmp interrupt_handler_stub
%endmacro
INT_STUB 0
INT_STUB 1
INT_STUB 2
INT_STUB 3
INT_STUB 4
INT_STUB 5
INT_STUB 6
INT_STUB 7
INT_STUB_ERROR_CODE 8
extern interrupt_handler
interrupt_handler_stub:
; Push created stack
mov eax, esp
push eax
; Call C-Function
mov eax, interrupt_handler
call eax
pop eax
; remove isr number and error code
add esp, 8
; restores EIP, CS, EFLAGS, ESP and SS
iret
Was ich noch machen müsste, wäre die Segmente des Kernels laden, falls ein Ring-Wechsel auftritt.
Aber welchen Sinn hat es, die kompletten Register an den Interrupthandler zu übergeben? So wird das ja in vielen Tutorials gemacht...
-
Übergeben musst du sie nicht zwingend (wird aber oft gemacht, weil dann z.B. die Syscallnummer und Parameter in Registern übergeben werden können), aber zumindest sichern und wiederherstellen wäre schon nett. Das Userspace-Programm wird sich bedanken, wenn du ihm seine Registerwerte überschreibst, sobald der Timer feuert. ;)
-
Stimmt...
Wo habe ich nur meinen Kopf... :-P
Da ist noch was unklar.
pusha schiebt die general purpose register auf den Stack und ignoriert dabei esp. Im Intel Manual 2B steht folgendes:
EDI ← Pop();
ESI ← Pop();
EBP ← Pop();
Increment ESP by 4; (* Skip next 4 bytes of stack *)
EBX ← Pop();
EDX ← Pop();
ECX ← Pop();
EAX ← Pop();
Heißt das, dass der Wert von ESP nicht auf den Stack gepusht wird, es aber trotzdem ein Platz von 4 Byte mit irgendwelchem Inhalt angelegt wird? Wenn dem so ist, müsste ich ja esp oder zumindest einen Platzhalter noch in meine register-struktur einbauen...
-
Gepusht wird es vermutlich schon, aber gepopt halt nicht.
-
Da war ich wohl wieder falsch. Schon blöd bei popa zu schauen, wenn man pusha haben will...
Gepusht wird es auf jeden fall, laut manual. Nur wird es nicht wieder zurückgesetzt. Das Zurücksetzen übernimmt dann der/das Mnemonic "iret". :-D
-
Ein Tipp:
; Macro for isr stub without an already pushed error code
%macro INT_STUB 1
global intr_stub%1
intr_stub%1:
cli
push byte %1 ; ISR-Number
push byte 0 ; Dummy error code for a uniform stack frame
jmp interrupt_handler_stub
%endmacro
Ich würde das Pushen von Pseudofehlercode und Interruptnummer vertauschen, da ja im Falle eines Fehlercodes zuerst der Fehlercode auf dem Stack liegt und dann die Nummer des Interrupts hinterher gepusht wird. :-)
-
Nun, anscheinend war das einer der Fehler, die nun gelöst sind... :roll:
Ich habe in den Makros nun anstatt "push byte %1" nur noch "push %1" stehen, da das byte ja Vorzeichenbehaftet ist, somit bei "int 0x80" immer -128 herauskommt.
Was macht das ganze für einen Unterschied? Nutzt nasm dann den Typ, der gerade passt oder wie sieht das aus?
-
Bei push byte oder word wird immer sign extended. Darauf hast du keinen Einfluss. Wenn du das nicht willst, musst du push dword nehmen. NASM hat da also auch keine Wahl, außer deinen Befehl zu befolgen und den Wert als vorzeichenbehaftetes byte und somit -128 auf den Stack zu legen.
Wenn du das Vorzeichen in der weiteren Behandlung in C ignorieren willst, könntest du den Parameter als unsigned char deklarieren. Dann solltest du wieder den Wert +128 bekommen.
-
Nun ja...
Die Werte (ISR Nummer und Error Code) sind in einer Struktur als unsigned int abgelegt... Ich müsste ja dann eigentlich auch push dword benutzen oder?
-
ja, das wäre vermutlich das einfachste.
-
Es gibt mal wieder etwas unklares... :roll:
Da ich vor kurzem mein System gründlich sabotiert habe und nicht mehr auf meine Daten zugreifen konnte, musste, bzw. muss, ich einige Teile von meinem Betriebssystem nochmals schreiben. Hier bin ich bei folgendem Stück Code hängen geblieben:
; Push created stack
mov eax, esp
push eax
; Call C-Function
mov eax, interrupts_handler
call eax
pop eax
Wenn man auf normalem Wege eine C-Funktion in Assembler aufruft (call my_cfunction), so muss man nach dem call esp um 4 erhöhen, damit der Rest danach noch ausgeführt wird.
Bei obiger Implementation, die ich aus einem Tutorial entnommen habe ist das nicht nötig, aber warum? Was passiert genau, wenn der Wert in eax per "call"-instruction aufgerufen wird?
-
Wie du ja vermutlich weißt, werden die Parameter einer Funktion in der C-Aufrufkonvention (auf i386) auf dem Stack übergeben. Dabei ist der Aufrufer sowohl dafür verantwortlich, die Parameter auf den Stack zu legen (das ist offensichtlich) als auch dafür, die Parameter hinterher wieder runterzunehmen. Das kann man entweder mit einem pop machen, wie das dein Beispielcode hier tut, oder wenn man den Wert nicht mehr braucht, kann man einfach den Stackpointer erhöhen, also die add-Variante.
-
Das mit den Aufrufkonventionen wußte ich, aber warum nur i386? Ist das bei einer 64Bit CPU anders?
Noch etwas, da ich ja grad bei Interrupts bin. Wenn ich den PIC nehme, mappe ich die IRQs von 32 bis 48. Das ist aber nur der PIC, wie sieht das mit dem local APIC aus? Da sind das ja mehr als nur die 16 IRQs. Muss ich da extra einen speziellen handler für den APIC-Krempel schreiben?
Btw. Ich habe mich noch nicht viel mit dem APIC beschäftigt, nur auslesen, ob dieser überhaupt vorhanden ist, und wo die Basisregister im Speicher anfangen...
-
Das mit den Aufrufkonventionen wußte ich, aber warum nur i386? Ist das bei einer 64Bit CPU anders?
Ja, auf amd64 werden erstmal Register hergenommen bevor der Stack drankommt.
Die APIC-Frage überlasse ich mal jemandem, der das schon implementiert hat.
-
Ich habe mal in den Code von lightOS rein geschaut...
Hier hatte die Klasse für den PIC, sowie APIC eine Methode "handle_irq". Daraus habe ich geschlossen, dass das getrennt behandelt werden muss. Da ich aber keine Lust habe jedes mal bei einem IRQ zu prüfen, ob denn ein APIC oder ein PIC vorhanden ist, habe ich einen globalen Funktionszeiger angelegt, der dann eben immer aufgerufen wird.
In den entsprechenden Funktionen muss man dann eben prüfen, ob es sich bei dem Interrupt überhaupt um einen IRQ handelt, da PIC und APIC anscheinend zu verschieden sind, als dass ich das mit einem If erledigen könnte:
//...
void (*hardware_interrupt_handler)(registers_t*) = 0;
//...void interrupts_handler(registers_t *r)
{
// An error occured
if(r->interrupt_number < 32)
{
video_puts(exception_messages[r->interrupt_number]);
video_puts(" Exception. System Halted!\n");
for(;;);
}
if(r->interrupt_number == 0x80)
{
// handle system call
}
// handle hardware interrupts
if(hardware_interrupt_handler != 0)
hardware_interrupt_handler(r);
}
Ist eben die Frage, was besser ist... die Lösung oben oder die, in der mit einer boolschen Variable geprüft und die entsprechende Funktion aufgerufen wird...
-
Doppel-Posts sind böse, aber beim Editieren besteht die Möglichkeit, dass das niemand liest...
Ich habe mich etwas weiter eingelesen in den Local APIC. Nun heißt es, dass bei einem System mit nur einem Prozessor kein I/O APIC vorhanden ist, weshalb der PIC für Hardware Interrupts genutzt wird. Muss ich also nach der Initialisierung des local APIC den PIC zusätzlich initialisieren, oder übernimmt das der local APIC während der Initialisierung?
-
Ich will ja keinem dazwischenpfuschen oder so, aber ich hab da noch mal ne Frage zum ersten Beitrag am Anfang. dieses align, was bewirkt dass, ich habs nicht ganz verstanden. im multiboot header, wird align 4 verwendet, und was genau verändert dieses macro?
das hab ich nicht so ganz verstanden. sry ;-)
-
Es sorgt dafür, dass die nächste Ausgabe auf eine 32-Bit-Grenze ausgerichtet wird, es wird also die Adresse auf die nächsten ganzen vier Bytes aufgerundet. Das ist einfach eine Voraussetzung, die so in der Multibootspezifikation steht.
-
Ich will ja keinem dazwischenpfuschen oder so, aber ich hab da noch mal ne Frage zum ersten Beitrag am Anfang. dieses align, was bewirkt dass, ich habs nicht ganz verstanden. im multiboot header, wird align 4 verwendet, und was genau verändert dieses macro?
das hab ich nicht so ganz verstanden. sry ;-)
Dafür ist der Thread doch da. :-D
So bin ich mal wieder ein bisschen schlauer geworden. :roll:
Ich beschäftige ja mich immer noch mit dem local APIC und hier gibt es "feste Events" (mir fällt kein besserer Begriff ein), wie den APIC Timer, LINT0, LINT1 usw.
Diese werden immer vom installierten I/O APIC ausgelöst. Folglich gibt es immer einen I/O APIC, wenn es einen local APIC gibt. Gibt es keinen local APIC, ist kein I/O APIC da und man muss den normalen PIC nehmen.
Sollte sich jemand bereits etwas mehr damit beschäftigt haben (local APIC, I/O APIC), und ich habe was falsches geschrieben, dann korrigiert mich bitte... :-)
-
Ich hab mal ne Frage zum Linker. wenn ich mein Linkerscript schreibe, warum muss dann der Linker wissen, wo die Datei am ende in den Speicher geladen wird?
Ich meine wenn ich jetzt nen programm schreibe, und wir davon ausgehen, das dass System ohne Paging arbeit, kann man doch garnicht wissen, wo dass Programm am Ende im Speicher landet, oder? Also beim Linken kann man dass doch garnicht wissen?
-
Du musst es aber wissen (außer bei Position Independent Code), denn der Linker muss aus deinen Labels ja konkrete Adressen machen. Wenn du kein Paging hast, willst du im Normalfall wenigstens Segmentierung nehmen, so dass du auch mehrere Programme an derselben virtuellen Adresse laufen lassen kannst.
-
wie soll das gehn (position independent?) ich dachte, dass wäre bei paging kein problem, da jedes programm seinen eigenen virtuellen adressraum hat. dass muss ich wissen? Ich kann mir nicht vorstellen dass jeder Programmierer sich dann noch Gedanken macht, wo dass OS das Programm hinladen könnte, woher soll er das auch wissen?! ... und ich meine wo ist dann der unterschied ob ich in einer asm datei "org 0x7c00"
oder im linkerscript ". = 0x7c00" schreibe?
-
wie soll das gehn (position independent?)
Im long mode gibt es die möglichkeit relativ zu RIP zu adressieren(z.B.: mov rax, [rip + 0xf00ba123])
ich dachte, dass wäre bei paging kein problem, da jedes programm seinen eigenen virtuellen adressraum hat. dass muss ich wissen? Ich kann mir nicht vorstellen dass jeder Programmierer sich dann noch Gedanken macht, wo dass OS das Programm hinladen könnte, woher soll er das auch wissen?!
Für gewöhnlich ist das eine feste(bekannte) Adresse. MS-DOS lädt .COM Dateien z.B. immer nach 0x100.
... und ich meine wo ist dann der unterschied ob ich in einer asm datei "org 0x7c00"
oder im linkerscript ". = 0x7c00" schreibe?
es gibt keinen
-
ist long mode nicht ein modus von 64 bit prozessoren?
-
Doch ist es. Das ganze geht aber soweit ich weiss auch mit 32 Bit Prozessoren. Da wird dann üblicherweise ein Register für die Adressierung dauerhaft besetzt. Für mehr Details siehe gcc-Doku zu -fpic und sowas.
-
Da es im entfernten Sinne auch eine Verständnisfrage ist, poste ich einfach hier. :wink:
Ich habe das Thema APIC und I/O APIC mal nach hinten verschoben, auch wenn mein Testrechner nen local APIC hat...
Und zwar bin ich ja immer noch am aufteilen/neuschreiben meines Codes. Nun habe ich einfach mal im GIT-Repository geschaut. An und für sich ist die physische Speicherverwaltung ja schnell gelöst (Bitmap). Nun ist es ja so, dass die Seitengröße von 4K Plattformabhängig ist, aber der ganze Rest (die eigentlichen Funktionen) ja nicht.
So weit kein Problem. Da ich aber nun einfach mal bei tyndur geschaut habe, habe ich den mmc-Funktionssatz gesehen. Ich vermute einfach mal, dass das für "Memory Management Context" steht. Was für ein Sinn steckt dahinter? Wofür werden solche Funktionen gebraucht?
-
Die mmc_*-Funktionen sind alles, was mit der MMU zu tun hat (und deine Vermutung bezüglich der Abkürzung ist richtig). Für i386 ist das also einfach die Implementierung für Paging. Ein Kontext entspricht in der Implementierung einem Page Directory.
-
Hiho :)
Ich habe mich vor kurzem dazu entschlossen von nasm auf den GNU Assembler umzusteigen. Unter nasm musste ich immer die "Architektur" angeben, z.B. [BITS 32]. Muss ich das bei dem Assembler von GNU auch so ähnlich machen, oder reicht es, wenn da einfach die Option "-melf32" gesetzt wird?
-
Hi,
es riecht vollkommen, wenn du das -melf-32-flag setzt.
Alles andere macht der Compiler dann von alleine
-
Moin moin,
finde ich gut. :)
Trotzdem steht am Anfang des Assemblercodes ein ".code 32". Ich denke mal, dass das angibt, dass es sich bei dem nachfolgenden Code um 32 Bit Code handelt.
Nun ist da noch etwas. Ich experimentiere mal wieder mit C++. Nun werden im Wiki-Tutorial per Linkerscript die Destruktoren entfernt. Gilt dies nur für globale und statische Klasseninstanzen, oder werden da auch lokale destruktoren mit entfernt, die aufgerufen werden müssten, wenn z.B. eine Klasse nur in einem bestimmten Abschnitt, z.B. einem if, benutzt wird?
-
Hallo,
... ".code 32". Ich denke mal, dass das angibt, dass es sich bei dem nachfolgenden Code um 32 Bit Code handelt.
Das wird wohl so sein.
Gilt dies nur für globale und statische Klasseninstanzen
Ja.
oder werden da auch lokale destruktoren mit entfernt, die aufgerufen werden müssten, wenn z.B. eine Klasse nur in einem bestimmten Abschnitt, z.B. einem if, benutzt wird?
Nein, diese Destruktor-Aufrufe stehen mitten im Code der entsprechenden Funktion.
Grüße
Erik