Хотя вопроса о переполнении стека сначала было достаточно, я понимаю из ваших комментариев, почему у вас все еще могут возникнуть сомнения по этому поводу. Для меня это как раз та критическая ситуация , которая возникает, когда две подсистемы UNIX (процессы и файлы) обмениваются данными.
Как вы, возможно, знаете, системы UNIX обычно делятся на две подсистемы: файловую подсистему и подсистему процессов. Теперь, если в системном вызове не указано иное, ядро не должно взаимодействовать между этими двумя подсистемами. Однако есть одно исключение: загрузка исполняемого файла в текстовые области процесса . Конечно, можно утверждать, что эта операция также запускается системным вызовом ( execve
), но обычно известно, что это тот случай, когда подсистема процесса делает неявный запрос к файловой подсистеме.
Поскольку у подсистемы процессов, естественно, нет способа обработки файлов (иначе не было бы смысла делить все это на две части), она должна использовать все, что файловая подсистема предоставляет для доступа к файлам. Это также означает, что подсистема процесса подчиняется любым мерам, которые файловая подсистема принимает в отношении редактирования / удаления файла. На данный момент, я рекомендую прочитать ответ Жиля на этот U & L вопрос . Остальная часть моего ответа основана на более общем ответе Жиля.
Первое, что следует отметить, это то, что внутренне файлы доступны только через inode . Если ядру дан путь, его первым шагом будет преобразование его в inode, который будет использоваться для всех других операций. Когда процесс загружает исполняемый файл в память, он делает это через свой инод, который был предоставлен файловой подсистемой после преобразования пути. Иноды могут быть связаны с несколькими путями (ссылками), а программы могут удалять только ссылки. Чтобы удалить файл и его индекс, пользовательская область должна удалить все существующие ссылки на этот индекс и убедиться, что он полностью не используется. Когда эти условия будут выполнены, ядро автоматически удалит файл с диска.
Если вы посмотрите на заменяющую исполняемую часть ответа Жиля, вы увидите, что в зависимости от того, как вы редактируете / удаляете файл, ядро будет реагировать / адаптироваться по-разному, всегда через механизм, реализованный в файловой подсистеме.
- Если вы попробуете первую стратегию ( открыть / усечь до нуля / записать или открыть / записать / усечь до нового размера ), вы увидите, что ядро не потрудится обработать ваш запрос. Вы получите ошибку 26: Текстовый файл занят (
ETXTBSY
). Никаких последствий.
- Если вы попробуете стратегию два, первым шагом будет удаление вашего исполняемого файла. Однако, поскольку он используется процессом, файловая подсистема включается и не позволяет файлу (и его inode) быть действительно удаленным с диска. С этой точки зрения, единственный способ получить доступ к содержимому старого файла - это сделать его через его inode, что и делает подсистема процесса всякий раз, когда ей нужно загрузить новые данные в текстовые разделы (внутренне нет смысла использовать пути, кроме при переводе их в иноды). Даже если вы не связаныфайл (удалил все его пути), процесс все еще может использовать его, как будто вы ничего не сделали. Создание нового файла со старым путем ничего не меняет: новому файлу будет предоставлен совершенно новый индекс, о котором исполняющий процесс не знает.
Стратегии 2 и 3 также безопасны для исполняемых файлов: хотя выполняемые исполняемые файлы (и динамически загружаемые библиотеки) не являются открытыми файлами в смысле наличия файлового дескриптора, они ведут себя очень похожим образом. Пока какая-то программа выполняет код, файл остается на диске даже без записи в каталоге.
- Стратегия три очень похожа, так как
mv
операция атомарная. Это, вероятно, потребует использования rename
системного вызова, и, поскольку процессы не могут быть прерваны в режиме ядра, ничто не может помешать этой операции, пока она не завершится (успешно или нет). Опять же, никакого изменения inode старого файла не происходит: создается новый, и уже запущенные процессы не будут знать об этом, даже если он связан с одной из ссылок старого inode.
В стратегии 3 шаг перемещения нового файла к существующему имени удаляет запись каталога, ведущую к старому контенту, и создает запись каталога, ведущую к новому контенту. Это делается за одну атомарную операцию, поэтому у этой стратегии есть главное преимущество: если процесс в любой момент открывает файл, он увидит либо старый, либо новый контент - нет риска получить смешанный контент или файл не будет существующий.
Перекомпиляция файла : при использовании gcc
(и поведение, вероятно, аналогичное для многих других компиляторов), вы используете стратегию 2. Это можно увидеть, запустив strace
процессы вашего компилятора:
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
- Компилятор определяет , что файл уже существует через
stat
и lstat
системные вызовы.
- Файл не связан . Здесь, хотя он больше не доступен через имя
a.out
, его индекс и содержимое остаются на диске до тех пор, пока они используются уже запущенными процессами.
- Новый файл создается и исполняется под именем
a.out
. Это совершенно новый инод и совершенно новое содержимое, о котором уже не заботятся уже запущенные процессы.
Теперь, когда дело доходит до разделяемых библиотек, применяется то же самое поведение. Пока объект библиотеки используется процессом, он не будет удален с диска, независимо от того, как вы измените его ссылки. Всякий раз, когда что-то должно быть загружено в память, ядро будет делать это через inode файла и, следовательно, будет игнорировать изменения, внесенные вами в его ссылки (например, связать их с новыми файлами).