Wie ist es möglich, ein Live-Update durchzuführen, während ein Programm ausgeführt wird?

15

Ich frage mich, wie Killeranwendungen wie Thunderbird oder Firefox über den Paketmanager des Systems aktualisiert werden können, während sie noch ausgeführt werden. Was passiert mit dem alten Code, während er aktualisiert wird? Was muss ich tun, wenn ich ein Programm a.out schreiben möchte, das sich selbst aktualisiert, während es ausgeführt wird?

ubuplex
quelle
@derobert Nicht genau: Dieser Thread geht nicht auf die Besonderheiten von ausführbaren Dateien ein. Es bietet relevante Hintergrundinformationen, ist jedoch kein Duplikat.
Gilles 'SO - hör auf böse zu sein'
@ Gilles Nun, wenn du den Rest des Zeuges verlässt, ist es zu breit. Hier gibt es (mindestens) zwei Fragen. Und die andere Frage ist ziemlich nah dran: Warum funktioniert das Ersetzen von Dateien zum Aktualisieren unter Unix?
Derobert
Wenn Sie dies wirklich mit Ihrem eigenen Code tun möchten, sollten Sie sich die Programmiersprache Erlang ansehen, in der "heiße" Code-Aktualisierungen eine Schlüsselfunktion darstellen. learnyousomeerlang.com/relups
mattdm
1
@ Gilles BTW: Nachdem ich den Aufsatz gesehen habe, den Sie als Antwort hinzugefügt haben, habe ich meine enge Abstimmung zurückgezogen. Sie haben diese Frage zu einem guten Ort gemacht, an dem Sie zeigen können, wie Updates funktionieren.
Derobert

Antworten:

19

Ersetzen von Dateien im Allgemeinen

Erstens gibt es verschiedene Strategien, um eine Datei zu ersetzen:

  1. Öffnen Sie die vorhandene Datei zum Schreiben, kürzen Sie sie auf die Länge 0 und schreiben Sie den neuen Inhalt. (Eine seltenere Variante besteht darin, die vorhandene Datei zu öffnen, den alten Inhalt mit dem neuen Inhalt zu überschreiben und die Datei auf die neue Länge zu kürzen, wenn sie kürzer ist.) In Shell-Begriffen:

    echo 'new content' >somefile
    
  2. Entfernen Sie die alte Datei und erstellen Sie eine neue Datei mit demselben Namen. In Shell-Begriffen:

    rm somefile
    echo 'new content' >somefile
    
  3. Schreiben Sie in eine neue Datei unter einem temporären Namen und verschieben Sie die neue Datei an den vorhandenen Namen. Durch das Verschieben wird die alte Datei gelöscht. In Shell-Begriffen:

    echo 'new content' >somefile.new
    mv somefile.new somefile
    

Ich werde nicht alle Unterschiede zwischen den Strategien auflisten, ich werde nur einige erwähnen, die hier wichtig sind. Wenn in Strategie 1 derzeit ein Prozess die Datei verwendet, wird der neue Inhalt beim Aktualisieren angezeigt. Dies kann zu Verwirrung führen, wenn der Prozess erwartet, dass der Dateiinhalt gleich bleibt. Beachten Sie, dass es sich nur um Prozesse handelt, bei denen die Datei geöffnet ist (wie in lsofoder in sichtbar) . Interaktive Anwendungen, bei denen ein Dokument geöffnet ist (z. B. Öffnen einer Datei in einem Editor), lassen die Datei normalerweise nicht geöffnet. Sie laden den Dateiinhalt während der Der Vorgang "Dokument öffnen" und sie ersetzen die Datei (unter Verwendung einer der oben genannten Strategien) während des Vorgangs "Dokument speichern"./proc/PID/fd/

Bei den Strategien 2 und 3 somefilebleibt die alte Datei während der Inhaltsaktualisierung geöffnet , wenn bei einem Prozess die Datei geöffnet ist. Bei Strategie 2 wird beim Entfernen der Datei tatsächlich nur der Eintrag der Datei im Verzeichnis entfernt. Die Datei selbst wird nur entfernt, wenn kein Verzeichniseintrag zu ihr führt (auf typischen Unix-Dateisystemen kann es mehr als einen Verzeichniseintrag für dieselbe Datei geben ) und kein Prozess hat sie geöffnet. Hier ist eine Möglichkeit, dies zu beobachten: Die Datei wird nur entfernt, wenn der sleepProzess beendet wird ( rmnur der Verzeichniseintrag wird entfernt).

echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .

Bei Strategie 3 wird beim Verschieben der neuen Datei auf den vorhandenen Namen der Verzeichniseintrag entfernt, der zum alten Inhalt führt, und ein Verzeichniseintrag erstellt, der zum neuen Inhalt führt. Dies geschieht in einer atomaren Operation, daher hat diese Strategie einen großen Vorteil: Wenn ein Prozess die Datei zu einem beliebigen Zeitpunkt öffnet, wird entweder der alte Inhalt oder der neue Inhalt angezeigt - es besteht kein Risiko, dass gemischte Inhalte angezeigt werden, oder die Datei nicht bestehender.

Ausführbare Dateien ersetzen

Wenn Sie Strategie 1 mit einer laufenden ausführbaren Datei unter Linux versuchen, wird eine Fehlermeldung angezeigt.

cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy

Eine „Textdatei“ ist eine Datei, die aus unklaren historischen Gründen ausführbaren Code enthält . Linux weigert sich, wie viele andere Unix-Varianten, den Code eines laufenden Programms zu überschreiben. Einige Unix-Varianten erlauben dies und führen zu Abstürzen, es sei denn, der neue Code ist eine sehr durchdachte Modifikation des alten Codes.

Unter Linux können Sie den Code einer dynamisch geladenen Bibliothek überschreiben. Es ist wahrscheinlich, dass das Programm, das es verwendet, abstürzt. (Möglicherweise können Sie dies nicht beobachten, sleepda der gesamte Bibliothekscode geladen wird, der beim Start benötigt wird. Probieren Sie ein komplexeres Programm aus, das nach dem Schlafengehen etwas Nützliches ausführt, z perl -e 'sleep 9; print lc $ARGV[0]'. B.. )

Wenn ein Interpreter ein Skript ausführt, wird die Skriptdatei auf normale Weise vom Interpreter geöffnet, sodass kein Schutz gegen das Überschreiben des Skripts besteht. Einige Interpreter lesen und analysieren das gesamte Skript, bevor sie mit der Ausführung der ersten Zeile beginnen, andere lesen das Skript nach Bedarf. Siehe Was passiert, wenn Sie ein Skript während der Ausführung bearbeiten? und wie geht Linux mit Shell-Skripten um? für mehr Details.

Die Strategien 2 und 3 sind auch für ausführbare Dateien sicher: Obwohl ausführbare Dateien (und dynamisch geladene Bibliotheken) keine offenen Dateien im Sinne eines Dateideskriptors sind, verhalten sie sich sehr ähnlich. Solange ein Programm den Code ausführt, bleibt die Datei auch ohne Verzeichniseintrag auf der Festplatte.

Aktualisieren einer Anwendung

Die meisten Paketmanager verwenden Strategie 3, um Dateien zu ersetzen, da der oben erwähnte große Vorteil darin besteht, dass das Öffnen der Datei zu jedem Zeitpunkt zu einer gültigen Version führt.

Anwendungs-Upgrades können zu Problemen führen, wenn das Upgrade einer einzelnen Datei nur aus einzelnen Dateien besteht (Programm, Bibliotheken, Daten usw.). Betrachten Sie die folgende Abfolge von Ereignissen:

  1. Eine Instanz der Anwendung wird gestartet.
  2. Die Anwendung wird aktualisiert.
  3. Die ausgeführte Instanzanwendung öffnet eine ihrer Datendateien.

In Schritt 3 öffnet die ausgeführte Instanz der alten Version der Anwendung eine Datendatei aus der neuen Version. Ob dies funktioniert oder nicht, hängt von der Anwendung ab, um welche Datei es sich handelt und in welchem ​​Umfang die Datei geändert wurde.

Nach einem Upgrade werden Sie feststellen, dass das alte Programm noch ausgeführt wird. Wenn Sie die neue Version ausführen möchten, müssen Sie das alte Programm beenden und die neue Version ausführen. Paketmanager beenden und starten Dämonen normalerweise bei einem Upgrade neu, lassen Endbenutzeranwendungen jedoch in Ruhe.

Einige Daemons haben spezielle Verfahren, um Upgrades durchzuführen, ohne den Daemon beenden und auf den Neustart der neuen Instanz warten zu müssen (was zu einer Dienstunterbrechung führt). Dies ist im Fall von init erforderlich , das nicht getötet werden kann. init-Systeme bieten eine Möglichkeit, den laufenden Instanzaufruf aufzufordern execve, sich selbst durch die neue Version zu ersetzen.

Gilles 'SO - hör auf böse zu sein'
quelle
"Verschieben Sie dann die neue Datei auf den vorhandenen Namen. Durch das Verschieben wird die alte Datei gelöscht." Das ist ein wenig verwirrend, da es wirklich nur ein ist unlink, wie Sie später behandeln. Vielleicht "ersetzt den vorhandenen Namen", aber das ist immer noch etwas verwirrend.
Derobert
@derobert Ich wollte mich nicht mit der Terminologie "Unlink" auseinandersetzen. Ich verwende "Löschen" in Bezug auf den Verzeichniseintrag, eine Feinfühligkeit, die später erklärt wird. Ist es zu diesem Zeitpunkt verwirrend?
Gilles 'SO- hör auf böse zu sein'
Vermutlich nicht verwirrend genug, um einen oder zwei zusätzliche Absätze zu rechtfertigen, in denen die Verknüpfung aufgehoben wird. Ich hoffe auf eine Formulierung, die nicht verwirrend, aber auch technisch korrekt ist. Vielleicht einfach nochmal "entfernen" benutzen, was du schon per Link erklären willst?
Derobert
2

Das Upgrade kann ausgeführt werden, während das Programm ausgeführt wird. Das angezeigte Programm ist jedoch die alte Version. Die alte Binärdatei bleibt auf der Festplatte, bis Sie das Programm schließen.

Erläuterung: Auf Linux-Systemen ist eine Datei nur eine Inode, die mehrere Verknüpfungen haben kann. Z.B. Das, was /bin/bashSie sehen, ist nur ein Link zu inode 3932163meinem System. Sie können herausfinden, zu welchem ​​Inode ein Link führt, indem Sie ls --inode /pathauf den Link klicken. Eine Datei (Inode) wird nur entfernt, wenn keine Links darauf verweisen, und wird von keinem Programm verwendet. Wenn der Paketmanager ein Upgrade durchführt, z. /usr/bin/firefoxwird zuerst die Verknüpfung aufgehoben (die feste Verknüpfung wird entfernt /usr/bin/firefox) und dann eine neue Datei mit dem Namen erstellt, die eine feste Verknüpfung /usr/bin/firefoxzu einem anderen Inode darstellt (demjenigen, der die neue firefoxVersion enthält ). Der alte Inode ist jetzt als frei markiert und kann zum Speichern neuer Daten wiederverwendet werden, verbleibt jedoch auf der Festplatte (Inodes werden nur erstellt, wenn Sie Ihr Dateisystem erstellen, und werden niemals gelöscht). Beim nächsten Start vonfirefoxwird der neue verwendet.

Wenn Sie ein Programm schreiben möchten, das sich während der Ausführung selbst "aktualisiert", ist die einzige mögliche Lösung, die ich mir vorstellen kann, regelmäßig den Zeitstempel der eigenen Binärdatei zu überprüfen. Wenn sie neuer als die Startzeit des Programms ist, laden Sie sich selbst neu.

psimon
quelle
1
Tatsächlich liegt es daran, wie das Löschen (Aufheben der Verknüpfung) von Dateien unter Unix funktioniert. Weitere Informationen finden Sie unter unix.stackexchange.com/questions/49299/…. Außerdem wird, zumindest unter Linux, der Fehler "Textdatei ausgelastet" angezeigt , wenn Sie nicht in eine laufende Binärdatei schreiben können.
Derobert
Seltsam ... Wie geht das dann zB. Debians aptUpgrade-Arbeit? Ich kann jedes laufende Programm problemlos aktualisieren, einschließlich Iceweasel( Firefox).
Psimon
2
APT (oder besser gesagt dpkg) überschreibt keine Dateien. Stattdessen werden die Verknüpfungen aufgehoben und eine neue unter demselben Namen eingefügt. In der Frage & Antwort, die ich verlinkt habe, finden Sie eine Erklärung.
Derobert
2
Es liegt nicht nur daran, dass es sich noch im RAM befindet, sondern auch auf der Festplatte. Die Datei wird erst tatsächlich gelöscht, wenn die letzte Instanz des Programms beendet wird.
Derobert
1
Auf der Website kann ich keine Bearbeitung vorschlagen. (Sobald Ihr Mitarbeiter hoch genug ist, können Sie nur Änderungen vornehmen und diese nicht mehr vorschlagen.) Als Kommentar: Eine Datei (Inode) auf einem Unix-System hat normalerweise einen Namen. Es kann aber noch mehr geben, wenn Sie Namen mit ln(harte Links) hinzufügen . Sie können Namen mit rm(Unlink) entfernen . Sie können eine Datei nicht direkt löschen, sondern nur ihre Namen entfernen. Wenn eine Datei keine Namen hat und zusätzlich nicht geöffnet ist, wird sie vom Kernel gelöscht. Ein laufendes Programm hat die Datei, von der es ausgeführt wird, geöffnet, sodass die Datei auch nach dem Entfernen aller Namen noch vorhanden ist.
Derobert
0

Ich frage mich, wie Killeranwendungen wie Thunderbird oder Firefox über den Paketmanager des Systems aktualisiert werden können, während sie noch ausgeführt werden. Nun, ich kann Ihnen sagen, dass dies nicht wirklich gut funktioniert ... Ich hatte eine schreckliche Firefox-Unterbrechung, wenn ich sie offen ließ, während ein Paket-Update lief. Ich musste es manchmal gewaltsam töten und neu starten, weil es so kaputt war, dass ich es nicht einmal richtig schließen konnte.

Was passiert mit dem alten Code, während er aktualisiert wird? Normalerweise wird unter Linux ein Programm in den Speicher geladen, sodass die ausführbare Datei auf der Festplatte nicht benötigt oder verwendet wird, während das Programm ausgeführt wird. Tatsächlich können Sie sogar die ausführbare Datei löschen, und das Programm sollte sich nicht darum kümmern ... Einige Programme benötigen jedoch möglicherweise die ausführbare Datei, und bestimmte Betriebssysteme (wie Windows) sperren die ausführbare Datei und verhindern das Löschen oder sogar das Umbenennen / Verschieben, während die Programm läuft. Firefox bricht ab, weil es eigentlich recht komplex ist und eine Reihe von Datendateien verwendet, die ihm sagen, wie er seine GUI (Benutzeroberfläche) erstellt. Während einer Paketaktualisierung werden diese Dateien überschrieben (aktualisiert). Wenn also eine ältere (im Speicher befindliche) Firefox-Programmdatei versucht, die neuen GUI-Dateien zu verwenden, können seltsame Dinge passieren ...

Was muss ich tun, wenn ich ein Programm a.out schreiben möchte, das sich selbst aktualisiert, während es ausgeführt wird? Es gibt bereits viele Antworten auf Ihre Frage. Überprüfen Sie dies: /programming/232347/how-should-i-implement-an-auto-updater Übrigens sind Fragen zur Programmierung bei StackOverflow besser gestellt.

Rouben Tchakhmakhtchian
quelle
1
Ausführbare Dateien werden tatsächlich nach Bedarf ausgelagert (getauscht). Sie werden nicht vollständig in den Arbeitsspeicher geladen und können aus dem Arbeitsspeicher gelöscht werden, wenn das System RAM für etwas anderes benötigt. Die alte Version verbleibt tatsächlich auf der Festplatte. Siehe unix.stackexchange.com/questions/49299/… . Zumindest unter Linux können Sie nicht in eine laufende ausführbare Datei schreiben, da die Fehlermeldung "Textdatei ausgelastet" angezeigt wird. Sogar root kann das nicht. (In Bezug auf Firefox haben Sie jedoch völlig recht).
Derobert