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är

01010111 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?

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.

; Lade den hexadezimalen Wert 0x61 in das AX-Register
MOV AX, 61h
; Sende den Wert im AL-Register an den I/O-Port mit der Adresse 0x60
OUT 0x60, AL
; 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
; 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.

; Maschinencode zum Addieren des Inhalts von CX zu AX
00000001 11000001  ; Binary code for ADD AX, CX
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.

ABA AND B
000
010
100
111

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)
ABA AND B
000
011
101
111

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.

ABA AND B
000
011
101
110

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.

ANOT A
01
10

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

  1. 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
  1. 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“!