Speicherverwaltung und Endianness

In diesem Artikel werden wir uns ansehen, wie genau Speicherverwaltung in einem Computer und der DCPU-16 funktioniert:
Es gibt in einem Computer mehrere Speicher.
Der (i.d.R.) größte Speicher ist physikalischer Datenspeicher, die Festplatte(n). Hier werden Daten permanent abgelegt. Filme, Musik, Textdateien…

Im Hauptspeicher eines Rechners werden Daten laufender Prozesse abgelegt. Der gesamte Programmcode ist im Hauptspeicher untergebracht, so auch Variablen. Außerdem wird der Hauptspeicher von Daten genutzt, die von der Anwendung erzeugt werden (wenn z.B. ein Zufallsstring generiert wird).

Als letztes, auf der niedrigsten Ebene, gibt es Speicher im Prozessor. Die Register speichern für einzelne Arbeitsschritte benötigte Daten, etwa die zwei Summanden einer Addition, es werden aber auch wichtige Verwaltungsdaten dort gespeichert (EIP, ESP…)
Wir interessieren uns vorerst nur für den Hauptspeicher.

Samsung K4S641632H-UC60

Samsung K4S641632H-UC60: 64MB SDRAM

In unseren PCs wird RAM als Hauptspeicher verwendet. RAM bedeutet Random Access Memory.  Dieser heißt nicht so, weil Zugriffe einfach zufällig sind, das “Random” bedeutet, dass jede Speicherzelle einzeln angesprochen werden kann. Man muss nicht Block 0 bis 9999 Lesen, man kann durch die Speicherverwaltung einfach bei 5487 beginnen.
Betrachten wir einen 256MB RAM:
256MegaBytes sind 268.435.456 Bytes. Im 8-Bit Speicher bilden 8 Bit einen Block. Dort bräuchte nun Jedes Byte(=8Bit) eine Adresse, um direkt angesprochen werden zu können. Wir brauchen also die Adressen 0 bis 268.435.455, diese werden aus verschiedenen Gründen, vor allem wegen der Lesbarkeit, in hexadezimaler Schreibweise ausgedrückt. Wir haben also die Adressen 0x0000000 bis 0xFFFFFFF.
Wir können mit einem Programm nun einen Wert in 0x1234567 schreiben, und ihn jederzeit dort wieder auslesen.
Die DCPU-16 in 0x10c Adressiert immer 16Bit Speicher mit einer Adresse. So können 16Bit, also 2 Byte an Daten, in einem Adressblock gespeichert werden. Mit 16 Bits kann man Werte von 0x0000 bis 0xFFFF (65535d) speichern.

0x0000 Adressiert die Bits 00 bis 15,
0x0001 Adressiert die Bits 16 bis 31,
0x0002 Adressiert die Bits 32 bis 47
[…]
0x000F Adressiert die Bits 240 bis 255[…]
Weisen wir der Adresse 0x0000 den Wert 16 zu, sieht der Speicher wie folgt aus:

Adresse (hex) 0x0000
Bit Nummer (dec) 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
Wert (bin) 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0

Ihr seht, die Mischung von hex, dec und bin mag das erste Verständnis vielleicht erleichtern, jedoch ist es auf Dauer zu unübersichtlich. Daher werden Adressen und Werte immer hexadezimal ausgedrückt. Die Bit-Nummern in diesem Beispiel dienen nur der Orientierung, damit jeder das 16-Bit Modell begreift.
In der Praxis wird die Bit-Adresse nie genutzt. Sie war nur nötig, da der Wert binär ausgedrückt ist.

Normalerweise sieht die Speicherübersicht so aus:
0x0000: 0x0010
Da nur hexadezimale Werte genutzt werden, wird der Präfix 0x ausgelassen. Ein Memory Dump könnte so aussehen:

0000: 0010 00F1 0F10 0000 0005 0000 FFFF B4C3
0008: 1234 5678 9ABC DEF0 0000 0000 CDCD BBBB
0010: CAFF EE00 AFFE B1AA 1010 B766 0012 0033

Sorry für die lange Tabelle, aber so ist es am übersichtlichsten:

Adresse Wert Hex Wert Binär
0x0000 0x0010 0000 0000 0001 0000
0x0001 0x00F1 0000 0000 1111 0001
0x0002 0x0F10 0000 1111 0001 0000
0x0003 0x0000 0000 0000 0000 0000
0x0004 0x0005 0000 0000 0000 0101
0x0005 0x0000 0000 0000 0000 0000
0x0006 0xFFFF 1111 1111 1111 1111
0x0007 0xB4C3 1011 0100 1100 0011
0x0008 0x1234 0001 0010 0011 0100
0x0009 0x5678 0101 0110 0111 1000
0x000A 0x9ABC 1001 1010 1011 1100
0x000B 0xDEF0 1101 1110 1111 0000
0x000C 0x0000 0000 0000 0000 0000
0x000D 0x0000 0000 0000 0000 0000
0x000E 0xCDCD 1100 1101 1100 1101
0x000F 0xBBBB 1011 1011 1011 1011
0x0010 0xCAFF 1100 1010 1111 1111
0x0011 0xEE00 1110 1110 0000 0000
0x0012 0xAFFE 1010 1111 1111 1110
0x0013 0xB1AA 1011 0001 1010 1010
0x0014 0x1010 0001 0000 0001 0000
0x0015 0xB766 1011 0111 0110 0110
0x0016 0x0012 0000 0000 0001 0010
0x0017 0x0033 0000 0000 0011 0011

Man sieht hoffentlich: mit hex-Zahlen ist es einfacher zu rechnen. Eine hex-Ziffer sind immer 4 Bits.
Versucht einmal 3856 in binär umzuwandeln… Da Finde ich 0x0F10 schon einfacher.
Schließlich speichern wir nicht einfach immer nur Integers.

Ich hoffe, das Prinzip der Speicherverwaltung und -Adressierung wurde nun verstanden, denn jetzt will ich noch eine kleine Besonderheit erklären:

Endianness.

Die Endianness besagt, in welcher Reihenfolge Daten im Speicher abgelegt werden. Genauer gesagt:
Wie die einzelnen Speicherblöcke angeordnet werden.
Als Beispiel nehmen wir einen 32bit Integer: 4.192.002.289d, 0xF9DCE0F1,
1111 1001 1101 1100 1110 0000 1111 0001b
Wir wissen nun, im 16-Bit Speichersystem werden 16 Bits pro Speicherblock gespeichert. Wir brauchen für einen Integer mit 32 Bit also 2 Speicherblöcke. Logischerweise würden wir die binäre Zahl nun in der Mitte teilen und 2 Speicherblöcke belegen:
0x0000: 1111 1001 1101 1100 (=0xF9DC)
0x0001: 1110 0000 1111 0001 (=0xE0F1)
Dieses Speicherverhalten heißt Big Endian. Der Block, der die höherwertigen Stellenwerte enthält, wird zuerst gespeichert.
Aber es gibt auch die Little Endian Reihenfolge: Hier wird das Bit mit dem niedrigsten Stellenwert (der niedrigsten Signifikanz) zuerst gespeichert:
0x0000: 1110 0000 1111 0001 (=0xE0F1)
0x0001: 1111 1001 1101 1100 (=0xF9DC)
Warum? Aus logischer Sicht ergibt das Big Endian Format mehr Sinn. Die Zahlen sind wie unsere Normalen Zahlen in richtiger Reihenfolge geschrieben, somit leichter lesbar für uns. Jedoch ist es in manchen Situationen effizienter, die Zahl umgekehrt zu Speichern und zu bearbeiten.
Die DCPU-16 soll eben dieses Little Endian Format nutzen, wodurch sich auch der Name des Spiels ergibt:

In 1988, a brand new deep sleep cell was released, compatible with all popular 16 bit computers. Unfortunately, it used big endian, whereas the DCPU-16 specifications called for little endian. This led to a severe bug in the included drivers, causing a requested sleep of 0x0000 0000 0000 0001 years to last for 0x0001 0000 0000 0000 years.

Diese “Deep Sleep Cell” nutzt also Big Endian Speicherung. Sie wurde wohl mit einem 64-Bit Integer gespeist, 0x0000 0000 0000 0001 – binär also:

0000 0000 0000 0000    0000 0000 0000 0000    0000 0000 0000 0000    0000 0000 0000 0001

Diese Zahl wird auf der Little-Endian DCPU-16 so gespeichert und verarbeitet:

0000 0000 0000 0001    0000 0000 0000 0000    0000 0000 0000 0000    0000 0000 0000 0000

(Wir erinnern uns: Es werden immer 16 Bit in einem Adressblock gespeichert.)

Die Deep Sleep Cell interpretierte diese Zahl falsch als Big Endian. Und diese Zahl ist hexadezimal ausgedrückt:
0x0001 0000 0000 0000
Eine kleine Einführung in die Exponentialrechnung im Hexadezimalsystem:
0x10^0x01 = 16^1 = 16 = 0x10 =0x10^1
0x10^0x02 = 16^2 = 256 = 0x100 = 0x10^2
0x10^0x03 = 16^3 = 4096 = 0x1000
0x10^0x07 = 16^7 = 268435456 = 0x1000 0000
0x10^0x0c = 16^12 = 281474976710656 = 0x0001 0000 0000 0000

0x10^c
Auf so viele Jahre war die Deep Sleep Cell nun eingestellt. Plus 1988d Jahre, dort ist der „Unfall“ passiert, sind das 281 474 976 712 644 Jahre. Kaum verwunderlich, stimmt dies mit den Angaben auf der offiziellen Website überein.

Nun wissen wir nicht nur, wie und wo Daten gespeichert werden, sondern auch, warum 0x10c 0x10c heißt.

Ich denke, wir können nun mit etwas praktischem beginnen. Im nächsten Artikel werden wir etwas über Assembler Code lernen, und unser erstes Programm für die DCPU-16 schreiben.
Seid gespannt, Stay tuned, bis zum nächsten Mal.
MfG
Damon

Dieser Eintrag wurde veröffentlicht in 0x10c und getagged , , .
Bookmarken: Permanent-Link Schreibe einen Kommentar oder hinterlasse einen Trackback: Trackback-URL.
Achtung: Wordpress interpretiert bestimmte Zeichenfolgen als Markup und verändert diese. Nutzt für Programmcode lieber Gist oder PasteBin-Services und verlinkt die Code-Schnipsel.

Post a Comment

Sie müssen angemeldet sein, um kommentieren zu können.