An der Oberfläche moderner Computer und CPUs kratzen.
September 30, 2024 #An der CPU kratzen #CPU #Assembler #Warum und Wie von Computern #Niedrig-Ebene-Computing #Binär01010111 01101000 01111001 00100000 01110100 01101000 01101001 01110011 ?
Wir haben alle die Filme gesehen, in denen Leute vor schwarzen Bildschirmen mit Nullen und Einsen hacken, und plötzlich passiert Magie. Uns wurde bestätigt, dass irgendwo etwas mit 01 passiert, und wir haben wahrscheinlich alle mal ein Malprogramm, einen Dateieditor oder sogar ein Videospiel geöffnet und uns gefragt: WIE?!
Okay, ich werde es versuchen, kurz zu erklären. Aber in der langen Version werden wir ein ganzes Betriebssystem (OS) erstellen. Es wird kein sehr originelles sein, um ehrlich zu sein – meist zusammengeklaubte Stücke, die mit Superkleber verbunden werden –, aber wir werden alles durchgehen und erklären, warum und wie es zusammenpasst, und am Ende wirst du hoffentlich alles verstehen. Naja, so ungefähr – du wirst verstehen, warum es schwer ist, dass jemand wirklich alles verstehen kann.
Wir werden speziell über GNU/Linux sprechen, das auf UNIX basiert. Android basiert auf Linux (hat sein eigenes Userland Android/Linux, nehme ich an?). MacOS (und iOS) ist auf dasselbe System UNIX aufgebaut. Gleiches gilt für alle BSDs (Free, Open...), auf denen unter anderem die Sony PlayStation basiert. Windows ist etwas anders (auf eine schlechte Weise), aber zumindest in Bezug auf die Kernkonzepte nicht so unterschiedlich.
Grundsätzlich hat ein OS (Betriebssystem) zwei Teile: den Kernel (z.B. Linux) und das Userland (z.B. GNU oder ein anderes, wie es z.B. Android hat). Bei Linux sind das zwei verschiedene Projekte, die unabhängig voneinander entwickelt werden. Die meisten Linux-Distributionen bauen einen App-Store (einen Paketmanager) oben drauf und konfigurieren ihn mit einer grafischen Benutzeroberfläche (GUI) und Software vor, für Leute, die sich nicht mit Systemadministration herumschlagen wollen. Selbst Linus Torvalds, der Gründer von Linux, sagte einmal, dass er viel Systemadministration vermeidet. Wenn dir also mal schwindelig von all dem wird, keine Sorge – du gewöhnst dich daran!
Bei anderen Betriebssystemen ist alles in einem System vereint, oft von großen Teams entwickelt. Das ist einer der Gründe, warum manche Leute glauben, dass der Übergang von binär (01) zu einer voll funktionsfähigen grafischen Benutzeroberfläche klar und linear abläuft. Aber lass uns nun darauf eingehen, was hier wirklich passiert.
Der Kernel abstrahiert die Notwendigkeit, dass Benutzer direkt mit der Hardware interagieren. Er ist wie ein kleines Logiksystem, das Binärcodes (wie 01000010 01001001 01001110 01000001 01010010 01011001) in ein menschenlesbares Format übersetzt. Ein Kernel wird normalerweise in einer Sprache wie C programmiert, die entworfen wurde, um die Interaktionen mit der Hardware zu abstrahieren, obwohl sie immer noch erlaubt, sehr nah an der Hardware zu arbeiten.
Selbst niedrigere Programmiersprachen, wie Assembler, die sehr nahe mit der Hardware interagieren, abstrahieren die Einsen und Nullen in gewisser Weise. Wenn du also jemanden siehst, der direkt mit binären Codes arbeitet, versucht er wahrscheinlich, das System zu manipulieren, um unbefugten Zugriff zu erlangen.
Das Userland ist ein wenig benutzerfreundlicher als der Kernel. Zwar wird das Einrichten nicht gerade in einfachem Englisch gemacht, aber es ist mehr wie das Zusammenbauen von Legosteinen als das Lösen komplexer Gleichungen. Deshalb betreffen die meisten alltäglichen Aufgaben in Linux und anderen Unix-basierten Systemen die Arbeit im Userland, wo du Dinge konfigurierst und Software installierst, um eine vollständige, funktionsfähige Umgebung zu schaffen.
Wenn du dich also jemals gefragt hast, wie binäre Codes (wie 01000010 01001001 01001110 01000001 01010010 01011001) irgendwann zu einer klickbaren grafischen Benutzeroberfläche werden, ist das ein grober Überblick. Aber wenn du tiefer in die technischen Details eintauchen möchtest, fangen wir jetzt damit an. Am Ende haben wir ein Betriebssystem (naja, so ungefähr) zusammengebastelt, und auch wenn es manchmal langweilig erscheinen mag, wirst du zumindest verstehen, wie alles funktioniert!
Betrachte das eher wie eine TV-Serie mit mehreren Staffeln als einen Film. Ich werde versuchen, es so kurz und spannend wie möglich zu halten, also danke fürs Einschalten – und los geht's!
Warum Binär?
-
Einfachheit und Zuverlässigkeit in digitalen Systemen: Binär vereinfacht das Design von elektronischen Schaltungen. Digitale Systeme, einschließlich CPUs, verwenden Transistoren als Schalter. Diese Schalter können einen von zwei Zuständen haben: an oder aus, was den binären 1 und 0 entspricht. Diese binäre Darstellung ist die einfachste und zuverlässigste Möglichkeit, Informationen auf Hardware-Ebene zu verarbeiten.
- Beispiel: Ein Transistor in einer CPU kann zwischen einer hohen Spannung (1) und einer niedrigen Spannung (0) wechseln. Diese klare Unterscheidung stellt sicher, dass die CPU schnell und zuverlässig arbeiten kann, da jeder Transistor nur zwei Zustände erkennen muss.
-
Robust gegenüber Störungen: Binäre Systeme sind robust gegen elektrische Störungen, da sie nur zwischen zwei Zuständen unterscheiden müssen. Diese binäre Unterscheidung vereinfacht das Design und erhöht die Störfestigkeit digitaler Schaltungen.
- Beispiel: In einer lauten Umgebung bleibt ein binäres Signal mit einem hohen Schwellenwert (z. B. alles über 2,5V ist 1) und einem niedrigen Schwellenwert (z. B. alles unter 0,5V ist 0) klar und einfach zu interpretieren, was eine zuverlässige Betrieb auch bei geringfügigen Spannungsschwankungen gewährleistet.
-
Effiziente logische Operationen: Binärzahlen passen natürlich zur Booleschen Logik, die die Grundlage aller digitalen Berechnungen bildet. Logische Operationen wie UND, ODER und NICHT sind einfach und effizient in binären Systemen zu implementieren.
- Beispiel: Eine binäre UND-Operation ist einfach und schnell: 1 UND 1 = 1, während alle anderen Kombinationen 0 ergeben. Diese Einfachheit ermöglicht die Erstellung komplexer logischer Funktionen mit grundlegenden binären Operationen.
Rolle der Assemblersprache
Die Assemblersprache ist eine Low-Level-Programmiersprache, die eine menschenlesbare Möglichkeit bietet, mit den binären Anweisungen der CPU zu interagieren. Sie dient als kritische Abstraktionsschicht, die es erleichtert, die CPU direkt zu verwalten und zu manipulieren.
- Menschlich lesbare Mnemonik:
Anstatt mit Rohbinär- oder Hexadezimalcodes umzugehen, verwendet die Assemblersprache mnemonische Codes, die einfacher zu verstehen und zu merken sind. Jede Assembler-Anweisung entspricht einer bestimmten Maschinencode-Anweisung, ist jedoch in einer zugänglicheren Form dargestellt.
- Beispiel: Statt 10111000 01100001 zu schreiben, um den Wert 97 in das AX-Register zu laden, schreiben Sie in der Assemblersprache MOV AX, 61h. Diese Mnemonik zeigt klar an, dass die Anweisung einen hexadezimalen Wert in ein Register lädt.
; Lade den hexadezimalen Wert 0x61 in das AX-Register
MOV AX, 61h
- Direkte Kontrolle über Hardware:
Die Assemblersprache ermöglicht es Programmierern, die Hardware direkt zu manipulieren. Dies ist besonders nützlich für Aufgaben, die eine fein abgestimmte Steuerung über die CPU und den Speicher erfordern, wie z. B. das Schreiben von Betriebssystemen, Firmware oder leistungs-kritische Anwendungen.
- Beispiel: Wenn Sie einen I/O-Port auf einem Mikrocontroller steuern möchten, könnten Sie eine Assembler-Anweisung wie OUT 0x60, AL verwenden, um den Inhalt des AL-Registers an den Port mit der Adresse 0x60 zu senden.
; Sende den Wert im AL-Register an den I/O-Port mit der Adresse 0x60
OUT 0x60, AL
- Leistungsoptimierung:
Das Schreiben in Assembler ermöglicht detaillierte Leistungsoptimierungen. Programmierer können ihren Code an spezifische CPU-Funktionen und Befehlssätze anpassen und eine größere Effizienz erzielen als mit höheren Programmiersprachen möglich wäre.
- Beispiel: In einer Schleife zur Datenverarbeitung kann ein Assembler-Programmierer die Schleife optimieren, indem er die Anzahl der Anweisungen minimiert und die CPU-Register effektiv nutzt, um den Overhead zu reduzieren und die Ausführungsgeschwindigkeit zu erhöhen.
; Eine einfache Schleife, die das CX-Register dekrementiert und überprüft, ob es Null ist
MOV CX, 10 ; Initialisiere CX mit 10
loop_start:
DEC CX ; Dekrementiere CX
JNZ loop_start ; Springe zu loop_start, wenn CX nicht null ist
- Vereinfachung komplexer Anweisungen:
Die Assemblersprache abstrahiert einige der Komplexitäten von Binär- und Maschinencode und bietet eine strukturiertere Möglichkeit, Operationen auszuführen. Beispielsweise erlaubt es die Assemblersprache, symbolische Namen für Speicheradressen und Variablen zu verwenden, was den Programmierprozess vereinfacht.
- Beispiel: Anstatt sich die Speicheradresse 0x7FFE merken zu müssen, kann ein Assembler-Programmierer ein Label wie BUFFER_START verwenden, um auf den Beginn eines Puffers zu verweisen, was den Code lesbarer und wartungsfreundlicher macht.
; Definiere einen Puffer, der an einer bestimmten Speicheradresse beginnt
BUFFER_START EQU 0x7FFE
; Bewege Daten an den Anfang des Puffers
MOV [BUFFER_START], AX
Über Assembly hinaus: Interaktionen auf niedrigerer Ebene
Die Interaktion mit der CPU auf einer Ebene unterhalb von Assembly beinhaltet die direkte Arbeit mit Maschinencode oder sogar die Manipulation der Hardware selbst. Obwohl dies die ultimative Kontrolle bietet, ist es komplex und in der Regel für spezialisierte Anwendungen oder Hardware-Design reserviert.
- Programmierung in Maschinencode:
Maschinencode ist der tatsächliche Binärcode, der von der CPU ausgeführt wird. Jede Anweisung im Maschinencode ist eine Sequenz von Bits, die von der CPU decodiert und ausgeführt werden. Die Programmierung in Maschinencode beinhaltet das direkte Schreiben dieser Binärsequenzen, was äußerst komplex und fehleranfällig ist.
- Beispiel:
Um zwei Zahlen zu addieren und das Ergebnis in einem Register zu speichern, könnten Sie
00000001 11000001
schreiben, um die AnweisungADD
AX
CX
darzustellen, wobei der Binärcode direkt der Operation entspricht. Diese Art der Programmierung erfordert ein tiefgehendes Verständnis des Befehlssatzes und der Kodierungsstrategien der CPU.
- Beispiel:
Um zwei Zahlen zu addieren und das Ergebnis in einem Register zu speichern, könnten Sie
; Maschinencode zum Addieren des Inhalts von CX zu AX
00000001 11000001 ; Binary code for ADD AX, CX
- Mikrocodierung:
Einige CPUs verwenden Mikrocode, um komplexe Anweisungen in einfachere Operationen aufzuschlüsseln. Mikrocode fungiert als Low-Level-Schicht, die hochstufige Anweisungen in Sequenzen grundlegender Operationen übersetzt, die dann von der Hardware ausgeführt werden. Diese Ebene der Kontrolle wird normalerweise von CPU-Designern genutzt.
- Beispiel:
Innerhalb einer CPU könnte eine Anweisung wie
MUL
(multiplizieren) in eine Reihe einfacherer Schritte zerlegt werden, die vom Mikrocode gesteuert werden, wie z. B. Verschieben und Addieren, die nacheinander ausgeführt werden, um die Multiplikation durchzuführen.
- Beispiel:
Innerhalb einer CPU könnte eine Anweisung wie
- Direkte Hardware-Manipulation mit HDLs:
Für Ingenieure, die sich mit Hardware-Design beschäftigen, kann die Interaktion mit CPUs den Einsatz von Hardwarebeschreibungssprachen (HDLs) wie VHDL oder Verilog umfassen. Diese Sprachen ermöglichen es Ingenieuren, das Verhalten elektronischer Schaltungen auf einer sehr niedrigen Ebene zu beschreiben und zu simulieren.
- Beispiel: Mit VHDL könnte ein Ingenieur eine einfache ALU (Arithmetic Logic Unit) entwerfen, die Addition, Subtraktion und logische Operationen durchführt:
entity ALU is
Port ( A : in STD_LOGIC_VECTOR (3 downto 0);
B : in STD_LOGIC_VECTOR (3 downto 0);
RESULT : out STD_LOGIC_VECTOR (3 downto 0);
OP : in STD_LOGIC_VECTOR (1 downto 0));
end ALU;
architecture Behavioral of ALU is
begin
process(A, B, OP)
begin
case OP is
when "00" =>
RESULT <= A + B; -- Addition
when "01" =>
RESULT <= A - B; -- Subtraction
when "10" =>
RESULT <= A and B; -- AND operation
when others =>
RESULT <= (others => '0'); -- Default
end case;
end process;
end Behavioral;
Detaillierte Untersuchung von Logikoperationen
Logikoperationen sind grundlegende Operationen, die binäre Daten auf bittweise Weise manipulieren. Sie sind wesentlich für Entscheidungsfindung, Datenmanipulation und Steuerfluss in Computersystemen. Die häufigsten logischen Operationen sind UND
ODER
XOR
(exklusives ODER) und NICHT
Jede dieser Operationen führt einen bitweisen Vergleich oder eine Manipulation zwischen binären Zahlen durch.
- AND Operation / UND-Operation
A | B | A AND B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
Die UND-Operation vergleicht jedes Bit zweier binärer Zahlen und gibt nur dann 1 zurück, wenn beide entsprechenden Bits 1 sind. Diese Operation wird häufig verwendet, um Bits zu maskieren und spezifische Bedingungen in digitalen Schaltungen festzulegen.
; Assume AX = 5 (0101) and BX = 3 (0011)
MOV AX, 5 ; Load 0101 into AX
MOV BX, 3 ; Load 0011 into BX
AND AX, BX ; Perform AND operation: 0101 AND 0011 = 0001
; Result in AX is now 1 (0001)
- OR operation / ODER-Operation
A | B | A AND B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
Die ODER-Operation vergleicht jedes Bit zweier binärer Zahlen und gibt 1 zurück, wenn mindestens eines der entsprechenden Bits 1 ist. Diese Operation wird verwendet, um spezifische Bits in einer binären Zahl festzulegen.
; Assume AX = 5 (0101) and BX = 3 (0011)
MOV AX, 5 ; Load 0101 into AX
MOV BX, 3 ; Load 0011 into BX
OR AX, BX ; Perform OR operation: 0101 OR 0011 = 0111
; Result in AX is now 7 (0111)
Use case : Setting Bits: To set specific bits in a number.
- XOR (Exclusive OR) Operation / (Exklusives ODER) Operation
A | B | A AND B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Die XOR-Operation vergleicht jedes Bit zweier binärer Zahlen und gibt 1 zurück, wenn die entsprechenden Bits unterschiedlich sind. Diese Operation wird häufig verwendet, um Bits umzuschalten oder für Fehlererkennung und -korrektur.
; Assume AX = 5 (0101) and BX = 3 (0011)
MOV AX, 5 ; Load 0101 into AX
MOV BX, 3 ; Load 0011 into BX
XOR AX, BX ; Perform XOR operation: 0101 XOR 0011 = 0110
; Result in AX is now 6 (0110)
Die XOR-Operation vergleicht jedes Bit zweier binärer Zahlen und gibt 1 zurück, wenn die entsprechenden Bits unterschiedlich sind. Diese Operation wird häufig verwendet, um Bits umzuschalten oder für Fehlererkennung und -korrektur.
- NOT Operation / NICHT Operation
A | NOT A |
---|---|
0 | 1 |
1 | 0 |
Die NOT-Operation kehrt jedes Bit einer binären Zahl um (bitweise Negation), ändert 1en zu 0en und 0en zu 1en. Dies ist nützlich für die Erstellung des Komplements einer Zahl.
; Assume AX = 5 (0101)
MOV AX, 5 ; Load 0101 into AX
NOT AX ; Perform NOT operation: 0101 -> 1010
; Result in AX is now -6 in 2's complement representation (11111010)
Praktische Umsetzung: Kombination logischer Operationen
- Beispiel: Maskierung und Verschiebung Maskierung und Verschiebung werden häufig verwendet, um spezifische Bits innerhalb eines Bytes oder Worts zu isolieren und zu manipulieren.
MOV AX, 0xFF32 ; AX = 11111111 00110010
AND AX, 0x00FF ; Mask out the upper byte: AX = 00000000 00110010
SHR AX, 2 ; Logical shift right by 2 bits: AX = 00000000 00001100
- Beispiel: Bitweise Operationen für die Steuerung Verwendung logischer Operationen zum Setzen oder Löschen spezifischer Flags oder Bits in Steuerregistern.
MOV AL, 0x5A ; AL = 01011010
OR AL, 0x01 ; Set the least significant bit: AL = 01011011
AND AL, 0xFE ; Clear the least significant bit: AL = 01011010
XOR AL, 0x0F ; Toggle the lower 4 bits: AL = 01010001
Logikoperationen sind integraler Bestandteil der Funktionsweise von CPUs und digitalen Systemen. Sie bilden die Grundlage für Entscheidungsfindung, Bitmanipulation und Steuerungsprozesse in Computerprogrammen und Hardware-Design. Das Verständnis und die Beherrschung dieser Operationen in Assemblersprache und darüber hinaus ermöglicht präzise und effiziente Manipulation von Daten und ebnet den Weg für fortgeschrittene Rechenaufgaben.
Ob man AND für die Maskierung, OR für das Setzen von Bits, XOR zum Umschalten oder NOT für die bitweise Negation verwendet, diese Operationen bilden den Kern der binären Verarbeitung. Durch den Einsatz dieser Werkzeuge können Programmierer und Ingenieure das volle Potenzial ihrer Rechensysteme ausschöpfen, von Softwareanwendungen bis hin zu Hardware-Implementierungen.
Also, im Grunde
Der Weg von 01 zu einer grafischen Benutzeroberfläche (GUI) ist ziemlich lang. Er beginnt sogar vor der 01.
Zuerst haben wir entschieden, das Binärsystem statt des Dezimalsystems zu verwenden, um Zahlen darzustellen, und das nutzen wir, um Zustände von Transistoren darzustellen: Hat Strom oder nicht.
Zweitens versuchen wir, Formen (Schaltkreise) zu entwerfen, durch die der Strom zu anderen Komponenten wie Bildschirmen oder Tastaturen fließen kann. Basierend auf diesen Eingaben wird die CPU durch logische Operationen (boolesche Algebra) wie UND und NICHT in verschiedene Zustände übergehen.
Drittens kombinieren wir diese grundlegenden Logikgatter, um komplexere Schaltkreise wie Addierer, Multiplexer und Speichereinheiten zu bilden. Diese werden zu den Bausteinen der CPU und anderer Computerkomponenten.
Viertens entwerfen wir Befehlssätze, um diese Komponenten zu manipulieren, und schaffen so eine Sprache, die die CPU direkt verstehen kann. Das bildet die Grundlage des Maschinencodes, der binäre Muster verwendet, um bestimmte CPU-Befehle darzustellen. Die CPU bewegt dann entsprechend die Transistoren.
Fünftens entwickeln wir Assemblersprachen, die Mnemoniken und Symbole bieten, die den Maschinencodebefehlen entsprechen. Oder ein Mini-System / vordefinierte Möglichkeiten, binäre Befehle auszuführen. Es gibt viele davon, weil es viele verschiedene CPU-Architekturen gibt. Dies ist der erste Schritt, um das Programmieren für Menschen zugänglicher zu machen. Und es ist im Grunde das Nächste an der Hardware, womit selbst Leute, die mit Hardware arbeiten, noch zu tun haben. Nur etwa 2% des Linux-Kernels sind in Assembler geschrieben.
Sechstens erstellen wir höhere Programmiersprachen wie C und C++. Wir machen etwas wie einen Kernel, ein Mini-System, das die direkte Interaktion mit der Hardware abstrahiert und deren Manipulation über Dinge wie Systemaufrufe ermöglicht. Wenn wir dann alles zusammensetzen, also wie all diese Dateien / Anweisungen voneinander lesen, haben wir eine minimale Userland-Umgebung. Und dann werden sogar andere Sprachen mit noch höheren Abstraktionsniveaus wie Python und JavaScript nutzbar. C ermöglicht System- (Userland-)Programmierung, während es immer noch etwas Hardwarezugriff bietet, was entscheidend für die Entwicklung von Kerneln und Systemkomponenten auf niedriger Ebene ist. Die meisten anderen Sprachen haben nicht den eingebauten Zugriff auf die Hardware und tun dies über C und Assembler, das heißt, du verwendest im Grunde Python-Code, um eine .c-Datei zu lesen und auszuführen, oder es gibt ein Framework mit bestimmten Vorlagen, das wieder in gewisser Weise eingeschränkt ist und direkt von C oder Assembler abhängt.
Schließlich, nachdem wir all dies getan haben und einige Terminal-Benutzeroberflächen oder grafische Benutzeroberflächen, Dienstprogramme und Software entwickelt haben – wie Dateieditoren, Paketmanager, Desktop-Umgebungen und Videospiele – haben wir endlich etwas, das wie ein modernes Betriebssystem aussieht!
Bleib dran für zukünftige „Episoden“!