Позвольте мне сломать это.
Когда вы запускаете исполняемый файл, выполняется последовательность системных вызовов, прежде всего fork()
и execve()
:
fork()
создает дочерний процесс вызывающего процесса, который (в основном) является точной копией родительского процесса, причем оба по-прежнему используют один и тот же исполняемый файл (используя страницы памяти копирования при записи, поэтому он эффективен). Он возвращает дважды: в родительском он возвращает дочерний PID. В дочернем процесс возвращает 0. Обычно дочерний процесс вызывает execve сразу же:
execve()
принимает полный путь к исполняемому файлу в качестве аргумента и заменяет вызывающий процесс исполняемым файлом. В этот момент вновь созданный процесс получает свое собственное виртуальное адресное пространство, то есть виртуальную память, и выполнение начинается в его точке входа (в состоянии, определяемом правилами платформы ABI для свежих процессов).
На этом этапе загрузчик ELF ядра отобразил сегменты текста и данных исполняемого файла в память, как если бы он использовал mmap()
системный вызов (с общими сопоставлениями только для чтения и частными сопоставлениями чтения и записи соответственно). BSS также отображается как будто с MAP_ANONYMOUS. (Кстати, я игнорирую динамическое связывание здесь для простоты: динамический компоновщик open()
и mmap()
все динамические библиотеки перед переходом к точке входа основного исполняемого файла.)
Только несколько страниц загружаются в память с диска до того, как вновь созданный exec () запустит собственный код. Другие страницы по требованию распределяются по мере необходимости, если / когда процесс касается этих частей своего виртуального адресного пространства. (Предварительная загрузка любых страниц кода или данных перед началом выполнения кода пользовательского пространства - это просто оптимизация производительности.)
Исполняемый файл идентифицируется индексом на нижнем уровне. После того, как файл начал выполняться, ядро сохраняет содержимое файла без изменений по ссылке на inode, а не по имени файла, как для открытых файловых дескрипторов или отображений памяти с файловой поддержкой. Таким образом, вы можете легко переместить исполняемый файл в другое место файловой системы или даже в другую файловую систему. В качестве примечания, чтобы проверить различные характеристики процесса, вы можете заглянуть в /proc/PID
каталог (PID - это идентификатор процесса данного процесса). Вы даже можете открыть исполняемый файл /proc/PID/exe
, даже если он был удален с диска.
Теперь давайте раскопаем движение:
Когда вы перемещаете файл в пределах одной и той же файловой системы, выполняется системный вызов rename()
, который просто переименовывает файл в другое имя, его индекс остается неизменным.
В то время как между двумя разными файловыми системами происходят две вещи:
Содержимое файла сначала копируется в новое местоположение, read()
иwrite()
После этого файл отсоединяется от исходного каталога с помощью, unlink()
и, очевидно, файл получит новый inode в новой файловой системе.
rm
на самом деле просто unlink()
-ing заданный файл из дерева каталогов, поэтому, имея разрешение на запись в каталог, вы получите достаточное право удалить любой файл из этого каталога.
А теперь представьте себе, что происходит, когда вы перемещаете файлы между двумя файловыми системами и у вас нет прав доступа к unlink()
файлу из исходного кода?
Ну, файл сначала будет скопирован в место назначения ( read()
, write()
), а затем unlink()
потерпит неудачу из-за недостаточного разрешения. Итак, файл останется в обеих файловых системах !!