Comment est-il possible de faire une mise à jour en direct pendant l'exécution d'un programme?

15

Je me demande comment des applications tueuses telles que Thunderbird ou Firefox peuvent être mises à jour via le gestionnaire de paquets du système alors qu'elles sont toujours en cours d'exécution. Que se passe-t-il avec l'ancien code pendant sa mise à jour? Que dois-je faire lorsque je veux écrire un programme a.out qui se met à jour pendant son exécution?

ubuplex
la source
@derobert Pas exactement: ce fil ne rentre pas dans les particularités des exécutables. Il fournit des informations pertinentes, mais ce n'est pas un doublon.
Gilles 'SO- arrête d'être méchant'
@ Gilles Eh bien, si vous laissez dans le reste des choses, c'est trop large. Il y a deux (au moins) questions ici. Et l'autre question est assez proche, elle demande pourquoi le remplacement de fichiers pour la mise à jour fonctionne sous Unix.
derobert
Si vous voulez vraiment le faire avec votre propre code, vous voudrez peut-être regarder le langage de programmation Erlang, où les mises à jour de code "à chaud" sont une fonctionnalité clé. Learnyousomeerlang.com/relups
mattdm
1
@Gilles BTW: Ayant vu l'essai que vous avez ajouté en réponse, j'ai retiré mon vote serré. Vous avez transformé cette question en un bon endroit pour indiquer à quiconque souhaite savoir comment fonctionnent les mises à jour.
derobert

Réponses:

20

Remplacement de fichiers en général

Tout d'abord, il existe plusieurs stratégies pour remplacer un fichier:

  1. Ouvrez le fichier existant pour l'écriture, tronquez-le à la longueur 0 et écrivez le nouveau contenu. (Une variante moins courante consiste à ouvrir le fichier existant, à remplacer l'ancien contenu par le nouveau contenu, à tronquer le fichier à la nouvelle longueur s'il est plus court.) En termes de shell:

    echo 'new content' >somefile
    
  2. Supprimez l'ancien fichier et créez un nouveau fichier du même nom. En termes de coquille:

    rm somefile
    echo 'new content' >somefile
    
  3. Écrivez dans un nouveau fichier sous un nom temporaire, puis déplacez le nouveau fichier vers le nom existant. Le déplacement supprime l'ancien fichier. En termes de coquille:

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

Je ne vais pas énumérer toutes les différences entre les stratégies, je vais juste en mentionner quelques-unes qui sont importantes ici. Avec la stratégie 1, si un processus utilise actuellement le fichier, le processus voit le nouveau contenu lors de sa mise à jour. Cela peut entraîner une certaine confusion si le processus s'attend à ce que le contenu du fichier reste le même. Notez que cela concerne uniquement les processus qui ont le fichier ouvert (comme visible dans lsofou dans ; les applications interactives qui ont un document ouvert (par exemple, ouvrir un fichier dans un éditeur) ne gardent généralement pas le fichier ouvert, elles chargent le contenu du fichier pendant la "Ouvrir le document" et ils remplacent le fichier (en utilisant l'une des stratégies ci-dessus) pendant l'opération "enregistrer le document"./proc/PID/fd/

Avec les stratégies 2 et 3, si un processus a le fichier somefileouvert, l'ancien fichier reste ouvert pendant la mise à niveau du contenu. Avec la stratégie 2, l'étape de suppression du fichier ne supprime en fait que l'entrée du fichier dans le répertoire. Le fichier lui-même n'est supprimé que s'il ne contient aucune entrée de répertoire (sur les systèmes de fichiers Unix typiques, il peut y avoir plusieurs entrées de répertoire pour le même fichier ) et qu'aucun processus ne l'a ouvert. Voici un moyen d'observer cela - le fichier n'est supprimé que lorsque le sleepprocessus est tué ( rmsupprime uniquement son entrée de répertoire).

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

Avec la stratégie 3, l'étape de déplacement du nouveau fichier vers le nom existant supprime l'entrée de répertoire menant à l'ancien contenu et crée une entrée de répertoire menant au nouveau contenu. Cela se fait en une seule opération atomique, donc cette stratégie a un avantage majeur: si un processus ouvre le fichier à tout moment, il verra l'ancien contenu ou le nouveau contenu - il n'y a aucun risque d'obtenir du contenu mixte ou du fichier non existant.

Remplacement des exécutables

Si vous essayez la stratégie 1 avec un exécutable en cours d'exécution sur Linux, vous obtiendrez une erreur.

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

Un «fichier texte» signifie un fichier contenant du code exécutable pour des raisons historiques obscures . Linux, comme beaucoup d'autres variantes d'Unix, refuse d'écraser le code d'un programme en cours d'exécution; quelques variantes Unix le permettent, entraînant des plantages à moins que le nouveau code ne soit une modification très bien pensée de l'ancien code.

Sous Linux, vous pouvez remplacer le code d'une bibliothèque chargée dynamiquement. Il est susceptible d'entraîner une panne du programme qui l'utilise. (Vous ne pourrez peut-être pas observer cela avec sleepcar il charge tout le code de bibliothèque dont il a besoin au démarrage. Essayez un programme plus complexe qui fait quelque chose d'utile après avoir dormi, comme perl -e 'sleep 9; print lc $ARGV[0]'.)

Si un interpréteur exécute un script, le fichier de script est ouvert de manière ordinaire par l'interpréteur, il n'y a donc aucune protection contre l'écrasement du script. Certains interprètes lisent et analysent l'intégralité du script avant de commencer à exécuter la première ligne, d'autres lisent le script si nécessaire. Voir Que se passe-t-il si vous modifiez un script pendant l'exécution? et comment Linux traite-t-il les scripts shell? pour plus de détails.

Les stratégies 2 et 3 sont également sans danger pour les exécutables: bien que l'exécution d'exécutables (et de bibliothèques chargées dynamiquement) ne soit pas des fichiers ouverts dans le sens d'avoir un descripteur de fichier, ils se comportent de manière très similaire. Tant qu'un programme exécute le code, le fichier reste sur le disque même sans entrée de répertoire.

Mettre à niveau une application

La plupart des gestionnaires de packages utilisent la stratégie 3 pour remplacer les fichiers, en raison de l'avantage majeur mentionné ci-dessus - à tout moment, l'ouverture du fichier conduit à une version valide de celui-ci.

Là où les mises à niveau d'application peuvent se briser, c'est que si la mise à niveau d'un fichier est atomique, la mise à niveau de l'application dans son ensemble ne l'est pas si l'application se compose de plusieurs fichiers (programme, bibliothèques, données,…). Considérez la séquence d'événements suivante:

  1. Une instance de l'application est démarrée.
  2. L'application est mise à niveau.
  3. L'application d'instance en cours d'exécution ouvre l'un de ses fichiers de données.

À l'étape 3, l'instance en cours d'exécution de l'ancienne version de l'application ouvre un fichier de données à partir de la nouvelle version. Que cela fonctionne ou non dépend de l'application, de quel fichier il s'agit et de la quantité de fichier modifié.

Après une mise à niveau, vous remarquerez que l'ancien programme est toujours en cours d'exécution. Si vous souhaitez exécuter la nouvelle version, vous devrez quitter l'ancien programme et exécuter la nouvelle version. Les gestionnaires de packages tuent et redémarrent généralement les démons lors d'une mise à niveau, mais laissent les applications des utilisateurs finaux tranquilles.

Quelques démons ont des procédures spéciales pour gérer les mises à niveau sans avoir à tuer le démon et à attendre que la nouvelle instance redémarre (ce qui provoque une interruption de service). Cela est nécessaire dans le cas d' init , qui ne peut pas être tué; Les systèmes init fournissent un moyen de demander à l'appel d'instance en cours execvede se remplacer par la nouvelle version.

Gilles 'SO- arrête d'être méchant'
la source
"puis déplacez le nouveau fichier vers le nom existant. Le déplacement supprime l'ancien fichier." C'est un peu déroutant, car c'est vraiment juste un unlink, comme vous le verrez plus tard. Peut-être que "remplace le nom existant", mais cela reste quelque peu déroutant.
derobert
@derobert Je ne voulais pas approfondir la terminologie de «dissociation». J'utilise «supprimer» en référence à l'entrée de répertoire, une subtilité qui sera expliquée plus loin. Est-ce déroutant à ce stade?
Gilles 'SO- arrête d'être méchant'
Probablement pas assez confus pour justifier un ou deux paragraphes supplémentaires expliquant la dissociation. J'espère une formulation qui ne prête pas à confusion, mais qui est également techniquement correcte. Peut-être utilisez-vous à nouveau "supprimer", que vous avez déjà mis un lien pour expliquer?
derobert
3

La mise à niveau peut être exécutée pendant l'exécution du programme, mais le programme en cours d'exécution que vous voyez est en fait l'ancienne version de celui-ci. L'ancien binaire reste sur le disque jusqu'à ce que vous fermiez le programme.

Explication: sur les systèmes Linux, un fichier est juste un inode, qui peut avoir plusieurs liens vers celui-ci. Par exemple. le que /bin/bashvous voyez est juste un lien vers inode 3932163mon système. Vous pouvez trouver quel inode fait quelque chose de lien vers en émettant ls --inode /pathsur le lien. Un fichier (inode) n'est supprimé que s'il n'y a aucun lien pointant vers lui et n'est utilisé par aucun programme. Lorsque le gestionnaire de paquets met à niveau par exemple. /usr/bin/firefox, il délie d'abord (supprime le lien dur /usr/bin/firefox), puis crée un nouveau fichier appelé /usr/bin/firefoxqui est un lien dur vers un autre inode (celui qui contient la nouvelle firefoxversion). L'ancien inode est désormais marqué comme libre et peut être réutilisé pour stocker de nouvelles données mais reste sur le disque (les inodes ne sont créés que lorsque vous construisez votre système de fichiers et ne sont jamais supprimés). Au prochain démarrage defirefox, le nouveau sera utilisé.

Si vous voulez écrire un programme qui se "met à jour" pendant son exécution, la seule solution possible à laquelle je peux penser est de vérifier périodiquement l'horodatage de son propre fichier binaire, et s'il est plus récent que l'heure de démarrage du programme, puis rechargez-vous.

psimon
la source
1
En fait, cela est dû au fonctionnement de la suppression (dissociation) des fichiers sous Unix; voir unix.stackexchange.com/questions/49299/… De plus, au moins sous Linux, vous ne pouvez pas réellement écrire sur un binaire en cours d'exécution, vous obtiendrez une erreur "fichier texte occupé".
derobert
Étrange ... Alors, comment par exemple. Le apttravail de mise à niveau de Debian ? Je peux mettre à niveau n'importe quel programme en cours d'exécution sans problème, y compris Iceweasel( Firefox).
psimon
2
APT (ou plutôt dpkg) n'écrase pas les fichiers. Il les dissocie à la place et en place un nouveau sous le même nom. Voir la question et la réponse à laquelle j'ai lié pour une explication.
derobert
2
Ce n'est pas seulement parce qu'il est toujours en RAM, il est toujours sur le disque. Le fichier n'est pas réellement supprimé jusqu'à la fin de la dernière instance du programme.
derobert
2
Le site ne me permet pas de suggérer une modification (une fois que votre représentant est suffisamment élevé, vous effectuez simplement des modifications, vous ne pouvez plus les suggérer). Donc, comme commentaire: un fichier (inode) sur un système Unix a généralement un nom. Mais il peut en avoir plus si vous ajoutez des noms avec ln(liens durs). Vous pouvez supprimer des noms avec rm(dissocier). Vous ne pouvez pas réellement supprimer directement un fichier, supprimez uniquement ses noms. Lorsqu'un fichier n'a pas de nom et n'est en outre pas ouvert, le noyau le supprimera. Un programme en cours d'exécution a le fichier à partir duquel il est ouvert, donc même après avoir supprimé tous les noms, le fichier est toujours là.
derobert
0

Je me demande comment des applications tueuses telles que Thunderbird ou Firefox peuvent être mises à jour via le gestionnaire de paquets du système alors qu'elles sont toujours en cours d'exécution? Eh bien, je peux vous dire que cela ne fonctionne pas vraiment bien ... J'ai eu Firefox se briser sur moi assez horriblement si je le laissais ouvert alors qu'une mise à jour du paquet était en cours d'exécution. Je devais parfois le tuer avec force et le redémarrer, car il était tellement cassé que je ne pouvais même pas le fermer correctement.

Que se passe-t-il avec l'ancien code pendant sa mise à jour? Normalement, sous Linux, un programme est chargé en mémoire, de sorte que l'exécutable sur le disque n'est pas nécessaire ou utilisé pendant l'exécution du programme. En fait, vous pouvez même supprimer l'exécutable et le programme ne devrait pas s'en soucier ... Cependant, certains programmes peuvent avoir besoin de l'exécutable, et certains systèmes d'exploitation (comme Windows) verrouillent le fichier exécutable, empêchant la suppression ou même renommer / déplacer, tandis que le programme est en cours d'exécution. Firefox tombe en panne, car il est en fait assez complexe et utilise un tas de fichiers de données qui lui indiquent comment construire son interface graphique (interface utilisateur). Lors d'une mise à jour de package, ces fichiers sont écrasés (mis à jour), donc lorsqu'un ancien exécutable Firefox (en mémoire) essaie d'utiliser les nouveaux fichiers GUI, des choses étranges peuvent se produire ...

Que dois-je faire lorsque je veux écrire un programme a.out qui se met à jour pendant son exécution? Il y a déjà beaucoup de réponses à votre question. Vérifiez ceci: /programming/232347/how-should-i-implement-an-auto-updater Soit dit en passant, les questions sur la programmation sont mieux sur StackOverflow.

Rouben Tchakhmakhtchian
la source
2
Les exécutables sont en fait paginés (échangés). Ils ne sont pas entièrement chargés en mémoire et peuvent être supprimés de la mémoire chaque fois que le système veut de la RAM pour autre chose. L'ancienne version reste en fait sur le disque; voir unix.stackexchange.com/questions/49299/… . Sous Linux au moins, vous ne pouvez pas réellement écrire sur un exécutable en cours d'exécution, vous obtiendrez l'erreur "fichier texte occupé". Même root ne peut pas le faire. (Vous avez tout à fait raison sur Firefox, cependant).
derobert