Dlaczego można przenieść działający program w Ubuntu?


24

Właśnie zdałem sobie sprawę, że jestem w stanie przenieść działający program do innego katalogu. Z mojego doświadczenia wynika, że ​​nie było to możliwe w MacOs lub Windows. Jak to działa w Ubuntu?

Edycja: Myślałem, że nie jest to możliwe na Macu, ale najwyraźniej jest to możliwe po zweryfikowaniu komentarzy. Może nie jest to możliwe tylko w systemie Windows. Dzięki za wszystkie odpowiedzi.


2
Prawie duplikat między witrynami: stackoverflow.com/a/196910/1394393 .
jpmc26,

1
Nie możesz rename(2)uruchomić działającego pliku wykonywalnego w systemie OS X? Co się dzieje, dostajesz EBUSYczy coś? Dlaczego to nie działa? Strona podręcznika zmiany nazwy (2) nie dokumentuje ETXTBUSYtego wywołania systemowego i mówi tylko o EBUSYmożliwości zmiany nazw katalogów, więc nie wiedziałem, że system POSIX może nawet zabronić zmiany nazw plików wykonywalnych.
Peter Cordes,

3
Aplikacje macOS można również przenosić podczas ich działania, ale nie można ich kasować. Zakładam, że niektóre aplikacje mogą później popełniać błędy, np. Jeśli przechowują adresy URL plików w swoich zasobach binarnych lub pakietowych gdzieś jako zmienną zamiast generować taki adres URL za pośrednictwem NSBundle i in. Podejrzewam, że jest to zgodność POSIX z macOS.
Constantino Tsarouhas,

1
W rzeczywistości działa tak, jak zamierza Linux, powinieneś wiedzieć, co robisz. : P
userDepth

2
Myślę, że innym sposobem myślenia o tym jest, dlaczego nie byłoby to możliwe? To, że system Windows na to nie pozwala, niekoniecznie oznacza, że ​​jest to zasadniczo niemożliwe ze względu na sposób działania procesów lub coś w tym rodzaju.
Thomas

Odpowiedzi:


32

Pozwól mi to zepsuć.

Po uruchomieniu pliku wykonywalnego wykonywana jest sekwencja wywołań systemowych, w szczególności fork()i execve():

  • fork()tworzy proces potomny procesu wywołującego, który jest (przeważnie) dokładną kopią elementu nadrzędnego, przy czym oba nadal uruchamiają ten sam plik wykonywalny (używając stron pamięci kopiowania przy zapisie, więc jest to wydajne). Zwraca dwa razy: W obiekcie nadrzędnym zwraca podrzędny PID. W potomku zwraca 0. Zwykle wywołania procesów potomnych wykonują natychmiast:

  • execve()przyjmuje pełną ścieżkę do pliku wykonywalnego jako argument i zastępuje proces wywołujący plikiem wykonywalnym. W tym momencie nowo utworzony proces otrzymuje własną wirtualną przestrzeń adresową, tj. Pamięć wirtualną, a wykonywanie rozpoczyna się w punkcie wejścia (w stanie określonym przez zasady ABI platformy dla nowych procesów).

W tym momencie moduł ładujący ELF jądra zmapował segmenty tekstu i danych pliku wykonywalnego do pamięci, tak jakby używał mmap()wywołania systemowego (odpowiednio ze współdzielonymi mapami tylko do odczytu i prywatnymi mapowaniami do odczytu i zapisu). BSS jest również odwzorowany tak, jakby zawierał MAP_ANONYMOUS. (BTW, ja ignorując dynamiczne łączenie tutaj dla uproszczenia: Dynamiczne łącznik open()S i mmap()wszystko bibliotek dynamicznych przed skokiem do punktu wejścia głównego wykonywalnego).

Tylko kilka stron jest ładowanych do pamięci z dysku, zanim nowo-exec () ed zacznie uruchamiać swój własny kod. Dalsze strony są wczytywane w razie potrzeby, jeśli / kiedy proces dotknie tych części wirtualnej przestrzeni adresowej. (Wstępne ładowanie dowolnych stron kodu lub danych przed rozpoczęciem wykonywania kodu przestrzeni użytkownika to tylko optymalizacja wydajności).


Plik wykonywalny jest identyfikowany przez i-węzeł na niższym poziomie. Po uruchomieniu pliku jądro utrzymuje zawartość pliku nienaruszoną przez odwołanie do i-węzła, a nie przez nazwę pliku, jak w przypadku otwartych deskryptorów plików lub mapowań pamięci opartych na pliku. Dzięki temu możesz łatwo przenieść plik wykonywalny w inne miejsce systemu plików lub nawet w innym systemie plików. Na marginesie, aby sprawdzić różne statystyki procesu, możesz zajrzeć do katalogu /proc/PID(PID to identyfikator procesu danego procesu). Możesz nawet otworzyć plik wykonywalny, ponieważ /proc/PID/exenawet on został odłączony od dysku.


Teraz wykopmy ruch:

Gdy przenosisz plik w tym samym systemie plików, wykonywane jest wywołanie systemowe rename(), które po prostu zmienia nazwę pliku na inną nazwę, i-węzeł pliku pozostaje taki sam.

Natomiast między dwoma różnymi systemami plików zdarzają się dwie rzeczy:

  • Zawartość pliku jest najpierw kopiowana do nowej lokalizacji przez read()iwrite()

  • Następnie plik jest odłączany od katalogu źródłowego przy użyciu unlink()i oczywiście plik otrzyma nowy i-węzeł w nowym systemie plików.

rmto po prostu unlink()-podanie danego pliku z drzewa katalogów, więc posiadanie uprawnienia do zapisu w katalogu zapewni ci wystarczające prawo do usunięcia dowolnego pliku z tego katalogu.

Teraz dla zabawy, wyobraź sobie, co się dzieje, gdy przenosisz pliki między dwoma systemami plików i nie masz uprawnień do unlink()pliku ze źródła?

Plik zostanie najpierw skopiowany do miejsca docelowego ( read(), write()), a następnie unlink()zakończy się niepowodzeniem z powodu niewystarczających uprawnień. Tak więc plik pozostanie w obu systemach plików !!


5
Masz nieco mylącą pamięć wirtualną i fizyczną. Twój opis sposobu załadowania programu do pamięci fizycznej jest niedokładny. Wywołanie systemowe exec wcale nie kopiuje różnych sekcji pliku wykonywalnego do pamięci fizycznej, ale ładuje tylko tę, której potrzebuje do uruchomienia procesu. Następnie wymagane strony są ładowane na żądanie, być może jeszcze długo. Bajty plików wykonywalnych są częścią pamięci wirtualnej procesu i mogą być odczytywane, i być może ponownie odczytywane przez cały czas trwania procesu.
jlliagre

@jlliagre Edytowane, mam nadzieję, że zostało to teraz wyjaśnione. Dzięki.
heemayl

6
Instrukcja „Proces nie korzysta już z systemu plików” jest nadal wątpliwa.
jlliagre,

2
Podstawowe zrozumienie, że dany plik w systemie plików nie jest bezpośrednio identyfikowany przez nazwę pliku, powinno być znacznie jaśniejsze.
Thorbjørn Ravn Andersen

2
Nadal istnieją niedokładności w Twojej aktualizacji. Te mmapi unmapwywołania systemowe nie są wykorzystywane do załadunku i rozładunku stron na żądanie strony są ładowane przez jądro podczas uzyskiwania dostępu do nich generuje błąd strony, strony są usuwane z pamięci, gdy czuje OS RAM byłyby lepiej wykorzystane do czegoś innego. Żadne wywołanie systemowe nie jest zaangażowane w te operacje ładowania / rozładowywania.
jlliagre,

14

Cóż, to całkiem proste. Weźmy plik wykonywalny o nazwie / usr / local / bin / whoopdeedoo. Jest to tylko odniesienie do tak zwanego i- węzła (podstawowa struktura plików w systemach plików Unix). To i-węzeł zostaje oznaczony jako „w użyciu”.

Teraz, gdy usuwasz lub przenosisz plik / usr / local / whoopdeedoo, jedyną rzeczą, która jest przenoszona (lub usuwana), jest odwołanie do i-węzła. Sam i-węzeł pozostaje niezmieniony. Zasadniczo to jest to.

Powinienem to zweryfikować, ale wierzę, że możesz to zrobić również w systemach plików Mac OS X.

Windows ma inne podejście. Czemu? Kto wie...? Nie znam wewnętrznych elementów NTFS. Teoretycznie wszystkie systemy plików, które używają odniesień do struktur wewnętrznych dla nazw plików, powinny mieć taką możliwość.

Przyznaję, że nadmiernie uprościłem, ale przeczytaj sekcję „Implikacje” na Wikipedii, która wykonuje znacznie lepszą pracę niż ja.


1
Cóż, jeśli używasz skrótu w systemie Windows, aby uruchomić plik wykonywalny, możesz też wyczyścić skrót, jeśli chcesz go w ten sposób porównać? = 3
Ray,

2
Nie, to byłoby jak wycieranie dowiązania symbolicznego. Gdzieś w innych komentarzach stwierdzono, że zachowanie jest spowodowane starszą obsługą systemów plików FAT. To brzmi jak prawdopodobny powód.
jawtheshark,

1
To nie ma nic specjalnie z węzłów. NTFS używa rekordów MFT do śledzenia stanu pliku, a FAT używa do tego wpisów w katalogu, ale Linux nadal działa w ten sam sposób z tymi systemami plików - z punktu widzenia użytkownika.
Ruslan,

13

Jedną z rzeczy, która wydaje się brakować we wszystkich innych odpowiedziach, jest to, że: gdy plik zostanie otwarty, a program utrzyma otwarty deskryptor pliku, plik nie zostanie usunięty z systemu, dopóki deskryptor pliku nie zostanie zamknięty.

Próby usunięcia odnośnego i-węzła będą opóźnione do momentu zamknięcia pliku: zmiana nazwy w tym samym lub innym systemie plików nie może wpływać na otwarty plik, niezależnie od zachowania zmiany nazwy, ani jawnie usuwać lub nadpisywać pliku nowym. Jedynym sposobem, w jaki można zepsuć plik, jest jawne otwarcie jego i-węzła i zepsuć zawartość, a nie operacje w katalogu, takie jak zmiana nazwy / usuwanie pliku.

Ponadto, gdy jądro wykonuje plik, zachowuje odniesienie do pliku wykonywalnego, co ponownie zapobiegnie jego modyfikacji podczas wykonywania.

W końcu, nawet jeśli wygląda na to, że jesteś w stanie usunąć / przenieść pliki tworzące działający program, tak naprawdę zawartość tych plików jest przechowywana w pamięci aż do zakończenia programu.


1
To nie jest w porządku. execve()nie zwraca FD, po prostu wykonuje program. Tak na przykład, jeśli uruchomić tail -f /foo.lognastępnie ich jest FD ( /proc/PID/fd/<fd_num>) związane ze taildla foo.logale nie dla samego pliku wykonywalnego tail, a nie na jego rodzica, jak również. Dotyczy to również pojedynczych plików wykonywalnych.
heemayl

@ heemayl Nie wspomniałem, execvewięc nie rozumiem, jak to jest istotne. Gdy jądro zacznie wykonywać plik, próba zastąpienia pliku nie zmodyfikuje programu, który jądro zamierza załadować, renderując punktową dyskusję. Jeśli chcesz „zaktualizować” plik wykonywalny podczas jego działania, możesz execvew pewnym momencie wywołać plik, aby jądro ponownie odczytało plik, ale nie rozumiem, jak to ma znaczenie. Chodzi o to: usunięcie „działającego pliku wykonywalnego” tak naprawdę nie powoduje usunięcia danych, dopóki plik wykonywalny się nie zatrzyma.
Bakuriu

Mówię o tej części, jeśli program składa się z jednego pliku wykonywalnego po uruchomieniu, program będzie działał poprawnie niezależnie od wszelkich zmian w katalogu: zmiana nazwy w tym samym lub innym systemie plików nie może wpływać na otwarty program obsługi , koniecznie mówisz o execve()i FD, gdy w tej sprawie nie występuje FD.
heemayl

2
Nie potrzebujesz uchwytu pliku, aby mieć odniesienie do pliku - wystarczy mapowanie stron.
Simon Richter,

1
Unix nie ma „uchwytów plików”. open()zwraca deskryptor pliku , o którym tu mówi Heemayl execve(). Tak, działający proces ma odniesienie do pliku wykonywalnego, ale nie jest to deskryptor pliku. Prawdopodobnie nawet jeśli munmap()edytuje wszystkie swoje odwzorowania pliku wykonywalnego, nadal będzie mieć odwołanie (odzwierciedlone w / proc / self / exe), które powstrzyma uwolnienie i-węzła. (Byłoby to możliwe bez awarii, gdyby zrobił to z funkcji biblioteki, która nigdy nie powróciła.) BTW, obcinanie lub modyfikowanie używanego pliku wykonywalnego może ci dać ETXTBUSY, ale może działać.
Peter Cordes,

7

W systemie plików Linux, gdy przenosisz plik, o ile nie przekracza on granic systemu plików (czytaj: pozostaje na tym samym dysku / partycji), zmieniasz tylko i-węzeł ..(katalog macierzysty) do katalogu nowej lokalizacji . Rzeczywiste dane w ogóle nie zostały przeniesione na dysk, tylko wskaźnik, aby system plików wiedział, gdzie je znaleźć.

Dlatego operacje przenoszenia są tak szybkie i prawdopodobnie nie ma problemu z przeniesieniem uruchomionego programu, ponieważ tak naprawdę nie przenosisz samego programu.


Twoja odpowiedź sugeruje, że przeniesienie binarnego pliku wykonywalnego do innego systemu plików wpłynęłoby na uruchomione procesy uruchomione z tego pliku binarnego.
jlliagre,

6

Jest to możliwe, ponieważ przeniesienie programu nie wpływa na uruchamianie procesów uruchomionych przez jego uruchomienie.

Po uruchomieniu programu jego bity na dysku są chronione przed nadpisaniem, ale nie ma potrzeby ochrony pliku, którego nazwę należy zmienić, przenieść do innej lokalizacji w tym samym systemie plików, co jest równoważne zmianie nazwy pliku lub przeniesieniu do innego systemu plików, co jest równoważne z skopiowaniem pliku w inne miejsce, a następnie usunięcie go.

Usunięcie używanego pliku albo dlatego, że proces ma otwarty deskryptor pliku, albo ponieważ proces go wykonuje, nie usuwa danych pliku, do których odwołuje się i-węzeł pliku, a jedynie usuwa pozycję katalogu, tj. ścieżka, z której można dotrzeć do i-węzła.

Pamiętaj, że uruchomienie programu nie powoduje załadowania wszystkiego naraz w (fizycznej) pamięci. Przeciwnie, ładowane jest tylko ścisłe minimum wymagane do rozpoczęcia procesu. Następnie wymagane strony są ładowane na żądanie przez cały czas trwania procesu. nazywa się to stronicowaniem na żądanie. Jeśli brakuje pamięci RAM, system operacyjny może zwolnić pamięć RAM zawierającą te strony, więc proces może ładować wielokrotnie tę samą stronę z wykonywalnego i-węzła.

Powodem, dla którego nie było to możliwe w systemie Windows, był pierwotnie prawdopodobny fakt, że bazowy system plików (FAT) nie obsługiwał podzielonego pojęcia pozycji katalogu w porównaniu z i-węzłami. To ograniczenie nie było już obecne w systemie plików NTFS, ale projekt systemu operacyjnego był utrzymywany przez długi czas, co prowadzi do nieznośnego ograniczenia konieczności ponownego uruchamiania podczas instalowania nowej wersji pliku binarnego, co nie ma miejsca w przypadku najnowszych wersji systemu Windows.


1
Wierzę, że nowsze wersje systemu Windows mogą zastąpić używane pliki binarne bez ponownego uruchamiania.
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Zastanawiam się, dlaczego wszystkie aktualizacje nadal wymagają ponownego uruchomienia :(
Braiam

1
@Braiam Oni nie. Przyjrzyj się bliżej. Mimo że pliki binarne mogą być aktualizowane, jądro nie może (według mojej wiedzy) i wymaga ponownego uruchomienia w celu zastąpienia nowszą wersją. Dotyczy to większości jąder systemu operacyjnego. Sprytniejsi ludzie niż ja napisałem kpatch dla Linuksa, który może załatać jądro Linuksa podczas działania - patrz en.wikipedia.org/wiki/Kpatch
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Miałem na myśli „wszystkie aktualizacje systemu Windows”
Braiam

@Braiam tak - ja też. Proszę przyjrzeć się bliżej.
Thorbjørn Ravn Andersen

4

Zasadniczo w systemie Unix i jego nazwie nazwa pliku (w tym ścieżka do katalogu prowadząca do niego) służy do skojarzenia / znalezienia pliku podczas jego otwierania (wykonanie pliku jest jednym ze sposobów jego otwarcia). Po tym momencie tożsamość pliku (poprzez „i-węzeł”) zostaje ustalona i nie jest już kwestionowana. Możesz usunąć plik, zmienić jego nazwę, zmienić uprawnienia. Tak długo, jak jakikolwiek proces lub ścieżka do pliku ma uchwyt na tym pliku / i-węźle, będzie się on trzymał, podobnie jak potok między procesami (w rzeczywistości w historycznym systemie UNIX rura była bezimiennym i-węzłem o rozmiarze, który właśnie pasował do odniesienie do pamięci dyskowej „bezpośrednich bloków” w i-węzle, coś w rodzaju 10 bloków).

Jeśli przeglądarka plików PDF jest otwarta na pliku PDF, możesz usunąć ten plik i otworzyć nowy o tej samej nazwie, i tak długo, jak stara przeglądarka jest otwarta, nadal będzie mieć dostęp do starego pliku (chyba że aktywnie ogląda system plików, aby zauważyć, kiedy plik znika pod oryginalną nazwą).

Programy wymagające plików tymczasowych mogą po prostu otworzyć taki plik pod jakąś nazwą, a następnie natychmiast go usunąć (a raczej pozycję katalogu), gdy jest on nadal otwarty. Następnie plik nie jest już dostępny według nazwy, ale każdy proces mający otwarty deskryptor pliku może nadal uzyskać do niego dostęp, a jeśli nastąpi nieoczekiwane zamknięcie programu, plik zostanie usunięty, a pamięć automatycznie odzyskana.

Zatem ścieżka do pliku nie jest właściwością samego pliku (w rzeczywistości twarde linki mogą zapewnić kilka różnych takich ścieżek) i jest potrzebna tylko do otwarcia, a nie do ciągłego dostępu przez procesy, które już go otwierają.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.