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.


Nachrichten - Svenska

Seiten: [1] 2 3 ... 90
1
OS-Design / Antw:Grub installieren
« am: 08. March 2022, 19:31 »
Wenn du ein Linux installiert hast, dann sollte GRUB2 bereits installiert sein.

Die Konfigurationsdatei liegt in /boot/grub/grub.cfg.
Da kannst du folgendes anhängen:
menuentry "MYOS" {
   multiboot /boot/myos.bin
   boot
}

Und dann kopierst du deinen Kernel nach /boot/myos.bin.
Sinnvoll wäre noch, an den Anfang der Datei ein "set timeout=20" einzutragen.

Ubuntu und einige andere Distributionen zeigen das GRUB-Menü standardmäßig nicht an.
Wenn du beim Start zur richtigen Zeit ESC drückst, siehst du es.
Drückst du zweimal ESC, landest du im GRUB-Terminal. Da kommst du mit "normal" wieder raus.

Hoffe, es hilft,
Svenska
2
OS-Design / Antw:Grub installieren
« am: 26. February 2022, 02:18 »
Woran scheitert es denn?

Moderne Systeme benutzen den GRUB2 - GRUB Legacy ist veraltet. Dürfte schwierig sein, den auf modernen Linux-Distributionen zu installieren. Der Artikel ist größtenteils von 2013 und früher, also nicht ganz aktuell.

Man kann Multiboot-Kernel auch mit SYSLINUX/EXTLINUX (MBOOT.C32) starten und es gibt auch einen DOS-Bootloader (MBLOAD). Ob letzterer in DOSBox funktioniert, müsste man mal testen... :-) Außerdem kann (oder konnte) Qemu einen Multibootkernel mit "-kernel" direkt starten. Das dürfte alles einfacher sein als den alten GRUB zum Laufen zu bekommen.
3
Lowlevel-Coding / Antw:Probleme mit aarch64
« am: 08. August 2021, 23:27 »
Zitat
Das mit dem .global versteh ich; was ich nciht verstehe, ist die Verwendung davon bzw. wieso dies nciht so funktiniert wie ich das erwarten würde...
Okay, dann gehe ich da mal durch...

Zitat
.global _test
_test:
...
aarch64-linux-gnu-ld: start.o: in function `_test':
(.text+0x3c): multiple definition of `_test'; start.o:(.text+0x3c): first defined here

In deiner start.o gibt es mindestens zwei Definitionen von "_test". Oder du gibst dem Linker die start.o mindestens zweimal, das hat den gleichen Effekt. Du hast in deinem System nicht zufällig eine "start.c" und eine "start.s", oder?

Zitat
.global test
_test:
als auch.global _test
test:
scheint der linker auch erstmal so hinzunehmen.
Das sind unterschiedliche Symbole. Im ersten Fall meldest du die Existenz von "test", aber es existiert nicht. Im zweiten Fall meldest du die Existenz von "_test", aber es existiert nicht. Die jeweils anderen Symbole sind rein lokal und daher von außen nicht sichtbar.

Zitat
Was ist denn die korrekte Syntax für .global?
Du machst das schon richtig, aber ich vermute, dass du deine Datei zweimal dem Linker übergibst. Dann hast du natürlich jedes Symbol doppelt...

Mit dem gdb-Output kann ich nichts anfangen, mit dem habe ich nicht viel Erfahrung.

Wenn dein Problem noch aktuell ist, geh ich mal in den Code rein.
4
Lowlevel-Coding / Antw:Probleme mit aarch64
« am: 12. July 2021, 01:45 »
Vorweg: Mit arm64 kenne ich mich nicht besonders aus.

Versuche mal, statt "short int" einen ganz normalen int oder long zu benutzen (also 32 oder 64 Bit). Vielleicht ist irgendwas mit Adressierung oder Alignment komisch. Für mehr müsstest du einen Debugger auspacken, du kannst qemu an einen gdb flanschen und einfach mal single-steppen.

Das ".global" im Assemblerquelltext macht ein Symbol für andere Dateien sichtbar. Zum Beispiel hast du in start.S das Symbol "start" global verfügbar gemacht (".global _start"). Wenn sich der Linker über mehrfache Definition beschwert, dann ist aus irgendeinem Grund das Symbol mehrfach vorhanden. Ohne das ".global" sind die Symbole auch mehrfach vorhanden, aber die stören sich nicht, weil sie lokal sind.

Dein Makefile greift sich sämtliche C- und S-Dateien. Das habe ich früher auch mal so gemacht, aber das ist kein guter Stil. Wenn du eine C-Datei mit "system_off" rumliegen hast - muss ja nichtmal deine Wunschdatei sein, sondern z.B. eine Kopie - dann erzeugt ein ".global _system_off" im Assembler einen Konflikt. Teste das mal aus; was du beschreibst, sollte funktionieren.
5
Lowlevel-Coding / Antw:Ein Spiel im Boot-Sektor
« am: 10. July 2021, 11:37 »
Qemu kann UEFI, aber ob er auch im UEFI-Modus das CSM unterstützt, weiß ich nicht.
Du kannst halt das normale BIOS benutzen.

Außerdem ist es sinnvoll, in verschiedenen Emulatoren zu testen, z.B. VirtualBox und VMware sind eher wie moderne Systeme gebaut (vllt funktioniert UEFI mit CSM dort), PCem eher wie ältere Systeme. DOSBox (mit Floppy-Image) ist auch interessant.
6
Lowlevel-Coding / Antw:Ein Spiel im Boot-Sektor
« am: 07. June 2021, 15:30 »
Das hängt mit der Speicheradressierung im Real Mode zusammen, und die wiederum ist eine Eigenheit des 8086/8088 und seiner Nachfolger. Der 8086 hat einen 20 Bit-Adressbus (kann also 1 MB adressieren), aber nur 16 Bit-Register (kann also nur 64 KB am Stück adressieren).

Weil ein Adressregister - einschließlich des Instruction Pointers IP - nur 64 KB am Stück adressieren kann, wird der Speicher in "Segmente" aufgeteilt. Jedes dieser Segmente ist 64 KB groß und hat eine Nummer (die steht in den Segmentregistern). Jetzt haben die Segmentregister aber auch 16 Bit, und zusammen mit einem normalen Adressregister kommt man auf 32 Bit - aber der Chip hat nur 20 Adressbits.

Also hat Intel die Segmente nicht brav hintereinander gelegt, sondern mit einem Abstand von jeweils 16 Bytes übereinander:

- Segment 0 geht von 0x00000 bis 0x0FFFF (64 KB ab Adresse 0)
- Segment 1 geht von 0x00010 bis 0x1000F (64 KB ab Adresse 16)
- Segment 2 geht von 0x00020 bis 0x1001F (64 KB ab Adresse 32)
- Segment 3 geht von 0x00030 bis 0x1003F (64 KB ab Adresse 48)
- ... und so weiter ...
- Segment 61439 geht von 0xEFFF0 bis 0xFFFEF (64 KB ab Adresse 983024)
- Segment 61440 geht von 0xF0000 bis 0xFFFFF (64 KB ab Adresse 983040)
- Segment 61441 geht von 0xF0010 bis 0x10000F (64 KB ab Adresse 983056)
- ... und so weiter ...
- Segment 65535 geht von 0xFFFF0 bis 0x10FFEF (64 KB ab Adresse 1048560)

Eine Nebenwirkung dieses Systems ist, dass die Segmente ab 61441 (Segment-Nummer 0xF001) hinter den 20 Bit-Adressraum zeigen (beachte den Übertrag in der Hex-Darstellung). Da der Chip aber nur 20 Adresspins hat, fällt das obere Bit weg und diese Segmente zeigen wieder auf das erste Segment.

Das heißt, die zweiten 16 Byte von Segment 0 (0x0000:0x0010 bis 0x0000:0x001F) sind gleichzeitig die ersten 16 Byte von Segment 1 (0x0001:0x0000 bis 0x0001:0x000F) und die letzten 16 Byte von Segment 61442 (0xF002:0xFFF0 bis 0xF002:0xFFFF) und die vorletzten 16 Byte von Segment 61441 und so weiter und so fort.

Alle diese Segmente überlagern sich. Das heißt, es gibt viele Adressen, die auf das gleiche Byte zeigen. Innerhalb eines Segments gibt es nur eine Adresse für jedes Byte (die Segment-Nummer ist ja festgelegt), aber es gibt 4096 Segmente, die auf dieses Byte zeigen können. Und in jedem dieser Segmente heißt das Byte anders.

Dein BIOS lädt den Bootloader an Adresse 0x07C00, aber du kennst den Wert von CS nicht. Das kann CS=0x0000 sein (dann ist IP=0x7C00), es kann aber auch CS=0x07C0 sein (dann ist IP=0x0000). Oder CS=0x03E0 (dann ist IP=0x3E00) oder über 4000 andere Möglichkeiten.

Du kannst den Bootloader theoretisch so schreiben, dass er an jeder möglichen Adresse funktioniert, aber das ist ineffizient (besonders auf dem 8086), daher macht man das nicht. Dein Code ist also normalerweise so gebaut, dass das erste Byte an Adresse 0x0000 liegt (das ist das ".org 0x0000"), aber davon weiß die CPU ja nichts. Also machst du noch einen "FAR JMP" hinterher, der gleichzeitig CS und IP setzt. Danach hat CS den Wert 0x07C0 und IP 0x000F (die Adresse von "main", wenn man ab 0 anfängt zu zählen).

Verständlich?
7
Lowlevel-Coding / Antw:Ein Spiel im Boot-Sektor
« am: 31. May 2021, 20:45 »
Du setzt DS und SS auf 0x0000. Das ist unklug, denn da befindet sich die IVT (Interruptvektortabelle).
In UEFI-Code habe ich noch nicht gewühlt, aber zumindest DOSBox und mein :YES schalten in ihren Interrupthandlern explizit Interrupts ein. Wenn du ungünstig an der IVT gespielt hast, dürfte der nächste Timerinterrupt das System neu starten.

Dein Code wird an die lineare Adresse 0x07C00 geladen, aber das BIOS kann daraus verschiedene Segment:Offset-Paare erzeugen. Du solltest das erstmal normalisieren. Im "tiny"-Speichermodell setzt du dann CS=DS=SS und hast dort erstmal garantiert 64 KB Speicher, in dem du arbeiten kannst.

Also ungefähr so:
  ; CS:IP ist unbekannt
  .org 0x0000
  mov ax, 0x07C0
  mov ds, ax
  mov ss, ax
  mov sp, 0xFFFE
  jmp WORD 0x07C0:main

main:
  ; CS=DS=SS=0x07C0
  ; hier geht's los
8
Lowlevel-Coding / Antw:Ein Spiel im Boot-Sektor
« am: 21. May 2021, 20:22 »
Spontan würde ich mal den Stack initialisieren, bevor ich das BIOS aufrufe. Dein "int 10h" sollte zwar einen eigenen Stack benutzen, aber garantiert ist das m.W. nicht. Vor allem nicht bei modernen VBIOS-UEFI-Fallback-Implementationen, die garantiert nicht mehr im Detail auf sowas getestet werden.

Als nächstes fällt mir das hier auf:
    mov     ax, MEM_VIDEO   ; zeiger auf bildschirmspeicher
    mov     es, ax          ; ES=MEM_VIDEO
    mov     al, 0x03
    int     0x10


Damit rufst du int 10h / AH=B8h / AL=03h auf. RBIL kennt diese Funktion nicht; kann gut sein, dass dein Video-BIOS dir das übel nimmt.

Ich kann mir gut vorstellen, dass die Legacy-VBIOS-Emulation auf UEFI-Systemen von Intels Referenzimplementation stammt und sich auf so ziemlich allen UEFI-Systemen ähnlich verhält (ein generisches VBIOS für den UEFI-Grafiktreiber). Das würde so ein Verhalten erklären.

Gruß,
Svenska
9
Lowlevel-Coding / Antw:Lochstreifen und Opcodes
« am: 13. June 2020, 13:54 »
Aber wenn das Paritätsbit auch falsch eingelesen wird, ist das doch eh egal, oder nicht? Oder meinst du, dass man per Hand nachschauen soll, ob nun ein Paritätsbit richtig eingelesen wurde, oder nicht?
Nein. Ein Paritätsbit kostet dich genau 1 Bit (egal, wie lang die Daten sind) und gibt dir dafür 50% Fehlererkennung und 0% Fehlerkorrektur. Die 50% kommen daher, dass das Bit nur erkennt, wenn die Anzahl der kaputten Bits (einschließlich des Paritätsbits selbst) ungerade ist. Doppelbitfehler werden nicht erkannt, Dreifachbitfehler schon. Und so weiter.

8bit soll nur der Opcode für die eigentliche Instruktion (<- ist das jetzt das richtige Fachwort? Ich bin langsam verwirrt, wenn für mich "Instruktion", "Anweisung" und "Befehl" fast das gleiche bedeuten und doch in verschiedenen Kontexten anders gedeutet werden können), ohne Argumente sein, also nicht der komplette Befehl.
Korrekt. Der Opcode gibt an, was getan werden soll; die Argumente/Parameter geben an, wie es getan werden soll; alles zusammen ist eine Instruktion/ein Befehl.

Das ist cool, aber ich finde die Idee vom eigenen Emulator und Befehlssatz gerade so spannend, dass ich gerne mal die Erfahrung machen möchte, beides zu erstellen. Ich bin gerade super motiviert dafür und diese Motivation kann ich echt gebrauchen.
Ich will dich nicht davon abbringen, die juckenden Finger sinnvoll anzuwenden - aber schau da einfach mal rein, wie einfach man solche Systeme bauen kann. (Und wie kompliziert x86 dagegen ist.)

Zitat
Hast du denn schon einen Stanzer und ein Lesegerät?
Der Stanzer ist da, das Lesegerät ist noch in Arbeit. Ich baue es selbst, daher habe ich noch Probleme mit Ungenauigkeit beim Einlesen. Aber ich muss erst mal verschiedene Dinge ausprobieren, optimieren usw. Damit habe ich wahrscheinlich noch etwas zu tun, daher habe ich währenddessen genug Zeit mich mit dem Befehlssatz hier auseinanderzusetzen :wink:.
Genau das meinte ich mit "Lochstreifenleser sind ungenau". :-) Wenn du ein Standardformat benutzt, dann kannst du auch Ausschau nach einem alten Lesegerät halten und damit testen. Vor langer Zeit habe ich ein Youtube-Video gefunden, was einen (vermutlich selbst gebauten) Leser zeigt: https://www.youtube.com/watch?v=uZnuu18FtQk.
10
Lowlevel-Coding / Antw:Lochstreifen und Opcodes
« am: 09. June 2020, 23:30 »
Ah, okay! Das bedeutet auch, dass "MOV [BX+0x85], 0xAX24" ein eigener Befehl ist und das [BX+0x85] nicht vom Assembler umgewandelt, sondern direkt zum Befehl gehört? Dieser spezielle Mov-Befehl beinhaltet also das Addieren der ersten beiden Argumente?
Jaein. Wie gesagt, x86 ist kompliziert und eigentlich eine schlechte Vorlage. Ich wollte das eher als schlechtes Beispiel nehmen.

Egal, dröseln wir das mal für den 8086 auf (und 0xAX24 ist natürlich keine gültige Hexadezimalzahl...):
$ cat test.asm
mov word [bx+0x85], 0xAF24
$ nasm -fbin -o test test.asm
$ hd test
00000000  c7 87 85 00 24 af                                 |....$.|
00000006

Das sind also tatsächlich 6 Bytes. Das erste Byte ist 0xC7, und meine Opcode-Tabelle besagt, dass das ein unvollständiger Opcode mit zwei Argumenten "Ev" und "Iv" ist. Es folgt also ein weiteres Byte (Modbyte), hier 0x87 (1000 0111).

Dessen Bits 5-3 geben an, welcher Unterbefehl von 0xC7 das genau ist. Diese Bits sind 000, also handelt es sich um "MOV" (andere Kombinationen sind übrigens ungültig, zumindest für 8086). Alle Unterbefehle von 0xC7 sind WORD-Befehle (auf dem 8086: 16 Bit), das Zielargument wird im Modbyte näher beschrieben, und danach folgt das Quellargument als imm16.

Die Bits 7-6 im Modbyte geben das Ziel an: Sind sie "11", dann ist das Ziel ein normales Register (und die Bits 2-0 beschreiben das Register). Sind sie "00" und die Bits 2-0 sind "110", dann folgen auf das Modbyte zwei weitere Bytes mit der Zieladresse. Andernfalls (das ist hier der Fall) handelt es sich um eine "effektive Adresse", also eine Speicheradresse, die auf verschiedene Formen gebildet werden kann.

Für diesen Fall geben die Bits 2-0 (in unserem Beispiel: 111) an, welches Basisregister wir haben (111 = BX). Die Bits 7-6 (im Beispiel: 10) geben an, welcher Wert dazu addiert wird (10 = imm16). "imm16" heißt, dass zwei weitere Bytes folgen, die direkt den Wert angeben (als "16 bit signed int"). Die folgenden Bytes sind "0x85 0x00", die musst du vertauschen (weil x86 ist "little endian"), also ist das Ziel vom MOV die Adresse (BX+0x0085).

Die Quelle (das Iv aus dem Decoder) ist ebenfalls ein imm16, also folgen nochmal zwei weitere Bytes, welche wieder direkt den Wert (wieder als "16 bit signed int") angeben. Diese Bytes sind "0x24 0xAF", und nach dem Vertauschen hast du "0xAF24". Und so macht die CPU aus den sechs Bytes den Befehl. Und NASM macht das Umgekehrte.

Kurz: Das ist alles wesentlich komplizierter als du dir das vorstellst, weil x86 einfach kompliziert ist.

Und tatsächlich überlege ich doch einen Assembler zu bauen, der meinen Code 1:1 übersetzt (dann von Compiler -> Assembler -> Computersprache).
Gute Entscheidung. :-)

Doch in welcher Weise hilft dies bei der Optimierung?
Garnicht. :-) Optimierung ist nicht Aufgabe des Assemblers, darum sind Assembler relativ einfach zu programmieren. Optimierung findet im Compiler statt, und deswegen sind Compiler kompliziert. Darum erstmal weglassen und per Hand optimieren.

(Kurz dazu: Ich muss bei "Optimierung" im Bezug auf Programmiersprachen immer an die Optimierung und Vereinfachungen von Compilern wie die GCC denken, bei denen die Optimierung des Programmcodes ja echt gut ist. Falls du was anderes meinst, lass es mich wissen.)
Nein, du hast schon recht.

Zitat
Lochstreifen sind nicht die zuverlässigste Technologie - nicht jetzt, und auch nicht vor 50 Jahren. Übliche Lochstreifensysteme sind daher 7 Bit, und das 8. Bit ist ein Paritätsbit, damit du wenigstens weißt, wenn dein Lesegerät falsch gelesen hat.
Ich hab noch ein Indexloch. Dann kann ich doch das 8. Bit als Wert noch dazu nehmen. Ist das das was du meintest? Nicht, dass ich wieder an deiner Aussage vorbeirede :-D
Das Indexloch sagt dem Lesegerät, dass hier Löcher sind. Aber es trägt selbst keine weitere Information (schließlich ist es immer gelocht). Ein Paritätsbit gibt an, ob die Summe aller Einsen gerade oder ungerade ist. Wenn der Leser also z.B. "10101010" sieht, aber das Paritätsbit auf "ungerade" steht, dann weißt du, dass du einen Lesefehler hattest.

Komplizierter und langsamer sind für mich kein Problem, da es ja, wie gesagt, nicht perfekt sein muss :wink:.
Naja, wenn du nicht den Anspruch hast, dass es funktionieren soll... :-)

Ich muss mir dazu wahrscheinlich mehrere Befehlssätze als Referenzen suchen, um zu entscheiden, welche Instruktionen ein CPU noch bräuchte und ob ich dann mit 8bit noch genug Möglichkeiten habe, so viele hinzuzufügen (nun gut, 255 mögliche Befehle sollten genug sein :-D ).
Frage: Wenn jeder Befehl 8 Bit hat, und du davon 8 Bit für den Opcode reservierst, wie würdest du dann "schreibe die Zahl 42 in Register 3" codieren? ;-)

Ich führe den Code nicht direkt vom Lochstreifen aus. Ich lese den Lochstreifen erst ein und speichere den Code, wie alle anderen. Habe ich was anderes gesagt? Vielleicht habe ich mich falsch ausgedrückt.
Achso, ich dachte, du wolltest den Code direkt vom Lochstreifen ausführen.

Ich will am Ende nur den Code vom Lochstreifen lesen. Der Code wird dem Emulator übergeben, dann gehts von da aus weiter.
Achso. Das ist einfach.

Ein Lochstreifenlesegerät ist ein serielles Gerät (wie ein Modem). Da kommen einfach Bytes rein, die schreibst du in ein Array und wenn du fertig bist, lässt du den Emulator mit dem Array loslaufen. Als Bytecode-Interpreter kannst du was fertiges nehmen, z.B. das hier. Da musst du nur die Funktion load_program() von COM10 lesen lassen und fertig. :-)
Der Assembler dafür ist in Python geschrieben und erzeugt dir die Binärdaten, die du dann einfach byteweise nacheinander in den Lochstreifen stanzen musst.

Hast du denn schon einen Stanzer und ein Lesegerät?
11
Lowlevel-Coding / Antw:Lochstreifen und Opcodes
« am: 09. June 2020, 01:01 »
Doch kurz vorab würde ich gerne noch etwas klären: Ich nehme mir bei meinen Projekten meist nur die grundlegenden Ideen von bereits bestehenden Prinzipien. Von da aus reiße ich gerne aus verschiedenen Konventionen aus.
Kannst du ja auch machen. Muss ja nicht perfekt sein. :-)

Zitat
Eine übliche Klassifikation unterscheidet zwischen RISC und CISC. Was das im Detail bedeutet, darüber kann man lange streiten, aber für die Diskussion hier vereinfache ich das mal so: In einem RISC-Prozessor hat jeder Befehl die gleiche Anzahl an Bits, in einem CISC-Prozessor nicht.
Könntest du dafür vielleicht ein Beispiele bringen (zu Befehlen mit unterschiedlich vielen Bits)?
Jeder AVR-Befehl besteht aus 16 Bits (=1 Flash-Word, bzw. 2 Bytes). Jeder ARM-Befehl hat 32 Bits (=1 Word). Ein 8086-Befehl hat mindestens 1 Byte (sowas wie "CLI") und maximal 6 Byte (sowas wie "MOV [BX+0x85], 0xAC24"). Dazu kommen dann noch eventuelle Präfixe (wie "LOCK", "ES:" oder "REP").

Zitat
Eine weitere Klassifikation unterscheidet zwischen Stack- und Registermaschinen. Was du beschreibst, ist eindeutig eine Stackmaschine, da Operanden auf den Stack gelegt werden und von dort verarbeitet werden.
Ah, ja, deshalb wollte ich das am Anfang kurz klarstellen. Ich dachte mir tatsächlich, dass ich beide Varianten vereine.
Das kannst du zwar machen, aber damit verkomplizierst du nur das System und gewinnst nichts. Der Vorteil einer Stackmaschine ist, dass du nur ein Register (den Stackpointer) für die Verwaltung brauchst. Da auf einer Stackmaschine alle Operationen implizit auf dem Stack arbeiten, brauchst du in den Befehlen keine Operanden codieren und bekommst damit einen sehr platzeffizienten Befehlssatz.

Wenn du jetzt zusätzliche Befehle einbaust, die auf einem Registerfile arbeiten (auch wenn die den gleichen Namen haben, haben sie andere Opcodes, sonst könnte die CPU sie nicht unterscheiden!), dann musst du die jeweiligen Quell- und Zielregister mit eincodieren. Dann kannst du auch gleich eine Registermaschine bauen und auf den Stack verzichten. Das einzige, was du mit "ich kann aber beides" erreichst, ist mehr Aufwand für die Hardware, mehr Aufwand für die Software, und mehr Aufwand für irgendwelche Compiler. Niemand hindert dich daran, in einer Registermaschine einen Stack zu unterstützen - allerdings kannst du dann gleich mehrere davon haben.

Als Beispiel: Ein 8086 bindet den Stack ziemlich fest an das SP-Register (PUSH/POP arbeiten damit). Auf ARM hingegen kannst du (fast) jedes Register als Stackpointer nehmen, weil das keine Sonderbefehle sind, sondern Adressierungsmodi (post-increment bzw. pre-decrement). Das sind aber keine Stackmaschinen. Wenn du z.B. einen FORTH-Interpreter bauen willst (das ist eine stackbasierte Programmiersprache), dann willst du eigentlich zwei Stacks in Hardware unterstützen.

Zitat
Assembler als Programmiersprache ist normalerweise eine direkte Abbildung der Fähigkeiten einer CPU. Da es so kaum Architekturen gibt, die dein "mov 2,4" direkt abbilden können, ist das fast nie ein gültiger Befehl.
Deswegen war das auch nur Pseudocode. Wir rennen gerade komplett am Thema und der eigentlichen Frage vorbei.
Naja, du hast es "Pseudoassembler" genannt. :-)

Eigentlich hatte ich vor den Mov-Befehl so ähnlich aufzubauen:

syntax:  mov <t1> <w1> <t2> <w2>
bsp.:    mov 1 1 3 2  # al = 2

(Alle Werte werden in 8bit-Blöcke aufgeteilt: t1, t2, w1 und w2 sind allesamt 8bit lang, genau wie der mov Befehl an sich) t1/t2 würde dabei dafür stehen, welche Art von Daten als Nächstes kommen. w1/w2 sind die Werte. Wenn t1 0 ist, muss w1 eine Speicheradresse sein. Wenn t1 1 ist, muss w1 die ID eines Registers (ich würde denen dann halt IDs geben; hier wäre ax jetzt 1) beinhalten. Ist t2 1 -> w2=Register-ID; 2 -> Speicheradresse; 3 -> Zahl in den nächsten 8bit. Das Beispiel sieht also die 1, nimmt die nächste 1 als Register-ID (al), sieht die 3 und läd die darauf folgenden 8 bit in Register al. Aber das ist wahrscheinlich zu kompliziert, oder? Ich muss mir eh erstmal ein paar Befehlssätze anschauen und wie die das Problem lösen. Aber gerne würde ich trotzdem eine Meinung dazu hören.
In deinem Beispiel wäre jeder MOV-Befehl also 5 Byte lang, richtig? Das kann man machen, ist aber ziemliche Platzverschwendung. :-)

Das bedeutet dann, dass es verschiedene Mov-Befehle gibt, oder? Einer, der Zahlen in Register verschiebt; einer, der Register in Register schiebt; usw... Die haben dann alle einen unterschiedlichen Opcode, oder? Wenn ich das richtig verstanden habe, hört sich das für mich super lohnend an.
Genau das ist die Idee. Der Prozessor betrachtet die alle als vollkommen unterschiedliche Befehle, aber für den Programmierer sind das alles MOVs. Der Assembler muss dann raussuchen, welche Variante er nimmt.

Zitat
Was du als Pseudoassembler bezeichnest, geht eher in Richtung einer Hochsprache, weil du den Code noch transformierst, also grundlegend veränderst ("mov 2, 4" ist etwas anderes als "push 4; push 2; mov"). Das machen einfache Assembler nicht (Makroassembler lasse ich hier mal außen vor).
Das finde ich gar nicht schlimm. Am Ende soll der Compiler (da es ja anscheinend kein Assembler ist) meinen Code einfach nur in Opcodes umwandeln, die ich in den Lochstreifen stanze. Den Umweg über einen "wirklichen" Assembler lohnt sich meiner Meinung nach dann nicht wirklich.
Du wirst ihn aber wahrscheinlich trotzdem haben, auch wenn das nur verschiedene Funktionen im gleichen Programm sind. Der Punkt ist, dass ein Assembler ein recht einfaches Programm ist (eben weil er einfach nur 1:1 von Assemblerbefehlen in Maschinenbefehle umwandelt), während ein Compiler - oder dein Transformator - beliebig komplex werden kann. Stichwort "Optimierung". :-)

Im Übrigen erzeugst du keine "Opcodes", sondern "Befehle" (Instruktionen). Der Opcode sind nur ein paar Bits davon. Wieviele und welche, legst du fest.

Die erste Frage kann ich schon beantworten: 8bit. Einfach, weil mein Lochstreifen 8 Löcher hat und ich jeden Befehl in einer Zeile haben möchte.
Lochstreifen sind nicht die zuverlässigste Technologie - nicht jetzt, und auch nicht vor 50 Jahren. Übliche Lochstreifensysteme sind daher 7 Bit, und das 8. Bit ist ein Paritätsbit, damit du wenigstens weißt, wenn dein Lesegerät falsch gelesen hat.

Es ist für mich praktikabler und übersichtlicher, auch wenn es größer ist. Oder muss der gesamte Syntax eine feste größe haben?
Das ist das, was ich oben meinte: Du kannst einen Befehlssatz bauen, wo jeder Befehl die gleiche Anzahl Bits hat (und das muss auch nicht ein Lochstreifenelement sein, das können auch immer zwei sein). Oder du baust einen Befehlssatz, wo verschiedene Befehle unterschiedlich viele Bits haben. Der Vorteil ist, dass du deinen Befehlssatz komplexer und damit ausdrucksstärker machen kannst. Der Nachteil ist, dass deine Hardware wesentlich komplizierter wird. Einen Teil könntest du ausgleichen, indem du geschickt codierst.

Beispiel: Der Lochstreifen besteht aus 7-Bit-Elementen (plus Parität). Du hast also Werte zwischen 0x00 (_000 0000) und 0x7F (_111 1111); der Unterstrich ist das Paritätsbit. Ein Befehl besteht aus 1-3 Elementen, wobei das erste Element immer das erste Bit gleich 1 setzt, also 0x40 (_100 0000) bis 0x7F (_111 1111). Alle folgenden Elemente haben das erste Bit immer gleich 0, also 0x00 (_000 0000) bis 0x3F (_011 1111). Um die Daten einzulesen, nimmst du ein Schieberegister mit 24 Bit. Immer, wenn dein Lochstreifenleser ein Byte liest, schiebst du das Byte hinten in das Schieberegister rein. Die Paritätsbits ignorierst du, also gehen aus dem Schieberegister 21 Datenleitungen raus. Wenn das erste Bit im Register eine "1" ist, dann kann deine Schaltung die folgenden 20 Bits nehmen und den Befehl ausführen. (Wenn nicht alle Bits genutzt werden, ist das auch egal. Außerdem kannst du Nullbytes als Füllbytes zwischen Befehle stopfen.)

Es gibt sicherlich auch andere Ansätze. Der Gedanke ist, dass du einen Datenstrom aus Bytes vom Lochstreifen vorne in die Schaltung reinkippst, und die Schaltung selbstständig rausfindet, wo ein Befehl anfängt (Stichwort "selbstsynchronisierender Code"). Wenn du das nicht machst und du schiebst den Lochstreifen schlecht rein (so dass er in der Mitte eines Befehls anfängt), dann macht dein Prozessor wahrscheinlich Unsinn und fängt sich auch nicht wieder.

Zur zweiten Frage habe ich eine Gegenfrage: Meinst du mit der Codierung von Zahlen deren System (Hex/Dez/Bin, würde ich alle einbauen per 0x..., 0b... und 0d..., wobei Dezimal ohne Präfix Standard wäre), oder die Bit der Zahlen? Auch hier wäre die Antwort 8bit, wegen genannten Gründen. Oder meinst du etwas anderes?
Ich meine etwas anderes. Schau dir mal an, wie ein "MOV R4, 0x12" auf einem ARM-Prozessor codiert wird, und "MOV A, 0x12" auf einem i8080: Bei ARM ist jeder Befehl 32 Bit breit, und die "0x12" sind einfach 8 Bit davon (die restlichen Bits sind z.B. das Zielregister, und der Opcode "MOV IMMEDIATE INTO REGISTER"). Das heißt aber auch, dass du 0x12345678 nicht mit einem Befehl in ein Register schreiben kannst, weil das 32 Bit bräuchte und die hast du nicht! Auf dem i8080 ist das kein Problem: Das erste Byte ist ein "MOV IMMEDIATE INTO A", das zweite Byte dann 0x12.

Und zu den Adressierungsmodi muss ich mir noch echt Gedanken machen... Vielleicht entscheide ich das spontan nach Gefühl. Oder sollte ich mich an ein einziges Prinzip halten?
Gefühl ist kacke. Du baust auch kein Haus "nach Gefühl". Solltest du zumindest nicht. :-)

Addressierungsmodi habe ich jetzt so verstanden, dass sie Aussagen, wie die Operanden interpretiert werden: Also ob eine gegebene Zahl bei z.B. "mov ax, 1", dass entweder 1 in ax geladen wird, oder ob die 1 dort für eine Speicheradresse steht.
Jaein. Was du meinst, sind üblicherweise verschiedene Opcodes ("MOV IMMEDIATE INTO REGISTER" bzw. "MOV INDIRECT INTO REGISTER"). Was ich meine ist, welche dieser Opcodes existieren. :-)

Und auch hier würde ich beides kombinieren. In NASM wird das doch mit [] geregelt. Ich bin auf die Darstellung mit @ gestoßen. Dann würde ich alle Immediates (<- so heißen doch konstante Zahlen im Programmcode?), die ohne @ stehen direkt als deren Wert nehmen. Wenn dann ein @ dazukommt, ist eine Speicheradresse gemeint. Also "mov ax, 1" setzt ax auf 1, während "mov ax, @1", ax auf die ersten 16bit im Speicher setzt (weil ax ja 16bit groß ist, wäre das gut, oder?). Apropos würde ich dazu gerne noch was sagen: Ich würde den Speicher auch in 8bit-Blöcke aufteilen. Man soll also nicht auf kleinere Einheiten zugreifen können. Dadurch würden also alle Speicheradressen als Vielfache von 8 umgerechnet werden. Schreibt man also "mov al, @2", würden der 2. 8bit-Block im Speicher in al (nicht 16bit sondern 8bit, weil al ja nur 8bit groß ist. Oder?) geladen werden. Wie findest du das?
Du beschreibst die Syntax der Assemblersprache im Editor auf dem Entwicklungssytem. Das hat nur wenig damit zu tun, wie die CPU die einzelnen Bits interpretiert, die da rauskommen. Will sagen: Nein, die CPU sieht weder "[]" noch "@" noch "0x", sondern die sieht nur Bits.

Aber wieder habe ich etwas auf der Recherche (zu Adressierungsmodi) dazugelernt: Man kann ja den Programmcode kürzen, indem man häufige Instruktionen in einem Programmcode zusammenführt.
Ja. Das Ergebnis sind dann zusätzliche Befehle, und das kann man beliebig weit treiben. Aktuelle Architekturen haben gern mal >5000 verschiedene Befehle, der MIPS I-Befehlssatz hatte 31. Faustregel: Je mehr Befehle du im Befehlssatz hast, desto weniger Befehle brauchst du in einem Programm für deinen Algorithmus (= Programme sind kleiner). Aber je mehr Befehle du im Befehlssatz hast, desto komplexer ist dein Prozessor (= Programme sind langsamer). Was optimal ist, hängt von der Anwendung ab.

In Kombination von deiner Erklärung zu (A1) könnte man aus Instruktionen wie "mov ax, bx" für jede Kombination von Registern einzelne Opcodes anfertigen, oder?
Richtig. Aber das nennt man nicht "dann mache ich viele neue Opcodes", sondern das nennt man "Operanden".

Beispiel: Nehmen wir einen Prozessor mit 4 Registern (A, B, C und D), und 16 Befehlen insgesamt. Die ersten 4 Bits sind der Opcode, die nächsten 2 Bits sind der Zieloperand, die nächsten 2 Bits sind der Quelloperand. Macht 8 Bits (=1 Byte) pro Befehl. Nehmen wir als XOR den Opcode 4, dann ergeben sich die folgenden Kombinationen:
0x40 = 0100 0000 = XOR A,A
0x41 = 0100 0001 = XOR A,B
0x42 = 0100 0010 = XOR A,C
0x43 = 0100 0011 = XOR A,D
0x44 = 0100 0100 = XOR B,A
...
0x47 = 0100 0111 = XOR B,D
0x48 = 0100 1000 = XOR C,A
...
0x4F = 0100 1111 = XOR D,D

Für ADD mit Opcode 8 sieht das dann so aus:
0x80 = 1000 0000 = ADD A,A
0x81 = 1000 0001 = ADD A,B
...
0x8F = 1000 1111 = ADD D,D

Ich hoffe, du siehst da ein System drin. :-)

Wie du siehst, hast du es mit einem Dulli auf diesem Fachgebiet zu tun :-D . Ich hoffe, dass ich dich mit meinen Ideen und Fragen nicht vergraule.
Nönö. Ich bin nur nicht jeden Tag im Forum unterwegs, daher dauert das mit den Antworten auch mal länger.

Der Punkt ist, was du eigentlich willst, ist eine Prozessorarchitektur entwerfen. Das ganze Lochstreifengerödel ist eigentlich nebensächlich.

Im Übrigen hast du noch ein Problem komplett vernachlässigt: Wenn du direkt Code vom Lochstreifen ausführen willst - wie willst du mit Sprüngen (und damit auch Schleifen) umgehen? Da folgt auf einen Befehl ja nicht "der nächste" Befehl, sondern irgendein anderer. Alle Computer, von denen ich weiß und die mit Lochstreifen umgehen können, lesen den Lochstreifen immer komplett in den Arbeitsspeicher ein und führen den dann von dort aus. Und wenn der Code erstmal im Speicher ist, ist es ja auch egal, ob er nun via Lochstreifen, Festplatte oder Funk da reingekommen ist. Du kannst auf Sprünge und sowas auch verzichten, aber dann ist das Teil nicht mehr turingmächtig, d.h. du kannst keine beliebigen Programme mehr dafür bauen.

Und dann kommt noch eine Frage: Was willst du eigentlich für Speicher nehmen? Oder soll dein System sowohl Lochstreifenstanzer und Lochstreifenleser haben, und du mischst Code und Daten auf dem Lochstreifen? Das verbraucht eine Menge Papier (und produziert Unmengen an Konfetti - und Lochstreifenkonfetti ist eklig aufzuräumen).
12
Lowlevel-Coding / Antw:Lochstreifen und Opcodes
« am: 04. June 2020, 00:35 »
Hallo,

was du eigentlich beschreibst, sind nicht "Opcodes", sondern der grundlegende Befehlssatz eines Prozessors. Das ist der Teil, der unter "Prozessorarchitektur" fällt (die genaue Implementation in Hardware ist dann die "Mikroarchitektur"). Zu einem Befehl gehört mehr als nur der Opcode selbst. Es gibt verschiedene Ansätze, wie man sowas bauen kann, und wenn man eine Architektur verstanden hat, dann hat man effektiv alle Architekturen verstanden, die den gleichen Ansatz benutzen. Daher ist es sinnvoll, erstmal grob zu klassifizieren, was man vor sich hat.

Eine übliche Klassifikation unterscheidet zwischen RISC und CISC. Was das im Detail bedeutet, darüber kann man lange streiten, aber für die Diskussion hier vereinfache ich das mal so: In einem RISC-Prozessor hat jeder Befehl die gleiche Anzahl an Bits, in einem CISC-Prozessor nicht. Die meisten "modernen" Architekturen (ARM, AVR, MIPS, RISC-V, ...) zählen zu den RISC-Architekturen, x86 und PowerPC sind CISC-Architekturen.

Eine weitere Klassifikation unterscheidet zwischen Stack- und Registermaschinen. Was du beschreibst, ist eindeutig eine Stackmaschine, da Operanden auf den Stack gelegt werden und von dort verarbeitet werden. Stackmaschinen kann man sehr einfach bauen und beschreiben, aber da der Stack im Speicher liegt und alle Operanden durch den Stack müssen, begrenzt der Speicher die erreichbare Geschwindigkeit solcher Systeme. Die bekannteste Stackmaschine dürfte der Java-Bytecode sein, und vielleicht noch ZPU (dafür gibt es immerhin einen GCC-Port), aber Registermaschinen haben gewonnen.

Das alles vorweg.

Assembler als Programmiersprache ist normalerweise eine direkte Abbildung der Fähigkeiten einer CPU. Da es so kaum Architekturen gibt, die dein "mov 2,4" direkt abbilden können, ist das fast nie ein gültiger Befehl. (Das Stichwort dafür sind "Adressierungsmodi", und weil x86 davon besonders viele hat, ist es eher die Ausnahme als die Regel.)

Was du als Pseudoassembler bezeichnest, geht eher in Richtung einer Hochsprache, weil du den Code noch transformierst, also grundlegend veränderst ("mov 2, 4" ist etwas anderes als "push 4; push 2; mov"). Das machen einfache Assembler nicht (Makroassembler lasse ich hier mal außen vor).

Einfache Assembler bestehen z.B. aus einer Liste von Mustern, und die Eingangsdaten werden damit abgeglichen. Für einen Befehl (z.B. "mov ax, bx") wird dann ein passendes Muster gefunden (z.B. "mov <reg16>,<reg16>"), dann werden die Parameter in das Muster eingesetzt ("mov <reg16>,<reg16>" ist "1000100111SSSDDD", S=Quellregister, D=Zielregister, also generiert "mov ax,bx" ein "0x89 0xD8"). Wenn es kein passendes Muster gibt, dann war der Befehl ungültig.

Auch hier: x86 ist der Außenseiter, weil es mehrere Möglichkeiten gibt, Befehle zu codieren (dann muss sich der Assembler für eine Variante entscheiden), die Befehle sind unterschiedlich lang, und die Anzahl der Muster ist durch die komplexe Befehlsstruktur (Opcode, Modbyte, evtl. weitere Dinge - ein einzelner 8086-Befehl hat zwischen 1 und 6 Bytes!) enorm groß.

Ich würde dir einfach mal empfehlen, eine einfache Rechnerarchitektur im Detail anzuschauen. Und damit meine ich nicht x86 (ich bastle an einem 8086-Emulator und die Codierung ist einfach nur eklig). Recht einfach (sprich: primitiv) ist der 8080, die gesamte Opcode-Tabelle dafür gibt es hier. Auch recht verständlich fand ich RISC-V (Spezifikation gibt es hier), wenn man sich auf RV32I/RV64I beschränkt, und wenn du dich mit echter, günstiger, verständlicher Hardware prügeln willst, dann kannst du dich mit dem AVR-Befehlssatz befassen (den gibt es hier). Oder natürlich die passenden Zusammenfassungen, Blogs, Videos und was es dazu so alles im Internet gibt.

Achte dabei mal auf folgende Fragen:
- Wie werden die Befehle codiert? Wie viele Bytes hat ein Befehl?
- Wie werden "immediates" (im Befehl hingeschriebene Zahlen, z.B. "mov r4, 4") codiert?
- Welche Adressierungsmodi gibt es, und wie sind sie beschränkt?

Ich glaube, dann verstehst du etwas besser, wie so ein Befehl aufgebaut ist und was davon der Opcode ist (und was du sonst noch brauchst). :-) Und dann können wir weiterdiskutieren.

Gruß,
Svenska
13
Lowlevel-Coding / Antw:3te wurzel berechnen
« am: 03. December 2019, 20:41 »
Hallo,

weder die 8087-FPU noch die C-Programmiersprache kennen einen Kubikwurzel-Befehl. Um die Kubikwurzel zu berechnen, musst du also die Funktion irgendwie nachbauen. Dazu brauchst du, wenn du die von mir genannte Identität benutzen möchtest, eine e-Funktion und einen natürlichen Logarithmus. Beide Funktionen sind im x87 nicht direkt vorhanden, lassen sich aber mit den gegebenen Methoden nachbauen.

Um rauszukriegen, wie ein Compiler das macht, kannst du ihn das einfach machen lassen:
$ cat test.c
#include <stdio.h>
#include <math.h>

double kw(double x)
{
        return exp(log(x)/3.0);
}

int main()
{
        printf("Kubikwurzel: %.2f\n", kw(8));
}

Dann einfach durch den Compiler jagen und disassemblieren:
$ i686-w64-mingw32-gcc -O3 -march=i486 -o test test.c
$ i686-w64-mingw32-objdump -d test.exe
[...]
00401510 <_kw>:
  401510:       83 ec 1c                sub    $0x1c,%esp
  401513:       dd 44 24 20             fldl   0x20(%esp)
  401517:       dd 1c 24                fstpl  (%esp)
  40151a:       e8 81 11 00 00          call   4026a0 <_log>
  40151f:       d8 35 14 40 40 00       fdivs  0x404014
  401525:       dd 5c 24 20             fstpl  0x20(%esp)
  401529:       83 c4 1c                add    $0x1c,%esp
  40152c:       e9 9f 0f 00 00          jmp    4024d0 <_exp>
  401531:       90                      nop
[...]
004026a0 <_log>:
  4026a0:       83 ec 4c                sub    $0x4c,%esp
  4026a3:       dd 44 24 50             fldl   0x50(%esp)
  4026a7:       d9 e5                   fxam   
  4026a9:       9b df e0                fstsw  %ax
  4026ac:       66 25 00 45             and    $0x4500,%ax
  4026b0:       66 3d 00 40             cmp    $0x4000,%ax
  4026b4:       0f 84 a6 00 00 00       je     402760 <_log+0xc0>
  4026ba:       89 c2                   mov    %eax,%edx
  4026bc:       d9 e5                   fxam   
  4026be:       9b df e0                fstsw  %ax
  4026c1:       f6 c4 02                test   $0x2,%ah
  4026c4:       75 2a                   jne    4026f0 <_log+0x50>
  4026c6:       66 81 fa 00 05          cmp    $0x500,%dx
  4026cb:       74 73                   je     402740 <_log+0xa0>
  4026cd:       66 81 fa 00 01          cmp    $0x100,%dx
  4026d2:       74 7c                   je     402750 <_log+0xb0>
  4026d4:       db 3c 24                fstpt  (%esp)
  4026d7:       e8 e4 00 00 00          call   4027c0 <___logl_internal>
  4026dc:       dd 5c 24 38             fstpl  0x38(%esp)
  4026e0:       dd 44 24 38             fldl   0x38(%esp)
  4026e4:       83 c4 4c                add    $0x4c,%esp
  4026e7:       c3                      ret   
  4026e8:       8d b4 26 00 00 00 00    lea    0x0(%esi,%eiz,1),%esi
  4026ef:       90                      nop
  4026f0:       dd 5c 24 30             fstpl  0x30(%esp)
  4026f4:       e8 6b 01 00 00          call   402864 <__errno>
  4026f9:       d9 05 90 42 40 00       flds   0x404290
  4026ff:       c7 00 21 00 00 00       movl   $0x21,(%eax)
  402705:       c7 44 24 04 88 42 40    movl   $0x404288,0x4(%esp)
  40270c:       00
  40270d:       dd 54 24 18             fstl   0x18(%esp)
  402711:       c7 04 24 01 00 00 00    movl   $0x1,(%esp)
  402718:       d9 5c 24 2c             fstps  0x2c(%esp)
  40271c:       d9 ee                   fldz   
  40271e:       dd 5c 24 10             fstpl  0x10(%esp)
  402722:       dd 44 24 30             fldl   0x30(%esp)
  402726:       dd 5c 24 08             fstpl  0x8(%esp)
  40272a:       e8 11 f1 ff ff          call   401840 <___mingw_raise_matherr>
  40272f:       d9 44 24 2c             flds   0x2c(%esp)
  402733:       83 c4 4c                add    $0x4c,%esp
  402736:       c3                      ret   
  402737:       8d b4 26 00 00 00 00    lea    0x0(%esi,%eiz,1),%esi
  40273e:       66 90                   xchg   %ax,%ax
  402740:       dd d8                   fstp   %st(0)
  402742:       d9 05 94 42 40 00       flds   0x404294
  402748:       83 c4 4c                add    $0x4c,%esp
  40274b:       c3                      ret   
  40274c:       8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
  402750:       dd d8                   fstp   %st(0)
  402752:       d9 05 90 42 40 00       flds   0x404290
  402758:       83 c4 4c                add    $0x4c,%esp
  40275b:       c3                      ret   
  40275c:       8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
  402760:       dd 5c 24 30             fstpl  0x30(%esp)
  402764:       e8 fb 00 00 00          call   402864 <__errno>
  402769:       d9 05 8c 42 40 00       flds   0x40428c
  40276f:       c7 00 22 00 00 00       movl   $0x22,(%eax)
  402775:       c7 44 24 04 88 42 40    movl   $0x404288,0x4(%esp)
  40277c:       00
  40277d:       dd 54 24 18             fstl   0x18(%esp)
  402781:       c7 04 24 03 00 00 00    movl   $0x3,(%esp)
  402788:       d9 5c 24 2c             fstps  0x2c(%esp)
  40278c:       d9 ee                   fldz   
  40278e:       dd 5c 24 10             fstpl  0x10(%esp)
  402792:       dd 44 24 30             fldl   0x30(%esp)
  402796:       dd 5c 24 08             fstpl  0x8(%esp)
  40279a:       e8 a1 f0 ff ff          call   401840 <___mingw_raise_matherr>
  40279f:       d9 44 24 2c             flds   0x2c(%esp)
  4027a3:       83 c4 4c                add    $0x4c,%esp
  4027a6:       c3                      ret   
  4027a7:       90                      nop
[...]

Ganz allgemein gilt: Wenn eine Funktion brauchst, die es in Hardware oder in der Programmiersprache nicht gibt, dann musst du dir einen Algorithmus (d.h. einen Satz an nacheinander auszuführenden Regeln) ausdenken, der diese Funktion mit den gegebenen Möglichkeiten nachbaut. Im Falle deiner Kubikwurzel sind das also Exponential- und Logarithmusfunktion. Das habe ich so hingeschrieben, und das hat der Compiler so gemacht. Dazu hat er entsprechende Bibliotheksfunktionen (die in libc/libm implementiert sind) benutzt.

Wie du an meinem Beispiel siehst, ist das nicht unbedingt trivial. Unter bestimmten Randbedingungen lässt sich das sicherlich noch deutlich weiter optimieren. Die FPU kann einen Logarithmus berechnen (FY2LX), die E-Funktion muss man irgendwie nachbauen (Stackoverflow findet eine Sequenz aus 9 Befehlen). Da ich mich mit der FPU nicht besonders auskenne, kann ich nur darauf verweisen. :-)

Gag am Rande: Wenn ich die Funktion "static" mache, so dass die Funktion nur innerhalb der Datei existiert, dann optimiert der Compiler die gesamte Berechnung raus, weil er kw(8) in dem Beispiel schon vorher ausrechnen kann - das Ergebnis ist konstant.

Gruß,
Svenska
14
Lowlevel-Coding / Antw:3te wurzel berechnen
« am: 02. December 2019, 21:53 »
Es gilt:
"nte Wurzel aus x" = "x hoch (1/n)" = "e hoch ( ln(x) / n )".

Du kannst also jede Wurzel (wie auch jede Potenz) auf die Exponential- und Logarithmusfunktion zurückführen.

Quelle: Wikipedia. :D

Nachtrag: Du kannst natürlich auch Näherungsverfahren anwenden.

Vermutlich ist es aber real sinnvoller, diese dann für Exponential- und Logarithmusfunktion zu benutzen, was im Übrigen vor allem sinnvoll ist, wenn die Hardware selbst keine FPU enthält oder die Genauigkeit davon nicht ausreicht (die meisten Mikrocontroller haben keine FPU, und die meisten mit FPU können nur float aber kein double) - oder aber die FPU nicht benutzt werden kann (z.B. im Kernel).
15
Offtopic / Antw:Suche Leute für Open Source OS
« am: 16. August 2019, 17:43 »
Der DiscTreiber ist im Mosa schon integriert,
Ich hab spontan keinen gefunden. Was kann er denn?

was noch gemacht werden muss ist eine Process verwaltung. Und ein Treiber Manager.
Das klingt erstmal wichtiger als ein GUI Framework... genauso wie der Fakt, dass das Gesamtsystem auf Garbage Collection aufsetzt, aber noch kein funktionierender Garbage Collector vorhanden ist.

Der Benutzer soll die volle Kontrolle bekommen was updates angeht.
Das ist eine Frage von "policy" und hat nichts mit dem Kernel zu tun. Unter Linux hat der Benutzer ebenfalls die volle Kontrolle über Updates, oder eben auch nicht, wenn der Administrator das festlegt.

Da die Basic Dinge von Mosa schon implementiert sind konzentriere ich mich auf das Gui framework.
Die Entwicklung scheint ziemlich durcheinander zu sein. Von x86 bis ESP32 sehe ich da vieles, aber dann eine große Kiste "switch" für Tastaturlayouts... wie stellst du dir denn die Texteingabe im GUI vor, wenn der Kernel noch nichtmal ordentliche Tastaturunterstützung hat?

Nee, da fehlen noch arg viele Grundlagen. Aber interessant zu wissen, dass man das in C# machen kann.
16
Offtopic / Antw:Suche Leute für Open Source OS
« am: 14. August 2019, 18:24 »
ich arbeite an einem OSS OS und suche Leute die mithelfen wollen.
Da musst du schon ein bisschen besser Werbung machen.

Treiber sollen von der Disk geladen werden
Auch der Disk-Treiber?

Update Mechanismus wie linux  ohne neustarten des systems
Linux kann nicht ohne Neustart aktualisiert werden. Man muss in jedem Fall neustarten, wenn man den Kernel oder die libc ersetzt.

Aktuell baue ich ein GrafikFramework auf um die GUI zu zeichnen.
Das heißt, der Kernel ist schon größtenteils fertig? Oder machst du erst die GUI, dann das OS?

Gruß,
Svenska
17
Lowlevel-Coding / Antw:Sleep Funktion implementieren
« am: 27. July 2019, 13:26 »
Hallo,

du kannst in einer Zeitschleife einfach Taktzyklen verbraten, hier ein Beispiel in C:
volatile int i;
for(i = 0; i < 1000000; i++) {
  /* nichts */
}
Wie lange die Schleife dauert, hängt von deiner CPU ab. Dieses Prinzip nutzt man normalerweise nur für Wartezeiten im Mikrosekundenbereich.

Für längere Zeiten nutzt man einen Timer, also zusätzliche Hardware. Jeder PC besitzt dafür einen PIT-Baustein (Details im Wiki). Der PIT läuft mit einer festen Frequenz (1,12 MHz) und kann regelmäßig einen Interrupt auslösen - wenn du die Interrupts zählst, weißt du, wie viele Zeiteinheiten vergangen sind. Oder du liest den Zählerstand direkt aus.

Ein moderner Ersatz für den PIT ist der HPET (Detail bei osdev.org). Außerdem gibt es noch die Echtzeituhr (Details im Wiki), mit der du die "wahre" Uhrzeit auslesen kannst.

In einem ordentlichen Betriebssystem wartet man nicht einfach längere Zeiten ab, sondern legt sich für eine gewisse Zeit schlafen (d.h. man lässt sich aufwecken, wenn die gewünschte Wartezeit vorbei ist).

Gruß,
Svenska
18
Ich behaupte mal, dass das nicht zuverlässig möglich ist. Zumindest kann ich mir vorstellen, dass verschiedene Prozessoren sich da unterschiedlich verhalten. Der sinnvollste Weg dürfte daher sein, erst die CPU zu identifizieren (cpuid) und daraus abzuleiten, welche für dich relevanten MSRs existieren. Meist geht es ja darum, die MSRs passend einzustellen - die bloße Existenz ist eher wenig nützlich.

Gilt natürlich nicht, wenn du einfach mal rumprobieren und dem Prozessor seine Geheimnisse entlocken willst. :-)
19
Lowlevel-Coding / Antw:Eigener Bootloader?!
« am: 09. June 2019, 22:10 »
Der Code sieht falsch aus, zumindest wenn er einen FAT-Bootsektor beschreiben soll.

Ungetestet und aus dem Handgelenk geschüttelt:
org 7C00h
section .text

jmp near start ; springe über BPB (bios parameter block)

.db "xxxxxxxx" ; oem name
; die restlichen fat-metadaten (BPB)

start:
    jmp far 0000h:start2 ; setze CS:IP

start2:
    mov ax, cs ; setze CS=DS=ES
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0FFFEh ; setze stack an ca. 64 KB

    mov [bootdrive], dl ; speichere bootlaufwerk

    ; hier geht der lade-code los
    ; also kernel suchen, laden und ausführen


section .data
  bootdrive db 1

times 510-($-$$) nop
dw 0xAA55
20
Lowlevel-Coding / Antw:Eigener Bootloader?!
« am: 09. June 2019, 14:15 »
Hallo,

du solltest mit einem FAR JMP anfangen, denn der setzt CS:IP auf bekannte Werte (z.B. CS=0x07C0, IP=0x0000), und dann auch DS=ES auf die gleichen Werte wie CS setzen. Das "dw 0xAA55" muss an das Ende des Bootsektors (also die Bytes 510/511), nicht irgendwo in die Mitte. Und du solltest DL nicht überschreiben, sondern irgendwo sichern (das BIOS füllt da den richtigen Wert rein).

Was den FAT-Sektor angeht, kann ich dir spontan nicht helfen. Damit habe ich mich nie groß befasst. :-)
Am einfachsten, du formatierst eine normale Diskette und popelst die Metadaten auseinander.

Gruß,
Svenska
Seiten: [1] 2 3 ... 90

Einloggen