(Photo by Roman Spiridonov on Unsplash)Nachdem wir Systeme definiert haben, können wir nun Systemprogrammierung als den Akt der Erstellung von Systemsoftware unter Verwendung von Systemprogrammiersprachen definieren. Einfach genug, nicht wahr?
Nun, eines haben wir übersprungen: Sprachen. Die Leute reden oft über Systemprogrammiersprachen in einer Art und Weise wie „X ist großartig, es ist schnell, kompiliert und eine Systemprogrammiersprache.“ Aber ist jeder auf der gleichen Seite, was eine Systemprogrammiersprache ist?
Angesichts unserer Definitionen von Systemen würde ich die Kriterien für eine Systemprogrammiersprache wie folgt definieren:
- Kompiliert zu einer nativen Binärdatei
- Kann ohne Abhängigkeiten von anderer Software (einschließlich eines Kernels) erstellt werden
- Leistungsmerkmale, die mit anderen Systemprogrammiersprachen vergleichbar sind
Haftungsausschluss: Dies ist meine Definition. Da es keine festen Kriterien gibt, leite ich eine Definition von dem ab, was mir in dem Kontext, in dem ich Systemsoftware definiert habe, sinnvoll erscheint.
Kompilieren zu nativem Binärcode
Wenn eine Sprache nicht zu einer ausführbaren Datei kompiliert werden kann, die von der CPU direkt interpretiert werden kann, dann läuft sie per Definition auf einer Plattform (z.B. JVM, Ruby VM, Python VM, etc.). Es mag hier einige Argumente geben, aber der Einfachheit halber halte ich dies für ein geeignetes Kriterium.
Keine Abhängigkeiten
Das Argument ist ähnlich wie bei der Kompilierung zu einer nativen Binärdatei. Wenn die Sprache immer die Anwesenheit anderer Software erfordert, um ausgeführt zu werden, dann läuft sie auf einer Plattform. Ein Beispiel hierfür ist Go und die darin enthaltene Standardbibliothek. Sie benötigt die Unterstützung des Betriebssystems, um grundlegende Aktionen wie die Zuweisung von Speicher, das Erzeugen von Threads (für die Ausführung von Goroutines), den eingebauten Netzwerkpoller und andere Aktionen durchzuführen. Obwohl es möglich ist, diese Kernfunktionen neu zu implementieren, stellt dies ein Hindernis für die Verwendung in diesem Kontext dar, und man kann sich leicht vorstellen, warum nicht alle Sprachen, selbst diejenigen, die zu statischen Binärdateien kompiliert werden können, als Systemprogrammiersprachen gedacht sind.
Ähnliche Leistungsmerkmale
Dies ist ein bisschen ein Ausweg. Es soll aber sagen, dass es innerhalb des Systems von Sprachen, die typischerweise als Systemprogrammiersprachen klassifiziert werden, keine großen (Größenordnungen) Unterschiede in den Leistungsmerkmalen geben sollte. Mit Eigenschaften meine ich ausdrücklich die Ausführungsgeschwindigkeit und die Speichereffizienz.
Der goldene Standard für den Vergleich ist C und/oder C++, wie er oft in vergleichenden Benchmarks dargestellt wird, die die Ausführungsgeschwindigkeit in der Größenordnung messen, um die Sprachen langsamer sind als C/C++.
Naming a Few
Die Sprachen, die einem angesichts der obigen Definition sofort einfallen, sind C und C++. Aber es gibt auch neuere Sprachen wie Rust und Nim, die diese Nische ebenfalls ausfüllen. Tatsächlich gibt es bereits ein Betriebssystem, das komplett in Rust geschrieben wurde (RedoxOS) und einen Kernel in Nim (nimkernel).
Lassen Sie uns über Go sprechen
Vorhin habe ich angedeutet, dass Go nicht unbedingt in die Familie der „Systemprogrammiersprachen“ fällt. Aber so wie nicht alle Anwendungen in Anwendungssoftware und Systemsoftware eingeteilt werden können, gilt das auch für Sprachen.
Oft wird Go als Systemprogrammiersprache bezeichnet und sogar golang.org wird zitiert:
Go ist eine Allzwecksprache, die mit dem Ziel der Systemprogrammierung entwickelt wurde.
Aber selbst das ist keine direkte Behauptung, dass Go eine Systemprogrammiersprache ist, sondern nur, dass sie mit diesem Ziel entwickelt wurde. Ich finde, dass es eher in der Mitte liegt.
Go lässt sich zwar zu nativen Binärdateien kompilieren, enthält nützliche Low-Level-Konzepte (rohe/unsichere Zeiger, native Typen wie Bytes und int32 und Inline-Assembly-Unterstützung) und ist relativ leistungsfähig, hat aber noch einige Herausforderungen zu bewältigen. Go wird mit einer Laufzeit und einem Garbage Collector ausgeliefert.
Eine Laufzeit bedeutet, dass ein Bootstrapping/Überschreiben der Laufzeit erforderlich ist, um in Umgebungen ohne Kernel zu laufen. Dies geht mehr in die interne Implementierung der Sprache, die sich in zukünftigen Versionen ändern könnte. Änderungen erfordern zusätzliche Bootstrapping-Arbeiten, wenn sich die Sprache weiterentwickelt.
Ein Garbage Collector (GC) bedeutet entweder, dass Go in den Anwendungsdomänen eingeschränkt ist, in denen es verwendet werden kann, oder dass der GC deaktiviert und durch manuelle Speicherverwaltung ersetzt werden muss. Für den Fall, dass der GC nicht ersetzt werden kann, würde die Echtzeit-Domäne (definiert durch Operationen, die innerhalb bestimmter Zeitgrenzen abgeschlossen werden müssen und/oder die Leistung wird in Nanosekunden gemessen) nicht in der Lage sein, nicht-deterministische Pausenzeiten eines GC zu riskieren.
Software für verteilte Systeme
Mit dem zunehmenden Gerede über verteilte Systeme und Anwendungen wie Kubernetes, die sehr populär werden, bekommen wir eine Menge neues Vokabular zu hören, das (wenn wir ehrlich sind) die meisten von uns nicht vollständig verstehen.
Bislang habe ich die Begriffe Systemprogrammierung und Systemingenieure in Zusammenhängen verwendet, in denen eigentlich verteilte Systemprogrammierung und verteilte Systemingenieure gemeint waren.
In diesem Beitrag haben wir Systemsoftware, Systemsprachen und Systemprogrammierung definiert. Wenn wir jedoch über verteilte Systeme sprechen, ändert sich die Bedeutung von System. Und obwohl ich hier nicht auf die spezifischen Unterschiede eingehen werde (vor allem, weil ich sie selbst noch besser verstehen muss), ist es wichtig, dass wir diese gedanklichen Unterscheidungen treffen und eine genauere Sprache verwenden, wenn wir können, um Verwirrung bei denjenigen zu vermeiden, die diesen Bereich noch lernen.