Заміна файлів взагалі
По-перше, існує кілька стратегій заміни файлу:
Відкрийте існуючий файл для запису, усічіть його до 0 довжини та напишіть новий вміст. (Менш поширений варіант - відкрити існуючий файл, перезаписати старий вміст новим вмістом, обрізати файл на нову довжину, якщо він коротший.) У термінах оболонки:
echo 'new content' >somefile
Видаліть старий файл і створіть новий файл з тим же ім’ям. У терміні оболонки:
rm somefile
echo 'new content' >somefile
Напишіть у новий файл під тимчасовим іменем, а потім перемістіть новий файл до існуючого імені. Цей хід видаляє старий файл. У терміні оболонки:
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 для заміни файлів через головну перевагу, згадану вище - у будь-який момент часу відкриття файлу призводить до дійсної його версії.
Якщо оновлення додатків може зламатися, це те, що при оновленні одного файлу є атомним, оновлення додатка в цілому не є, якщо програма складається з декількох файлів (програма, бібліотеки, дані тощо). Розглянемо таку послідовність подій:
- Запускається екземпляр програми.
- Додаток оновлено.
- Запущена програма екземпляра відкриває один із своїх файлів даних.
На кроці 3 запущений примірник старої версії програми відкриває файл даних з нової версії. Працює це чи ні, залежить від програми, якого файлу він є і наскільки файл був модифікований.
Після оновлення ви помітите, що стара програма все ще працює. Якщо ви хочете запустити нову версію, вам доведеться вийти зі старої програми та запустити нову версію. Менеджери пакунків зазвичай вбивають і перезапускають демони під час оновлення, але додатки кінцевого користувача залишають у спокої.
Кілька демонів мають спеціальні процедури для обробки оновлень, не вбиваючи демона і чекати, коли новий екземпляр перезапуститься (що спричиняє зрив служби). Це необхідно в разі ініта , якого не можна вбити; Системи init надають спосіб вимагати, щоб запущений екземпляр викликав execve
заміну себе новою версією.