¿Qué sucede cuando un archivo que está 100% paginado en la caché de la página es modificado por otro proceso?

14

Sé que cuando se modifica una página de caché de página, se marca como sucia y requiere una reescritura, pero qué sucede cuando:

Escenario: el archivo / aplicaciones / EXE, que es un archivo ejecutable, se pagina en la caché de la página por completo (todas sus páginas están en la caché / memoria) y se ejecuta mediante el proceso P

La versión continua luego reemplaza / apps / EXE con un nuevo ejecutable.

Supuesto 1: supongo que el proceso P (y cualquier otra persona con un descriptor de archivo que haga referencia al ejecutable anterior) continuará utilizando el antiguo, en memoria / aplicaciones / EXE sin ningún problema, y ​​cualquier proceso nuevo que intente ejecutar esa ruta obtendrá El nuevo ejecutable.

Supuesto 2: supongo que si no todas las páginas del archivo se asignan a la memoria, todo estará bien hasta que haya un error de página que requiera páginas del archivo que se han reemplazado, y probablemente se producirá una falla predeterminada.

Pregunta 1: Si bloquea todas las páginas del archivo con algo como vmtouch, ¿eso cambia el escenario?

Pregunta 2: Si / apps / EXE está en un NFS remoto, ¿haría alguna diferencia? (Supongo que no)

Corrija o valide mis 2 supuestos y responda mis 2 preguntas.

Supongamos que este es un cuadro CentOS 7.6 con algún tipo de kernel 3.10.0-957.el7

Actualización: Pensando más en ello, me pregunto si este escenario no es diferente a cualquier otro escenario de página sucia

Supongo que el proceso que escribe el nuevo binario hará una lectura y obtendrá todas las páginas de caché, ya que está todo paginado, y luego todas esas páginas se marcarán como sucias. Si están bloqueados, solo serán páginas inútiles que ocuparán la memoria central después de que el conteo de referencias llegue a cero.

Sospecho que cuando finalizan los programas que se están ejecutando actualmente, cualquier otra cosa usará el nuevo binario. Suponiendo que todo sea correcto, supongo que solo es interesante cuando solo se paginó parte del archivo.

Gregg Leventhal
fuente
Solo para que sea explícito, reemplazar un archivo no será una gran cosa (dependiendo de si la aplicación vuelve a abrirlo y de cómo reacciona la aplicación al contenido modificado), pero la modificación de archivos mapeados puede bloquear las aplicaciones de gravedad (es un problema común en el mundo java cuando se cambia un archivo zip que tiene una entrada de directorio mmaped). Sin embargo, depende de la plataforma, no se garantiza que las regiones mapeadas vean el cambio o no.
Eckes

Respuestas:

12

La versión continua luego reemplaza / apps / EXE con un nuevo ejecutable.

Esta es la parte importante.

La forma en que se publica un nuevo archivo es creando un nuevo archivo (por ejemplo /apps/EXE.tmp.20190907080000), escribiendo el contenido, estableciendo permisos y propiedad y finalmente renombrándolo (2) con el nombre final /apps/EXE, reemplazando el archivo antiguo.

El resultado es que el nuevo archivo tiene un nuevo número de inodo (lo que significa, en efecto, que es un archivo diferente).

Y el archivo antiguo tenía su propio número de inodo, que en realidad todavía está presente a pesar de que el nombre del archivo ya no apunta a él (o ya no hay nombres de archivo que apunten a ese inodo).

Entonces, la clave aquí es que cuando hablamos de "archivos" en Linux, a menudo hablamos realmente de "inodes" ya que una vez que se ha abierto un archivo, el inodo es la referencia que mantenemos en el archivo.

Supuesto 1 : supongo que el proceso P (y cualquier otra persona con un descriptor de archivo que haga referencia al ejecutable anterior) continuará utilizando el antiguo, en memoria / aplicaciones / EXE sin ningún problema, y ​​cualquier proceso nuevo que intente ejecutar esa ruta obtendrá El nuevo ejecutable.

Correcto.

Supuesto 2 : supongo que si no todas las páginas del archivo se asignan a la memoria, todo estará bien hasta que haya un error de página que requiera páginas del archivo que se han reemplazado, y probablemente se producirá una falla predeterminada.

Incorrecto. El viejo inodo todavía está presente, por lo que las fallas de página del proceso que usa el viejo binario aún podrán encontrar esas páginas en el disco.

Puede ver algunos efectos de esto mirando el /proc/${pid}/exeenlace simbólico (o, de manera equivalente, la lsofsalida) para el proceso que ejecuta el binario antiguo, que se mostrará /app/EXE (deleted)para indicar que el nombre ya no está allí, pero el inodo todavía está presente.

También puede ver que el espacio en disco utilizado por el binario solo se liberará después de que el proceso muera (suponiendo que sea el único proceso con ese inodo abierto). Verifique la salida de dfantes y después de matar el proceso, verá que cae por el tamaño de ese viejo binario que creías que ya no existía.

Por cierto, esto no es solo con binarios, sino con cualquier archivo abierto. Si abre un archivo en un proceso y elimina el archivo, el archivo se mantendrá en el disco hasta que ese proceso cierre el archivo (o muera). De manera similar a cómo los enlaces duros mantienen un contador de cuántos nombres apuntan a un inodo en el disco, el El controlador del sistema de archivos (en el kernel de Linux) mantiene un contador de cuántas referencias existen para ese inodo en la memoria , y solo liberará el inodo del disco una vez que se hayan liberado también todas las referencias del sistema en ejecución.

Pregunta 1 : Si bloquea todas las páginas del archivo con algo como vmtouch, ¿eso cambia el escenario?

Esta pregunta se basa en la suposición incorrecta 2 de que no bloquear las páginas provocará segfaults. No lo hará.

Pregunta 2 : Si / apps / EXE está en un NFS remoto, ¿haría alguna diferencia? (Supongo que no)

Está destinado a funcionar de la misma manera y la mayoría de las veces, pero hay algunas "trampas" con NFS.

A veces puede ver los artefactos de eliminar un archivo que todavía está abierto en NFS (aparece como un archivo oculto en ese directorio).

También tiene alguna forma de asignar números de dispositivo a exportaciones NFS, para asegurarse de que no se "reorganicen" cuando el servidor NFS se reinicie.

Pero la idea principal es la misma. El controlador de cliente NFS todavía usa inodes e intentará mantener los archivos (en el servidor) mientras el inode todavía está referenciado.

filbranden
fuente
1
¿Rename (2) se bloquea hasta que el recuento de referencias del archivo oldname va a cero?
Gregg Leventhal
2
No, renombrar (2) no bloqueará. El viejo inodo se mantiene durante un tiempo potencialmente largo.
filbranden
1
Vea la respuesta de @ mosvy sobre por qué no puede escribir en un archivo que se está ejecutando (obtiene ETXTBSY). Desvincular y crear nuevos tiene el mismo efecto de renombrar: terminas con un nuevo inodo. (Cambiar el nombre es mejor porque entonces no hay un momento en el que el nombre del archivo no exista, es una operación atómica que reemplaza el nombre para apuntar al nuevo inodo.)
filbranden
44
@GreggLeventhal: "¿Qué suposición estás haciendo sobre el proceso de liberación continua que estoy usando que te asegura que usa archivos temporales?" - Porque mientras exista Unix, esa es y ha sido la única forma sensata de hacerlo. renamees prácticamente la única operación de archivos y sistemas de archivos que se garantiza que es atómica (suponiendo que no crucemos los límites del sistema de archivos o del dispositivo), por lo que "crear archivo temporal y luego rename" es el patrón estándar para actualizar archivos. También es lo que usa cada editor de texto en Unix, por ejemplo.
Jörg W Mittag
1
@ grahamj42: renamees parte de POSIX. Por supuesto, se incluye por referencia a ISO C (sección 7.21.4.2 en el borrador actual), pero está ahí.
Jörg W Mittag
6

Supuesto 2: supongo que si no todas las páginas del archivo se asignan a la memoria, todo estará bien hasta que haya una falla de página que requiera páginas del archivo que se han reemplazado, y probablemente se producirá una falla predeterminada.

No, eso no sucederá, porque el núcleo no le permitirá abrir para escribir y reemplazar nada dentro de un archivo que se está ejecutando actualmente. Tal acción fallará con ETXTBSY[1]:

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

Cuando dpkg, etc. actualiza un binario, no lo sobrescribe, sino rename(2)que usa el que simplemente señala la entrada del directorio a un archivo completamente diferente, y cualquier proceso que todavía tenga asignaciones o identificadores abiertos al archivo antiguo continuará usándolo sin problemas .

[1] dicha protección no se extiende a otros archivos que también pueden considerarse "texto" (código en vivo / ejecutable): bibliotecas compartidas, clases de Java, etc. modificar dicho archivo mientras está mapeado por otro proceso hará que se bloquee. En linux, el enlazador dinámico pasa obedientemente la MAP_DENYWRITEbandera a mmap(2), pero no se equivoque, no tiene ningún efecto.

Mosvy
fuente
1
En el escenario dpkg, ¿en qué punto se completa el cambio de nombre de modo que la dentry para / apps / EXE haga referencia al inodo del nuevo binario? Cuando no hay más referencias a la anterior? ¿Cómo funciona?
Gregg Leventhal
2
rename(2)es atómico Tan pronto como se haya completado, la entrada dir se refiere al nuevo archivo. Los procesos que todavía usaban el archivo antiguo en ese momento solo podrían acceder a él a través de asignaciones existentes, o a través de identificadores abiertos (que pueden hacer referencia a una negación huérfana, a la que ya no se puede acceder a través de /proc/PID/fd).
mosvy
1
Me gusta su respuesta mejor porque su mención ETXTBSY me llevó a esta utcc.utoronto.ca/~cks/space/blog/unix/WhyTextFileBusyError que responde a todas mis preguntas.
Gregg Leventhal
4

La respuesta de filbranden es correcta, suponiendo que el proceso de liberación continua realiza un reemplazo atómico adecuado de los archivos a través de rename. Si no lo hace, pero modifica el archivo en el lugar, las cosas son diferentes. Sin embargo, su modelo mental todavía está equivocado.

No hay posibilidad de que las cosas se modifiquen en el disco y sean inconsistentes con el caché de la página, porque el caché de la página es la versión canónica y la que está modificada. Cualquier escritura en un archivo se realiza a través de la memoria caché de la página. Si ya está presente allí, las páginas existentes se modifican. Si aún no está presente, los intentos de modificar una página parcial harán que toda la página se almacene en caché, seguido de la modificación como si ya estuviera almacenada en caché. Las escrituras que abarcan una página entera o más pueden (y casi seguramente lo hacen) optimizar el paso de lectura pagándolos. En cualquier caso, solo existe una versión canónica modificable de un archivo (*), la que está en la memoria caché de la página .

(*) Mentí un poco. Para NFS y otros sistemas de archivos remotos, puede haber más de uno, y generalmente (dependiendo de cuál y qué opciones de montaje y del lado del servidor se usen) no implementan correctamente la atomicidad y la semántica de pedidos para las escrituras. Es por eso que muchos de nosotros los consideramos fundamentalmente rotos y nos negamos a usarlos en situaciones donde habrá escrituras concurrentes con el uso.

R .. GitHub DEJA DE AYUDAR AL HIELO
fuente