Warum ist das Software-Betriebssystem spezifisch?


77

Ich versuche herauszufinden, warum Software, die mit Programmiersprachen für bestimmte Betriebssysteme erstellt wurde, nur mit ihnen funktioniert.

Ich verstehe, dass Binärdateien aufgrund der von ihnen verstandenen prozessorspezifischen Maschinensprache und der unterschiedlichen Befehlssätze zwischen den verschiedenen Prozessoren für bestimmte Prozessoren spezifisch sind. Aber woher kommt die Betriebssystemspezifität? Früher habe ich angenommen, dass es sich um APIs handelt, die vom Betriebssystem bereitgestellt wurden, aber dann habe ich dieses Diagramm in einem Buch gesehen: Diagramm

Betriebssysteme - Interna und Designprinzipien 7. Aufl. - W. Stallings (Pearson, 2012)

Wie Sie sehen, werden APIs nicht als Teil des Betriebssystems angezeigt.

Wenn ich zum Beispiel ein einfaches Programm in C mit dem folgenden Code erstelle:

#include<stdio.h>

main()
{
    printf("Hello World");

}

Tut der Compiler beim Kompilieren etwas Betriebssystemspezifisches?


15
Drucken Sie in ein Fenster? oder eine Konsole? oder zum Grafikspeicher? Wie legen Sie die Daten dort ab? Printf für Apple betrachten] [+ wäre etwas ganz anderes als für Mac OS 7 und wieder etwas ganz anderes als für Mac OS X (nur bei einer "Reihe" von Computern bleiben).

3
Wenn Sie diesen Code für Mac OS 7 geschrieben haben, wird er in einem neuen Fenster als Text angezeigt. Wenn Sie es auf Apple] [+ machen würden, würde es direkt in ein bestimmtes Speichersegment schreiben. Unter Mac OS X wird es in eine Konsole geschrieben. Das sind also drei verschiedene Arten des Schreibens, den Code basierend auf der Ausführungshardware zu handhaben, die von der Bibliotheksschicht behandelt wird.

2
@StevenBurnap yep - en.wikipedia.org/wiki/Aztec_C

10
Ihre FFT-Funktion kann problemlos unter Windows oder Linux (auf derselben CPU) ausgeführt werden, ohne dass eine erneute Kompilierung erforderlich ist. Aber wie wollen Sie dann das Ergebnis anzeigen? Natürlich mit einer Betriebssystem-API. ( printfvon msvcr90.dll ist nicht dasselbe wie printfvon libc.so.6)
user253751

9
Auch wenn APIs "nicht Teil des Betriebssystems" sind, unterscheiden sie sich dennoch, wenn Sie von einem Betriebssystem zum anderen wechseln. (Was natürlich die Frage aufwirft, was der Ausdruck "nicht Teil des Betriebssystems" laut Diagramm wirklich bedeutet.)
Theodoros Chatzigiannakis

Antworten:


78

Sie erwähnen, warum der Code auch für ein Betriebssystem spezifisch sein muss, wenn er für eine CPU spezifisch ist. Dies ist eigentlich eher eine interessante Frage, die viele der Antworten hier angenommen haben.

CPU-Sicherheitsmodell

Das erste Programm, das auf den meisten CPU-Architekturen ausgeführt wird, wird im sogenannten inneren Ring oder Ring 0 ausgeführt . Wie ein bestimmter CPU-Bogen Ringe implementiert, ist unterschiedlich, aber es steht fest, dass fast jede moderne CPU mindestens zwei Betriebsmodi hat, von denen einer privilegiert ist und Bare-Metal-Code ausführt, der alle legalen Operationen ausführen kann, die die CPU ausführen kann, und der andere nicht vertrauenswürdig und führt geschützten Code aus, der nur einen definierten sicheren Satz von Funktionen ausführen kann. Einige CPUs weisen jedoch eine weitaus höhere Granularität auf. Um VMs sicher zu verwenden, sind mindestens 1 oder 2 zusätzliche Ringe erforderlich (häufig mit negativen Zahlen gekennzeichnet). Dies würde jedoch den Rahmen dieser Antwort sprengen.

Woher kommt das Betriebssystem?

Frühe Single-Tasking-Betriebssysteme

In sehr frühen DOS- und anderen frühen Single-Tasking-basierten Systemen wurde der gesamte Code im inneren Ring ausgeführt. Jedes Programm, das Sie jemals ausgeführt haben, hatte die volle Leistung über den gesamten Computer und konnte buchstäblich alles tun, wenn es sich schlecht verhielt, einschließlich des Löschens aller Ihrer Daten oder sogar des Verursachens von Hardwareschäden In einigen extremen Fällen, wie dem Einstellen ungültiger Anzeigemodi auf sehr alten Bildschirmen, kann dies schlimmer sein, wenn der Code einfach fehlerfrei ist.

Dieser Code war in der Tat weitgehend betriebssystemunabhängig, solange Sie einen Loader hatten, der das Programm in den Speicher laden konnte (ziemlich einfach für frühe Binärformate), und der Code war nicht auf Treiber angewiesen und implementierte den gesamten Hardware-Zugriff selbst, unter dem er ausgeführt werden sollte Jedes Betriebssystem, solange es in Ring 0 ausgeführt wird. Beachten Sie, dass ein sehr einfaches Betriebssystem wie dieses normalerweise als Monitor bezeichnet wird, wenn es nur zum Ausführen anderer Programme verwendet wird und keine zusätzlichen Funktionen bietet.

Moderne Multi-Tasking-Betriebssysteme

Modernere Betriebssysteme, einschließlich UNIX , Windows- Versionen ab NT und verschiedene andere mittlerweile unklare Betriebssysteme, haben beschlossen, diese Situation zu verbessern. Benutzer wollten zusätzliche Funktionen wie Multitasking, damit sie mehr als eine Anwendung gleichzeitig ausführen können, und Schutz, also einen Fehler ( oder böswilliger Code) in einer Anwendung können nicht mehr unbegrenzt Schaden an Maschine und Daten anrichten.

Dies geschah mit den oben erwähnten Ringen, das Betriebssystem würde den einzigen Platz in Ring 0 einnehmen und Anwendungen würden in den äußeren nicht vertrauenswürdigen Ringen ausgeführt, nur in der Lage, einen eingeschränkten Satz von Operationen auszuführen, die das Betriebssystem erlaubte.

Diese Erhöhung der Nützlichkeit und des Schutzes war jedoch mit Kosten verbunden. Programme mussten nun mit dem Betriebssystem zusammenarbeiten, um Aufgaben auszuführen, die sie nicht selbst ausführen durften. Sie konnten beispielsweise nicht mehr die direkte Kontrolle über die Festplatte übernehmen, indem sie auf ihren Speicher zugegriffen und willkürlich geändert haben Stattdessen mussten sie das Betriebssystem auffordern, diese Aufgaben für sie auszuführen, damit überprüft werden konnte, ob sie die Operation ausführen durften. Dabei wurden keine Dateien geändert, die nicht zu ihnen gehörten. Außerdem wurde überprüft, ob die Operation tatsächlich gültig und gültig war würde die Hardware nicht in einem undefinierten Zustand belassen.

Jedes Betriebssystem entschied sich für eine andere Implementierung dieser Schutzfunktionen, teilweise basierend auf der Architektur, für die das Betriebssystem entwickelt wurde, und teilweise basierend auf dem Design und den Prinzipien des betreffenden Betriebssystems. UNIX legte beispielsweise den Schwerpunkt auf Maschinen, die für die Verwendung durch mehrere Benutzer geeignet und fokussiert sind Die dafür verfügbaren Funktionen wurden zwar so konzipiert, dass Windows einfacher ist und auf langsamerer Hardware mit einem einzelnen Benutzer ausgeführt werden kann. Die Art und Weise, in der User-Space-Programme auch mit dem Betriebssystem kommunizieren, ist auf X86 völlig anders als beispielsweise auf ARM oder MIPS, sodass ein Betriebssystem mit mehreren Plattformen Entscheidungen treffen muss, die auf der Notwendigkeit basieren, an der Hardware zu arbeiten, für die es gedacht ist.

Diese betriebssystemspezifischen Interaktionen werden in der Regel als "Systemaufrufe" bezeichnet und umfassen die vollständige Interaktion eines User Space-Programms mit der Hardware über das Betriebssystem. Sie unterscheiden sich grundlegend je nach der Funktion des Betriebssystems, und daher muss ein Programm, das seine Arbeit über Systemaufrufe ausführt, dies tun OS-spezifisch sein.

Der Program Loader

Zusätzlich zu den Systemaufrufen bietet jedes Betriebssystem eine andere Methode zum Laden eines Programms vom sekundären Speichermedium und in den Speicher . Um von einem bestimmten Betriebssystem geladen werden zu können, muss das Programm einen speziellen Header enthalten, der dem Betriebssystem beschreibt, wie es sein kann geladen und laufen.

Früher war dieser Header so einfach, dass das Schreiben eines Loaders für ein anderes Format fast trivial war. Mit modernen Formaten wie elf, die erweiterte Funktionen wie dynamische Verknüpfungen und schwache Deklarationen unterstützen, ist es für ein Betriebssystem jedoch nahezu unmöglich, Binärdateien zu laden was nicht dafür gedacht war, bedeutet, dass es auch ohne Systemaufruf-Inkompatibilitäten immens schwierig ist, ein Programm so in den RAM zu legen, dass es ausgeführt werden kann.

Bibliotheken

Programme verwenden selten Systemaufrufe direkt, sie erhalten ihre Funktionalität jedoch fast ausschließlich durch Bibliotheken, die die Systemaufrufe in ein etwas freundlicheres Format für die Programmiersprache umschließen, z. B. C hat die C-Standardbibliothek und glibc unter Linux und ähnlichen und win32-Bibliotheken unter Windows NT und höher, die meisten anderen Programmiersprachen haben ähnliche Bibliotheken, die die Systemfunktionalität in geeigneter Weise umschließen.

Diese Bibliotheken kann bis zu einem gewissen Grad auch die Cross - Plattform - Probleme überwinden , wie oben beschrieben, gibt es eine Reihe von Bibliotheken , die sich um die Bereitstellung eine einheitliche Plattform für Anwendungen ausgelegt sind , während intern Anrufe an eine breite Palette von OSes Verwaltung wie SDL , bedeutet dies , dass , obwohl Programme können nicht binärkompatibel sein. Programme, die diese Bibliotheken verwenden, können eine gemeinsame Quelle für die Plattformen haben, was die Portierung so einfach wie das erneute Kompilieren macht.

Ausnahmen nach oben

Trotz allem, was ich hier gesagt habe, gab es Versuche, die Einschränkungen zu überwinden, dass Programme nicht auf mehr als einem Betriebssystem ausgeführt werden können. Einige gute Beispiele sind das Wine-Projekt, bei dem sowohl der Win32-Programmlader als auch das Binärformat und die Systembibliotheken erfolgreich emuliert wurden, sodass Windows-Programme unter verschiedenen UNIX-Betriebssystemen ausgeführt werden können. Es gibt auch eine Kompatibilitätsschicht, mit der mehrere BSD UNIX-Betriebssysteme Linux-Software ausführen können, und natürlich Apples eigenes Shim, mit dem man alte MacOS-Software unter MacOS X ausführen kann.

Diese Projekte erfordern jedoch einen enormen manuellen Entwicklungsaufwand. Abhängig davon, wie unterschiedlich die beiden Betriebssysteme sind, reicht der Schwierigkeitsgrad von einer relativ kleinen Abweichung bis zur nahezu vollständigen Emulation des anderen Betriebssystems, was häufig komplexer ist als das Schreiben eines gesamten Betriebssystems an sich. Dies ist also die Ausnahme und nicht die Regel.


6
+1 "Warum ist das Software-Betriebssystem spezifisch?" Weil die Geschichte.
Paul Draper

2
stammt das CPU-Sicherheitsmodell von x86? warum und wann wurde das modell erfunden?
n611x007

8
@naxa Nein, es ist schon lange älter als x86. Es wurde erstmals teilweise für Multics im Jahr 1969 implementiert. Dies ist das erste Betriebssystem mit nützlichen Timesharing-Funktionen für mehrere Benutzer, für das dieses Modell auf dem GE-645- Computer erforderlich ist. Diese Implementierung war jedoch unvollständig und verlässlich Softwareunterstützung, die erste vollständige und sichere Implementierung in Hardware erfolgte in seinem Nachfolger, dem Honeywell 6180 . Dies war vollständig hardwarebasiert und ermöglichte es Multics, Code von mehreren Benutzern auszuführen, ohne sich gegenseitig einzumischen.
Vality

@ Qualität IBM LPAR ist auch ~ 1972.
Elliott Frisch

@ElliottFrisch wow, das ist beeindruckend. Ich hatte nicht bemerkt, dass es so früh war. Danke für diese Info.
Vality

48

Wie Sie sehen, werden APIs nicht als Teil des Betriebssystems angezeigt.

Ich denke, Sie lesen zu viel in das Diagramm. Ja, ein Betriebssystem gibt eine binäre Schnittstelle für den Aufruf von Betriebssystemfunktionen an und definiert auch ein Dateiformat für ausführbare Dateien, bietet jedoch auch eine API im Sinne eines Funktionskatalogs, der von aufgerufen werden kann eine Anwendung zum Aufrufen von Betriebssystemdiensten.

Ich denke, das Diagramm versucht nur zu betonen, dass Betriebssystemfunktionen normalerweise über einen anderen Mechanismus als einen einfachen Bibliotheksaufruf aufgerufen werden. Die meisten gängigen Betriebssysteme verwenden Prozessor-Interrupts, um auf Betriebssystemfunktionen zuzugreifen. Typische moderne Betriebssysteme werden nicht ein Anwenderprogramm direkt zugreifen lassen , jede Hardware. Wenn Sie ein Zeichen in die Konsole schreiben möchten, müssen Sie das Betriebssystem bitten, dies für Sie zu tun. Der zum Schreiben auf die Konsole verwendete Systemaufruf ist von Betriebssystem zu Betriebssystem unterschiedlich. Es gibt also ein Beispiel dafür, warum Software betriebssystemspezifisch ist.

printf ist eine Funktion aus der C-Laufzeitbibliothek und in einer typischen Implementierung eine ziemlich komplexe Funktion. Wenn Sie googeln, können Sie die Quelle für mehrere Versionen online finden. Auf dieser Seite finden Sie eine geführte Tour . Unten im Gras führt es jedoch zu einem oder mehreren Systemaufrufen, und jeder dieser Systemaufrufe ist spezifisch für das Host-Betriebssystem.


4
Was wäre, wenn das Programm nur zwei Zahlen ohne Eingabe oder Ausgabe addieren würde? Wäre das Programm immer noch betriebssystemspezifisch?
Paul

2
Betriebssysteme sollen die meisten hardwarespezifischen Dinge hinter / in eine Abstraktionsschicht stellen. Das Betriebssystem selbst (die Abstraktion) kann sich jedoch von Implementierung zu Implementierung unterscheiden. Es gibt POSIX, an dem einige Betriebssysteme (mehr oder weniger) festhalten, und vielleicht auch andere, aber die Betriebssysteme unterscheiden sich insgesamt einfach zu stark in ihrem "sichtbaren" Teil der Abstraktion. Wie bereits gesagt: Sie können / home / user unter Windows nicht öffnen und auf einem * N * X-System nicht auf HKEY_LOCAL_MACHINE \ ... zugreifen. Sie können dafür eine virtuelle ("Emulations" -) Software schreiben, um diese Systeme näher zusammenzubringen. Dies ist jedoch immer "3rd Party" (von OS POV).
RobIII

16
@ Paul Ja. Insbesondere wäre die Art und Weise, wie es in eine ausführbare Datei gepackt wird, betriebssystemspezifisch.
OrangeDog

4
@TimSeguine Ich bin nicht einverstanden mit Ihrem Beispiel für XP vs. 7. Microsoft unternimmt viel, um sicherzustellen, dass in 7 dieselbe API wie in XP vorhanden ist. Es ist klar, dass das Programm für die Ausführung mit einer bestimmten API oder einem bestimmten Vertrag entwickelt wurde. Das neue Betriebssystem hat nur die gleiche API / den gleichen Vertrag eingehalten. Bei Windows ist diese API jedoch sehr proprietär, weshalb kein anderer Betriebssystemhersteller sie unterstützt. Selbst dann gibt es eine Menge Beispiele für Programme, die NICHT auf 7 laufen.
Kunst

3
@Paul: Ein Programm, das keine Ein- / Ausgabe ausführt, ist das leere Programm , das zu einem No-Op kompiliert werden soll.
Bergi

14

Tut der Compiler beim Kompilieren etwas Betriebssystemspezifisches?

Wahrscheinlich. Irgendwann während des Kompilierungs- und Verknüpfungsprozesses wird Ihr Code in eine betriebssystemspezifische Binärdatei umgewandelt und mit allen erforderlichen Bibliotheken verknüpft. Ihr Programm muss in einem vom Betriebssystem erwarteten Format gespeichert werden, damit das Betriebssystem das Programm laden und ausführen kann. Außerdem rufen Sie die Standardbibliotheksfunktion auf printf(), die auf einer bestimmten Ebene in Bezug auf die vom Betriebssystem bereitgestellten Dienste implementiert ist.

Bibliotheken bieten eine Schnittstelle - eine Abstraktionsebene vom Betriebssystem und der Hardware -, mit der Sie Ihr Programm für ein anderes Betriebssystem oder eine andere Hardware neu kompilieren können. Diese Abstraktion existiert jedoch auf der Quellebene - sobald das Programm kompiliert und verknüpft ist, ist es mit einer bestimmten Implementierung dieser Schnittstelle verbunden, die für ein bestimmtes Betriebssystem spezifisch ist.


12

Es gibt eine Reihe von Gründen, aber ein sehr wichtiger Grund ist, dass das Betriebssystem die Reihe von Bytes, aus denen sich Ihr Programm zusammensetzt, lesen und in den Speicher laden muss Starten Sie dann die Ausführung Ihres Programmcodes. Zu diesem Zweck erstellen die Ersteller des Betriebssystems ein bestimmtes Format für diese Reihe von Bytes, damit der Betriebssystemcode weiß, wo die verschiedenen Teile der Struktur Ihres Programms zu suchen sind. Da die wichtigsten Betriebssysteme unterschiedliche Autoren haben, haben diese Formate oft wenig miteinander zu tun. Insbesondere das ausführbare Windows-Format hat wenig mit dem ELF-Format gemein, das die meisten Unix-Varianten verwenden. Das Laden, dynamische Verknüpfen und Ausführen von Code muss also betriebssystemspezifisch sein.

Als nächstes stellt jedes Betriebssystem einen anderen Satz von Bibliotheken zur Verfügung, um mit der Hardwareschicht zu kommunizieren. Dies sind die APIs, die Sie erwähnen, und es handelt sich im Allgemeinen um Bibliotheken, die dem Entwickler eine einfachere Benutzeroberfläche bieten, während sie in komplexere, spezifischere Aufrufe in die Tiefe des Betriebssystems selbst übersetzt werden. Diese Aufrufe sind häufig nicht dokumentiert oder gesichert. Diese Ebene ist häufig recht grau, wobei neuere "OS" -APIs teilweise oder vollständig auf älteren APIs basieren. Beispielsweise sind in Windows viele der neueren APIs, die Microsoft im Laufe der Jahre erstellt hat, im Wesentlichen Schichten über den ursprünglichen Win32-APIs.

Ein Problem, das in Ihrem Beispiel nicht auftritt, bei dem es sich jedoch um eines der größeren Probleme handelt, mit denen Entwickler konfrontiert sind, ist die Schnittstelle mit dem Fenstermanager, um eine GUI zu präsentieren. Ob der Fenstermanager Teil des "Betriebssystems" ist, hängt manchmal von Ihrer Sichtweise sowie vom Betriebssystem selbst ab, wobei die GUI in Windows auf einer tieferen Ebene in das Betriebssystem integriert ist, während die GUIs unter Linux und OS X vorhanden sind direkter getrennt. Dies ist sehr wichtig, da das, was heutzutage üblicherweise als "Betriebssystem" bezeichnet wird, viel umfangreicher ist als das, was in Lehrbüchern beschrieben wird, da es viele, viele Komponenten auf Anwendungsebene enthält.

Schließlich handelt es sich nicht unbedingt um ein Betriebssystemproblem, sondern um ein wichtiges Problem bei der Generierung ausführbarer Dateien. Unterschiedliche Computer haben unterschiedliche Assemblersprachenziele, sodass der tatsächlich generierte Objektcode unterschiedlich sein muss. Dies ist streng genommen kein "Betriebssystem" -Problem, sondern ein Hardwareproblem, bedeutet jedoch, dass Sie für verschiedene Hardwareplattformen unterschiedliche Builds benötigen.


2
Es kann sich lohnen, darauf hinzuweisen, dass einfachere ausführbare Formate nur mit einer geringen Menge an RAM (falls vorhanden) geladen werden können, die über die für den geladenen Code erforderliche Menge hinausgeht, während und in einigen Fällen komplexere Formate eine viel größere RAM-Stellfläche erfordern auch nach dem Laden. MS-DOS würde COM-Dateien mit einer Größe von bis zu 63,75 KB laden, indem einfach sequentielle Bytes beginnend mit dem Offset 0x100 eines beliebigen Segments in den RAM eingelesen, CX mit der Endadresse geladen und zu dieser Adresse gesprungen werden. Single-Pass-Kompilierung konnte ohne Back-Patching (nützlich bei Disketten) von ...
Supercat

1
... dass der Compiler zu jeder Routine eine Liste aller Patch-Punkte hinzufügt, von denen jeder die Adresse der vorherigen Liste enthält und die Adresse der letzten Liste an das Ende des Codes setzt. Das Betriebssystem würde den Code nur als unformatierte Bytes laden, aber eine kleine Routine innerhalb des Codes könnte alle erforderlichen Adresspatches anwenden, bevor der Hauptteil des Codes ausgeführt wird.
Supercat

9

Aus einer anderen Antwort von mir:

Betrachten Sie die frühen DOS-Maschinen und den tatsächlichen Beitrag von Microsoft für die Welt:

Autocad musste Treiber für jeden Drucker schreiben, auf dem gedruckt werden konnte. Lotus 1-2-3 auch. Wenn Sie wollten, dass Ihre Software druckt, mussten Sie Ihre eigenen Treiber schreiben. Wenn es 10 Drucker und 10 Programme gäbe, müssten 100 verschiedene Teile des im Wesentlichen gleichen Codes separat und unabhängig geschrieben werden.

Was Windows 3.1 versucht hat (zusammen mit GEM und so vielen anderen Abstraktionsebenen), ist, dass der Druckerhersteller einen Treiber für seinen Drucker und der Programmierer einen Treiber für die Windows-Druckerklasse geschrieben hat.

Jetzt, mit 10 Programmen und 10 Druckern, müssen nur 20 Codeteile geschrieben werden. Da die Microsoft-Seite des Codes für alle gleich war, bedeuteten Beispiele von MS, dass Sie nur sehr wenig Arbeit zu tun hatten.

Nun war ein Programm nicht nur auf die 10 zu unterstützenden Drucker beschränkt, sondern auf alle Drucker, deren Hersteller Treiber für Windows bereitgestellt haben.

Das Betriebssystem stellt also Dienste für die Anwendungen bereit, damit die Anwendungen keine redundanten Aufgaben ausführen müssen.

Ihr C-Beispielprogramm verwendet printf, das Zeichen an stdout sendet - eine betriebssystemspezifische Ressource, die die Zeichen auf einer Benutzeroberfläche anzeigt. Das Programm muss nicht wissen, wo sich die Benutzeroberfläche befindet - es könnte sich in DOS befinden, es könnte sich in einem grafischen Fenster befinden, es könnte zu einem anderen Programm weitergeleitet und als Eingabe für einen anderen Prozess verwendet werden.

Da das Betriebssystem diese Ressourcen zur Verfügung stellt, können Programmierer mit wenig Arbeit viel mehr erreichen.

Das Starten eines Programms ist jedoch kompliziert. Das Betriebssystem erwartet, dass eine ausführbare Datei zu Beginn bestimmte Informationen enthält, die das Betriebssystem darüber informieren, wie sie gestartet werden soll, und in einigen Fällen (in fortgeschritteneren Umgebungen wie Android oder iOS), welche Ressourcen erforderlich sind, die eine Genehmigung erfordern, da sie Ressourcen außerhalb des Betriebssystems berühren "Sandbox" - eine Sicherheitsmaßnahme zum Schutz von Benutzern und anderen Apps vor fehlerhaften Programmen.

Selbst wenn der ausführbare Maschinencode identisch ist und keine Betriebssystemressourcen erforderlich sind, kann ein für Windows kompiliertes Programm nicht unter einem OS X-Betriebssystem ohne zusätzliche Emulations- oder Übersetzungsschicht ausgeführt werden, auch nicht auf derselben Hardware.

Frühere Betriebssysteme im DOS-Stil konnten häufig Programme gemeinsam nutzen, da sie dieselbe API in der Hardware (BIOS) und das Betriebssystem in die Hardware eingebunden haben, um Dienste bereitzustellen. Wenn Sie also ein COM-Programm geschrieben und kompiliert haben, das nur ein Speicherabbild einer Reihe von Prozessoranweisungen ist, können Sie es auf CP / M, MS-DOS und mehreren anderen Betriebssystemen ausführen. Tatsächlich können Sie COM-Programme weiterhin auf modernen Windows-Computern ausführen. Andere Betriebssysteme verwenden nicht dieselben BIOS-API-Hooks, sodass die COM-Programme ohne Emulations- oder Übersetzungsschicht nicht auf ihnen ausgeführt werden können. EXE-Programme folgen einer Struktur, die viel mehr als nur Prozessoranweisungen enthält, und werden daher zusammen mit den API-Problemen nicht auf einem Computer ausgeführt, der nicht versteht, wie er in den Speicher geladen und ausgeführt wird.


7

Die eigentliche Antwort lautet: Wenn jedes Betriebssystem dasselbe ausführbare Binärdateilayout verstehen würde und Sie sich nur auf standardisierte Funktionen (wie in der C-Standardbibliothek) beschränken würden, die vom Betriebssystem bereitgestellt werden (von den Betriebssystemen bereitgestellt), würde Ihre Software dies tun In der Tat laufen auf jedem Betriebssystem.

In Wirklichkeit ist das natürlich nicht der Fall. Eine EXEDatei nicht über das gleiche Format wie eine ELFDatei, obwohl beide binären Code für die gleiche CPU enthalten. * Also jedes Betriebssystem in der Lage sein müssten alle Dateiformate zu interpretieren und sie nicht einfach tun dies in der Es gab keinen Grund, dies später zu tun (mit ziemlicher Sicherheit aus kommerziellen und nicht aus technischen Gründen).

Darüber hinaus muss Ihr Programm wahrscheinlich Dinge tun, die die C-Bibliothek nicht definiert (selbst für einfache Dinge wie das Auflisten des Inhalts eines Verzeichnisses), und in diesen Fällen bietet jedes Betriebssystem seine eigenen Funktionen, um Ihre Ziele zu erreichen Aufgabe, was natürlich bedeutet, dass es keinen kleinsten gemeinsamen Nenner für Sie gibt (es sei denn, Sie machen diesen Nenner selbst).

Im Prinzip ist das also durchaus möglich. Tatsächlich führt WINE ausführbare Windows-Dateien direkt unter Linux aus.
Aber es ist eine Menge Arbeit und (normalerweise) kommerziell ungerechtfertigt.

* Hinweis: In einer ausführbaren Datei steckt viel mehr als nur Binärcode. Es gibt unzählige Informationen, die dem Betriebssystem mitteilen, von welchen Bibliotheken die Datei abhängt, wie viel Stapelspeicher sie benötigt, welche Funktionen sie in andere Bibliotheken exportiert, die möglicherweise davon abhängen, wo das Betriebssystem möglicherweise relevante Debug-Informationen findet. Suchen Sie "die Datei im Speicher neu", falls erforderlich, um sicherzustellen, dass die Ausnahmebehandlung ordnungsgemäß funktioniert, usw. usw. Auch hier könnte es ein einziges Format geben, über das sich alle einig sind, das es aber einfach nicht gibt.


Unterhaltsame Tatsache: Es gibt ein standardisiertes posiz-Binärformat, das auf allen Betriebssystemen ausgeführt werden kann. Es wird nur selten verwendet.
Marcin

@Marcin: Scheint, als würdest du Windows nicht als Betriebssystem betrachten. (Oder sagen Sie, dass Windows POSIX-Binärdateien ausführen kann ?!) Für die Zwecke meiner Antwort ist POSIX nicht der Standard, auf den ich mich beziehe. Das X in POSIX steht für Unix. Es sollte zB nie von Windows verwendet werden, obwohl Windows zufällig ein POSIX-Subsystem hat.
Mehrdad

1. Etwas kann auf mehreren Betriebssystemen ausgeführt werden, ohne dass es auf allen Betriebssystemen ausgeführt werden muss. 2. Windows konnte seit NT Posix-Binärdateien ausführen.
Marcin

1
@Marcin: (1) Wie gesagt, das X in POSIX steht für UNIX . Es ist kein Standard, der von anderen Betriebssystemen befolgt werden sollte, es war nur ein Versuch, einen gemeinsamen Nenner zwischen den verschiedenen Unixen zu erreichen, was großartig, aber nicht so erstaunlich ist. Die Tatsache, dass es verschiedene Varianten von Unix-Betriebssystemen gibt, ist für mich völlig irrelevant, was die Kompatibilität mit anderen Betriebssystemen als Unix betrifft. (2) Können Sie eine Referenz für # 2 angeben?
Mehrdad

1
@Mehrdad: Marcin hat recht; Windows SUA (Subsystem für Unix-Anwendungen) ist POSIX-kompatibel
MSalters

5

Das Diagramm hat die "Anwendungs" -Schicht (meistens), die durch die "Bibliotheken" von der "Betriebssystem" -Schicht getrennt ist, und dies impliziert, dass "Anwendung" und "Betriebssystem" sich nicht kennen müssen. Das ist eine Vereinfachung im Diagramm, aber nicht ganz richtig.

Das Problem ist, dass die "Bibliothek" tatsächlich drei Teile enthält: die Implementierung, die Schnittstelle zur Anwendung und die Schnittstelle zum Betriebssystem. Grundsätzlich können die ersten beiden für das Betriebssystem "universell" gemacht werden (es hängt davon ab, wo Sie sie aufteilen), der dritte Teil - die Schnittstelle zum Betriebssystem - kann dies im Allgemeinen nicht. Die Schnittstelle zum Betriebssystem hängt notwendigerweise vom Betriebssystem, den bereitgestellten APIs, dem Paketierungsmechanismus (z. B. dem von der Windows-DLL verwendeten Dateiformat) usw. ab.

Da die "Bibliothek" im Allgemeinen als einzelnes Paket zur Verfügung gestellt wird, bedeutet dies, dass das Programm, sobald es eine "Bibliothek" zur Verwendung auswählt, eine Festschreibung für ein bestimmtes Betriebssystem vornimmt. Dies geschieht auf zwei Arten: a) Der Programmierer wählt die Option vollständig im Voraus aus, und dann kann die Bindung zwischen der Bibliothek und der Anwendung universell sein, die Bibliothek selbst ist jedoch an das Betriebssystem gebunden. oder b) der Programmierer richtet die Dinge so ein, dass die Bibliothek ausgewählt wird, wenn Sie das Programm ausführen, der Bindungsmechanismus selbst zwischen dem Programm und der Bibliothek jedoch vom Betriebssystem abhängt (z. B. der DLL-Mechanismus in Windows). Jedes hat seine Vor- und Nachteile, aber so oder so muss man eine Entscheidung im Voraus treffen.

Das heißt nicht, dass es unmöglich ist, aber man muss sehr schlau sein. Um das Problem zu lösen, müssten Sie die Bibliothek zur Laufzeit auswählen und einen universellen Bindungsmechanismus entwickeln, der nicht vom Betriebssystem abhängt. viel mehr Arbeit). Manchmal lohnt es sich.

Das müssen Sie nicht, aber wenn Sie sich die Mühe machen, das zu tun, besteht eine gute Chance, dass Sie auch nicht an einen bestimmten Prozessor gebunden sein möchten. Sie werden also eine virtuelle Maschine schreiben und kompilieren Ihr Programm in ein prozessorneutrales Code-Format.

Inzwischen solltest du gemerkt haben, wohin ich gehe. Sprachplattformen wie Java machen genau das. Die Java-Laufzeit (Bibliothek) definiert die betriebssystemneutrale Bindung zwischen Ihrem Java-Programm und der Bibliothek (wie die Java-Laufzeit Ihr Programm öffnet und ausführt) und stellt eine Implementierung bereit, die für das aktuelle Betriebssystem spezifisch ist. .NET macht in gewissem Umfang dasselbe, außer dass Microsoft für nichts anderes als Windows eine "Bibliothek" (Laufzeit) bereitstellt (andere jedoch - siehe Mono). Tatsächlich macht Flash das Gleiche, obwohl der Umfang auf den Browser beschränkt ist.

Schließlich gibt es Möglichkeiten, dasselbe ohne einen benutzerdefinierten Bindungsmechanismus zu tun. Sie können herkömmliche Tools verwenden, den Bindungsschritt für die Bibliothek jedoch verschieben, bis der Benutzer das Betriebssystem auswählt. Genau das passiert, wenn Sie den Quellcode verteilen. Der Benutzer nimmt Ihr Programm und bindet es an den Prozessor (kompiliert es) und das Betriebssystem (verknüpft es), wenn der Benutzer bereit ist, es auszuführen.

Es hängt alles davon ab, wie Sie die Ebenen schneiden. Letztendlich haben Sie immer ein Computergerät, das mit spezifischer Hardware und spezifischem Maschinencode hergestellt wurde. Die Ebenen dienen dort weitgehend als konzeptioneller Rahmen.


3

Software ist nicht immer betriebssystemspezifisch. Sowohl Java als auch das frühere p-Code-System (und sogar ScummVM) ermöglichen Software, die für alle Betriebssysteme portierbar ist. Infocom (Hersteller von Zork und der Z-Maschine ) verfügte auch über eine relationale Datenbank, die auf einer anderen virtuellen Maschine basierte. Auf einer gewissen Ebene muss jedoch etwas selbst diese Abstraktionen in tatsächliche Anweisungen umwandeln, die auf einem Computer ausgeführt werden sollen.


3
Java läuft jedoch auf einer virtuellen Maschine, die kein Betriebssystem ist. Sie müssen für jedes Betriebssystem eine andere JVM-Binärdatei verwenden
Izkata

3
@Izkata Stimmt, aber Sie kompilieren die Software nicht neu (nur die JVM). Siehe auch meinen letzten Satz. Aber ich werde darauf hinweisen, dass Sun einen Mikroprozessor hatte, der direkt Byte-Code ausführen konnte.
Elliott Frisch

3
Java ist ein Betriebssystem, obwohl es normalerweise nicht als eines angesehen wird. Java-Software ist spezifisch für das Java-Betriebssystem und es gibt Java-OS-Emulatoren für die meisten "echten" Betriebssysteme. Sie können jedoch mit jedem Host- und Ziel-Betriebssystem dasselbe tun - wie Windows-Software unter Linux mit WINE ausführen.
user253751

@immibis Ich wäre genauer. Die Java Foundation Classes (JFC, Javas Standardbibliothek) sind ein Framework. Java selbst ist eine Sprache. Die JVM ähnelt einem Betriebssystem: Sie hat den Namen "Virtual Machine" (Virtuelle Maschine) und führt aus Sicht des darin ausgeführten Codes ähnliche Funktionen wie ein Betriebssystem aus.

1

Du sagst

Software, die mit Programmiersprachen für bestimmte Betriebssysteme erstellt wurde, funktioniert nur mit diesen

Das von Ihnen als Beispiel angegebene Programm funktioniert jedoch auf vielen Betriebssystemen und sogar in einigen Bare-Metal-Umgebungen.

Wichtig ist hier die Unterscheidung zwischen dem Quellcode und der kompilierten Binärdatei. Die Programmiersprache C wurde speziell so entwickelt, dass sie vom Betriebssystem unabhängig ist. Dies geschieht, indem die Interpretation von Dingen wie "Print to the Console" dem Implementierer überlassen wird. C kann jedoch mit betriebssystemspezifischen Anforderungen konform sein (Gründe finden Sie in den anderen Antworten). Zum Beispiel die ausführbaren Formate PE oder ELF.


6
Es scheint ziemlich klar zu sein, dass das OP nach Binärdateien fragt, nicht nach Quellcode.
Caleb

0

Andere Leute haben die technischen Details gut abgedeckt, ich möchte einen weniger technischen Grund nennen, die UX / UI-Seite der Dinge:

Einmal schreiben, sich überall peinlich fühlen

Jedes Betriebssystem verfügt über eigene APIs für Benutzeroberflächen und Designstandards. Es ist möglich, eine Benutzeroberfläche für ein Programm zu schreiben und auf mehreren Betriebssystemen ausführen zu lassen, dies garantiert jedoch, dass das Programm überall fehl am Platz ist. Für eine gute Benutzeroberfläche müssen die Details für jede unterstützte Plattform angepasst werden.

Viele davon sind kleine Details, aber wenn Sie sie falsch verstehen, werden Sie Ihre Benutzer frustrieren:

  • Bestätigungsdialogfelder haben ihre Schaltflächen in Windows und OSX in unterschiedlicher Reihenfolge. Verstehen Sie dies falsch und Benutzer werden durch Muskelgedächtnis auf den falschen Knopf klicken. Windows hat "Ok", "Abbrechen" in dieser Reihenfolge. In OSX wurde die Reihenfolge vertauscht, und der Text der Schaltfläche zum Ausführen enthält eine kurze Beschreibung der auszuführenden Aktion: "Abbrechen", "In den Papierkorb verschieben".
  • Das Verhalten "Zurück" ist für iOS und Android unterschiedlich. iOS-Anwendungen zeichnen bei Bedarf ihre eigene Zurück-Schaltfläche, normalerweise oben links. Android verfügt je nach Bildschirmdrehung über eine eigene Schaltfläche unten links oder unten rechts. Schnelle Ports zu Android verhalten sich falsch, wenn die Schaltfläche "Zurück" des Betriebssystems ignoriert wird.
  • Das Momentum-Scrollen unterscheidet sich zwischen iOS, OSX und Android. Leider müssen Sie, wenn Sie keinen nativen UI-Code schreiben, wahrscheinlich Ihr eigenes Bildlaufverhalten schreiben.

Auch wenn es technisch möglich ist, eine UI-Codebasis zu schreiben, die überall ausgeführt wird, empfiehlt es sich, Anpassungen für jedes unterstützte Betriebssystem vorzunehmen.


-2

Ein wichtiger Unterschied besteht an dieser Stelle darin, den Compiler vom Linker zu trennen. Der Compiler erzeugt höchstwahrscheinlich mehr oder weniger die gleiche Ausgabe (wobei die Unterschiede hauptsächlich auf verschiedene #if WINDOWSs zurückzuführen sind). Der Linker hingegen muss alle plattformspezifischen Dinge erledigen - die Bibliotheken verknüpfen, die ausführbare Datei erstellen usw.

Mit anderen Worten, der Compiler kümmert sich hauptsächlich um die CPU-Architektur, da er den eigentlichen ausführbaren Code erzeugt und die Anweisungen und Ressourcen der CPU verwenden muss (beachten Sie, dass der IL- oder JVM-Bytecode von .NET als Anweisungssätze einer virtuellen CPU gelten würde in dieser Ansicht). Aus diesem Grund müssen Sie den Code beispielsweise für x86und separat kompilieren ARM.

Der Linker hingegen muss all diese Rohdaten und Anweisungen in ein Format bringen, das der Loader (heutzutage wäre dies fast immer das Betriebssystem) verstehen kann, und alle statisch verknüpften Bibliotheken verknüpfen (Dazu gehört auch der Code, der für die dynamische Verknüpfung, die Speicherzuweisung usw. erforderlich ist.)

Mit anderen Worten, Sie können den Code möglicherweise nur einmal kompilieren und sowohl unter Linux als auch unter Windows ausführen. Sie müssen ihn jedoch zweimal verknüpfen , um zwei verschiedene ausführbare Dateien zu erstellen . In der Praxis müssen Sie jetzt häufig auch Code berücksichtigen (hier kommen die (Vor-) Compiler-Direktiven zum Einsatz), sodass selbst das zweimalige Kompilieren von Einmal-Links nicht häufig verwendet wird. Ganz zu schweigen davon, dass die Leute das Kompilieren und Verknüpfen während des Builds als einen einzigen Schritt behandeln (genau wie Sie sich nicht mehr um die Teile des Compilers selbst kümmern).

DOS-Software war oft eher binär portierbar, aber Sie müssen verstehen, dass sie nicht nur für DOS oder Unix kompiliert wurde, sondern auch für einen bestimmten Vertrag, der für die meisten PCs im IBM-Stil üblich war Software-Interrupts. Hierfür war keine statische Verknüpfung erforderlich, da Sie nur die erforderlichen Register setzen, z. B. int 13hGrafikfunktionen aufrufen und die CPU gerade zu einem in der Interrupt-Tabelle deklarierten Speicherzeiger gesprungen ist. Natürlich war das Üben auch hier viel kniffliger, da Sie alle diese Methoden selbst schreiben mussten, um die Leistung auf dem Metall zu verbessern, aber das bedeutete im Grunde, das Betriebssystem insgesamt zu umgehen. Und natürlich gibt es etwas, das immer eine Interaktion mit der OS-API erfordert - die Programmbeendigung. Wenn Sie jedoch die einfachsten verfügbaren Formate verwendet haben (zCOMauf DOS, das keinen Header hat, nur Anweisungen) und nicht beenden wollte, na ja, viel Glück! Und natürlich können Sie die ordnungsgemäße Beendigung auch in der Laufzeit erledigen, sodass Sie Code für die Beendigung von Unix und DOS in derselben ausführbaren Datei haben und zur Laufzeit ermitteln können, welche zu verwenden ist :)


dies scheint nur Punkte in erklärt zu wiederholen diese und diese vor Antwort , die gestern veröffentlicht wurden
gnat
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.