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/PID
directory (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.
rm
in 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 !!