Pourquoi est-il possible de déplacer un programme en cours d'exécution dans Ubuntu?

24

Je viens de réaliser que je peux déplacer un programme actif en cours d'exécution vers un répertoire différent. D'après mon expérience, cela n'était pas possible sous MacOs ou Windows. Comment ça marche dans Ubuntu?

Edit: je pensais que ce n'était pas possible sur Mac mais apparemment c'est possible comme le vérifient les commentaires. Ce n'est peut-être pas possible sous Windows, merci pour toutes les réponses.

n0.ob
la source
2
Quasiment une dupe intersite : stackoverflow.com/a/196910/1394393 .
jpmc26
1
Vous ne pouvez pas rename(2)un exécutable en cours d'exécution sur OS X? Qu'est-ce qui se passe, obtenez-vous EBUSYou quelque chose? Pourquoi ça ne marche pas? La page de manuel rename (2) ne documente pas ETXTBUSYcet appel système et ne parle que de la EBUSYpossibilité de renommer les répertoires, donc je ne savais pas qu'un système POSIX pouvait même interdire de renommer les exécutables.
Peter Cordes
3
Les applications macOS peuvent également être déplacées pendant leur exécution, mais pas supprimées. Je suppose que certaines applications peuvent se tromper après cela, par exemple, si elles stockent des URL de fichier vers leurs ressources binaires ou groupées quelque part sous forme de variable au lieu de générer une telle URL via NSBundle et al. Je soupçonne que c'est la conformité POSIX de macOS.
Constantino Tsarouhas
1
Cela fonctionne réellement comme le veut Linux, vous devez savoir ce que vous faites. : P
userDepth
2
Je suppose qu'une autre façon de penser est pourquoi ce ne serait pas possible? Ce n'est pas parce que Windows ne vous le permet pas que cela est fondamentalement impossible en raison du fonctionnement des processus ou de quelque chose.
Thomas

Réponses:

32

Permettez-moi de le décomposer.

Lorsque vous exécutez un exécutable, une séquence d'appels système est exécutée, notamment fork()et execve():

  • fork()crée un processus enfant du processus appelant, qui est (principalement) une copie exacte du parent, les deux exécutant toujours le même exécutable (en utilisant des pages de mémoire de copie sur écriture, il est donc efficace). Il renvoie deux fois: dans le parent, il renvoie le PID enfant. Chez l'enfant, il renvoie 0. Normalement, les appels de processus enfant s'exécutent immédiatement:

  • execve()prend un chemin complet vers l'exécutable comme argument et remplace le processus appelant par l'exécutable. À ce stade, le processus nouvellement créé obtient son propre espace d'adressage virtuel, c'est-à-dire la mémoire virtuelle, et l'exécution commence à son point d'entrée (dans un état spécifié par les règles de la plateforme ABI pour les nouveaux processus).

À ce stade, le chargeur ELF du noyau a mappé les segments de texte et de données de l'exécutable en mémoire, comme s'il avait utilisé l' mmap()appel système (avec des mappages partagés en lecture seule et privés en lecture-écriture respectivement). Le BSS est également mappé comme avec MAP_ANONYMOUS. (BTW, j'ignore les liens dynamiques ici pour plus de simplicité: l'éditeur de liens dynamiques open()et mmap()toutes les bibliothèques dynamiques avant de passer au point d'entrée de l'exécutable principal.)

Seules quelques pages sont réellement chargées dans la mémoire à partir du disque avant qu'un nouvel exécuteur () commence à exécuter son propre code. Des pages supplémentaires sont demandées en fonction des besoins, si / quand le processus touche ces parties de son espace d'adressage virtuel. (Le préchargement des pages de code ou de données avant de commencer à exécuter le code de l'espace utilisateur n'est qu'une optimisation des performances.)


Le fichier exécutable est identifié par l'inode au niveau inférieur. Une fois que le fichier a commencé à être exécuté, le noyau conserve le contenu du fichier intact par la référence d'inode, pas par nom de fichier, comme pour les descripteurs de fichier ouverts ou les mappages de mémoire sauvegardés sur fichier. Vous pouvez donc facilement déplacer l'exécutable vers un autre emplacement du système de fichiers ou même sur un autre système de fichiers. En remarque, pour vérifier les différentes statistiques du processus, vous pouvez consulter le /proc/PIDrépertoire (PID est l'ID de processus du processus donné). Vous pouvez même ouvrir le fichier exécutable car /proc/PID/exe, même s'il a été dissocié du disque.


Maintenant, creusons le mouvement:

Lorsque vous déplacez un fichier dans un même système de fichiers, l'appel système qui est exécuté est rename(), qui renomme simplement le fichier sous un autre nom, l'inode du fichier reste le même.

Alors qu'entre deux systèmes de fichiers différents, deux choses se produisent:

  • Le contenu du fichier est d'abord copié vers le nouvel emplacement, par read()etwrite()

  • Après cela, le fichier est dissocié du répertoire source à l'aide unlink()et, évidemment, le fichier obtiendra un nouvel inode sur le nouveau système de fichiers.

rmest en fait juste unlink()-ing le fichier donné de l'arborescence de répertoires, donc avoir l'autorisation d'écriture sur le répertoire vous donnera le droit suffisant pour supprimer n'importe quel fichier de ce répertoire.

Maintenant, pour le plaisir, imaginez ce qui se passe lorsque vous déplacez des fichiers entre deux systèmes de fichiers et que vous n'avez pas l'autorisation d'accéder au unlink()fichier à partir de la source?

Eh bien, le fichier sera copié vers la destination dans un premier temps ( read(), write()) puis unlink()échouera en raison d'une autorisation insuffisante. Ainsi, le fichier restera dans les deux systèmes de fichiers !!

heemayl
la source
5
Votre mémoire virtuelle et physique est quelque peu déroutante. Votre description de la façon dont le programme est chargé dans la mémoire physique est inexacte. L'appel système exec ne copie pas du tout les différentes sections d'un exécutable dans la mémoire physique mais charge uniquement celle dont il a besoin pour démarrer le processus. Par la suite, les pages requises sont chargées à la demande, peut-être longtemps après. Les octets du fichier exécutable font partie de la mémoire virtuelle du processus et peuvent être lus et éventuellement relus pendant toute la durée de vie du processus.
jlliagre
@jlliagre Edited, j'espère que c'est clarifié maintenant. Merci.
heemayl
6
La déclaration "Le processus n'utilise plus le système de fichiers" est toujours discutable.
jlliagre
2
La compréhension de base qu'un fichier donné dans le système de fichiers n'est pas directement identifié par le nom du fichier devrait être beaucoup plus claire.
Thorbjørn Ravn Andersen
2
Il y a encore des inexactitudes dans votre mise à jour. Les appels système mmapet unmapne sont pas utilisés pour charger et décharger les pages à la demande, les pages sont chargées par le noyau lorsque leur accès génère une erreur de page, les pages sont déchargées de la mémoire lorsque le système d'exploitation estime que la RAM serait mieux utilisée pour autre chose. Aucun appel système n'est impliqué dans ces opérations de chargement / déchargement.
jlliagre
14

Eh bien, c'est assez simple. Prenons un exécutable nommé / usr / local / bin / whoopdeedoo. Ce n'est qu'une référence à ce qu'on appelle l' inode (structure de base des fichiers sur les systèmes de fichiers Unix). C'est l'inode qui est marqué "en cours d'utilisation".

Maintenant, lorsque vous supprimez ou déplacez le fichier / usr / local / whoopdeedoo, la seule chose qui est déplacée (ou effacée) est la référence à l'inode. L'inode lui-même reste inchangé. C'est essentiellement ça.

Je devrais le vérifier, mais je pense que vous pouvez également le faire sur les systèmes de fichiers Mac OS X.

Windows adopte une approche différente. Pourquoi? Qui sait...? Je ne connais pas les composants internes de NTFS. Théoriquement, tous les systèmes de fichiers qui utilisent des références à des structures intenales pour les noms de fichiers devraient être capables de le faire.

J'avoue, j'ai trop simplifié, mais allez lire la section "Implications" sur Wikipédia, qui fait un bien meilleur travail que moi.

jawtheshark
la source
1
Eh bien, si vous utilisez un raccourci dans Windows pour démarrer l'exécutable, vous pouvez également effacer le raccourci, si vous voulez le comparer comme ça, peut-être? = 3
Ray
2
Non, ce serait comme effacer un lien symbolique. Quelque part dans d'autres commentaires, il est indiqué que le comportement est dû à la prise en charge héritée des systèmes de fichiers FAT. Cela ressemble à une raison probable.
jawtheshark
1
Cela n'a rien à voir spécifiquement avec les inodes. NTFS utilise des enregistrements MFT pour suivre l'état des fichiers et FAT utilise des entrées de répertoire pour cela, mais Linux fonctionne toujours de la même manière avec ces systèmes de fichiers - du point de vue de l'utilisateur.
Ruslan
13

Une chose qui semble manquer dans toutes les autres réponses est la suivante: une fois qu'un fichier est ouvert et qu'un programme contient un descripteur de fichier ouvert, le fichier ne sera pas supprimé du système tant que ce descripteur de fichier n'est pas fermé.

Les tentatives de suppression de l'inode référencé seront retardées jusqu'à la fermeture du fichier: renommer dans le même système de fichiers ou dans un système de fichiers différent ne peut pas affecter le fichier ouvert, indépendamment du comportement du renommage, ni supprimer ou remplacer explicitement le fichier par un nouveau. La seule façon dont vous pouvez gâcher un fichier est d'ouvrir explicitement son inode et de gâcher le contenu, pas par des opérations sur le répertoire telles que renommer / supprimer le fichier.

De plus, lorsque le noyau exécute un fichier, il conserve une référence au fichier exécutable et cela empêchera à nouveau toute modification de celui-ci pendant l'exécution.

Donc, à la fin, même s'il semble que vous puissiez supprimer / déplacer les fichiers qui composent un programme en cours d'exécution, en fait, le contenu de ces fichiers est conservé en mémoire jusqu'à la fin du programme.

Bakuriu
la source
1
Ça n'est pas correct. execve()ne renvoie aucun FD, il exécute simplement le programme. Ainsi , par exemple, si vous exécutez tail -f /foo.logalors leur est un FD ( /proc/PID/fd/<fd_num>) associée à tailla foo.logmais pas pour l'exécutable lui - même, tailet non pas sur son parent aussi bien. Cela est également vrai pour les exécutables uniques.
heemayl
@heemayl Je n'ai pas mentionné, execvedonc je ne vois pas comment cela est pertinent. Une fois que le noyau a commencé à exécuter un fichier, essayer de le remplacer ne modifiera pas le programme que le noyau va charger en rendant le point théorique. Si vous voulez "mettre à jour" l'exécutable en cours d'exécution, vous pouvez appeler execveà un moment donné pour que le noyau relise le fichier, mais je ne vois pas en quoi cela est important. Le fait est que: la suppression d'un "exécutable en cours d'exécution" ne déclenche pas vraiment de suppression de données jusqu'à ce que l'exécutable s'arrête.
Bakuriu
Je parle de cette partie si le programme se compose d'un seul fichier exécutable une fois que vous démarrez l'exécution, le programme fonctionnera correctement indépendamment de tout changement dans le répertoire: renommer dans le même système de fichiers ou différent ne peut pas affecter le gestionnaire ouvert , vous parlez nécessairement environ execve()et un FD quand il n'y a pas FD impliqué dans cette affaire.
heemayl
2
Vous n'avez pas besoin d'un descripteur de fichier pour avoir une référence au fichier - avoir des pages mappées est également suffisant.
Simon Richter
1
Unix n'a pas de "descripteurs de fichiers". open()renvoie un descripteur de fichier , dont Heemayl parle ici avec execve(). Oui, un processus en cours a une référence à son exécutable, mais ce n'est pas un descripteur de fichier. Probablement, même s'il munmap()éditait tous ses mappages de son exécutable, il aurait toujours une référence (reflétée dans / proc / self / exe) qui empêchait l'inode d'être libéré. (Cela serait possible sans se bloquer s'il le faisait à partir d'une fonction de bibliothèque qui n'est jamais revenue.) BTW, tronquer ou modifier un exécutable en cours d'utilisation pourrait vous donner ETXTBUSY, mais pourrait fonctionner.
Peter Cordes
7

Dans un système de fichiers Linux, lorsque vous déplacez un fichier, tant qu'il ne dépasse pas les limites du système de fichiers (lecture: reste sur le même disque / partition), tout ce que vous changez est l'inode de ..(répertoire parent) à celui du nouvel emplacement . Les données réelles n'ont pas du tout bougé sur le disque, juste le pointeur pour que le système de fichiers sache où les trouver.

C'est pourquoi les opérations de déplacement sont si rapides et probablement pourquoi il n'y a aucun problème à déplacer un programme en cours d'exécution car vous ne déplacez pas réellement le programme lui-même.

I_GNU_it_all_along
la source
Votre réponse semble impliquer que le déplacement d'un exécutable binaire vers un autre système de fichiers aurait un impact sur les processus en cours lancés à partir de ce binaire.
jlliagre
6

Cela est possible car le déplacement d'un programme n'affecte pas les processus en cours démarrés par son lancement.

Une fois qu'un programme est lancé, ses bits sur disque sont protégés contre l'écrasement, mais il n'est pas nécessaire de protéger le fichier à renommer, à déplacer vers un emplacement différent sur le même système de fichiers, ce qui équivaut à renommer le fichier ou à déplacer vers un autre système de fichiers, ce qui équivaut à copier le fichier ailleurs puis à le supprimer.

La suppression d'un fichier en cours d'utilisation, soit parce qu'un descripteur de fichier est ouvert sur un processus, soit parce qu'un processus l'exécute, ne supprime pas les données du fichier, qui restent référencées par l'inode de fichier mais supprime uniquement l'entrée de répertoire, c'est-à-dire un chemin à partir duquel l'inode peut être atteint.

Notez que le lancement d'un programme ne charge pas tout à la fois dans la mémoire (physique). Au contraire, seul le strict minimum nécessaire au démarrage du processus est chargé. Ensuite, les pages requises sont chargées à la demande pendant toute la durée du processus. c'est ce qu'on appelle la pagination de la demande. En cas de pénurie de RAM, le système d'exploitation est libre de libérer la RAM contenant ces pages, il est donc possible qu'un processus charge plusieurs fois la même page à partir de l'inode exécutable.

La raison pour laquelle cela n'était pas possible avec Windows est probablement due au fait que le système de fichiers sous-jacent (FAT) ne supportait pas le concept divisé d'entrées de répertoire vs inodes. Cette limitation n'était plus présente avec NTFS mais la conception du système d'exploitation a été conservée pendant longtemps, ce qui a conduit à la contrainte odieuse de devoir redémarrer lors de l'installation d'une nouvelle version d'un binaire, ce qui n'est plus le cas avec les versions récentes de Windows.

jlliagre
la source
1
Je crois que les nouvelles versions de Windows peuvent remplacer les binaires utilisés sans redémarrage.
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen Je me demande pourquoi toutes les mises à jour nécessitent toujours un redémarrage :(
Braiam
1
@Braiam Ils ne le font pas. Regardez de plus près. Même si les binaires peuvent être mis à jour, le noyau ne peut pas (à ma connaissance) et nécessite un redémarrage pour être remplacé par une version plus récente. Ceci est valable pour la plupart des noyaux de système d'exploitation. Des gens plus intelligents que moi ont écrit kpatch pour Linux qui peut patcher un noyau Linux en cours d'exécution - voir en.wikipedia.org/wiki/Kpatch
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen Je voulais dire "toutes les mises à jour Windows"
Braiam
@Braiam oui - moi aussi. Veuillez regarder de plus près.
Thorbjørn Ravn Andersen
4

Fondamentalement, sous Unix et ses semblables, un nom de fichier (y compris le chemin d'accès au répertoire qui y mène) est utilisé pour associer / rechercher un fichier lors de son ouverture (l'exécution d'un fichier est une façon de l'ouvrir en quelque sorte). Après ce moment, l'identité du fichier (via son "inode") est établie et n'est plus remise en cause. Vous pouvez supprimer le fichier, le renommer, modifier ses autorisations. Tant qu'un processus ou un chemin de fichier a une poignée sur ce fichier / inode, il restera, tout comme un tuyau entre les processus (en fait, dans UNIX historique, un tuyau était un inode sans nom avec une taille qui vient de s'adapter dans le "blocs directs" référence de stockage sur disque dans l'inode, quelque chose comme 10 blocs).

Si vous avez un visualiseur PDF ouvert sur un fichier PDF, vous pouvez supprimer ce fichier et en ouvrir un nouveau avec le même nom, et tant que l'ancien visualiseur est ouvert, il pourra toujours accéder à l'ancien fichier (à moins qu'il ne regarde activement le système de fichiers afin de remarquer quand le fichier disparaît sous son nom d'origine).

Les programmes qui ont besoin de fichiers temporaires peuvent simplement ouvrir un tel fichier sous un certain nom, puis le supprimer immédiatement (ou plutôt son entrée de répertoire) pendant qu'il est encore ouvert. Par la suite, le fichier n'est plus accessible par son nom, mais tous les processus ayant un descripteur de fichier ouvert peuvent toujours y accéder, et s'il y a une sortie inattendue du programme par la suite, le fichier sera supprimé et le stockage récupéré automatiquement.

Ainsi, le chemin d'accès à un fichier n'est pas une propriété du fichier lui-même (en fait, les liens matériels peuvent fournir plusieurs chemins différents) et n'est nécessaire que pour l'ouvrir, pas pour un accès continu par les processus l'ayant déjà ouvert.

user584745
la source