Perché è possibile spostare un programma in esecuzione in Ubuntu?


24

Ho appena realizzato che sono in grado di spostare un programma attivo in esecuzione in una directory diversa. Nella mia esperienza ciò non è stato possibile in MacOs o Windows. Come funziona in Ubuntu?

Modifica: ho pensato che non fosse possibile su Mac, ma a quanto pare è possibile come i commenti verificano. Forse non è possibile solo su Windows. Grazie per tutte le risposte.


2
Praticamente un duplicato cross-site: stackoverflow.com/a/196910/1394393 .
jpmc26,

1
Non è possibile rename(2)eseguire un eseguibile su OS X? Cosa succede, ricevi EBUSYo qualcosa del genere? Perché non funziona? La pagina man rename (2) non documenta ETXTBUSYquella chiamata di sistema, e parla solo della EBUSYpossibilità di rinominare la directory, quindi non sapevo che un sistema POSIX potesse addirittura impedire la ridenominazione degli eseguibili.
Peter Cordes,

3
le app macOS possono anche essere spostate mentre sono in esecuzione, ma non vengono eliminate. Presumo che alcune app potrebbero errare dopo, ad esempio, se memorizzano gli URL dei file nelle loro risorse binarie o raggruppate da qualche parte come una variabile invece di generare tale URL tramite NSBundle et al. Sospetto sia la conformità POSIX di macOS.
Constantino Tsarouhas,

1
In realtà funziona come intende Linux, dovresti sapere cosa stai facendo. : P
userDepth

2
Immagino che un altro modo di pensarci sia, perché non sarebbe possibile? Solo perché Windows non te lo consente, non significa necessariamente che fondamentalmente non sia possibile a causa di come funzionano i processi o qualcosa del genere.
Thomas,

Risposte:


32

Lasciami scomporre.

Quando si esegue un eseguibile, viene eseguita una sequenza di chiamate di sistema, in particolare fork()e execve():

  • fork()crea un processo figlio del processo chiamante, che è (principalmente) una copia esatta del genitore, entrambi eseguono ancora lo stesso eseguibile (utilizzando pagine di memoria di copia su scrittura, quindi è efficiente). Restituisce due volte: nel genitore restituisce il PID figlio. Nel figlio restituisce 0. Normalmente, le chiamate del processo figlio vengono eseguite immediatamente:

  • execve()prende un percorso completo dell'eseguibile come argomento e sostituisce il processo di chiamata con l'eseguibile. A questo punto il processo appena creato ottiene il proprio spazio di indirizzi virtuale, cioè la memoria virtuale, e l'esecuzione inizia al suo punto di ingresso (in uno stato specificato dalle regole ABI della piattaforma per i nuovi processi).

A questo punto, il caricatore ELF del kernel ha mappato in memoria i segmenti di testo e di dati dell'eseguibile, come se avesse usato la mmap()chiamata di sistema (rispettivamente con mappature condivise di sola lettura e private di lettura / scrittura). Il BSS è anche mappato come se fosse MAP_ANONYMOUS. (A proposito, sto ignorando dinamico che collega qui per semplicità: il linker dinamico open()s e mmap()s tutte le librerie dinamiche prima di saltare al punto di ingresso del eseguibile principale.)

Solo poche pagine vengono effettivamente caricate nella memoria dal disco prima che un nuovo editor exec () inizi a eseguire il proprio codice. Ulteriori pagine vengono richieste come richiesto, se / quando il processo tocca quelle parti del suo spazio di indirizzi virtuale. (Il precaricamento di qualsiasi pagina di codice o dati prima di iniziare l'esecuzione del codice spazio utente è solo un'ottimizzazione delle prestazioni.)


Il file eseguibile è identificato dall'inode al livello inferiore. Dopo che il file ha iniziato a essere eseguito, il kernel mantiene intatto il contenuto del file dal riferimento inode, non dal nome del file, come per i descrittori di file aperti o i mapping di memoria supportati da file. Quindi puoi facilmente spostare l'eseguibile in un'altra posizione del filesystem o anche su un filesystem diverso. Come nota a margine, per controllare le varie statistiche del processo è possibile dare un'occhiata alla /proc/PIDdirectory (PID è l'ID del processo dato). Puoi anche aprire il file eseguibile come /proc/PID/exe, anche se è stato scollegato dal disco.


Ora scendiamo giù lo spostamento:

Quando sposti un file all'interno di uno stesso filesystem, la chiamata di sistema che viene eseguita è rename(), che semplicemente rinomina il file con un altro nome, l'inode del file rimane lo stesso.

Considerando che tra due diversi filesystem, accadono due cose:

  • Il contenuto del file viene prima copiato nella nuova posizione, per read()ewrite()

  • Successivamente, il file viene scollegato dalla directory di origine utilizzando unlink()e ovviamente il file otterrà un nuovo inode sul nuovo filesystem.

rmin realtà sta semplicemente unlink()inserendo il file specificato dall'albero delle directory, quindi avere l'autorizzazione di scrittura sulla directory ti darà il diritto sufficiente per rimuovere qualsiasi file da quella directory.

Ora per divertimento, immagini cosa succede quando sposti i file tra due file system e non hai i permessi per unlink()il file dal sorgente?

Bene, il file verrà prima copiato nella destinazione ( read(), write()) e poi unlink()fallirà a causa di un'autorizzazione insufficiente. Quindi, il file rimarrà in entrambi i filesystem !!


5
Stai creando un po 'di confusione nella memoria virtuale e fisica. La descrizione del modo in cui il programma viene caricato nella memoria fisica non è precisa. La chiamata di sistema exec non copia affatto le varie sezioni di un eseguibile nella memoria fisica, ma carica solo quella necessaria per avviare il processo. Successivamente, le pagine richieste vengono caricate su richiesta, probabilmente molto tempo dopo. I byte del file eseguibile fanno parte della memoria virtuale del processo e potrebbero essere letti e possibilmente letti di nuovo durante l'intera vita del processo.
jlliagre,

@jlliagre Modificato, spero che ora sia chiarito. Grazie.
heemayl

6
L'istruzione "Il processo non utilizza più il file system" è ancora discutibile.
jlliagre,

2
La comprensione di base che un determinato file nel file system non è identificato direttamente dal nome del file dovrebbe essere molto più chiara.
Thorbjørn Ravn Andersen,

2
Ci sono ancora imprecisioni nel tuo aggiornamento. Le chiamate di sistema mmape unmapnon vengono utilizzate per caricare e scaricare le pagine su richiesta, le pagine vengono caricate dal kernel quando accedendo generano un errore di pagina, le pagine vengono scaricate dalla memoria quando il sistema operativo ritiene che la RAM sarebbe meglio utilizzata per qualcos'altro. Nessuna chiamata di sistema è coinvolta in queste operazioni di carico / scarico.
jlliagre,

14

Bene, è piuttosto semplice. Prendiamo un eseguibile chiamato / usr / local / bin / whoopdeedoo. Questo è solo un riferimento al cosiddetto inode (struttura di base dei file sui filesystem Unix). È l'inode che viene contrassegnato come "in uso".

Ora quando elimini o sposti il ​​file / usr / local / whoopdeedoo, l'unica cosa che viene spostata (o cancellata) è il riferimento all'inode. L'inode stesso rimane invariato. È praticamente tutto.

Dovrei verificarlo, ma credo che tu possa farlo anche sui filesystem di Mac OS X.

Windows ha un approccio diverso. Perché? Chissà...? Non ho familiarità con gli interni di NTFS. Teoricamente, tutti i filesystem che usano riferimenti a strutture intenzionali per i filesnames dovrebbero essere in grado di farlo.

Lo ammetto, ho semplificato eccessivamente, ma vai a leggere la sezione "Implicazioni" su Wikipedia, che fa un lavoro molto migliore di me.


1
Bene, se usi un collegamento in Windows per avviare l'eseguibile, puoi anche cancellare il collegamento, se vuoi confrontarlo in quel modo, forse? = 3
Raggio

2
No, sarebbe come cancellare un collegamento simbolico. Da qualche parte in altri commenti, si afferma che il comportamento è dovuto al supporto legacy con i file system FAT. Sembra una probabile ragione.
jawtheshark,

1
Questo non ha nulla a che fare specificamente con gli inode. NTFS utilizza i record MFT per tenere traccia dello stato dei file e FAT utilizza le voci di directory per questo, ma Linux funziona ancora allo stesso modo con questi filesystem - dal punto di vista dell'utente.
Ruslan,

13

Una cosa che sembra mancare da tutte le altre risposte è che: una volta aperto un file e un programma contiene un descrittore di file aperto, il file non verrà rimosso dal sistema fino a quando quel descrittore di file non verrà chiuso.

I tentativi di eliminare l'inode di riferimento verranno ritardati fino alla chiusura del file: la ridenominazione nello stesso o diverso file system non può influire sul file aperto, indipendentemente dal comportamento della ridenominazione, né eliminare o sovrascrivere esplicitamente il file con uno nuovo. L'unico modo in cui è possibile rovinare un file è aprire esplicitamente il suo inode e pasticciare con il contenuto, non tramite operazioni sulla directory come rinominare / eliminare il file.

Inoltre, quando il kernel esegue un file, mantiene un riferimento al file eseguibile e ciò impedirà nuovamente qualsiasi modifica durante l'esecuzione.

Quindi, alla fine, anche se sembra che tu sia in grado di eliminare / spostare i file che compongono un programma in esecuzione, in realtà il contenuto di tali file viene tenuto in memoria fino al termine del programma.


1
Questo non è giusto. execve()non restituisce alcun FD, esegue semplicemente il programma. Quindi, ad esempio, se esegui il tail -f /foo.logloro è un FD ( /proc/PID/fd/<fd_num>) associato tailal foo.logma non all'eseguibile stesso tail, non anche sul suo genitore. Questo vale anche per i singoli eseguibili.
heemayl

@heemayl Non ho menzionato, execvequindi non vedo quanto sia rilevante. Una volta che il kernel inizia a eseguire un file, provare a sostituire il file non modificherà il programma che il kernel caricherà eseguendo il rendering del punto controverso. Se si desidera "aggiornare" l'eseguibile mentre è in esecuzione, è possibile chiamare execvead un certo punto per fare in modo che il kernel rilegga il file, ma non vedo come questo abbia importanza. Il punto è: l'eliminazione di un "eseguibile in esecuzione" in realtà non provoca alcuna cancellazione dei dati fino a quando l'eseguibile non si arresta.
Bakuriu,

Sto parlando di questa parte se il programma è costituito da un singolo file eseguibile una volta avviata l'esecuzione, il programma funzionerà correttamente indipendentemente da qualsiasi modifica nella directory: la ridenominazione nello stesso o diverso file system non può influenzare il gestore aperto , stai necessariamente parlando circa execve()e un FD quando non vi è FD coinvolto in questo caso.
heemayl,

2
Non è necessario un handle di file per avere un riferimento al file: anche avere pagine mappate è sufficiente.
Simon Richter,

1
Unix non ha "handle di file". open()restituisce un descrittore di file , di cui sta parlando heemayl execve(). Sì, un processo in esecuzione ha un riferimento al suo eseguibile, ma questo non è un descrittore di file. Probabilmente anche se modificasse munmap()tutte le sue mappature del suo eseguibile, avrebbe comunque un riferimento (riflesso in / proc / self / exe) che impediva la liberazione dell'inode. (Ciò sarebbe possibile senza arresti anomali se lo facesse da una funzione di libreria che non è mai tornata.) A proposito, troncare o modificare un eseguibile in uso potrebbe darti ETXTBUSY, ma potrebbe funzionare.
Peter Cordes,

7

In un filesystem Linux, quando sposti un file, purché non oltrepassi i limiti del filesystem (leggi: rimane sullo stesso disco / partizione) tutto ciò che stai cambiando è l'inode di ..(directory padre) in quello della nuova posizione . I dati effettivi non si sono affatto spostati sul disco, ma solo il puntatore in modo che il filesystem sappia dove trovarli.

Questo è il motivo per cui le operazioni di spostamento sono così rapide e probabilmente perché non vi è alcun problema a spostare un programma in esecuzione poiché in realtà non lo si sta spostando.


La tua risposta sembra implicare che spostare un eseguibile binario in un altro file system avrebbe un impatto sui processi in esecuzione lanciati da quel binario.
jlliagre,

6

È possibile perché lo spostamento di un programma non influisce sui processi in esecuzione avviati avviandolo.

Una volta avviato un programma, i suoi bit su disco sono protetti contro la sovrascrittura, ma non è necessario proteggere il file da rinominare, spostare in una posizione diversa sullo stesso file system, che equivale a rinominare il file o spostato in un file system diverso, che equivale a copiare il file altrove, quindi rimuoverlo.

La rimozione di un file in uso, sia perché un processo ha un descrittore di file aperto su di esso, sia perché un processo lo sta eseguendo, non rimuove i dati del file, a cui fa riferimento l'inode del file ma rimuove solo la voce della directory, cioè un percorso da cui è possibile raggiungere l'inode.

Nota che l'avvio di un programma non carica tutto in una volta nella memoria (fisica). Al contrario, viene caricato solo il minimo strettamente necessario per l'avvio del processo. Quindi, le pagine richieste vengono caricate su richiesta per tutta la durata del processo. questo si chiama paging della domanda. Se c'è carenza di RAM, il sistema operativo è libero di rilasciare la RAM che contiene queste pagine, quindi è possibile che un processo carichi più volte la stessa pagina dall'inode eseguibile.

Il motivo per cui non era possibile con Windows è originariamente probabilmente dovuto al fatto che il file system sottostante (FAT) non supportava il concetto diviso di voci di directory vs inode. Questa limitazione non era più presente con NTFS, ma la progettazione del sistema operativo è stata mantenuta per lungo tempo, portando all'odioso vincolo di dover riavviare quando si installa una nuova versione di un file binario, che non è più il caso delle recenti versioni di Windows.


1
Credo che le versioni più recenti di Windows possano sostituire i binari in uso senza riavviare.
Thorbjørn Ravn Andersen,

@ ThorbjørnRavnAndersen Mi chiedo perché tutti gli aggiornamenti richiedano ancora il riavvio :(
Braiam

1
@Braiam Non lo fanno. Dai un'occhiata più da vicino. Anche se i file binari possono essere aggiornati, il kernel non può (per quanto ne sappia) e richiede che un riavvio sia sostituito con una versione più recente. Questo è valido per la maggior parte dei kernel del sistema operativo. Persone più intelligenti di me hanno scritto kpatch per Linux che può patchare un kernel Linux mentre è in esecuzione - vedi en.wikipedia.org/wiki/Kpatch
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen intendevo "tutti gli aggiornamenti di Windows"
Braiam

@Braiam sì, anche io. Per favore, dai un'occhiata più da vicino.
Thorbjørn Ravn Andersen,

4

Fondamentalmente, in Unix e nei suoi simili, un nome file (incluso il percorso della directory che lo conduce) viene usato per associare / trovare un file quando lo si apre (eseguire un file è un modo per aprirlo in un modo). Dopo quel momento, l'identità del file (tramite il suo "inode") viene stabilita e non viene più messa in discussione. È possibile rimuovere il file, rinominarlo, modificarne le autorizzazioni. Fintanto che qualsiasi processo o percorso di un file ha un handle su quel file / inode, rimarrà come un pipe tra i processi (in realtà, in UNIX storico un pipe era un inode senza nome con una dimensione che si adattava semplicemente al "in blocchi diretti" riferimento di memoria su disco nell'inode, qualcosa come 10 blocchi).

Se hai un visualizzatore PDF aperto su un file PDF, puoi eliminare quel file e aprirne uno nuovo con lo stesso nome, e finché il vecchio visualizzatore è aperto accederà comunque correttamente al vecchio file (a meno che non guardi attivamente il file system per notare quando il file scompare con il suo nome originale).

I programmi che necessitano di file temporanei possono semplicemente aprire tale file con un certo nome e quindi rimuoverlo immediatamente (o meglio la sua voce di directory) mentre è ancora aperto. Successivamente il file non è più accessibile per nome, ma tutti i processi che hanno un descrittore di file aperto sul file possono comunque accedervi e, in seguito, in caso di uscita imprevista del programma, il file verrà rimosso e la memoria verrà automaticamente recuperata.

Quindi il percorso di un file non è una proprietà del file stesso (in effetti, i collegamenti fisici possono fornire diversi percorsi di questo tipo) ed è necessario solo per aprirlo, non per un accesso continuo da parte di processi che lo hanno già aperto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.