¿Cómo es posible hacer una actualización en vivo mientras se ejecuta un programa?

15

Me pregunto cómo se pueden actualizar aplicaciones asesinas como Thunderbird o Firefox a través del administrador de paquetes del sistema mientras aún se están ejecutando. ¿Qué sucede con el código antiguo mientras se actualizan? ¿Qué debo hacer cuando quiero escribir un programa que se actualice mientras se está ejecutando?

ubuplex
fuente
@derobert No exactamente: ese hilo no entra en las peculiaridades de los ejecutables. Proporciona antecedentes relevantes pero no es un duplicado.
Gilles 'SO- deja de ser malvado'
@Gilles Bueno, si dejas el resto de las cosas, es demasiado amplio. Hay dos (al menos) preguntas aquí. Y la otra pregunta está bastante cerca, es por qué reemplazar archivos para actualizar funciona en Unix.
derobert
Si realmente desea hacer esto con su propio código, es posible que desee ver el lenguaje de programación Erlang, donde las actualizaciones de código "hot" son una característica clave. learnyousomeerlang.com/relups
mattdm
1
@Gilles BTW: Habiendo visto el ensayo que has agregado en respuesta, me retracté de mi voto cercano. Has convertido esta pregunta en un buen lugar para señalar a cualquiera que quiera saber cómo funcionan las actualizaciones.
derobert

Respuestas:

20

Reemplazar archivos en general

Primero, hay varias estrategias para reemplazar un archivo:

  1. Abra el archivo existente para escribir, trúnquelo a 0 de longitud y escriba el nuevo contenido. (Una variante menos común es abrir el archivo existente, sobrescribir el contenido antiguo con el nuevo contenido, truncar el archivo a la nueva longitud si es más corto). En términos de shell:

    echo 'new content' >somefile
    
  2. Elimine el archivo antiguo y cree un nuevo archivo con el mismo nombre. En términos de shell:

    rm somefile
    echo 'new content' >somefile
    
  3. Escriba en un nuevo archivo con un nombre temporal, luego mueva el nuevo archivo al nombre existente. El movimiento elimina el archivo antiguo. En términos de shell:

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

No enumeraré todas las diferencias entre las estrategias, solo mencionaré algunas que son importantes aquí. Con la estrategia 1, si algún proceso está usando el archivo actualmente, el proceso ve el nuevo contenido a medida que se actualiza. Esto puede causar cierta confusión si el proceso espera que el contenido del archivo permanezca igual. Tenga en cuenta que esto solo se trata de procesos que tienen el archivo abierto (como se ve en lsofo dentro ; las aplicaciones interactivas que tienen un documento abierto (por ejemplo, abrir un archivo en un editor) generalmente no mantienen el archivo abierto, cargan el contenido del archivo durante el Operación "abrir documento" y reemplazan el archivo (usando una de las estrategias anteriores) durante la operación "guardar documento"./proc/PID/fd/

Con las estrategias 2 y 3, si algún proceso tiene el archivo somefileabierto, el archivo antiguo permanece abierto durante la actualización de contenido. Con la estrategia 2, el paso de eliminar el archivo de hecho solo elimina la entrada del archivo en el directorio. El archivo en sí solo se elimina cuando no tiene una entrada de directorio que lo lleve (en los sistemas de archivos Unix típicos, puede haber más de una entrada de directorio para el mismo archivo ) y ningún proceso lo tiene abierto. Aquí hay una manera de observar esto: el archivo solo se elimina cuando se cierra el sleepproceso ( rmsolo elimina su entrada de directorio).

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

Con la estrategia 3, el paso de mover el nuevo archivo al nombre existente elimina la entrada del directorio que conduce al contenido anterior y crea una entrada de directorio que conduce al nuevo contenido. Esto se realiza en una operación atómica, por lo que esta estrategia tiene una gran ventaja: si un proceso abre el archivo en cualquier momento, verá el contenido antiguo o el nuevo contenido; no hay riesgo de obtener contenido mixto o el archivo no existente.

Sustitución de ejecutables

Si prueba la estrategia 1 con un ejecutable en ejecución en Linux, obtendrá un error.

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

Un "archivo de texto" significa un archivo que contiene código ejecutable por razones históricas oscuras . Linux, como muchas otras variantes de Unix, se niega a sobrescribir el código de un programa en ejecución; Algunas variantes de Unix permiten esto, lo que lleva a bloqueos a menos que el nuevo código fuera una muy buena modificación del antiguo código.

En Linux, puede sobrescribir el código de una biblioteca cargada dinámicamente. Es probable que conduzca a un bloqueo del programa que lo está usando. (Es posible que no pueda observar esto sleepporque carga todo el código de biblioteca que necesita cuando se inicia. Pruebe un programa más complejo que haga algo útil después de dormir, como perl -e 'sleep 9; print lc $ARGV[0]').

Si un intérprete ejecuta un script, el intérprete abre el archivo de script de manera normal, por lo que no hay protección contra la sobrescritura del script. Algunos intérpretes leen y analizan el guión completo antes de comenzar a ejecutar la primera línea, otros leen el guión según sea necesario. Consulte ¿Qué sucede si edita un script durante la ejecución? y ¿Cómo trata Linux los scripts de shell? para más detalles.

Las estrategias 2 y 3 también son seguras para los ejecutables: aunque ejecutar ejecutables (y bibliotecas cargadas dinámicamente) no son archivos abiertos en el sentido de tener un descriptor de archivo, se comportan de una manera muy similar. Mientras algún programa ejecute el código, el archivo permanece en el disco incluso sin una entrada de directorio.

Actualizar una aplicación

La mayoría de los administradores de paquetes usan la estrategia 3 para reemplazar archivos, debido a la gran ventaja mencionada anteriormente: en cualquier momento, abrir el archivo conduce a una versión válida del mismo.

Donde las actualizaciones de la aplicación pueden romperse es que si bien la actualización de un archivo es atómica, la actualización de la aplicación en su conjunto no lo es si la aplicación consta de múltiples archivos (programa, bibliotecas, datos, ...). Considere la siguiente secuencia de eventos:

  1. Se inicia una instancia de la aplicación.
  2. La aplicación está actualizada.
  3. La aplicación de instancia en ejecución abre uno de sus archivos de datos.

En el paso 3, la instancia en ejecución de la versión anterior de la aplicación está abriendo un archivo de datos de la nueva versión. Si esto funciona o no depende de la aplicación, de qué archivo es y cuánto se ha modificado el archivo.

Después de una actualización, notará que el antiguo programa aún se está ejecutando. Si desea ejecutar la nueva versión, deberá salir del programa anterior y ejecutar la nueva versión. Los administradores de paquetes generalmente matan y reinician los demonios en una actualización, pero dejan en paz las aplicaciones del usuario final.

Algunos demonios tienen procedimientos especiales para manejar las actualizaciones sin tener que matar al demonio y esperar a que la nueva instancia se reinicie (lo que causa una interrupción del servicio). Esto es necesario en el caso de init , que no se puede matar; Los sistemas init proporcionan una manera de solicitar que la instancia en ejecución llame execvepara reemplazarse con la nueva versión.

Gilles 'SO- deja de ser malvado'
fuente
"luego mueva el nuevo archivo al nombre existente. El movimiento elimina el archivo anterior". Eso es un poco confuso, ya que en realidad es solo un unlink, como cubrimos más adelante. Tal vez "reemplaza el nombre existente", pero eso sigue siendo algo confuso.
derobert
@derobert No quería profundizar en la terminología de "desvincular". Estoy usando "eliminar" en referencia a la entrada del directorio, una sutileza que se explica más adelante. ¿Es confuso en esa etapa?
Gilles 'SO- deja de ser malvado'
Probablemente no sea lo suficientemente confuso como para justificar un párrafo adicional o dos arriba explicando desvincular. Espero una redacción que no sea confusa, pero que también sea técnicamente correcta. ¿Tal vez simplemente use "eliminar" nuevamente, que ya ha puesto un enlace para explicar?
derobert
3

La actualización se puede ejecutar mientras se ejecuta el programa, pero el programa en ejecución que ve es en realidad la versión anterior. El viejo binario permanece en el disco hasta que cierre el programa.

Explicación: en sistemas Linux, un archivo es solo un inodo, que puede tener varios enlaces. P.ej. lo /bin/bashque ves es solo un enlace a inode 3932163mi sistema. Puede encontrar a qué inodo hace algo el enlace emitiéndolo ls --inode /pathen el enlace. Un archivo (inodo) solo se elimina si hay cero enlaces apuntando a él y ningún programa lo está utilizando. Cuando el administrador de paquetes se actualiza, por ejemplo. /usr/bin/firefox, primero se desenlaza (elimina el enlace duro /usr/bin/firefox), luego crea un nuevo archivo llamado /usr/bin/firefoxque es un enlace duro a un inodo diferente (el que contiene la nueva firefoxversión). El antiguo inodo ahora está marcado como libre y se puede reutilizar para almacenar nuevos datos, pero permanece en el disco (los inodos solo se crean cuando construye su sistema de archivos y nunca se eliminan). En el próximo comienzo defirefox, se usará el nuevo.

Si desea escribir un programa que se "actualice" mientras se ejecuta, la única solución posible que se me ocurre es verificar periódicamente la marca de tiempo de su propio archivo binario, y si es más reciente que la hora de inicio del programa, vuelva a cargarla.

psimon
fuente
1
En realidad, se debe a cómo funciona la eliminación (desvinculación) de archivos en Unix; vea unix.stackexchange.com/questions/49299/… Además, al menos en Linux, no puede escribir en un binario en ejecución, obtendrá un error de "archivo de texto ocupado".
derobert
Extraño ... Entonces, ¿cómo funciona, por ejemplo. ¿El apttrabajo de actualización de Debian ? Puedo actualizar cualquier programa en ejecución sin problemas, incluido Iceweasel( Firefox).
psimon
2
APT (o, más bien, dpkg) no sobrescribe los archivos. En su lugar, los desvincula y coloca uno nuevo con el mismo nombre. Consulte la pregunta y la respuesta a las que me vinculé para obtener una explicación.
derobert
2
No es solo porque todavía está en la RAM, todavía está en el disco. El archivo no se elimina realmente hasta que sale la última instancia del programa.
derobert
2
El sitio no me permite sugerir una edición (una vez que su representante sea lo suficientemente alto, simplemente realice modificaciones, ya no podrá sugerirlas). Entonces, como comentario: un archivo (inodo) en un sistema Unix generalmente tiene un nombre. Pero puede tener más si agrega nombres con ln(enlaces duros). Puede eliminar nombres con rm(desvincular). En realidad, no puede eliminar directamente un archivo, solo eliminar sus nombres. Cuando un archivo no tiene nombres y, además, no está abierto, el núcleo lo eliminará. Un programa en ejecución tiene abierto el archivo, por lo que incluso después de eliminar todos los nombres, el archivo sigue existiendo.
derobert
0

Me pregunto cómo se pueden actualizar aplicaciones asesinas como Thunderbird o Firefox a través del administrador de paquetes del sistema mientras aún se están ejecutando. Bueno, puedo decirte que esto realmente no funciona bien ... He tenido un ataque de Firefox sobre mí horriblemente si lo dejo abierto mientras se ejecutaba una actualización del paquete. A veces tenía que matarlo con fuerza y ​​reiniciarlo, porque estaba tan roto que ni siquiera podía cerrarlo correctamente.

¿Qué sucede con el código antiguo mientras se actualizan? Normalmente en Linux, un programa se carga en la memoria, por lo que el ejecutable en el disco no se necesita ni se utiliza mientras el programa se está ejecutando. De hecho, incluso puede eliminar el ejecutable y el programa no debería importarle ... Sin embargo, algunos programas pueden necesitar el ejecutable, y ciertos sistemas operativos (como Windows) bloquearán el archivo ejecutable, evitando la eliminación o incluso renombrar / mover, mientras que el El programa se está ejecutando. Firefox se rompe, porque en realidad es bastante complejo y utiliza un montón de archivos de datos que le indican cómo construir su GUI (interfaz de usuario). Durante la actualización de un paquete, estos archivos se sobrescriben (actualizan), por lo que cuando un ejecutable anterior de Firefox (en la memoria) intenta usar los nuevos archivos GUI, pueden suceder cosas extrañas ...

¿Qué debo hacer cuando quiero escribir un programa que se actualice mientras se está ejecutando? Ya hay muchas respuestas a su pregunta. Mira esto: /programming/232347/how-should-i-implement-an-auto-updater Por cierto, las preguntas sobre programación son mejores en StackOverflow.

Rouben Tchakhmakhtchian
fuente
2
Los ejecutables son en realidad paginados por demanda (intercambiados). No están completamente cargados en la memoria, y se pueden eliminar de la memoria cada vez que el sistema quiere RAM para otra cosa. La versión anterior en realidad permanece en el disco; ver unix.stackexchange.com/questions/49299/… . Al menos en Linux, en realidad no puede escribir en un ejecutable en ejecución, obtendrá el error "archivo de texto ocupado". Incluso la raíz no puede hacerlo. (Sin embargo, tienes bastante razón sobre Firefox).
derobert