Как сделать живое обновление во время работы программы?

15

Интересно, как приложения-убийцы, такие как Thunderbird или Firefox, могут обновляться через системный менеджер пакетов, пока они еще работают? Что происходит со старым кодом во время их обновления? Что мне делать, если я хочу написать программу a.out, которая обновляется во время работы?

ubuplex
источник
@derobert Не совсем: этот поток не учитывает особенности исполняемых файлов. Это обеспечивает соответствующий фон, но это не дубликат.
Жиль "ТАК - перестань быть злым"
@ Жиль: Ну, если ты оставишь все остальное, оно слишком широкое. Здесь есть два (по крайней мере) вопроса. И другой вопрос довольно близок, он спрашивает, почему замена файлов для обновления работает в Unix.
Дероберт
Если вы действительно хотите сделать это с вашим собственным кодом, вы можете взглянуть на язык программирования Erlang, где «горячие» обновления кода являются ключевой функцией. learnyousomeerlang.com/relups
mattdm
1
@ Жиль BTW: увидев эссе, которое вы добавили в ответ, я отказался от своего закрытого голосования. Вы превратили этот вопрос в хорошее место, чтобы указать любому, кто хочет знать, как работают обновления.
Дероберт

Ответы:

21

Замена файлов в целом

Во-первых, существует несколько стратегий для замены файла:

  1. Откройте существующий файл для записи, обрежьте его до 0 длины и запишите новый контент. (Менее распространенный вариант - открыть существующий файл, перезаписать старый контент новым, урезать файл до новой длины, если он короче.) В терминах оболочки:

    echo 'new content' >somefile
    
  2. Удалите старый файл и создайте новый файл с тем же именем. В терминах оболочки:

    rm somefile
    echo 'new content' >somefile
    
  3. Запишите новый файл с временным именем, затем переместите новый файл к существующему имени. Этот шаг удаляет старый файл. В терминах оболочки:

    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 для замены файлов из-за основного преимущества, упомянутого выше - в любой момент времени открытие файла приводит к его действительной версии.

Обновление приложения может прерваться, если обновление одного файла является атомарным, обновление приложения в целом не происходит, если приложение состоит из нескольких файлов (программы, библиотеки, данные и т. Д.). Рассмотрим следующую последовательность событий:

  1. Экземпляр приложения запущен.
  2. Приложение обновлено.
  3. Приложение работающего экземпляра открывает один из своих файлов данных.

На шаге 3 запущенный экземпляр старой версии приложения открывает файл данных из новой версии. Работает ли это или нет, зависит от приложения, из какого он файла и насколько файл был изменен.

После обновления вы заметите, что старая программа все еще работает. Если вы хотите запустить новую версию, вам придется выйти из старой программы и запустить новую версию. Менеджеры пакетов обычно убивают и перезапускают демонов при обновлении, но оставляют приложения конечного пользователя в покое.

У некоторых демонов есть специальные процедуры для обработки обновлений без необходимости убивать демона и ждать перезапуска нового экземпляра (что приводит к прерыванию работы службы). Это необходимо в случае init , который нельзя убить; Системы init предоставляют способ, чтобы запрос запущенного экземпляра execveзаменил себя новой версией.

Жиль "ТАК - прекрати быть злым"
источник
"затем переместите новый файл к существующему имени. Этот шаг удаляет старый файл." Это немного сбивает с толку, поскольку это действительно просто unlink, как вы расскажете позже. Может быть, «заменяет существующее имя», но это все еще несколько сбивает с толку.
Дероберт
@derobert Я не хотел вдаваться в терминологию «unlink». Я использую «удалить» в отношении записи каталога, тонкость, которая будет объяснена позже. На этом этапе это сбивает с толку?
Жиль "ТАК - перестать быть злым"
Вероятно, не достаточно запутанно, чтобы оправдать дополнительный параграф или два, объясняющих отмену связи. Я надеюсь на некоторую формулировку, которая не смущает, но также технически правильна. Может быть, просто используйте «удалить» снова, что вы уже поместили ссылку, чтобы объяснить?
Дероберт
3

Обновление можно запустить во время работы программы, но запущенная программа, которую вы видите, на самом деле является ее старой версией. Старый двоичный файл остается на диске, пока вы не закроете программу.

Объяснение: в системах Linux файл является просто индексом, который может иметь несколько ссылок на него. Например. /bin/bashТо, что вы видите, это просто ссылка inode 3932163на мою систему. Вы можете найти, на какой инод что-то ссылается, выполнив ls --inode /pathссылку. Файл (inode) удаляется только в том случае, если на него указывают нулевые ссылки, и он не используется какой-либо программой. Когда менеджер пакетов обновляется, например. /usr/bin/firefox, он сначала отменяет связь (удаляет жесткую ссылку /usr/bin/firefox), затем создает новый файл с именем, /usr/bin/firefoxкоторый является жесткой ссылкой на другой индекс (тот, который содержит новую firefoxверсию). Старый inode теперь помечен как свободный и может использоваться повторно для хранения новых данных, но остается на диске (inode создается только при сборке файловой системы и никогда не удаляется). На следующем стартеfirefoxбудет использован новый.

Если вы хотите написать программу, которая «обновляет» себя во время работы, единственное возможное решение, о котором я могу подумать, - это периодически проверять временную метку своего двоичного файла, и, если она новее, чем время запуска программы, то перезагрузить себя.

psimon
источник
1
На самом деле, это связано с тем, как удаление (удаление ссылок) файлов работает в Unix; см. unix.stackexchange.com/questions/49299/… Кроме того, по крайней мере в Linux вы не можете писать в работающий двоичный файл, вы получите ошибку «text file busy».
Дероберт
Странно ... Тогда как, например. aptРабота по обновлению Debian ? Я могу обновить любую работающую программу без проблем, включая Iceweasel( Firefox).
Псимон
2
APT (или, скорее, dpkg) не перезаписывает файлы. Вместо этого он отменяет связь и помещает новый под тем же именем. Смотрите вопрос и ответ, на который я ссылаюсь, для объяснения.
Дероберт
2
Это не только потому, что он все еще находится в оперативной памяти, он все еще находится на диске. Файл фактически не удаляется, пока не завершится последний экземпляр программы.
Дероберт
2
Сайт не позволит мне предложить редактирование (если ваш представитель достаточно высок, вы просто вносите изменения, вы больше не можете их предлагать). Итак, как комментарий: файл (inode) в системе Unix обычно имеет одно имя. Но он может иметь больше, если вы добавите имена с ln(жесткие ссылки). Вы можете удалить имена с rm(unlink). Вы не можете удалить файл напрямую, только удалить его имена. Если файл не имеет имен и, кроме того, не открыт, ядро ​​удалит его. У запущенной программы есть файл, из которого она запускается, так что даже после удаления всех имен файл все еще присутствует.
Дероберт
0

Интересно, как приложения-убийцы, такие как Thunderbird или Firefox, могут обновляться через системный менеджер пакетов, пока они еще работают? Что ж, я могу сказать вам, что это не очень хорошо работает ... У меня был ужасный перерыв в работе Firefox, если я оставил его открытым, пока выполнялось обновление пакета. Мне иногда приходилось насильно его убивать и перезапускать, потому что он был настолько сломан, что даже не смог его правильно закрыть.

Что происходит со старым кодом во время их обновления? Обычно в Linux программа загружается в память, поэтому исполняемый файл на диске не нужен и не используется во время работы программы. На самом деле вы можете даже удалить исполняемый файл, и программе должно быть все равно ... Однако некоторым программам может понадобиться исполняемый файл, и некоторые ОС (например, Windows) блокируют исполняемый файл, предотвращая удаление или даже переименовывание / перемещение, в то время как Программа запущена. Firefox ломается, потому что он на самом деле довольно сложный и использует кучу файлов данных, которые говорят ему, как создать свой GUI (пользовательский интерфейс). Во время обновления пакета эти файлы перезаписываются (обновляются), поэтому, когда старый исполняемый файл Firefox (в памяти) пытается использовать новые файлы GUI, могут происходить странные вещи ...

Что мне делать, если я хочу написать программу a.out, которая обновляется во время работы? На ваш вопрос уже есть много ответов. Проверьте это: /programming/232347/how-should-i-implement-an-auto-updater Кстати, вопросы о программировании лучше решать в StackOverflow.

Рубен Чахмахтчян
источник
2
Исполняемые файлы фактически распределяются по требованию (обмениваются). Они не полностью загружены в память и могут быть удалены из памяти всякий раз, когда система хочет ОЗУ для чего-то другого. Старая версия фактически остается на диске; см. unix.stackexchange.com/questions/49299/… . По крайней мере, в Linux вы не можете писать в работающий исполняемый файл, вы получите сообщение об ошибке «текстовый файл занят». Даже root не может этого сделать. (Вы совершенно правы насчет Firefox, хотя).
Дероберт