Il buffer verrà automaticamente scaricato su disco quando termina un processo?


21

Quando reindirizzerò l'output di un comando su un file (ad es. echo Hello > file) Quel file avrà la garanzia di avere tali dati subito dopo la fine del comando? O c'è ancora una finestra molto piccola tra il comando esce e i dati scritti nel file? Vorrei leggere il file subito dopo la fine del comando, ma non voglio leggere un file vuoto.


1
Probabilmente esegue immediatamente il comando, ma il tempo necessario per aprire effettivamente il file, scrivere e chiudere dipenderà dalla velocità e dal tipo di disco rigido, da eventuali programmi in esecuzione, ecc.
freginold

In termini di esempio fornito, qual è il "processo"? Sono echoe >processi non separati (di breve durata)? E dove rimane l'output di echoprima che >venga eseguito?
oɔɯǝɹ

1
@ oɔɯǝɹ >è il reindirizzamento della shell. È come se il programma avesse aperto il file con nome per la scrittura e sostituito stdout con esso, che è esattamente ciò che fa la shell.
Dan D.

7
Penso che sia responsabilità del sistema operativo darti il filecontenuto Helloindipendentemente dal fatto che sia scaricato o meno.
Salman A

1
Se il programma è in esecuzione sulla macchina A e stai leggendo il file sulla macchina B, con il filesystem della macchina A montato sulla rete, potresti finire per leggere un file vuoto, a seconda del tipo di filesystem di rete e delle impostazioni di mount. Quindi potresti voler disabilitare la memorizzazione nella cache per quel mount.
punti

Risposte:


21

Sono coinvolti più livelli di buffer / cache.

  1. La cache della CPU.

    I dati vengono raccolti byte per byte e memorizzati nella cache della CPU. Se la cache della CPU è piena e non è stato possibile accedere ai dati per un po ', il blocco contenente i nostri dati potrebbe essere scritto nella memoria principale. Questi sono, per la maggior parte, nascosti ai programmatori dell'applicazione.

  2. I buffer in-process.

    C'è un po 'di memoria messa da parte nel processo in cui vengono raccolti i dati, quindi è necessario effettuare il minor numero possibile di richieste al sistema operativo, perché è relativamente costoso. Il processo copia i dati in questi buffer, che possono essere nuovamente supportati dalle cache della CPU, quindi non è garantito che i dati vengano copiati nella memoria principale. L'applicazione deve svuotare esplicitamente questi buffer, ad esempio usando fclose (3) o fsync (3). La funzione exit (3) lo fa anche prima che il processo sia terminato, mentre la funzione _exit (2) non lo fa , motivo per cui nella pagina del manuale c'è un grande avvertimento per quella funzione per chiamarlo solo se sai cosa sei facendo.

  3. I buffer del kernel

    Il sistema operativo mantiene quindi la propria cache, per ridurre al minimo il numero di richieste che deve inviare ai dischi. Questa cache non appartiene a nessun processo in particolare, quindi i dati in essa contenuti potrebbero appartenere a processi che sono già terminati e poiché tutti gli accessi passano da qui, il programma successivo vedrà i dati se sono arrivati ​​qui. Il kernel scriverà questi dati sui dischi quando avrà il tempo di farlo o quando richiesto esplicitamente.

  4. La cache dell'unità

    Le unità disco stesse mantengono anche una cache per accelerare gli accessi. Questi vengono scritti abbastanza rapidamente e c'è un comando per scrivere i dati rimanenti nelle cache e riportare quando è completo, che il sistema operativo utilizza allo spegnimento per assicurarsi che nessun dato venga lasciato non scritto prima di spegnerlo.

Per la tua applicazione, è sufficiente che i dati siano registrati nei buffer del kernel (i dati effettivi potrebbero ancora vivere nella cache della CPU a questo punto e potrebbero non essere stati scritti nella memoria principale): il processo "echo" termina, che significa che tutti i buffer in-process devono essere stati cancellati e i dati consegnati al sistema operativo, e quando si avvia un nuovo processo, è garantito che il sistema operativo restituirà gli stessi dati quando richiesto.


7
Considerare la memorizzazione nella cache della CPU non mi sembra rilevante. Questo è un livello di dettaglio non necessario qui. Come attraverso tutti i dettagli, fino a quando una quantità fisica che rappresenta un po 'su un piatto del disco rigido o su una memoria SDS viene modificata per capovolgerla.
tra il

3
In effetti, la cache della CPU è abbastanza ortogonale.
Simon Richter,

2
E ancora più importante, la cache della CPU è coerente tra i core, motivo per cui è totalmente fuori dal quadro. Su x86, è persino coerente con DMA (e x86 ha una modalità di ordinamento della memoria per l'intero ordine del negozio), quindi tutto ciò che può leggere la memoria vedrà i dati archiviati più di recente a quell'indirizzo nell'ordine globale delle operazioni di memoria. (Un core della CPU vedrà i propri negozi anche prima che diventino visibili a livello globale, a causa dell'inoltro del negozio dalla coda del negozio). Su piattaforme non x86 senza DMA coerente con la cache, il kernel Linux si assicura che la cache venga scaricata prima di DMA su quegli indirizzi.
Peter Cordes,

1
"Questi sono, per la maggior parte, nascosti ai programmatori dell'applicazione." Perché il "per la maggior parte"? Sono uno sviluppatore incorporato e tranne durante il boot loader (quindi non "applicazione") ignoro completamente la cache della CPU. Non credo che nessuno sviluppatore di applicazioni possa essere influenzato dagli effetti della cache della CPU.
Sam,

1
Le miss / hit della cache di @Sam insieme all'esecuzione speculativa possono essere sfruttate in alcune CPU per bypassare le restrizioni di accesso in lettura. Forse è a questo che si è riferita la risposta?
John Dvorak,

22

Se l'applicazione non ha cache interne, le modifiche verranno immediatamente scritte nel file. Lo stesso per il tuo esempio. Il file è un'entità logica in memoria che verrà immediatamente aggiornata. Eventuali successive operazioni sul file vedranno le modifiche apportate dal programma.

Tuttavia , ciò non significa che la modifica sia stata scritta sul disco fisico. Le modifiche potrebbero persistere nelle cache del filesystem del sistema operativo o nelle cache hardware. Per svuotare i buffer del filesystem, utilizzare il synccomando

Vorrei leggere il file subito dopo la fine del comando, ma non voglio leggere un file vuoto.

Non dovresti incontrare problemi pratici qui.


1
"Se l'applicazione non ha cache interne", questo è un "se" molto grande: la stragrande maggioranza delle implementazioni di librerie I / O utilizza il buffer stdout per impostazione predefinita. Detto questo, lo standard C, ad esempio, impone che il buffer stdout venga scaricato all'uscita (ma potenzialmente non se exitnon almeno viene chiamato implicitamente). Altre librerie / lingue (ad es. Java!) Offrono meno garanzie.
Konrad Rudolph,

Che cosa succede se limitarlo alla primitiva di reindirizzamento (ovvero il comando nella mia domanda)? Non ha cache interne, giusto?
Eric

@Eric No, dovresti stare bene.
martedì

10
Non sono sicuro di ottenere questa risposta. La domanda riguarda "quando il processo termina". Ogni applicazione con cache di scrittura interna li scaricherà sul disco all'uscita dal processo, se ciò non è accaduto in precedenza. IOW, quelle cache non contano qui.
Salterio

2
Inoltre, un buffer interno verrà scaricato all'uscita o semplicemente scomparirà dall'esistenza, giusto? Quindi, anche se i buffer interni non si scaricano, il contenuto non sarà osservabile, indipendentemente da quanto tempo si aspetterebbe.
WorldSEnder,

21

Il buffer verrà automaticamente scaricato su disco quando termina un processo?

In generale la risposta è no .

Dipende dal comando. Come menzionano le altre risposte, se il comando non esegue il buffer interno dei dati, tutti i dati saranno disponibili al termine del comando.

Ma la maggior parte, se non tutte, le librerie I / O standard eseguono il buffer stdout per impostazione predefinita (in una certa misura) e offrono diverse garanzie sul flush automatico dei buffer alla chiusura dell'applicazione.

C garantisce che un'uscita normale svuota i buffer . "Uscita normale" significa che exitviene chiamato - esplicitamente o al ritorno da main. Tuttavia, un'uscita anomala può eludere questa chiamata (e quindi lasciare indietro i buffer non scaricati).

Ecco un semplice esempio:

#include <signal.h>
#include <stdio.h>

int main() {
    printf("test");
    raise(SIGABRT);
}

Se lo compili ed eseguilo, nontest sarà necessariamente scritto su stdout.

Altri linguaggi di programmazione offrono ancora meno garanzie: Java, ad esempio, non si scarica automaticamente al termine del programma . Se il buffer di output contiene una linea non terminata, potrebbe quindi essere perso, a meno che non sia System.out.flush()stato chiamato esplicitamente.

Detto questo, il vostro corpo questione si chiede qualcosa di leggermente diverso: se i dati arrivano nel file a tutti , dovrebbe farlo immediatamente dopo le termina di comando (ferme restando le riserve descritte nelle altre risposte).


7
Ho anche visto un'uscita anomala quando uno strumento da riga di comando sta scrivendo su un file e su stdout o stderr, come un registro di debug, e l'utente ha fatto una pipe per dirigersi o meno, quindi ha digitato 'q' per uscire di meno. Il file del disco non viene sempre scaricato completamente se lo strumento da riga di comando non ha gestito SIGPIPE.
Zan Lynx,

+1, ma "dovrebbe farlo subito dopo le termina comando" non è giusto: qualsiasi write()o pwrite()chiamata di sistema avverrà prima che le uscite di processo, e che, quando i cambiamenti dei file diventano visibili. Quindi l'ultima modifica del file è sicuramente prima della conclusione del processo, immediatamente prima al più tardi. Penso che anche con un mmap(MAP_SHARED)file, non ci sia modo di osservare nulla per terminare il processo prima che avvengano tutte le modifiche ai file.
Peter Cordes,

9

Penso che nessuna domanda affronti sufficientemente questo problema:

Vorrei leggere il file subito dopo la fine del comando, ma non voglio leggere un file vuoto.

Come spiegano le altre risposte, un programma ben funzionante scarica i suoi buffer di file interni prima che il processo si concluda normalmente . Successivamente, i dati possono ancora persistere nei buffer del kernel o dell'hardware prima di essere scritti nell'archivio permanente. Tuttavia , la semantica del file system di Linux garantisce che tutti i processi vedano il contenuto dei file nello stesso modo in cui il kernel include i buffer interni 1 .

Questo è in genere implementato avendo al massimo un buffer nel kernel per oggetto file e per richiedere tutto l'accesso ai file per passare attraverso questo buffer.

  • Se un processo legge un file, il kernel presenterà il contenuto del buffer al processo, se la parte del file richiesta è attualmente nel buffer; in caso contrario, il kernel recupererà i dati dal supporto di memorizzazione sottostante e li posizionerà all'interno del buffer, quindi tornerà al passaggio precedente.

  • Se un processo scrive in un file, i dati vengono prima inseriti nel buffer interno al kernel per quel file. Alla fine il contenuto del buffer verrà scaricato nella memoria. Nel frattempo l'accesso in lettura è soddisfatto dallo stesso buffer (vedi sopra).


1 Almeno per file regolari, directory e collegamenti simbolici. FIFO e socket sono una questione diversa poiché il loro contenuto non viene mai archiviato in modo persistente. Ci sono alcuni casi speciali di file regolari il cui contenuto dipende da chi lo sta chiedendo; esempi sono i file in procfs e sysfs (si pensi /proc/selfche sia un collegamento simbolico all'ID processo del processo che legge il collegamento simbolico).


2
A rigor di termini, non è la semantica del filesystem di Linux a garantire questo, è la semantica POSIX che lo fa. In particolare, BSD si comporta esattamente allo stesso modo, così come macOS e persino Windows (anche se questo è uno dei pochi casi in cui Windows segue la semantica POSIX). Questo presuppone anche che nessuno mmap()stia facendo cose strane con e O_DIRECT, il che può portare a cose non sincronizzate tra il disco e la cache della pagina (ma ciò risolverà il momento in cui il processo che lo fa termina).
Austin Hemmelgarn,

2
@AustinHemmelgarn: A rigor di termini, entrambi abbiamo ragione, dato che Linux è stato progettato con il supporto per le applicazioni Unix (System V) in mente e successivamente realizzato per supportare POSIX che basa anche molti concetti su System V.
David Foerster

5

Supponendo che il tuo comando sia eseguito da qualche programma usando la libreria di runtime C, ad un certo punto dovrebbe invocare fcloseper chiudere il file aperto.

La pagina man per la fclosefunzione C dice:

NOTE Notare che fclose () scarica solo i buffer dello spazio utente forniti dalla libreria C. Per garantire che i dati siano archiviati fisicamente su disco, anche i buffer del kernel devono essere scaricati, ad esempio con sync (2) o fsync (2).

e la pagina man per fflushha la stessa nota. La pagina man per closedice:

Una chiusura corretta non garantisce che i dati siano stati salvati correttamente su disco, come scrive il kernel defers. Non è comune per un file system svuotare i buffer quando il flusso viene chiuso. Se è necessario accertarsi che i dati siano archiviati fisicamente, utilizzare fsync (2). (A questo punto dipenderà dall'hardware del disco.)

Si noti che i dati sono disponibili per altri processi anche se non sono sincronizzati con l'unità. Forse è già abbastanza buono per te.

In caso di dubbi, scrivere un test.


2
C o no, tutto / dovrà usare close()syscall per chiudere il descrittore di un file.
Attie

@Attie: Non c'è bisogno di closefile prima di uscire (nei programmi hacky che non controllare gli errori); il kernel li pulirà, chiamandoti efficacemente closedopo la fine del processo. Hai bisogno di fclosequalsiasi flusso stdio bufferizzato, comunque, o lascia che libc lo faccia per te exit(3), al contrario della chiamata di sistema di uscita direttamente.
Peter Cordes,

In caso di dubbi, scrivere un test. Questo è un cattivo consiglio per il rilevamento delle condizioni di gara. Il test su un kernel in esecuzione su un pezzo di hardware potrebbe dire che la gara non può avvenire nelle condizioni software prodotte dal test su quel sistema, o se lo fa è troppo raro da rilevare. Ma non può dirti se tale comportamento dovrebbe essere sicuro su tutti i filesystem, i kernel e tutto l'hardware (ad esempio PowerPC). cioè non si può dire se la garanzia da cui si dipende è un dettaglio di implementazione o una garanzia intenzionale a prova di futuro! (In questo caso lo è.)
Peter Cordes,

Dipende dalla situazione. Alcune persone che cercano di far funzionare la sua sceneggiatura della shell potrebbero essere aiutate da questo consiglio. Non era inteso come soluzione generale per ambienti più avanzati ma meno probabili, ad esempio un ingegnere del software che lavora su un kernel del sistema operativo, alcune persone che lavorano sull'aggiornamento del microcodice Intel o alcune ragazze che lavorano su un sistema per l'ISS.
MV,

3

Quando reindirizzerò l'output di un comando su un file (ad es. echo Hello > file) Quel file avrà la garanzia di avere tali dati subito dopo la fine del comando?

Sì. La shell apre il file di echooutput e lo invia direttamente a quello. Dopo che il comando è terminato, il gioco è fatto.

O c'è ancora una finestra molto piccola tra il comando esce e i dati scritti nel file?

Se i dati sono già presenti sul supporto è un'altra questione, che conta solo se si verifica in seguito un errore hardware o se si ispeziona la partizione live con un software forense, ignorando il filesystem montato.

Vorrei leggere il file subito dopo la fine del comando, ma non voglio leggere un file vuoto.

Non ti preoccupare, il kernel mantiene solo una vista del file, indipendentemente dalla frequenza con cui viene aperto.


"il kernel mantiene solo una vista del file": non del tutto vero mmap(MAP_SHARED): gli archivi nella regione mmaped non sono coerenti con le letture del file (tramite quel thread o altri processi). Questo è il motivo per cui msync(2)esiste. Almeno questo è ciò che le pagine man avvertono; a seconda dell'implementazione, Linux potrebbe effettivamente mappare le pagine fisiche dal pagecache, nel qual caso immagino che fondamentalmente sia coerente (modulo memory-ordering). Comunque, succede ancora tutto prima _exit(2).
Peter Cordes,

2

Come regola generale, tutti i dati posseduti dal kernel vengono mantenuti e ripuliti dal kernel, punto. Tali dati includono i dati trasferiti nella memoria del kernel da una chiamata di sistema come write(2).

Tuttavia, se l'applicazione (ad esempio C Library) esegue il buffering sulla cima di questo, allora il kernel ha ovviamente alcuna idea e di conseguenza non garantisce il suo clean-up.

Inoltre, non credo che ci sia alcuna garanzia di tempismo per il clean-up, che è, in generale, eseguito sulla base del "miglior sforzo" (leggi: "quando ho un secondo").


C'è una garanzia che qualsiasi cleanup / buffer-flushing accadrà prima che waitpid()ritorni un processo genitore , se la cleanup avviene affatto. vale a dire che altri processi non possono osservare direttamente l' interruzione del processo prima di qualsiasi modifica dei file effettuata da quel processo. (Ho detto "direttamente" per escludere l'osservazione indiretta attraverso i timestamp dei file NFS, perché la cache NFS non è perfettamente coerente tra gli host.)
Peter Cordes,

@PeterCordes: suppongo che dipenda da cosa intendi per "pulizia" invece di "mantenere". Per me "mantenere" è "fornire una visione coerente" (che ha la garanzia che hai menzionato) e "ripulire" è "a filo con il disco" che non credo abbia una garanzia di tempismo.
Mehrdad,

Oh, vedo, stai rispondendo alla parte "flushed to disk" della domanda che è irrilevante per ciò che i processi successivi vedranno quando leggono il file. "ripulire" nel senso di "rendere puliti i / o cache / memoria buffer pulita". Giusto, nessuna garanzia di temporizzazione a meno che tu non usi fsync/ fdatasync, anche se la riscrittura del buffer su Linux inizierà dopo /proc/sys/vm/dirty_writeback_centisecscentesimi di secondo (se non ritardato da altro traffico I / O), e vari altri parametri sintonizzabili in quella directory procfs influiscono anche sulle cose (ad es. Come grande per consentire ai buffer di crescere prima di eseguire qualsiasi riscrittura).
Peter Cordes,

2

O c'è ancora una finestra molto piccola tra il comando esce e i dati scritti nel file?

No, non c'è.

Vorrei leggere il file subito dopo la fine del comando, ma non voglio leggere un file vuoto.

Puoi leggere il contenuto finale del file subito dopo la fine del comando, non leggerai mai il file vuoto. (In C e C ++, utilizzare le chiamate di sistema wait , waitpid , wait3 o wait4 per attendere la chiusura del programma e solo successivamente leggere il file. Se si utilizza una shell, un altro linguaggio di programmazione o una libreria (ad esempio la libreria C sistema di chiamata o la classe di processo Java ), probabilmente utilizza già una di queste chiamate di sistema.)

Come hanno sottolineato altre risposte e commenti, potresti finire per leggere un file vuoto dopo l'uscita del programma se il programma è uscito senza svuotare i buffer di output interni (ad esempio a causa di _exit , interruzione o ricezione di un segnale fatale o perché è un programma Java che esce normalmente). Tuttavia, non c'è nulla che tu possa fare al riguardo a questo punto: i dati non scaricati vengono persi per sempre, l'attesa aggiuntiva non li ripristinerà.


0

Scusate forse per l'aggiunta di un'altra risposta superflua, ma la maggior parte sembra concentrarsi sull'aringa rossa del titolo della domanda. Ma per quanto posso dire, la domanda non riguarda affatto il buffering, ma questo:

Quando reindirizzerò l'output di un comando su un file (ad esempio, echo Hello> file) quel file sarà garantito per avere tali dati subito dopo la chiusura del comando?

Sì, incondizionatamente. L'uso di ">" che stai descrivendo, insieme a "|" e "<", è il modello di elaborazione basato su pipe su cui il mondo Unix e Linux è fortemente basato. Troverai centinaia, se non migliaia di script totalmente dipendenti da questo comportamento in ogni installazione di Linux.

Funziona come desideri per progetto, e se ci fosse anche la minima possibilità di una condizione di gara, sarebbe stato corretto probabilmente decenni fa.


Questo è superfluo, sfortunatamente. Solo un paio di risposte si concentrano principalmente sull'aringa rossa del commit dei dati nella memoria non volatile. Vedi la risposta di @ pts e molti altri per una descrizione chiara: la modifica del file avviene prima dell'uscita, o per niente.
Peter Cordes,
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.