original in es Luis Colorado
en to de Philipp Gühring
Dieser Vorgang besteht aus den folgenden Schritten:
Erstellung des Sourcecodes in einer Hochsprache mit einem Texteditor. Sehr große Programme können schwer zu handhaben sein, wenn wir versuchen, sie in eine große Datei zu zwängen. Aus diesem Grund wird der Sourcecode in funktionale Module geteilt, die aus einem oder mehreren Dateien mit Sourcecode bestehen. Es müssen nicht alle Module in derselben Programmiersprache geschrieben sein, weil manche Sprachen geeigneter sind, Teilprobleme zu lösen, als andere.
Nachdem die Dateien mit den Sourcecodes für das Programm gemacht wurden, muß der Sourcecode in für die Maschine ausführbare Codesegmente übersetzt werden. Dieser Code wird als Objektcode/object code bezeichnet. Dieser Code führt dieselben Operationen wie der Sourcecode aus, mit dem Unterschied, daß er in einer speziellen Sprache ist, die von der Maschine direkt ausgeführt werden kann. Die Übersetzung von Sourcecode in Objectcode wird als kompilieren/compilation bezeichnet. Der kompilierte Objektcode enthält ein Programm, eine Subroutine, Variablen, ... -- generell die Teile eines Programms, die schon übersetzt wurden und an die nächste Stufe geliefert werden können.
Nachdem alle Dateien in Maschinensprache generiert wurden, werden alle zusammengefügt, mit dem sogenannten Linker. Bei diesem Vorgang werden alle Referenzen von einem Codeteil auf einen anderen Codeteil aufgelöst/resolved (z.B.: Unterprogrammaufrufe, Variablen, ...). Das Ergebnis ist ein Programm, daß normalerweise geladen und direkt gestartet werden kann.
Das Laden des Programmes wird von einem speziellen Teil der Software ausgeführt, einem wichtigen Teil des Betriebssystems, das im Falle von Linux der Systemcall exec() ist. Diese Funktion sucht die Datei, reserviert Hauptspeicher für das Programm, lädt die benötigten Teile des Dateiinhalts (die Codeteile, die Startwerte der Variablen, ...) und übergibt die Kontrolle des Prozessors an das Programm, indem an den Einsprungpunkt (Entry Point) des Programmes gesprungen wird. Die Position des Einsprungpunktes ist in der Datei festgelegt.
Am Anfang wurden die Programme direkt als Maschinencode geschrieben. Später erkannte man, daß Sourcecode von einer Hochsprache und die darauffolgende Übersetzung in Maschinencode automatisiert werden kann. Dies steigerte die Produktivität der Software.
Durch die Errungenschaft des Kompilierens von Programmen (es war natürlich ein weiter und komplizierter Weg), bestand der Vorgang der Programmgeneration aus dem Schreiben des Sourcecodes, des Kompilierens und des Ausführens als letzten Schritt.
Doch bald merkte man, daß der Kompilierungsvorgang teuer war, und zuviele Ressourcen (vor allem Prozessorzeit) brauchte, und daß viele Funktionen in vielen Programmen an verschiedenen Stellen verwendet wurden. Zusätzlich mußte immer das ganze Programm neu kompiliert werden, wenn jemand an einer Stelle etwas geändert hatte, um den neuen Code einzufügen.
Das war der Grund, das Kompilieren von Modulen einzuführen. Dies besteht aus der Trennung des Hauptprogramms von den verschiedenen Funktionen, die oft wiederverwendet werden. Diese Funktionen wurden schon vorkompiliert und an einem speziellen Platz archiviert, der "precursor der Bibliothek" genannt wird.
Dadurch kann man dann diese Funktionen verwenden, ohne ihren Code immer wieder und wieder zu verwenden. Trotzdem war es kompliziert, weil der Programmierer jedesmal genau angeben muß, welche Funktion wo zu finden ist.
Die Bibliotheken haben sich seit damals nur insofern verändert, daß am Anfang der Datei ein Index eingeführt wurde, in dem Beschreibungen der Module und Bezeichner, die der Linker auflösen muß, vorhanden sind. Dadurch muß der Linker nicht mehr die ganze Bibliothek durchsuchen. Dies geschieht unter Linux mit dem Programm ranlib. Diese Art der Bibliotheken werden statische Bibliotheken/static libraries genannt.
Eine Neuerung kam durch die ersten Multitasking Systeme, da die laufenden Programme den Code teilten, um möglichst wenig Arbeitsspeicher zu benötigen. Sobald 2 Kopien desselben Codes verwendet werden ist es interessant, den Code nur einmal im Speicher zu halten, da die meisten Programme ihren Code nicht ändern. Diese Idee ermöglicht es bei großen Multi-User Systemen viel Speicher zu sparen.
Später fiel auf, daß oft verschiedene Programme dieselben Bibliotheken eingebunden hatten, aber da es ja verschiedene Programme waren, verwendeten sie unterschiedliche Bereiche der Bibliotheken. Die Hauptprogramme waren auch nicht identisch (es waren ja unterschiedliche Programme), dadurch konnte nichts eingespart werden. Heute teilen sich unterschiedliche Programme die gesamte Bibliothek, ohne identischen Programmcode haben zu müssen.
Dadurch wurde der Vorgang des Generierens komplizierter. Das ausführbare Programm ist nicht fertig gelinkt, sondern referenziert die Bezeichner der Bibliothek. Diese Referenzen werden dann erst vom Programmlader exec() aufgelöst werden. Der Linker erkennt, daß gegen eine Shared Library gelinkt werden soll, und fügt den Code der Bibliothek nicht ein. Das System, der Kernel, erkennt dann, daß das gestartete Programm eine geteilte Bibliothek erwartet, und lädt die erwartete Bibliothek, wenn diese nicht schon geladen ist. Der Programmcode der Bibliothek kommt in einen geteilten Speicher (Shared Memory), die Variablen der Bibliothek werden im privaten Programmspeicher angelegt, ... Dieser Vorgang wird bei jedem Programmstart gemacht, und die ganze Prozedur wird dadurch komplizierter.
Natürlich verhält sich der Linker bei normalen statischen Bibliotheken genau wie vorher.
Eine Shared Library ist kein Archiv von Dateien mehr, die Objektcode beinhalten, sondern nur eine Datei mit Objektcode. Beim Linken einer Shared Library mit dem Programm untersucht der Linker nicht, welche Teile der Bibliothek gebraucht werden, und welche nicht. Er kontrolliert nur, ob alle Referenzen aufgelöst werden. Man könnte ein Archiv von mehreren Bibliotheken machen, doch ist dies nicht üblich, da eine Shared Library oft das Resulat einiger gelinkter Module ist. Man könnte eine Shared Library also auch Shared Object nennen.
Die Shared Libraries sind im Gegensatz dazu verschiebbare Objekte, die mit einem speziellen Code versehen sind. Der Linker ld(1) fügt die Bibliotheken nicht zum Programm hinzu, sondern verwendet die Bezeichner der Bibliothek, und fügt die von der Bibliothek benötigten hinzu, und fährt fort, ohne Code zum Programm hinzuzufügen. Der Linker erkennt Shared Libraries an der Endung .so (nicht so.xxx.yyy, dazu kommen wir später).
ld(1) unterstützt verschiedene Optionen, die sein Verhalten beeinflussen, aber wir beschäftigen uns hier nur mit Bibliothek-relevanten. ld(1) wird nicht direkt vom Anwender, sondern vom Compiler gcc(1) im letzten Arbeitsschritt gestartet. Oberflächliches Wissen über die modus operandis des Linkers wird uns beim verstehen der Bibliotheken unter Linux helfen.
ld(1) braucht als Argumente die Liste der Objekte, die er zu einem Programm verbinden soll. Diese Objekte können in beliebiger Reihenfolge(*) angegeben werden, solange die Konventionen (.a für statische Bibliotheken, .so für Shared Libraries und .o für normale Objektdateien) eingehalten werden.
(*) Das stimmt nicht ganz. ld(1) inkludiert nur die Module, die Referenzen auflösen, die er im Moment des Einfügens der Bibliothek braucht. Es können aber immer Referenzen von einem Modul, das später eingebunden wird gebraucht werden. Die Reihenfolge kann also wichtig werden.
Auf der anderen Seite erlaubt ld(1) das Einbinden von Standardbibliotheken mit den Optionen -l und -L.
Aber... Wo ist der Unterschied zu Standardbibliotheken? Es gibt keinen. ld(1) sucht nach den Standardbibliotheken in vorgegebenen Verzeichnissen. Die als Parameter angegebenen Objekte werden mit ihrem Dateinamen gesucht.
Die Bibliotheken werden normalerweise in den Verzeichnisen /lib und /usr/lib gesucht. (Obwohl ich hörte, daß manche Version von ld(1) an anderen Plätzen suchen). Der Parameter -L erlaubt uns weitere Verzeichnisse anzugeben, in denen nach Bibliotheken gesucht wird. Für jedes weitere Verzeichnis muß -L Verzeichnis angegeben werden. Die Standardbibliotheken werden mit der Option -l Dateiname angegeben. ld(1) wird in den Verzeichnissen den Dateinamen libName.so suchen. Wenn nichts gefunden wird, wird nach libName.a gesucht, der zugehörigen statischen Bibliothek.
Genauer gesagt gibt es 2 Module für das dynamische Linken: /lib/ld.so (für Bibliotheken, die noch das alte a.out Format verwenden), und /lib/ld-linux.so (für Bibliotheken, die das neue ELF Format verwenden).
Diese Module sind speziell, weil sie jedesmal geladen werden, wenn ein Programm dynamisch gelinkt wird. Ihre Namen sind standardisiert. Wenn die beiden Bibliotheken umbenannt würden, könnte man alle weiteren Programme, die Shared-Libraries verwenden wollen nicht mehr starten, weil diese Module alle Referenzen bei Laufzeit auflösen.
Das letzte Modul verwendet die Datei /etc/ld.so.cache, um sich für jede Bibliothek die passendste Datei zu merken, die diese Bibliothek enthält. Wir werden darauf später zurückkommen.
Eine leider oft gesehene Meldung ist: 'library libX11.so.3 not found,' wobei man dann mit der Frustration zurückbleibt, die Bibliothek libX11.so.6 zu haben, und nichts machen zu können. Wie ist es möglich, daß ld.so(8) die Bibliotheken libpepe.so.45.0.1und libpepe.so.45.22.3 als austauschbar anerkennt, aber libpepe.so.46.22.3? nicht?
Unter Linux (und allen anderen Betriebssystemen, die das ELF Format verwenden) werden die Bibliotheken durch eine Sequenz von Zeichen identifiziert, die sie unterscheiden, dem 'soname'.
Der soname ist in der Bibliothek eingebettet, und die Sequenz wird erstellt, wenn die Objekte zur Bibliothek verarbeitet werden. Wenn die Shared-Library erstellt wird, muß man an ld(1) die Option (-soname Name) übergeben, um den Wert der Sequenz zu bestimmen.
Diese Sequenz von Zeichen wird vom dynamischen Lader zur identifizierung
der Shared Library, die geladen werden soll, und der zugehörigen Datei verwendet.
Der Vorgang ist wie folgt:
Ld-linux.so erkennt, daß das Programm eine Bibliothek braucht,
und bestimmt den soname. Dann wird die Datei /etc/ld.so.cache
durchsucht, nach dem soname, um den Namen der Datei zu finden, in der die
Bibliothek ist. Als nächstes wird der soname der Bibliothek in der Datei
mit dem vom Programm angeforderten soname verglichen, und wenn
sie übereinstimen, wars das. Wenn sie nicht stimmen, wird weitergesucht,
wenn keine Bibliothek gefunden wird, wird eine Fehlermeldung ausgegeben:
'libXXX.so.Y' not found.
Dies löst natürlich Verwirrung aus, wenn man den Dateinamen einer Bibliothek ändert, und das Problem natürlich weiterbesteht. Aber es ist keine gute Idee, den soname zu ändern, weil es eine Konvention für die Erstellung des soname in der Linux Gemeinde gibt:
Der soname einer Bibliothek muß die Bibliothek UND die SCHNITTSTELLE identfizieren. Wenn nur interne Details einer Bibliothek geändert werden, aber die Schnittstelle intakt bleibt (Anzahl der Funktionen, Variablen, Parameter der Funktionen), also die beiden Bibliotheken austauschbar sind. Andererseits, wenn wir Funktionen hinzufügen, löschen oder generell die SCHNITTSTELLE ÄNDERN, dann ist die Bibliothek nicht mehr austauschbar. Zum Beispiel wurde von libX11.so.3 auf libX11.so.6, ein Teil der Änderungen von X11R5 nach X11R6, neue Funktionen eingeführt und die Schnittstelle geändert. Die Änderung von X11R6-v3.1.2 nach X11R6-v3.1.3 beinhaltet keine Änderungen der Schnittstelle, dadurch werden beide Bibliotheken denselben soname haben. Daher fließt die gesamte Versionsnummer in den Dateinamen ein, und nur die Hauptnummern in den soname.
Das Laden des Programmes passiert in mehreren Stufen. Eine für das Laden des Hauptprogramm, und für jede dynamische Bibliothek wieder ein. Wir werden sehen, daß dieses für dynamische Bibliotheken paßt, weil es hier aufhört unbequem zu sein, und anfängt ein Vorteil zu werden.
Die dynamische Bibliothek muß verschiebbaren Code beinhalten, da die Adressen im virtuellen Adreßraum des Prozesses erst zur Laufzeit bekannt sind. Der Compiler muß also Platz reservieren, um die Bibliothek bei Laufzeit einhängen zu können. Dadurch haben wir eine Möglichkeit weniger, den Code zu optimieren, aber das ist nur ein geringes Problem.
Damit eine dynamische Bibliothek sinnvoll ist, muß sie die meiste Zeit von irgendwelchen Programmen verwenden (dies vermeidet das Problem des erneuten Ladens des Programmcodes der Bibliothek nach dem Tod des Prozesses, der die Bibliothek gestartet hat. Während andere Prozesse die Bibliothek noch verwenden bleibt sie im Speicher).
Die Shared Libraries werden immer ganz in den Speicher geladen (nicht nur die benötigten Module), dadurch sollte die Bibliothek ganzheitlich sinnvoll sein. Das schlechteste Beispiel für eine dynamische Bibliothek ist eine, bei der nur eine Funktion verwendet wird, und 90% der Bibliothek so gut wie nie verwendet werden.
Ein gutes Beispiel für dynamische Bibliotheken ist die C Standard Bibliothek, die von fast allen Programmen verwendet wird, die in C geschrieben werden. Im Durchschnitt werden alle Funktionen immer wieder verwendet.
In einer statischen Bibliothek ist es unwichtig, ob Funktionen, die selten verwendet werden dabei sind, solange sie in eigenen Modulen untergebracht werden, damit sie nicht mitgelinkt werden.
Dieser Schritt ist wichtig, da in einem statisch gelinkten Programm die Positionen der Bibliotheks Objekte bei Linkzeit berechnet wird. In den alten a.out Programmen war es unmöglich, diesen Schritt durchzuführen, was dazu führte, daß die Shared Libraries an einer fixen Stelle im Adressraum stehen mußten. Da die Programme Probleme bekamen, wenn sie zwei verschiedene überlappende Bibliotheken geladen hatten, wurde eine Liste eingeführt, in der alle Bibliotheken eingetragen wurden, damit sie sich nicht in die Quere kommen.
Jetzt mit den dynamischen Bibliotheken wird diese offizielle Liste nicht mehr benötigt, weil die Programme so kompiliert werden, daß sie an eine beliebige Stelle geladen werden können.
gcc -shared -o libName.so.xxx.yyy.zzz-Wl,-soname,libName.so.xxxWie sie sehen schaut es nach einer ganz normalen Linkoperation aus, mit Ausnahme der Serie von Optionen die zum Erstellen einer Shared Library führen. Erklären wir die Optionen mal Schritt für Schritt:
-o libName.so.xxx.yyy.zzz
Dies ist der Name der Ausgabedatei (Shared Library). Es ist nicht notwendig,
die Namens-Konvention zu erfüllen, aber wenn wir wollen, daß diese Bibliothek
ein Standard für zukünftige Entwicklungen wird, sollten wir die Konventionen
einhalten.
-Wl,-soname,libName.so.xxx
Die Option -Wl sagt gcc(1) daß die nächsten Optionen
(mit einem Komma getrennt) für den Linker gedacht sind.
Dieser Mechanismus ist von gcc(1), um Argumente an ld(1)
weiterzugeben. Oben geben wir die folgenden Argumente dem Linker weiter:
-soname libName.so.xxxDiese Option ändert den soname der Bibliothek so daß es nur von den Programmen verwendet werden kann, die eine Bibliothek mit diesem soname anfordern.
Um ein Programm zu kompilieren, daß unsere neue Bibliothek braucht, bräuchte man folgende Zeile:
gcc -o Programm libName.so.xxx.yyy.zzzoder wenn die Bibliothek schon am richtigen Platz ist (/usr/lib), würde folgendes reichen:
gcc -o Programm -lName(wenn die Bibliothek stattdessen in /usr/local/lib wäre, käme noch die Option '-L/usr/local/lib' hinzu). Um die Bibliothek zu installieren, sind folgende Schritte notwendig:
Die Bibliothek in das Verzeichnis /lib oder /usr/lib kopieren. Wenn man sich entschließt, die Bibliothek an eine andere Stelle zu kopieren (zum Beispiel /usr/local/lib),kann man sich nicht sicher sein, daß der Linker ld(1) sie auch sicher findet.
Dann muß man ldconfig(1) starten, um den symbolischen Link von libName.so.xxx.yyy.zzz nach libName.so.xxx. zu generieren. Dieser Schritt wird uns zeigen, ob alle vorhergehenden Schritte erfolgreich waren, und die Bibliothek als dynamische Bibliothek erkannt wird. Die Art, wie Programme gelinkt werden, wird hiervon nicht beeinflußt, nur das Laden der Bibliotheken bei Laufzeit wird beeinflusst.
Erstelle einen symbolischen Link von libName.so.xxx.yyy.zzz (oder von libName.so.xxx, der soName) nach libName.so, um dem Linker das Finden der Bibliothek mit der Option -l zu ermöglichen. Damit dieser Mechanismus funktioniert ist es nötig, daß der Name der Bibliothek dem Muster libName.so entspricht.
Anmerkung: Die Suchfunktion des Linkers schaut zuerst nach einer Datei namens libName.so, dann nach libName.a . Wenn wir beide Bibliotheken gleich benennen, ist es nicht möglich, die beiden zu unterscheiden.
Wenn man beide Versionen einer Bibliothek braucht wird empfohlen, die statische libName_s.a, und die dynamische libName.so zu nennen. Wenn man nun gegen die statische Version linken will:
gcc -o program -lName_sUnd bei der dynamischen geht es so:
gcc -o program -lName
Um die Sourcen zu kompilieren werden wir keine besondern Maßnahmen anwenden. In der selben Weise, wie die Objekte während dem Linken gefunden werden ist es nicht nötig, mit -f PIC zu arbeiten, obwohl es immer noch möglich ist, es so anzugeben.
Die statische Bibliothek wird im Format libName.a benannt, wenn man nur eine statische Bibliothek machen will. Wenn man auch eine dynamische Bibliothek machen will, sollte die statische Bibliothek libName_s.a benannt werden, um leichter kontrollieren zu können, welche Bibliothek verwendet wird.
Betrachten wir nun die Option -static. Diese Option kontrolliert das Laden der Module /lib/ld-linux.so, verändert die Suchreihenfolge der Bibliotheken aber nicht. Wenn man -static schreibt, und ld(1) findet eine dynamische Bibliothek, dann wird er diese nehmen, statt nach einer statischen zu suchen. Dies führt zu Laufzeitfehlern wegen der Verweise auf Routinen in einer Bibliothek, die nicht zum ausführbaren Programm gehören -- das Modul für automatisches dynamisches Laden wird nicht mitgelinkt, und dadurch kann der Prozeß nicht weiterlaufen.
Angenommen, wir wollen ein Programm verteilen, welches eine Bibliothek braucht, die wir aber nur an das Programm statisch gebunden verteilen dürfen. Ein Beispiel dafür wären die Programme, die auf Motif aufbauen.
Um diese Art von Software zu produzieren gibt es zwei Möglichkeiten. Die erste ist es, ein ausführbares, statisch gelinktes Progrmam (indem man nur .a Bibliotheken verwendet, und den dynamischen Lader vermeidet). Diese Art von Programmen werden nur einmal geladen und benötigen keine installierten Bibliotheken im System, nicht einmal /lib/ld-linux.so. Trotzdem sind sie ein Nachteil, weil sie alle benötigten Codeteile in einer Binärdatei haben, und daher diese Binärdateien sehr groß werden. Die nächste Möglichkeit ist es eine dynamisch gelinktes Programm zu machen, unter der Annahme, daß auf dem Zielsystem alle benötigten dynamischen Bibliotheken verfügbar sind. Die ausführbare Datei wird dabei meistens sehr klein, doch ist es nicht immer möglich, wenn nicht alle Bibliotheken verfügbar sind. (Es gibt eine Menge Leute, die Motif nicht haben!).
Die dritte Möglichkeit ist eine vermischte Distribution, in der einige Bibliotheken dynamisch und andere statisch gebunden sind. In diesem Fall wird man die konfliktverursachenden Bibliotheken statisch binden, und alle anderen dynamisch. Dies ist eine sehr brauchbare Form der Software-Distribution.
Als Beispiel, kann man folgendes Programm auf 3 verschiedene Arten machen:
gcc -static -o Programm.statisch Programm.o -lm_s -lXm_s -lXt_s -lX11_s -lXmu_s -lXpm_s gcc -o Programm.dynamisch program.o -lm -lXm -lXt -lX11 -lXmu -lXpm gcc -o Programm.gemischt Programm.o -lm -lXm_s -lXt -lX11 -lXmu -lXpmIm dritten Fall wird nur die Motif Bibliothek Motif (-lXm_s) statisch gelinkt, alle anderen werden dynamisch gelinkt. Die Umgebung in der das Programm laufen soll muß nun passende Versionen der Bibliotheken libm.so.xx libXt.so.xx libX11.so.xx libXmu.so.xx y libXpm.so.xx anbieten.