¿Por qué es posible mover un programa en ejecución en Ubuntu?

24

Me acabo de dar cuenta de que puedo mover un programa activo en ejecución a un directorio diferente. En mi experiencia, eso no era posible en MacOs o Windows. ¿Cómo funciona en Ubuntu?

Editar: pensé que no era posible en Mac, pero aparentemente es posible como lo comprueban los comentarios. Tal vez solo no sea posible en Windows. Gracias por todas las respuestas.

n0.ob
fuente
2
Casi un engaño entre sitios: stackoverflow.com/a/196910/1394393 .
jpmc26
1
¿No puedes rename(2)ejecutar un ejecutable en OS X? ¿Qué pasa, obtienes EBUSYo algo? ¿Por qué no funciona? La página del comando man rename (2) no documenta ETXTBUSYesa llamada del sistema, y ​​solo habla sobre la EBUSYposibilidad de cambiar el nombre del directorio, por lo que no sabía que un sistema POSIX incluso podría rechazar el cambio de nombre de los ejecutables.
Peter Cordes
3
Las aplicaciones de macOS también se pueden mover mientras se ejecutan, solo que no se eliminen. Supongo que algunas aplicaciones pueden errar después de eso, por ejemplo, si almacenan URL de archivos en sus recursos binarios o agrupados en algún lugar como una variable en lugar de generar dicha URL a través de NSBundle et al. Sospecho que es el cumplimiento POSIX de macOS.
Constantino Tsarouhas
1
En realidad funciona como Linux pretende, debes saber lo que estás haciendo. : P
userDepth
2
Supongo que otra forma de pensarlo es, ¿por qué no sería posible? El hecho de que Windows no lo permita no significa necesariamente que no sea posible debido a cómo funcionan los procesos o algo así.
Thomas

Respuestas:

32

Déjame desglosarlo.

Cuando ejecuta un ejecutable, se ejecuta una secuencia de llamadas al sistema, más notablemente fork()y execve():

  • fork()crea un proceso hijo del proceso de llamada, que es (en su mayoría) una copia exacta del padre, ambos ejecutan el mismo ejecutable (usando páginas de memoria de copia en escritura, por lo que es eficiente). Devuelve dos veces: en el padre, devuelve el PID hijo. En el elemento secundario, devuelve 0. Normalmente, el proceso secundario llama a execve de inmediato:

  • execve()toma una ruta completa al ejecutable como argumento y reemplaza el proceso de llamada con el ejecutable. En este punto, el proceso recién creado obtiene su propio espacio de dirección virtual, es decir, memoria virtual, y la ejecución comienza en su punto de entrada (en un estado especificado por las reglas de la plataforma ABI para procesos nuevos).

En este punto, el cargador ELF del kernel ha mapeado los segmentos de texto y datos del ejecutable en la memoria, como si hubiera usado la mmap()llamada al sistema (con mapeos de lectura y escritura privados de solo lectura y respectivamente). El BSS también se asigna como con MAP_ANONYMOUS. (Por cierto, estoy ignorando el enlace dinámico aquí por simplicidad: el enlazador dinámico open()s y mmap()s todas las bibliotecas dinámicas antes de saltar al punto de entrada del ejecutable principal).

Solo unas pocas páginas se cargan realmente en la memoria del disco antes de que un ed recién ejecutado () comience a ejecutar su propio código. Se requiere paginación de páginas adicionales según sea necesario, si / cuando el proceso toca esas partes de su espacio de direcciones virtuales. (Precargar cualquier página de código o datos antes de comenzar a ejecutar el código de espacio de usuario es solo una optimización del rendimiento).


El archivo ejecutable se identifica por el inodo en el nivel inferior. Una vez que el archivo ha comenzado a ejecutarse, el núcleo mantiene el contenido del archivo intacto por la referencia del inodo, no por el nombre del archivo, como para los descriptores de archivos abiertos o las asignaciones de memoria respaldadas por archivos. Por lo tanto, puede mover fácilmente el ejecutable a otra ubicación del sistema de archivos o incluso a un sistema de archivos diferente. Como nota al margen, para verificar las diversas estadísticas del proceso, puede echar un vistazo al /proc/PIDdirectorio (PID es el ID del proceso del proceso dado). Incluso puede abrir el archivo ejecutable como /proc/PID/exe, incluso si se ha desvinculado del disco.


Ahora profundicemos en el movimiento:

Cuando mueve un archivo dentro de un mismo sistema de archivos, la llamada al sistema que se ejecuta es rename(), que simplemente cambia el nombre del archivo a otro nombre, el inodo del archivo sigue siendo el mismo.

Mientras que entre dos sistemas de archivos diferentes, suceden dos cosas:

  • El contenido del archivo se copia primero en la nueva ubicación, por read()ywrite()

  • Después de eso, el archivo se desvincula del directorio de origen usando unlink()y obviamente el archivo obtendrá un nuevo inodo en el nuevo sistema de archivos.

rmen realidad solo está unlink()haciendo el archivo dado del árbol de directorios, por lo que tener el permiso de escritura en el directorio le dará el derecho suficiente para eliminar cualquier archivo de ese directorio.

Ahora, por diversión, ¿qué sucede cuando mueves archivos entre dos archivos y no tienes permiso para unlink()el archivo desde el origen?

Bueno, el archivo se copiará al destino al principio ( read(), write()) y luego unlink()fallará debido a un permiso insuficiente. ¡Entonces, el archivo permanecerá en ambos sistemas de archivos!

heemayl
fuente
55
Su memoria virtual y física es algo confusa. Su descripción de la forma en que se carga el programa en la memoria física es incorrecta. La llamada al sistema ejecutivo no copia en absoluto las diversas secciones de un ejecutable en la memoria física, sino que solo carga la que necesita para iniciar el proceso. Después, las páginas requeridas se cargan a pedido, posiblemente mucho tiempo después. Los bytes del archivo ejecutable son parte de la memoria virtual del proceso y pueden leerse y posiblemente leerse nuevamente durante toda la vida del proceso.
jlliagre
@jlliagre Editado, espero que se haya aclarado ahora. Gracias.
heemayl
66
La declaración "El proceso ya no usa el sistema de archivos" sigue siendo cuestionable.
jlliagre
2
La comprensión básica de que un archivo dado en el sistema de archivos no está directamente identificado por el nombre del archivo debería ser mucho más claro.
Thorbjørn Ravn Andersen
2
Todavía hay imprecisiones en su actualización. Los mmapy unmapsistema de llamadas no se utilizan para cargar y descargar las páginas de la demanda, las páginas se cargan por el núcleo cuando se accede a ellas generan un fallo de página, las páginas se descargan de la memoria cuando el sistema operativo se siente la RAM sería mejor utilizar para otra cosa. Ninguna llamada al sistema está involucrada en estas operaciones de carga / descarga.
jlliagre
14

Bueno, eso es bastante sencillo. Tomemos un ejecutable llamado / usr / local / bin / whoopdeedoo. Eso es solo una referencia al llamado inodo (estructura básica de archivos en Unix Filesystems). Es el inodo que se marca "en uso".

Ahora, cuando elimina o mueve el archivo / usr / local / whoopdeedoo, lo único que se mueve (o borra) es la referencia al inodo. El inodo en sí permanece sin cambios. Eso es básicamente todo.

Debería verificarlo, pero creo que también puedes hacerlo en sistemas de archivos Mac OS X

Windows toma un enfoque diferente. ¿Por qué? Quién sabe...? No estoy familiarizado con los aspectos internos de NTFS. Teóricamente, todos los sistemas de archivos que usan referencias a estructuras internas para nombres de archivos deberían poder hacer esto.

Admito que simplifiqué demasiado, pero ve a leer la sección "Implicaciones" en Wikipedia, que hace un trabajo mucho mejor que yo.

jawtheshark
fuente
1
Bueno, si usas un acceso directo en Windows para iniciar el ejecutable, también puedes borrar el acceso directo, si quieres compararlo así, ¿quizás? = 3
Rayo
2
No, eso sería como borrar un enlace simbólico. En algún lugar de otros comentarios, se afirma que el comportamiento se debe al soporte heredado con los sistemas de archivos FAT. Eso suena como una razón probable.
jawtheshark
1
Esto no tiene nada que ver específicamente con los inodes. NTFS usa registros MFT para rastrear el estado del archivo, y FAT usa entradas de directorio para esto, pero Linux aún funciona de la misma manera con estos sistemas de archivos, desde el punto de vista del usuario.
Ruslan
13

Una cosa que parece faltar en todas las otras respuestas es que: una vez que se abre un archivo y un programa contiene un descriptor de archivo abierto, el archivo no se eliminará del sistema hasta que se cierre ese descriptor de archivo.

Los intentos de eliminar el inodo referenciado se retrasarán hasta que se cierre el archivo: el cambio de nombre en el mismo sistema de archivos o uno diferente no puede afectar el archivo abierto, independientemente del comportamiento del cambio de nombre, ni eliminar o sobrescribir explícitamente el archivo con uno nuevo. La única forma en que puede desordenar un archivo es abriendo explícitamente su inodo y desordenando el contenido, no mediante operaciones en el directorio, como renombrar / eliminar el archivo.

Además, cuando el kernel ejecuta un archivo, mantiene una referencia al archivo ejecutable y esto evitará nuevamente cualquier modificación durante la ejecución.

Entonces, al final, incluso si parece que puede eliminar / mover los archivos que componen un programa en ejecución, en realidad el contenido de esos archivos se mantiene en la memoria hasta que finaliza el programa.

Bakuriu
fuente
1
Esto no está bien. execve()no devuelve ningún FD, simplemente ejecuta el programa. Así, por ejemplo, si se ejecuta tail -f /foo.loga continuación, su es un FD ( /proc/PID/fd/<fd_num>) asociado con tailel foo.logpero no para el propio ejecutable, taily no en su matriz también. Esto también es cierto para los ejecutables individuales.
heemayl
@heemayl No lo mencioné, execveasí que no veo cómo esto es relevante. Una vez que el kernel comienza a ejecutar un archivo, intentar reemplazar el archivo no modificará el programa que el kernel va a cargar, lo que hace que el punto sea discutible. Si desea "actualizar" el ejecutable mientras se está ejecutando, puede llamar execveen algún momento para que el núcleo vuelva a leer el archivo, pero no veo cómo esto importa. El punto es: eliminar un "ejecutable en ejecución" realmente no desencadena ninguna eliminación de datos hasta que se detiene el ejecutable.
Bakuriu
Estoy hablando de esta parte si el programa consta de un solo archivo ejecutable una vez que comience la ejecución, el programa se ejecutará bien independientemente de cualquier cambio en el directorio: el cambio de nombre en el mismo sistema de archivos o uno diferente no puede afectar el controlador abierto , necesariamente está hablando aproximadamente execve()y un FD cuando no hay FD involucrado en este caso.
heemayl
2
No necesita un identificador de archivo para tener una referencia al archivo; tener páginas asignadas también es suficiente.
Simon Richter
1
Unix no tiene "identificadores de archivo". open()devuelve un descriptor de archivo , del que habla Heemayl aquí execve(). Sí, un proceso en ejecución tiene una referencia a su ejecutable, pero ese no es un descriptor de archivo. Probablemente, incluso si munmap()editara todas sus asignaciones de su ejecutable, todavía tendría una referencia (reflejada en / proc / self / exe) que impedía que se liberara el inodo. (Esto sería posible sin fallar si lo hiciera desde una función de biblioteca que nunca regresó). Por cierto, truncar o modificar un ejecutable en uso podría darle ETXTBUSY, pero podría funcionar.
Peter Cordes
7

En un sistema de archivos de Linux, cuando mueve un archivo, siempre que no cruce los límites del sistema de archivos (léase: permanece en el mismo disco / partición), todo lo que está cambiando es el inodo de ..(directorio padre) al de la nueva ubicación . Los datos reales no se han movido en absoluto en el disco, solo el puntero para que el sistema de archivos sepa dónde encontrarlos.

Esta es la razón por la cual las operaciones de movimiento son tan rápidas y es probable que no haya ningún problema en mover un programa en ejecución, ya que en realidad no está moviendo el programa en sí.

I_GNU_it_all_along
fuente
Su respuesta parece implicar que mover un ejecutable binario a otro sistema de archivos afectaría los procesos en ejecución iniciados desde ese binario.
jlliagre
6

Es posible porque mover un programa no afecta los procesos en ejecución iniciados al iniciarlo.

Una vez que se inicia un programa, sus bits en el disco están protegidos para que no se sobrescriban, pero no es necesario proteger el archivo para cambiarle el nombre, moverlo a una ubicación diferente en el mismo sistema de archivos, lo que equivale a cambiar el nombre del archivo o moverlo a un sistema de archivos diferente, que es equivalente a copiar el archivo en otro lugar y luego eliminarlo.

Al eliminar un archivo que está en uso, ya sea porque un proceso tiene un descriptor de archivo abierto o porque un proceso lo está ejecutando, no elimina los datos del archivo, que permanece referenciado por el inodo del archivo, sino que solo elimina la entrada del directorio, es decir, una ruta desde la cual se puede alcanzar el inodo.

Tenga en cuenta que iniciar un programa no carga todo de una vez en la memoria (física). Por el contrario, solo se carga el mínimo estricto requerido para que comience el proceso. Luego, las páginas requeridas se cargan a pedido durante toda la vida del proceso. Esto se llama paginación de demanda. Si hay escasez de RAM, el sistema operativo es libre de liberar la RAM que contiene estas páginas, por lo que es muy posible que un proceso cargue varias veces la misma página desde el inodo ejecutable.

La razón por la que no fue posible con Windows originalmente se debe probablemente al hecho de que el sistema de archivos subyacente (FAT) no admitía el concepto dividido de entradas de directorio frente a inodos. Esta limitación ya no estaba presente con NTFS, pero el diseño del sistema operativo se ha mantenido durante mucho tiempo, lo que lleva a la restricción desagradable de tener que reiniciar al instalar una nueva versión de un binario, que ya no es el caso con las versiones recientes de Windows.

jlliagre
fuente
1
Creo que las versiones más nuevas de Windows pueden reemplazar los archivos binarios en uso sin reiniciar.
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen Me pregunto por qué todas las actualizaciones aún requieren reinicio :(
Braiam
1
@Braiam No lo hacen. Echa un vistazo más de cerca. Aunque los archivos binarios se pueden actualizar, el núcleo no puede (que yo sepa) y requiere un reinicio para ser reemplazado por una versión más nueva. Esto es válido para la mayoría de los núcleos del sistema operativo. Gente más inteligente que yo he escrito kpatch para Linux que puede parchear un kernel de Linux mientras se ejecuta - ver en.wikipedia.org/wiki/Kpatch
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen
Quise
@Braiam sí, yo también. Por favor, eche un vistazo más de cerca.
Thorbjørn Ravn Andersen
4

Básicamente, en Unix y sus características, se usa un nombre de archivo (incluida la ruta del directorio que lo conduce) para asociar / encontrar un archivo al abrirlo (ejecutar un archivo es una forma de abrirlo de alguna manera). Después de ese momento, la identidad del archivo (a través de su "inodo") se establece y ya no se cuestiona. Puede eliminar el archivo, cambiarle el nombre, cambiar sus permisos. Siempre que un proceso o una ruta de archivo tenga un identificador en ese archivo / inodo, se mantendrá, al igual que una tubería entre procesos (en realidad, en UNIX histórico una tubería era un inodo sin nombre con un tamaño que simplemente encajaba en el referencia de almacenamiento en disco de "bloques directos" en el inodo, algo así como 10 bloques).

Si tiene un visor de PDF abierto en un archivo PDF, puede eliminar ese archivo y abrir uno nuevo con el mismo nombre, y mientras el antiguo visor esté abierto, seguirá accediendo al archivo antiguo (a menos que lo vea activamente el sistema de archivos para notar cuándo desaparece el archivo con su nombre original).

Los programas que necesitan archivos temporales solo pueden abrir dicho archivo con algún nombre y luego inmediatamente eliminarlo (o más bien su entrada de directorio) mientras aún está abierto. Posteriormente, el archivo ya no es accesible por nombre, pero cualquier proceso que tenga un descriptor de archivo abierto para el archivo aún puede acceder a él, y si después hay una salida inesperada del programa, el archivo se eliminará y el almacenamiento se recuperará automáticamente.

Por lo tanto, la ruta a un archivo no es una propiedad del archivo en sí (de hecho, los enlaces duros pueden proporcionar varias rutas diferentes) y solo se necesita para abrirlo, no para el acceso continuo de los procesos que ya lo tienen abierto.

usuario584745
fuente