Si bien la pregunta de Stack Overflow parecía ser suficiente al principio, entiendo, por sus comentarios, por qué aún puede tener dudas al respecto. Para mí, este es exactamente el tipo de situación crítica involucrada cuando los dos subsistemas UNIX (procesos y archivos) se comunican.
Como ya sabrá, los sistemas UNIX generalmente se dividen en dos subsistemas: el subsistema de archivos y el subsistema de procesos. Ahora, a menos que se indique lo contrario a través de una llamada al sistema, el núcleo no debe hacer que estos dos subsistemas interactúen entre sí. Sin embargo, hay una excepción: la carga de un archivo ejecutable en las regiones de texto de un proceso . Por supuesto, uno puede argumentar que esta operación también se activa mediante una llamada al sistema ( execve
), pero esto es usualmente conocido por ser el único caso en el que el subsistema de proceso hace una solicitud implícita al subsistema de archivos.
Debido a que el subsistema de proceso, naturalmente, no tiene forma de manejar los archivos (de lo contrario no tendría sentido dividir todo en dos), tiene que usar lo que el subsistema de archivos proporciona para acceder a los archivos. Esto también significa que el subsistema de proceso se somete a cualquier medida que tome el subsistema de archivos con respecto a la edición / eliminación de archivos. En este punto, recomendaría leer la respuesta de Gilles a esta pregunta de U&L . El resto de mi respuesta se basa en esta más general de Gilles.
Lo primero que debe tenerse en cuenta es que internamente, solo se puede acceder a los archivos a través de inodos . Si se le da una ruta al núcleo, su primer paso será traducirlo a un inodo para ser utilizado para todas las demás operaciones. Cuando un proceso carga un ejecutable en la memoria, lo hace a través de su inodo, que ha sido proporcionado por el subsistema de archivos después de la traducción de una ruta. Los inodos pueden estar asociados a varias rutas (enlaces), y los programas solo pueden eliminar enlaces. Para eliminar un archivo y su inodo, userland debe eliminar todos los enlaces existentes a ese inodo y asegurarse de que no se utilice por completo. Cuando se cumplen estas condiciones, el núcleo eliminará automáticamente el archivo del disco.
Si echas un vistazo a la parte de los ejecutables de reemplazo de la respuesta de Gilles, verás que dependiendo de cómo edites / elimines el archivo, el kernel reaccionará / se adaptará de manera diferente, siempre a través de un mecanismo implementado dentro del subsistema de archivos.
- Si prueba la estrategia uno ( abrir / truncar a cero / escribir o abrir / escribir / truncar a un nuevo tamaño ), verá que el núcleo no se molestará en manejar su solicitud. Obtendrá un error 26: Archivo de texto ocupado (
ETXTBSY
). No hay consecuencias de ningún tipo.
- Si prueba la estrategia dos, el primer paso es eliminar su ejecutable. Sin embargo, dado que está siendo utilizado por un proceso, el subsistema de archivos se activará y evitará que el archivo (y su inodo) se eliminen realmente del disco. Desde este punto, la única forma de acceder al contenido del archivo antiguo es hacerlo a través de su inodo, que es lo que hace el subsistema de proceso cada vez que necesita cargar nuevos datos en secciones de texto (internamente, no tiene sentido usar rutas, excepto al traducirlos en inodes). Aunque hayas desvinculadoel archivo (eliminó todas sus rutas), el proceso aún puede usarlo como si no hubiera hecho nada. Crear un nuevo archivo con la ruta anterior no cambia nada: el nuevo archivo recibirá un inodo completamente nuevo, del cual el proceso en ejecución no tiene conocimiento.
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.
- La estrategia tres es bastante similar ya que la
mv
operación es atómica. Esto probablemente requerirá el uso de la rename
llamada al sistema, y dado que los procesos no se pueden interrumpir mientras está en modo kernel, nada puede interferir con esta operación hasta que se complete (con éxito o no). Nuevamente, no hay alteración del inodo del archivo antiguo: se crea uno nuevo, y los procesos que ya se están ejecutando no tendrán conocimiento de él, incluso si se ha asociado con uno de los enlaces del inodo anterior.
Con la estrategia 3, el paso de mover el nuevo archivo al nombre existente elimina la entrada de directorio que conduce al contenido anterior y crea una entrada de directorio que conduce al nuevo contenido. Esto se hace 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.
Recompilar un archivo : cuando se usa gcc
(y el comportamiento es probablemente similar para muchos otros compiladores), está usando la estrategia 2. Puede ver eso ejecutando uno strace
de los procesos de su compilador:
stat("a.out", {st_mode=S_IFREG|0750, st_size=8511, ...}) = 0
unlink("a.out") = 0
open("a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
chmod("a.out", 0750) = 0
- El compilador detecta que el archivo ya existe a través de las
stat
y lstat
llamadas al sistema.
- El archivo está desvinculado . Aquí, aunque ya no es accesible a través del nombre
a.out
, su inodo y contenido permanecen en el disco, siempre y cuando estén siendo utilizados por procesos que ya se están ejecutando.
- Se crea un nuevo archivo y se hace ejecutable con el nombre
a.out
. Este es un inodo completamente nuevo, y nuevos contenidos, que los procesos que ya se ejecutan no se preocupan.
Ahora, cuando se trata de bibliotecas compartidas, se aplicará el mismo comportamiento. Mientras un objeto de biblioteca sea utilizado por un proceso, no se eliminará del disco, sin importar cómo cambie sus enlaces. Cuando algo tiene que cargarse en la memoria, el núcleo lo hará a través del inodo del archivo y, por lo tanto, ignorará los cambios que realizó en sus enlaces (como asociarlos con nuevos archivos).