Замена файлов в целом
Во-первых, существует несколько стратегий для замены файла:
Откройте существующий файл для записи, обрежьте его до 0 длины и запишите новый контент. (Менее распространенный вариант - открыть существующий файл, перезаписать старый контент новым, урезать файл до новой длины, если он короче.) В терминах оболочки:
echo 'new content' >somefile
Удалите старый файл и создайте новый файл с тем же именем. В терминах оболочки:
rm somefile
echo 'new content' >somefile
Запишите новый файл с временным именем, затем переместите новый файл к существующему имени. Этот шаг удаляет старый файл. В терминах оболочки:
echo 'new content' >somefile.new
mv somefile.new somefile
Я не буду перечислять все различия между стратегиями, я просто упомяну некоторые важные здесь. С помощью Stategy 1, если какой-либо процесс в настоящее время использует файл, процесс видит новое содержимое по мере его обновления. Это может вызвать некоторую путаницу, если процесс ожидает, что содержимое файла останется прежним. Обратите внимание, что речь идет только о процессах, которые открывают файл (как видно в lsof
или в ; интерактивные приложения, у которых открыт документ (например, открытие файла в редакторе), обычно не оставляют файл открытым, они загружают содержимое файла во время Операция «открыть документ» и они заменяют файл (используя одну из стратегий выше) во время операции «сохранить документ»./proc/PID/fd/
При стратегиях 2 и 3, если файл somefile
открыт в каком-либо процессе , старый файл остается открытым во время обновления содержимого. В стратегии 2 этап удаления файла фактически удаляет только запись файла в каталоге. Сам файл удаляется только в том случае, если у него нет ведущей к нему записи каталога (в типичных файловых системах Unix может быть несколько записей каталога для одного и того же файла ), и ни у одного процесса его нет. Вот способ убедиться в этом - файл удаляется только после завершения sleep
процесса ( rm
удаляется только его запись в каталоге).
echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .
В стратегии 3 шаг перемещения нового файла к существующему имени удаляет запись каталога, ведущую к старому контенту, и создает запись каталога, ведущую к новому контенту. Это делается за одну атомарную операцию, поэтому у этой стратегии есть главное преимущество: если процесс в любой момент открывает файл, он увидит либо старый, либо новый контент - нет риска получить смешанный контент или файл не будет существующий.
Замена исполняемых файлов
Если вы попробуете стратегию 1 с запущенным исполняемым файлом в Linux, вы получите ошибку.
cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy
«Текстовый файл» означает файл, содержащий исполняемый код по неясным историческим причинам . Linux, как и многие другие варианты Unix, отказывается перезаписывать код работающей программы; несколько вариантов Unix позволяют это, приводя к сбоям, если новый код не был очень хорошо продуманной модификацией старого кода.
В Linux вы можете перезаписать код динамически загружаемой библиотеки. Это может привести к сбою программы, которая его использует. (Возможно, вы не сможете наблюдать это, sleep
поскольку он загружает весь библиотечный код, который ему нужен при запуске. Попробуйте более сложную программу, которая делает что-то полезное после сна, например perl -e 'sleep 9; print lc $ARGV[0]'
.)
Если интерпретатор выполняет сценарий, файл сценария открывается интерпретатором обычным способом, поэтому защита от перезаписи сценария отсутствует. Некоторые интерпретаторы читают и анализируют весь сценарий перед началом выполнения первой строки, другие читают сценарий по мере необходимости. См. Что произойдет, если вы редактируете скрипт во время выполнения? и как Linux работает со скриптами оболочки? Больше подробностей.
Стратегии 2 и 3 также безопасны для исполняемых файлов: хотя выполняемые исполняемые файлы (и динамически загружаемые библиотеки) не являются открытыми файлами в смысле наличия файлового дескриптора, они ведут себя очень похожим образом. Пока какая-то программа выполняет код, файл остается на диске даже без записи в каталоге.
Обновление приложения
Большинство менеджеров пакетов используют стратегию 3 для замены файлов из-за основного преимущества, упомянутого выше - в любой момент времени открытие файла приводит к его действительной версии.
Обновление приложения может прерваться, если обновление одного файла является атомарным, обновление приложения в целом не происходит, если приложение состоит из нескольких файлов (программы, библиотеки, данные и т. Д.). Рассмотрим следующую последовательность событий:
- Экземпляр приложения запущен.
- Приложение обновлено.
- Приложение работающего экземпляра открывает один из своих файлов данных.
На шаге 3 запущенный экземпляр старой версии приложения открывает файл данных из новой версии. Работает ли это или нет, зависит от приложения, из какого он файла и насколько файл был изменен.
После обновления вы заметите, что старая программа все еще работает. Если вы хотите запустить новую версию, вам придется выйти из старой программы и запустить новую версию. Менеджеры пакетов обычно убивают и перезапускают демонов при обновлении, но оставляют приложения конечного пользователя в покое.
У некоторых демонов есть специальные процедуры для обработки обновлений без необходимости убивать демона и ждать перезапуска нового экземпляра (что приводит к прерыванию работы службы). Это необходимо в случае init , который нельзя убить; Системы init предоставляют способ, чтобы запрос запущенного экземпляра execve
заменил себя новой версией.