Як можливо зробити оновлення в реальному часі під час роботи програми?


15

Цікаво, як програми-вбивці, такі як Thunderbird або Firefox, можна оновити через менеджер пакунків системи, поки вони все ще працюють. Що відбувається зі старим кодом під час їх оновлення? Що мені робити, коли я хочу написати програму a.out, яка оновлює себе під час роботи?



@derobert Не зовсім так: цей потік не вникає в особливості виконуваних файлів. Він надає відповідну інформацію, але це не дублікат.
Жил "ТАК - перестань бути злим"

@Gilles Добре, якщо ви залишите в решті речі занадто широкі. Тут є два (принаймні) питання. А інше питання досить близьке, воно задає питання, чому заміна файлів на оновлення працює на Unix.
дероберт

Якщо ви дійсно хочете зробити це за допомогою власного коду, ви можете поглянути на мову програмування Erlang, де "гарячі" оновлення коду є ключовою особливістю. learnnyousomeerlang.com/relups
mattdm

1
@Gilles 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
    

Я не буду перераховувати всі відмінності між стратегіями, я лише зазначу деякі важливі тут. Зі статтею 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 надають спосіб вимагати, щоб запущений екземпляр викликав execveзаміну себе новою версією.


"потім перемістіть новий файл до існуючого імені. Цей хід видаляє старий файл." Це трохи заплутано, оскільки це справді просто unlink, як ви висвітлюєте пізніше. Можливо, "замінює існуючу назву", але це все ще дещо заплутано.
дероберт

@derobert Я не хотів важко втручатися в термінологію "відключення". Я використовую "delete" для посилання на запис в каталозі, тонкощі, яке пояснюється пізніше. На цьому етапі це заплутано?
Жил 'ТАК - перестань бути злим'

Можливо, недостатньо заплутаного, щоб гарантувати додатковий абзац або два зверху, пояснюючи від’єднання. Я сподіваюсь на формулювання, яке не бентежить, але також є технічно правильним. Може, просто знову використати «видалити», на яке ви вже поставили посилання, щоб пояснити?
дероберт

3

Оновлення можна запустити під час роботи програми, але запущена програма, яку ви бачите, - це насправді стара версія її. Старий бінарний файл залишається на диску, поки ви не закриєте програму.

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

Якщо ви хочете написати програму, яка "оновлює" себе під час запуску, єдине можливе рішення, про яке я можу придумати, - це періодично перевіряти часову позначку власного бінарного файлу, а якщо вона новіша за час початку програми, то перезавантажте себе.


1
Насправді, це пов'язано з тим, як видалення (від’єднання) файлів працює на Unix; см unix.stackexchange.com/questions/49299 / ... Крім того , по крайней мере , на Linux, ви не можете на самому справі писати на запущену довічним, ви отримаєте «текстовий файл зайнятий» помилки.
дероберт

Дивно ... Тоді як наприклад. aptРобота з оновлення Debian ? Я можу оновити будь-яку запущену програму без проблем, включаючи Iceweasel( Firefox).
psimon

2
APT (або, вірніше, dpkg) не перезаписує файли. Він замість цього від’єднує їх і ставить нову під такою ж назвою. Дивіться запитання та відповіді, до яких я пов’язаний.
дероберт

2
Це не тільки тому, що він все ще знаходиться в оперативній пам’яті, він все ще знаходиться на диску. Файл фактично не видаляється до завершення останнього примірника програми.
дероберт

2
Сайт не дозволить мені запропонувати редагування (як тільки ваша репліка буде достатньо високою, ви просто внесете зміни, ви більше не можете їх запропонувати). Отже, як коментар: файл (inode) в системі Unix зазвичай має одне ім’я. Але це може бути більше, якщо ви додасте імена з ln(жорсткі посилання). Ви можете видалити імена за допомогою rm(від’єднати). Фактично ви не можете безпосередньо видалити файл, лише видаліть його імена. Якщо у файлу немає імен і додатково не відкрито, ядро ​​видалить його. У запущеній програмі файл, з якого він працює, відкритий, тому навіть після видалення всіх імен файл все ще існує.
дероберт

0

Цікаво, як додатки-вбивці, такі як Thunderbird або Firefox, можна оновити через менеджер пакунків системи, поки вони все ще працюють? Ну, я можу вам сказати, що це не дуже добре працює ... Мені Firefox зламався досить жахливо, якщо я залишив його відкритим під час запуску оновлення пакета. Мені доводилося іноді вбивати його і перезавантажувати, бо воно було настільки зламане, що я навіть не міг його належним чином закрити.

Що відбувається зі старим кодом під час їх оновлення? Зазвичай в Linux програма завантажується в пам'ять, тому виконуваний файл на диску не потрібен і не використовується під час роботи програми. Насправді ви навіть можете видалити виконуваний файл і програмі це не хвилюватиметься ... Однак, деякі програми можуть потребувати виконуваного файлу, а деякі ОС (наприклад, Windows) заблокують виконуваний файл, запобігаючи видаленню або навіть перейменуванню / переміщенню, в той час як програма працює. Firefox виходить з ладу, тому що він насправді досить складний і використовує купу файлів даних, які розповідають, як створити його графічний інтерфейс (інтерфейс користувача). Під час оновлення пакета ці файли перезаписуються (оновлюються), тому коли старший виконуваний файл Firefox (у пам'яті) намагається використовувати нові файли GUI, можуть статися дивні речі ...

Що мені робити, коли я хочу написати програму a.out, яка оновлює себе під час роботи? На ваше запитання вже багато відповідей. Перевірте це: /programming/232347/how-should-i-implement-an-auto-updater До речі, питання щодо програмування краще вирішити на StackOverflow.


2
Виконавчі файли є фактично спрощеними (заміненими). Вони не повністю завантажені в пам'ять, і можуть випадати з пам'яті, коли система хоче оперативної пам'яті чогось іншого. Стара версія фактично залишається на диску; см unix.stackexchange.com/questions/49299 / ... . Принаймні, у Linux ви не можете насправді записати у запущений виконуваний файл, ви отримаєте помилку "текстовий файл зайнятий". Навіть root не може це зробити. (Ти, правда, щодо Firefox, правда).
derobert
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.