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.
rename(2)
un exécutable en cours d'exécution sur OS X? Qu'est-ce qui se passe, obtenez-vousEBUSY
ou quelque chose? Pourquoi ça ne marche pas? La page de manuel rename (2) ne documente pasETXTBUSY
cet appel système et ne parle que de laEBUSY
possibilité de renommer les répertoires, donc je ne savais pas qu'un système POSIX pouvait même interdire de renommer les exécutables.Réponses:
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()
etexecve()
: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 dynamiquesopen()
etmmap()
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/PID
ré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.rm
est en fait justeunlink()
-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()
) puisunlink()
échouera en raison d'une autorisation insuffisante. Ainsi, le fichier restera dans les deux systèmes de fichiers !!la source
mmap
etunmap
ne 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.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.
la source
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.
la source
execve()
ne renvoie aucun FD, il exécute simplement le programme. Ainsi , par exemple, si vous exécuteztail -f /foo.log
alors leur est un FD (/proc/PID/fd/<fd_num>
) associée àtail
lafoo.log
mais pas pour l'exécutable lui - même,tail
et non pas sur son parent aussi bien. Cela est également vrai pour les exécutables uniques.execve
donc 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 appelerexecve
à 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.execve()
et un FD quand il n'y a pas FD impliqué dans cette affaire.open()
renvoie un descripteur de fichier , dont Heemayl parle ici avecexecve()
. 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'ilmunmap()
é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 donnerETXTBUSY
, mais pourrait fonctionner.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.
la source
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.
la source
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.
la source