Mentre la domanda Stack Overflow all'inizio sembrava essere abbastanza, capisco, dai tuoi commenti, perché potresti ancora avere dubbi su questo. Per me, questo è esattamente il tipo di situazione critica coinvolta quando i due sottosistemi UNIX (processi e file) comunicano.
Come forse saprai, i sistemi UNIX sono generalmente divisi in due sottosistemi: il sottosistema di file e il sottosistema di processo. Ora, a meno che non sia diversamente indicato tramite una chiamata di sistema, il kernel non dovrebbe avere questi due sottosistemi che interagiscono tra loro. Esiste tuttavia un'eccezione: il caricamento di un file eseguibile nelle aree di testo di un processo . Naturalmente, si potrebbe sostenere che questa operazione è anche innescata da una chiamata di sistema ( execve
), ma questo è generalmente noto per essere l' unico caso in cui il sottosistema di processo fa una richiesta implicita al sottosistema di file.
Poiché il sottosistema di processo naturalmente non ha modo di gestire i file (altrimenti non avrebbe senso dividere il tutto in due), deve usare qualunque cosa il sottosistema di file fornisca per accedere ai file. Ciò significa anche che il sottosistema di processo è sottoposto a qualsiasi misura il sottosistema di file prende in merito all'edizione / eliminazione dei file. Su questo punto, consiglierei di leggere la risposta di Gilles a questa domanda di U&L . Il resto della mia risposta si basa su questo più generale di Gilles.
La prima cosa da notare è che internamente i file sono accessibili solo tramite inode . Se al kernel viene assegnato un percorso, il suo primo passo sarà quello di tradurlo in un inode da utilizzare per tutte le altre operazioni. Quando un processo carica un eseguibile in memoria, lo fa attraverso il suo inode, che è stato fornito dal sottosistema di file dopo la traduzione di un percorso. Gli Inodi possono essere associati a più percorsi (collegamenti) e i programmi possono eliminare solo collegamenti. Per eliminare un file e il suo inode, userland deve rimuovere tutti i collegamenti esistenti a tale inode e assicurarsi che sia completamente inutilizzato. Quando queste condizioni sono soddisfatte, il kernel cancellerà automaticamente il file dal disco.
Se dai un'occhiata alla parte sostituibile degli eseguibili della risposta di Gilles, vedrai che a seconda di come modifichi / elimini il file, il kernel reagirà / si adatterà in modo diverso, sempre attraverso un meccanismo implementato all'interno del sottosistema di file.
- Se provi la strategia 1 ( apri / tronca a zero / scrivi o apri / scrivi / tronca a nuove dimensioni ), vedrai che il kernel non si preoccuperà di gestire la tua richiesta. Verrà visualizzato un errore 26: File di testo occupato (
ETXTBSY
). Nessuna conseguenza.
- Se provi la seconda strategia, il primo passo è eliminare il tuo eseguibile. Tuttavia, poiché viene utilizzato da un processo, il sottosistema di file avvierà e impedirà che il file (e il suo inode) vengano effettivamente eliminati dal disco. Da questo punto, l'unico modo per accedere al contenuto del vecchio file è farlo attraverso il suo inode, che è ciò che fa il sottosistema di processo ogni volta che è necessario caricare nuovi dati in sezioni di testo (internamente, non ha senso usare percorsi, tranne quando li traduce in inode). Anche se hai scollegatoil file (rimosso tutti i suoi percorsi), il processo può ancora usarlo come se non avessi fatto nulla. La creazione di un nuovo file con il vecchio percorso non cambia nulla: al nuovo file verrà assegnato un inode completamente nuovo, di cui il processo in esecuzione non è a conoscenza.
Le strategie 2 e 3 sono sicure anche per gli eseguibili: sebbene i file eseguibili (e le librerie caricate dinamicamente) non siano file aperti nel senso di avere un descrittore di file, si comportano in modo molto simile. Finché alcuni programmi eseguono il codice, il file rimane sul disco anche senza una voce di directory.
- La strategia tre è abbastanza simile poiché l'
mv
operazione è atomica. Ciò richiederà probabilmente l'uso della rename
chiamata di sistema e poiché i processi non possono essere interrotti mentre si è in modalità kernel, nulla può interferire con questa operazione fino al completamento (corretto o meno). Ancora una volta, non c'è alterazione dell'inode del vecchio file: ne viene creato uno nuovo e i processi già in esecuzione non ne avranno conoscenza, anche se è stato associato a uno dei collegamenti del vecchio inode.
Con la strategia 3, la fase di spostamento del nuovo file sul nome esistente rimuove la voce della directory che porta al vecchio contenuto e crea una voce della directory che porta al nuovo contenuto. Questo viene fatto in un'unica operazione atomica, quindi questa strategia ha un grande vantaggio: se un processo apre il file in qualsiasi momento, vedrà il vecchio contenuto o il nuovo contenuto - non c'è rischio di ottenere contenuti misti o il file no esistente.
Ricompilazione di un file : quando si utilizza gcc
(e il comportamento è probabilmente simile per molti altri compilatori), si utilizza la strategia 2. È possibile vederlo eseguendo uno strace
dei processi del compilatore:
stat("a.out", {st_mode=S_IFREG|0750, st_size=8511, ...}) = 0
unlink("a.out") = 0
open("a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
chmod("a.out", 0750) = 0
- Il compilatore rileva che il file esiste già tramite le chiamate di sistema
stat
e lstat
.
- Il file non è collegato . Qui, sebbene non sia più accessibile tramite il nome
a.out
, il suo inode e il suo contenuto rimangono sul disco, fintanto che vengono utilizzati da processi già in esecuzione.
- Un nuovo file viene creato e reso eseguibile con il nome
a.out
. Questo è un inode nuovo di zecca e nuovi contenuti, ai quali non sono interessati i processi già in esecuzione.
Ora, quando si tratta di librerie condivise, si applicherà lo stesso comportamento. Finché un oggetto libreria viene utilizzato da un processo, non verrà eliminato dal disco, indipendentemente da come si cambiano i suoi collegamenti. Ogni volta che qualcosa deve essere caricato in memoria, il kernel lo farà attraverso l'inode del file e quindi ignorerà le modifiche apportate ai suoi collegamenti (come associarli a nuovi file).