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/PID
directorio (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.
rm
en 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!