Cosa succede quando un file con il 100% di paging nella cache della pagina viene modificato da un altro processo


14

So che quando una pagina cache della pagina viene modificata, viene contrassegnata come sporca e richiede una riscrittura, ma cosa succede quando:

Scenario: il file / apps / EXE, che è un file eseguibile, è completamente impaginato nella cache della pagina (tutte le sue pagine sono nella cache / memoria) ed eseguito dal processo P

La versione continua quindi sostituisce / apps / EXE con un nuovo eseguibile.

Presupposto 1: presumo che il processo P (e chiunque altro abbia un descrittore di file che faccia riferimento al vecchio eseguibile) continuerà a utilizzare il vecchio, in memoria / app / EXE senza problemi, e qualsiasi nuovo processo che tenti di eseguire quel percorso otterrà il nuovo eseguibile.

Assunzione 2: suppongo che se non tutte le pagine del file sono mappate in memoria, le cose andranno bene fino a quando non si verifica un errore di pagina che richiede pagine del file che sono state sostituite e probabilmente si verificherà un segfault?

Domanda 1: Se blocchi tutte le pagine del file con qualcosa come vmtouch, questo cambia lo scenario?

Domanda 2: Se / apps / EXE si trova su un NFS remoto, farebbe qualche differenza? (Suppongo di no)

Correggi o convalida i miei 2 presupposti e rispondi alle mie 2 domande.

Supponiamo che questa sia una scatola di CentOS 7.6 con una specie di kernel 3.10.0-957.el7

Aggiornamento: Ripensandoci, mi chiedo se questo scenario non è diverso da qualsiasi altro scenario di pagina sporca.

Suppongo che il processo che scrive il nuovo binario farà una lettura e otterrà tutte le pagine della cache poiché è tutto impaginato, e quindi tutte quelle pagine saranno contrassegnate come sporche. Se sono bloccati, saranno solo pagine inutili che occupano la memoria principale dopo che il conteggio di riferimento è pari a zero.

Sospetto che alla fine dei programmi attualmente in esecuzione, qualsiasi altra cosa utilizzerà il nuovo binario. Supponendo che sia tutto corretto, immagino sia interessante solo quando viene eseguito il paging solo in parte del file.


Solo per renderlo esplicito, la sostituzione di un file non sarà una grande cosa (a seconda se viene riaperta dall'applicazione e come l'applicazione reagisce al contenuto modificato), ma la modifica dei file mmaped può causare gravi crash delle applicazioni (è un problema comune nel mondo Java quando viene modificato un file zip con una voce di directory mmaped). Tuttavia, dipende dalla piattaforma, non è garantito che le regioni mmaped vedano il cambiamento o meno.
Verifica l'

Risposte:


12

La versione continua quindi sostituisce / apps / EXE con un nuovo eseguibile.

Questa è la parte importante.

Il modo in cui viene rilasciato un nuovo file è creando un nuovo file (ad es. /apps/EXE.tmp.20190907080000), Scrivendo il contenuto, impostando autorizzazioni e proprietà e infine rinominandolo (2) inserendolo nel nome finale/apps/EXE , sostituendo il vecchio file.

Il risultato è che il nuovo file ha un nuovo numero di inode (il che significa, in effetti, è un file diverso).

E il vecchio file aveva il suo numero di inode, che in realtà è ancora in circolazione anche se il nome del file non punta più ad esso (o non ci sono più nomi di file che puntano a quel inode).

Quindi, la chiave qui è che quando parliamo di "file" in Linux, molto spesso parliamo di "inode" poiché una volta che un file è stato aperto, l'inode è il riferimento che teniamo al file.

Presupposto 1 : presumo che il processo P (e chiunque altro abbia un descrittore di file che faccia riferimento al vecchio eseguibile) continuerà a utilizzare il vecchio, in memoria / app / EXE senza problemi, e qualsiasi nuovo processo che tenti di eseguire quel percorso otterrà il nuovo eseguibile.

Corretta.

Presupposto 2 : suppongo che se non tutte le pagine del file sono mappate in memoria, le cose andranno bene fino a quando non si verifica un errore di pagina che richiede pagine del file che sono state sostituite e probabilmente si verificherà un segfault?

Non corretto. Il vecchio inode è ancora presente, quindi gli errori di pagina del processo che usano il vecchio binario saranno ancora in grado di trovare quelle pagine sul disco.

Puoi vedere alcuni effetti di questo guardando il /proc/${pid}/exelink simbolico (o, equivalentemente, lsofoutput) per il processo che esegue il vecchio binario, che mostrerà/app/EXE (deleted) per indicare che il nome non è più lì ma l'inode è ancora in giro.

Puoi anche vedere che lo spazio su disco utilizzato dal binario verrà rilasciato solo dopo la fine del processo (supponendo che sia l'unico processo con quell'inode aperto). Controlla l'output di dfprima e dopo aver ucciso il processo, vedrai che diminuirà della dimensione di quel vecchio binario che pensavi non esistesse più.

A proposito, questo non è solo con i binari, ma con tutti i file aperti. Se si apre un file in un processo e si rimuove il file, il file rimarrà su disco fino a quando quel processo non chiude il file (o muore). Analogamente a come i collegamenti fissi mantengono un contatore di quanti nomi puntano a un inode nel disco, il il driver del filesystem (nel kernel Linux) tiene un contatore del numero di riferimenti a quell'inode in memoria e rilascerà l'inode dal disco solo una volta rilasciati anche tutti i riferimenti dal sistema in esecuzione.

Domanda 1 : Se blocchi tutte le pagine del file con qualcosa come vmtouch, questo cambia lo scenario

Questa domanda si basa sul presupposto errato 2 che non bloccare le pagine provocherà segfault. Non lo farà.

Domanda 2 : Se / apps / EXE si trova su un NFS remoto, farebbe qualche differenza? (Suppongo di no)

È pensato per funzionare allo stesso modo e il più delle volte, ma ci sono alcuni "problemi" con NFS.

A volte puoi vedere gli artefatti dell'eliminazione di un file che è ancora aperto in NFS (visualizzato come file nascosto in quella directory).

Hai anche un modo per assegnare i numeri di dispositivo alle esportazioni NFS, per assicurarti che non vengano "rimescolate" quando il server NFS si riavvia.

Ma l'idea principale è la stessa. Il driver client NFS utilizza ancora gli inode e proverà a mantenere i file (sul server) mentre l'inode è ancora referenziato.


1
Rename (2) si blocca fino a quando il conteggio dei riferimenti del file oldname va a zero?
Gregg Leventhal,

2
No, rinominare (2) non si bloccherà. Il vecchio inode è tenuto potenzialmente inutilizzato per molto tempo.
filbranden,

1
Vedi la risposta di @ mosvy sul perché non puoi scrivere su un file in esecuzione (ottieni ETXTBSY). Scollegare e creare nuovo ha lo stesso effetto di rinominare: si finisce con un nuovo inode. (Rinomina è meglio perché non c'è momento in cui il nome del file non esiste, è un'operazione atomica che sostituisce il nome per indicare il nuovo inode.)
filbranden

4
@GreggLeventhal: "Che ipotesi stai facendo riguardo al processo di rilascio continuo che sto utilizzando che ti rende sicuro che utilizza file temporanei?" - Perché finché esiste Unix, questo è ed è stato l'unico modo sano per farlo. renameè praticamente l'unica operazione di file e filesystem che è garantita essere atomica (supponendo che non attraversiamo i limiti del filesystem o del dispositivo), quindi "creare un file temporaneo e quindi rename" è il modello standard per l'aggiornamento dei file. È anche ciò che utilizza ogni editor di testo su Unix, ad esempio.
Jörg W Mittag,

1
@ grahamj42: renamefa parte di POSIX. Certo, è incluso facendo riferimento a ISO C (sezione 7.21.4.2 nella bozza corrente), ma è lì.
Jörg W Mittag,

6

Presupposto 2: suppongo che se non tutte le pagine del file sono mappate in memoria, le cose andranno bene fino a quando non si verifica un errore di pagina che richiede pagine del file che sono state sostituite e probabilmente si verificherà un segfault?

No, ciò non accadrà, perché il kernel non ti permetterà di aprire per scrivere e sostituire qualcosa all'interno di un file che è attualmente in esecuzione. Tale azione fallirà con ETXTBSY[1]:

cp /bin/sleep sleep; ./sleep 3600 & echo none > ./sleep
[9] 5332
bash: ./sleep: Text file busy

Quando dpkg, etc aggiorna un file binario, non lo sovrascrive, ma lo utilizza rename(2) che punta semplicemente la voce della directory a un file completamente diverso e tutti i processi che hanno ancora mappature o handle aperti sul vecchio file continueranno a usarlo senza problemi .

[1] tale protezione non è estesa ad altri file che possono anche essere considerati "testo" (codice live / eseguibile): librerie condivise, classi java, ecc .; modificando tale file un po mappato da un altro processo sarà causare il crash. Su Linux, il linker dinamico passa diligentemente la MAP_DENYWRITEbandiera mmap(2), ma non commettere errori: non ha alcun effetto.


1
Nello scenario dpkg, a che punto quindi la ridenominazione viene completata in modo tale che l'odontoiatria per / apps / EXE farà riferimento all'inode del nuovo binario? Quando non ci sono più riferimenti a quello vecchio? Come funziona?
Gregg Leventhal,

2
rename(2)è atomico; non appena è stato completato, la voce dir fa riferimento al nuovo file. I processi che stavano ancora utilizzando il vecchio file a quel punto sarebbero stati in grado di accedervi solo tramite mappature esistenti o tramite handle aperti (che potrebbero fare riferimento a una dentatura orfana, non più accessibile se non tramite via /proc/PID/fd).
mosvy,

1
Mi piace la tua risposta al meglio perché la tua menzione su ETXTBSY mi ha portato a questo utcc.utoronto.ca/~cks/space/blog/unix/WhyTextFileBusyError che risponde a tutte le mie domande.
Gregg Leventhal,

4

La risposta di filbranden è corretta supponendo che il processo di rilascio continuo esegua correttamente la sostituzione atomica dei file tramite rename. In caso contrario, ma modifica il file sul posto, le cose sono diverse. Tuttavia, il tuo modello mentale è ancora sbagliato.

Non è possibile che le cose vengano modificate sul disco e siano incompatibili con la cache della pagina, poiché la cache della pagina è la versione canonica e quella modificata. Qualsiasi scrittura su un file avviene tramite la cache della pagina. Se è già presente lì, le pagine esistenti vengono modificate. Se non è ancora presente, i tentativi di modificare una pagina parziale causeranno la memorizzazione nella cache dell'intera pagina, seguita dalla modifica come se fosse già memorizzata nella cache. Scrive che si estendono su un'intera pagina o più possono (e quasi sicuramente lo fanno) ottimizzare il passo di lettura paginandoli. In ogni caso, esiste una sola versione canonica modificabile di un file (*) esistente, quella nella cache della pagina .

(*) Ho leggermente mentito. Per NFS e altri filesystem remoti, potrebbero essercene più di uno e in genere (a seconda di quale e quali opzioni di mount e lato server vengono utilizzate) non implementano correttamente l'atomicità e ordinano la semantica per le scritture. Ecco perché molti di noi li considerano fondamentalmente rotti e si rifiutano di usarli per situazioni in cui ci saranno scritture in concomitanza con l'uso.

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.