Come è possibile eseguire un aggiornamento live mentre un programma è in esecuzione?


15

Mi chiedo come applicazioni killer come Thunderbird o Firefox possano essere aggiornate tramite il gestore di pacchetti del sistema mentre sono ancora in esecuzione. Cosa succede con il vecchio codice mentre vengono aggiornati? Cosa devo fare quando voglio scrivere un programma a.out che si aggiorna mentre è in esecuzione?



@derobert Non esattamente: quel thread non rientra nelle peculiarità degli eseguibili. Fornisce un background rilevante ma non è un duplicato.
Gilles 'SO- smetti di essere malvagio' il

@Gilles Bene, se lasci il resto delle cose è troppo ampio. Ci sono due (almeno) domande qui. E l'altra domanda è piuttosto vicina, si sta chiedendo perché la sostituzione dei file per l'aggiornamento funzioni su Unix.
derobert,

Se vuoi davvero farlo con il tuo codice, potresti voler esaminare il linguaggio di programmazione Erlang, dove gli aggiornamenti del codice "hot" sono una caratteristica chiave. learnyousomeerlang.com/relups
mattdm

1
@Gilles BTW: dopo aver visto il saggio che hai aggiunto in risposta, ho ritirato il mio voto ravvicinato. Hai trasformato questa domanda in un buon posto per indicare a chiunque desideri sapere come funzionano gli aggiornamenti.
derobert,

Risposte:


20

Sostituzione dei file in generale

Innanzitutto, esistono diverse strategie per sostituire un file:

  1. Aprire il file esistente per la scrittura, troncarlo a lunghezza 0 e scrivere il nuovo contenuto. (Una variante meno comune è aprire il file esistente, sovrascrivere il vecchio contenuto con il nuovo contenuto, troncare il file alla nuova lunghezza se è più corto.) In termini di shell:

    echo 'new content' >somefile
    
  2. Rimuovere il vecchio file e creare un nuovo file con lo stesso nome. In termini di shell:

    rm somefile
    echo 'new content' >somefile
    
  3. Scrivi in ​​un nuovo file con un nome temporaneo, quindi sposta il nuovo file sul nome esistente. La mossa cancella il vecchio file. In termini di shell:

    echo 'new content' >somefile.new
    mv somefile.new somefile
    

Non elencherò tutte le differenze tra le strategie, ne citerò solo alcune importanti qui. Con la categoria 1, se un processo sta attualmente utilizzando il file, il processo vede il nuovo contenuto mentre viene aggiornato. Ciò può causare confusione se il processo prevede che il contenuto del file rimanga lo stesso. Si noti che si tratta solo di processi che hanno il file aperto (come visibile in lsofo in ; le applicazioni interattive che hanno un documento aperto (ad es. Apertura di un file in un editor) di solito non mantengono il file aperto, caricano il contenuto del file durante Operazione "apri documento" e sostituiscono il file (usando una delle strategie sopra) durante l'operazione "salva documento"./proc/PID/fd/

Con le strategie 2 e 3, se un processo ha il file somefileaperto, il vecchio file rimane aperto durante l'aggiornamento del contenuto. Con la strategia 2, la fase di rimozione del file rimuove infatti solo la voce del file nella directory. Il file stesso viene rimosso solo quando non ha una voce di directory che lo porta (su file system Unix tipici, può esserci più di una voce di directory per lo stesso file ) e nessun processo lo ha aperto. Ecco un modo per osservarlo: il file viene rimosso solo quando il sleepprocesso viene interrotto ( rmrimuove solo la sua voce di directory).

echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .

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.

Sostituzione degli eseguibili

Se provi la strategia 1 con un eseguibile in esecuzione su Linux, otterrai un errore.

cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy

Un "file di testo" indica un file contenente codice eseguibile per oscuri motivi storici . Linux, come molte altre varianti unix, rifiuta di sovrascrivere il codice di un programma in esecuzione; alcune varianti unix lo consentono, causando arresti anomali a meno che il nuovo codice non sia stato una modifica molto ben concepita del vecchio codice.

Su Linux, puoi sovrascrivere il codice di una libreria caricata dinamicamente. È probabile che porti a un arresto anomalo del programma che lo sta utilizzando. (Potresti non essere in grado di osservare questo sleepperché carica tutto il codice della libreria di cui ha bisogno all'avvio. Prova un programma più complesso che fa qualcosa di utile dopo aver dormito, come perl -e 'sleep 9; print lc $ARGV[0]'.)

Se un interprete esegue uno script, il file di script viene aperto in modo ordinario dall'interprete, quindi non esiste protezione contro la sovrascrittura dello script. Alcuni interpreti leggono e analizzano l'intero script prima di iniziare l'esecuzione della prima riga, altri leggono lo script secondo necessità. Vedi Cosa succede se modifichi uno script durante l'esecuzione? e in che modo Linux gestisce gli script della shell? per ulteriori dettagli.

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.

Aggiornamento di un'applicazione

La maggior parte dei gestori di pacchetti utilizza la strategia 3 per sostituire i file, a causa del grande vantaggio di cui sopra - in qualsiasi momento, l'apertura del file porta a una versione valida di esso.

Il punto in cui gli aggiornamenti dell'applicazione possono interrompersi è che durante l'aggiornamento di un file è atomico, l'aggiornamento dell'applicazione nel suo insieme non lo è se l'applicazione è composta da più file (programma, librerie, dati, ...). Considera la seguente sequenza di eventi:

  1. Viene avviata un'istanza dell'applicazione.
  2. L'applicazione è stata aggiornata.
  3. L'applicazione di istanza in esecuzione apre uno dei suoi file di dati.

Nel passaggio 3, l'istanza in esecuzione della versione precedente dell'applicazione sta aprendo un file di dati dalla nuova versione. Se funziona o meno dipende dall'applicazione, da quale file è e da quanto è stato modificato.

Dopo un aggiornamento, noterai che il vecchio programma è ancora in esecuzione. Se vuoi eseguire la nuova versione, dovrai uscire dal vecchio programma ed eseguire la nuova versione. I gestori di pacchetti di solito uccidono e riavviano i daemon durante un aggiornamento, ma lasciano solo le applicazioni dell'utente finale.

Alcuni demoni hanno procedure speciali per gestire gli aggiornamenti senza dover uccidere il demone e attendere il riavvio della nuova istanza (che causa un'interruzione del servizio). Ciò è necessario nel caso di init , che non può essere ucciso; I sistemi init forniscono un modo per richiedere che l'istanza in esecuzione chiami execveper sostituirsi con la nuova versione.


"quindi sposta il nuovo file sul nome esistente. Lo spostamento cancella il vecchio file." È un po 'confuso, in quanto è davvero solo un unlink, come tratterai più avanti. Forse "sostituisce il nome esistente", ma è ancora un po 'confuso.
derobert,

@derobert Non volevo approfondire la terminologia di "scollegamento". Sto usando "Elimina" in riferimento alla voce della directory, una sottigliezza che verrà spiegata in seguito. È confuso in quella fase?
Gilles 'SO- smetti di essere malvagio' il

Probabilmente non abbastanza confuso da giustificare un ulteriore paragrafo o due in alto che spiegano il collegamento. Spero in alcune parole che non siano confuse, ma anche tecnicamente corrette. Forse basta usare nuovamente "rimuovi", di cui hai già messo un link per spiegare?
derobert,

3

L'aggiornamento può essere eseguito mentre il programma è in esecuzione, ma il programma in esecuzione che vedi è in realtà la versione precedente di esso. Il vecchio binario rimane sul disco fino alla chiusura del programma.

Spiegazione: sui sistemi Linux, un file è solo un inode, che può avere diversi collegamenti ad esso. Per esempio. il /bin/bashvedete è solo un link al inode 3932163mio sistema. Puoi trovare a quale inode fa qualcosa collegando emettendo ls --inode /pathsul link. Un file (inode) viene rimosso solo se ci sono zero collegamenti che puntano ad esso e non è in uso da alcun programma. Quando il gestore pacchetti si aggiorna ad es. /usr/bin/firefox, prima scollega (rimuove il collegamento reale /usr/bin/firefox), quindi crea un nuovo file chiamato /usr/bin/firefoxche è un collegamento fisico a un inode diverso (quello che contiene la nuova firefoxversione). Il vecchio inode è ora contrassegnato come libero e può essere riutilizzato per memorizzare nuovi dati ma rimane sul disco (gli inode vengono creati solo quando si crea il file system e non vengono mai eliminati). Al prossimo avvio difirefox, verrà utilizzato quello nuovo.

Se vuoi scrivere un programma che si "aggiorna" durante l'esecuzione, l'unica soluzione che mi viene in mente è quella di controllare periodicamente il timestamp del suo file binario e se è più recente dell'ora di inizio del programma, quindi ricaricare se stesso.


1
In realtà, è dovuto al modo in cui la cancellazione (scollegamento) dei file funziona su Unix; vedi unix.stackexchange.com/questions/49299/… Inoltre, almeno su Linux, non puoi effettivamente scrivere su un binario in esecuzione, otterrai un errore "file di testo occupato".
derobert,

Strano ... Allora come funziona ad es. L' aptaggiornamento di Debian funziona? Posso aggiornare qualsiasi programma in esecuzione senza problemi incluso Iceweasel( Firefox).
psimon,

2
APT (o, piuttosto, dpkg) non sovrascrive i file. Invece li scollega e ne mette uno nuovo con lo stesso nome. Vedi la domanda e la risposta a cui mi sono collegato per una spiegazione.
derobert,

2
Non è solo perché è ancora nella RAM, è ancora sul disco. Il file non viene effettivamente eliminato fino all'uscita dell'ultima istanza del programma.
derobert,

2
Il sito non mi permette di suggerire una modifica (una volta che il tuo rappresentante è abbastanza alto, fai solo delle modifiche, non puoi più suggerirle). Quindi, come commento: un file (inode) su un sistema Unix in genere ha un nome. Ma può avere di più se aggiungi nomi con ln(hard link). Puoi rimuovere i nomi con rm(scollega). Non puoi effettivamente cancellare direttamente un file, rimuoverne solo i nomi. Quando un file non ha nomi e inoltre non è aperto, il kernel lo eliminerà. Un programma in esecuzione ha il file in esecuzione da aperto, quindi anche dopo aver rimosso tutti i nomi, il file è ancora presente.
derobert,

0

Mi chiedo come applicazioni killer come Thunderbird o Firefox possano essere aggiornate tramite il gestore di pacchetti del sistema mentre sono ancora in esecuzione? Bene, posso dirti che questo non funziona davvero bene ... Ho avuto Firefox in modo abbastanza orribile se l'ho lasciato aperto mentre era in esecuzione un aggiornamento del pacchetto. Ho dovuto ucciderlo con forza a volte e riavviarlo, perché era così rotto che non riuscivo nemmeno a chiuderlo correttamente.

Cosa succede con il vecchio codice mentre vengono aggiornati? Normalmente su Linux un programma viene caricato in memoria, quindi l'eseguibile su disco non è necessario o utilizzato mentre il programma è in esecuzione. In effetti puoi persino eliminare l'eseguibile e il programma non dovrebbe interessarti ... Tuttavia, alcuni programmi potrebbero richiedere l'eseguibile e alcuni sistemi operativi (come Windows) bloccheranno il file eseguibile, impedendo l'eliminazione o addirittura rinominando / spostando, mentre il il programma è in esecuzione. Firefox si rompe, perché in realtà è piuttosto complesso e utilizza un mucchio di file di dati che gli dicono come costruire la sua GUI (interfaccia utente). Durante un aggiornamento del pacchetto questi file vengono sovrascritti (aggiornati), quindi quando un vecchio eseguibile di Firefox (in memoria) tenta di utilizzare i nuovi file della GUI, possono accadere cose strane ...

Cosa devo fare quando voglio scrivere un programma a.out che si aggiorna mentre è in esecuzione? Ci sono già molte risposte alla tua domanda. Dai un'occhiata a: /programming/232347/how-should-i-implement-an-auto-updater A proposito, le domande sulla programmazione sono migliori su StackOverflow.


2
Gli eseguibili sono in realtà paginati su richiesta (scambiati). Non sono completamente caricati in memoria e possono essere eliminati dalla memoria ogni volta che il sistema desidera RAM per qualcos'altro. La vecchia versione in realtà rimane sul disco; vedi unix.stackexchange.com/questions/49299/… . Almeno su Linux, in realtà non puoi scrivere su un eseguibile in esecuzione, otterrai l'errore "file di testo occupato". Anche il root non può farlo. (Però hai ragione su Firefox).
derobert,
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.