Jak można wykonać aktualizację na żywo, gdy program jest uruchomiony?

15

Zastanawiam się, jak aplikacje typu Killer, takie jak Thunderbird lub Firefox, można aktualizować za pomocą menedżera pakietów systemu, gdy są one nadal uruchomione. Co dzieje się ze starym kodem podczas ich aktualizacji? Co muszę zrobić, gdy chcę napisać program, który aktualizuje się podczas działania?

ubuplex
źródło
@derobert Niezupełnie: ten wątek nie wchodzi w osobliwości plików wykonywalnych. Zapewnia odpowiednie tło, ale nie jest duplikatem.
Gilles „SO- przestań być zły”
@Gilles Cóż, jeśli pozostawisz resztę rzeczy, jest to zbyt szerokie. Tutaj są dwa (przynajmniej) pytania. Drugie pytanie jest dość bliskie: pyta, dlaczego zamiana plików w celu aktualizacji działa na Uniksie.
derobert
Jeśli naprawdę chcesz to zrobić z własnym kodem, możesz spojrzeć na język programowania Erlang, w którym kluczową cechą jest „gorąca” aktualizacja kodu. learnyousomeerlang.com/relups
mattdm
1
@Gilles BTW: Widząc esej, który dodałeś w odpowiedzi, wycofałem swój głos. To pytanie zmieniło się w dobre miejsce, aby wskazać każdemu, kto chce wiedzieć, jak działają aktualizacje.
derobert

Odpowiedzi:

21

Ogólne zastępowanie plików

Po pierwsze, istnieje kilka strategii zastępowania pliku:

  1. Otwórz istniejący plik do zapisu, skróć go do 0 długości i napisz nową zawartość. (Mniej powszechnym wariantem jest otwarcie istniejącego pliku, zastąpienie starej treści nową treścią, obcięcie pliku do nowej długości, jeśli jest krótszy.)

    echo 'new content' >somefile
    
  2. Usuń stary plik i utwórz nowy plik o tej samej nazwie. W skrócie:

    rm somefile
    echo 'new content' >somefile
    
  3. Napisz do nowego pliku pod tymczasową nazwą, a następnie przenieś nowy plik do istniejącej nazwy. Przeniesienie usuwa stary plik. W skrócie:

    echo 'new content' >somefile.new
    mv somefile.new somefile
    

Nie wymienię wszystkich różnic między strategiami, wymienię tylko niektóre z nich, które są tutaj ważne. W przypadku strategii 1, jeśli jakikolwiek proces aktualnie korzysta z pliku, proces widzi nową zawartość w trakcie aktualizacji. Może to powodować pewne zamieszanie, jeśli proces oczekuje, że zawartość pliku pozostanie taka sama. Należy pamiętać, że dotyczy to tylko procesów, w których plik jest otwarty (jak widać w lsoflub w ; aplikacje interaktywne, które mają otwarty dokument (np. Otwieranie pliku w edytorze) zwykle nie utrzymują pliku otwartego, ładują zawartość pliku podczas Operacja „otwórz dokument” i zastępują plik (przy użyciu jednej z powyższych strategii) podczas operacji „zapisuj dokument”./proc/PID/fd/

W przypadku strategii 2 i 3, jeśli jakiś proces ma somefileotwarty plik , stary plik pozostaje otwarty podczas aktualizacji zawartości. W przypadku strategii 2 krok usuwania pliku w rzeczywistości usuwa tylko pozycję pliku w katalogu. Sam plik jest usuwany tylko wtedy, gdy nie prowadzi do niego pozycja katalogu (w typowych systemach plików Unix może istnieć więcej niż jedna pozycja katalogu dla tego samego pliku ) i żaden proces nie ma otwartego. Oto sposób, aby to zaobserwować - plik jest usuwany tylko po sleepzabiciu procesu ( rmusuwa tylko pozycję katalogu).

echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .

W strategii 3 krok przeniesienia nowego pliku do istniejącej nazwy usuwa pozycję katalogu prowadzącą do starej treści i tworzy pozycję katalogu prowadzącą do nowej treści. Odbywa się to w jednej operacji atomowej, więc ta strategia ma główną zaletę: jeśli proces otworzy plik w dowolnym momencie, zobaczy albo starą lub nową zawartość - nie ma ryzyka, że ​​zawartość zostanie zmieszana lub plik nie będzie istniejący.

Zastępowanie plików wykonywalnych

Jeśli wypróbujesz strategię 1 z działającym plikiem wykonywalnym w systemie Linux, pojawi się błąd.

cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy

„Plik tekstowy” oznacza plik zawierający kod wykonywalny z niejasnych powodów historycznych . Linux, podobnie jak wiele innych wariantów uniksowych, odmawia zastąpienia kodu działającego programu; na to pozwala kilka wariantów uniksowych, co prowadzi do awarii, chyba że nowy kod był bardzo przemyślaną modyfikacją starego kodu.

W systemie Linux można zastąpić kod biblioteki dynamicznie ładowanej. Prawdopodobnie doprowadzi to do awarii używającego go programu. (Możesz nie być w stanie tego zaobserwować, sleepponieważ ładuje cały kod biblioteki, którego potrzebuje, gdy się uruchamia. Wypróbuj bardziej złożony program, który robi coś pożytecznego po spaniu, jak perl -e 'sleep 9; print lc $ARGV[0]'.)

Jeśli interpreter uruchamia skrypt, plik skryptu jest otwierany przez interpretera w zwykły sposób, więc nie ma ochrony przed zastąpieniem skryptu. Niektórzy tłumacze czytają i analizują cały skrypt przed rozpoczęciem wykonywania pierwszego wiersza, inni czytają skrypt w razie potrzeby. Zobacz Co się stanie, jeśli edytujesz skrypt podczas wykonywania? i jak Linux radzi sobie ze skryptami powłoki? po więcej szczegółów.

Strategie 2 i 3 są również bezpieczne dla plików wykonywalnych: chociaż uruchamianie plików wykonywalnych (i bibliotek ładowanych dynamicznie) nie jest plikami otwartymi w sensie posiadania deskryptora plików, zachowują się w bardzo podobny sposób. Tak długo, jak jakiś program uruchamia kod, plik pozostaje na dysku, nawet bez wpisu katalogu.

Aktualizowanie aplikacji

Większość menedżerów pakietów używa strategii 3 do zastępowania plików, ze względu na główną zaletę wspomnianą powyżej - w dowolnym momencie otwarcie pliku prowadzi do jego prawidłowej wersji.

Uaktualnienia aplikacji mogą się zepsuć, ponieważ podczas aktualizacji jednego pliku jest atomowy, aktualizacja aplikacji jako całości nie jest taka, że ​​aplikacja składa się z wielu plików (program, biblioteki, dane,…). Rozważ następującą sekwencję zdarzeń:

  1. Wystąpiła instancja aplikacji.
  2. Aplikacja została zaktualizowana.
  3. Działająca aplikacja instancji otwiera jeden ze swoich plików danych.

W kroku 3 działająca instancja starej wersji aplikacji otwiera plik danych z nowej wersji. To, czy to działa, zależy od aplikacji, jakiego pliku to jest i jak bardzo plik został zmodyfikowany.

Po aktualizacji zauważysz, że stary program nadal działa. Jeśli chcesz uruchomić nową wersję, musisz wyjść ze starego programu i uruchomić nową wersję. Menedżerowie pakietów zwykle zabijają i ponownie uruchamiają demony podczas aktualizacji, ale pozostawiają aplikacje użytkowników w spokoju.

Kilka demonów ma specjalne procedury do obsługi aktualizacji bez konieczności zabijania demona i czekania na ponowne uruchomienie nowej instancji (co powoduje zakłócenie usługi). Jest to konieczne w przypadku init , którego nie można zabić; Systemy init zapewniają sposób żądania wywołania działającej instancji w execvecelu zastąpienia się nową wersją.

Gilles „SO- przestań być zły”
źródło
„następnie przenieś nowy plik do istniejącej nazwy. Przeniesienie usuwa stary plik”. To trochę mylące, ponieważ tak naprawdę unlink, jak omówisz później. Może „zastępuje istniejącą nazwę”, ale nadal jest to nieco mylące.
derobert
@derobert Nie chciałem się zbytnio zajmować terminologią „unlink”. Używam „delete” w odniesieniu do pozycji katalogu, subtelności, która zostanie wyjaśniona później. Czy na tym etapie jest to mylące?
Gilles „SO- przestań być zły”
Prawdopodobnie niewystarczająco mylące, aby uzasadnić dodatkowy akapit lub dwa do góry wyjaśniające odsyłacz. Mam nadzieję na pewne sformułowania, które nie będą mylące, ale również technicznie poprawne. Może po prostu ponownie użyj „usuń”, który już umieściłeś link do wyjaśnienia?
derobert
3

Uaktualnienie można uruchomić podczas działania programu, ale uruchomiony program, który widzisz, jest w rzeczywistości jego starą wersją. Stary plik binarny pozostaje na dysku, dopóki nie zamkniesz programu.

Objaśnienie: w systemach Linux plik jest tylko i-węzłem, do którego może mieć kilka łączy. Na przykład. /bin/bashwidzisz jest tylko link inode 3932163w moim systemie. Możesz sprawdzić, z którym i-węzłem łączy się coś, wydając ls --inode /pathlink. Plik (i-węzeł) jest usuwany tylko wtedy, gdy wskazuje na niego zero łączy i nie jest używany przez żaden program. Gdy menedżer pakietów aktualizuje np. /usr/bin/firefox, najpierw odłącza (usuwa twardy link /usr/bin/firefox), a następnie tworzy nowy plik o nazwie /usr/bin/firefoxhardlink do innego i-węzła (tego, który zawiera nową firefoxwersję). Stary i-węzeł jest teraz oznaczony jako wolny i może być ponownie użyty do przechowywania nowych danych, ale pozostaje na dysku (i-węzły są tworzone tylko podczas budowania systemu plików i nigdy nie są usuwane). Na następnym początkufirefox, zostanie użyty nowy.

Jeśli chcesz napisać program, który sam się „aktualizuje” podczas działania, jedynym możliwym rozwiązaniem, jakie mogę wymyślić, jest okresowe sprawdzanie znacznika czasu własnego pliku binarnego, a jeśli jest on nowszy niż czas uruchomienia programu, przeładuj się.

psimon
źródło
1
W rzeczywistości wynika to ze sposobu usuwania (odłączania) plików w systemie Unix; zobacz unix.stackexchange.com/questions/49299/... Ponadto, przynajmniej w Linuksie, nie można tak naprawdę napisać do działającego pliku binarnego, pojawi się błąd „plik tekstowy zajęty”.
derobert
Dziwne ... To jak np. aptPrace aktualizacyjne Debiana ? Mogę zaktualizować dowolny działający program bez problemów, w tym Iceweasel( Firefox).
psimon
2
APT (a raczej dpkg) nie zastępuje plików. Zamiast tego rozłącza je i umieszcza nowy pod tą samą nazwą. Wyjaśnienia znajdują się w pytaniu i odpowiedzi, z którą się połączyłem.
derobert
2
Nie chodzi tylko o to, że wciąż jest w pamięci RAM, wciąż jest na dysku. Plik nie jest faktycznie usuwany, dopóki ostatnia instancja programu nie zostanie zamknięta.
derobert
2
Witryna nie pozwala mi sugerować edycji (gdy twój przedstawiciel będzie wystarczająco wysoki, po prostu wprowadzasz zmiany, nie możesz ich już sugerować). Tak więc, jako komentarz: plik (i-węzeł) w systemie Unix zwykle ma jedną nazwę. Ale może mieć więcej, jeśli dodasz nazwy za pomocą ln(twardych linków). Możesz usunąć nazwy za pomocą rm(rozłącz). Nie można bezpośrednio usunąć pliku, wystarczy usunąć jego nazwy. Gdy plik nie ma nazw i dodatkowo nie jest otwarty, jądro go usunie. Działający program ma otwarty plik, więc nawet po usunięciu wszystkich nazw plik jest nadal dostępny.
derobert
0

Zastanawiam się, jak aplikacje typu Killer, takie jak Thunderbird lub Firefox, można aktualizować za pomocą menedżera pakietów systemowych, gdy są one nadal uruchomione? Cóż, mogę ci powiedzieć, że to naprawdę nie działa dobrze ... Miałem Firefoksa dość okropnie, gdybym zostawił otwarty, gdy działała aktualizacja pakietu. Czasami musiałem go zabijać z dużą siłą i uruchamiać ponownie, ponieważ był tak zepsuty, że nawet nie mogłem go poprawnie zamknąć.

Co dzieje się ze starym kodem podczas ich aktualizacji? Zwykle w systemie Linux program jest ładowany do pamięci, więc plik wykonywalny na dysku nie jest potrzebny ani używany podczas działania programu. W rzeczywistości możesz nawet usunąć plik wykonywalny, a program nie powinien się tym przejmować ... Jednak niektóre programy mogą wymagać pliku wykonywalnego, a niektóre systemy operacyjne (np. Windows) zablokują plik wykonywalny, zapobiegając usunięciu lub nawet zmianie nazw / przeniesień, podczas gdy program działa. Firefox się psuje, ponieważ jest dość skomplikowany i korzysta z wielu plików danych, które mówią mu, jak zbudować GUI (interfejs użytkownika). Podczas aktualizacji pakietu pliki te są nadpisywane (aktualizowane), więc kiedy starszy plik wykonywalny Firefoksa (w pamięci) próbuje użyć nowych plików GUI, mogą się zdarzyć dziwne rzeczy ...

Co muszę zrobić, gdy chcę napisać program, który aktualizuje się podczas działania? Istnieje już wiele odpowiedzi na twoje pytanie. Sprawdź to: /programming/232347/how-should-i-implement-an-auto-updater Przy okazji, pytania o programowanie są lepsze na StackOverflow.

Rouben Tchakhmakhtchian
źródło
2
Pliki wykonywalne są w rzeczywistości stronicowane na żądanie (zamieniane). Nie są całkowicie załadowane do pamięci i mogą zostać usunięte z pamięci, gdy system chce pamięci RAM na coś innego. Stara wersja faktycznie pozostaje na dysku; patrz unix.stackexchange.com/questions/49299/… . Przynajmniej w systemie Linux nie można pisać do działającego pliku wykonywalnego, pojawi się błąd „plik tekstowy zajęty”. Nawet root nie może tego zrobić. (Masz jednak całkowitą rację co do Firefoksa).
derobert