Dlaczego vfork () jest przeznaczony do użycia, gdy proces potomny wywołuje exec () lub exit () natychmiast po utworzeniu?

11

Pojęcia dotyczące systemu operacyjnego i APUE mówią

W przypadku vfork () proces nadrzędny jest zawieszany, a proces podrzędny wykorzystuje przestrzeń adresową nadrzędnego. Ponieważ vfork () nie używa funkcji kopiowania przy zapisie, jeśli proces potomny zmieni dowolne strony w przestrzeni adresowej rodzica, zmienione strony będą widoczne dla rodzica po wznowieniu. Dlatego vfork () należy używać ostrożnie, aby proces potomny nie modyfikował przestrzeni adresowej rodzica.

vfork () jest przeznaczony do użycia, gdy proces potomny wywołuje exec () lub exit () natychmiast po utworzeniu.

Jak mam zrozumieć ostatnie zdanie?

Kiedy proces potomny utworzony przez vfork()wywołania exec()nie exec()modyfikuje przestrzeni adresowej procesu nadrzędnego, ładując nowy program?

Gdy proces dziecko stworzony przez vfork()rozmowy exit(), czy exit()nie zmodyfikować przestrzeń adresową procesu macierzystego gdy kończące dziecko?

Preferuję Linuksa.

Dzięki.

Tim
źródło

Odpowiedzi:

15

Kiedy proces potomny utworzony przez vfork()wywołania exec()nie exec()modyfikuje przestrzeni adresowej procesu nadrzędnego, ładując nowy program?

Nie, exec()zapewnia nową przestrzeń adresową dla nowego programu; nie modyfikuje nadrzędnej przestrzeni adresowej. Zobacz na przykład omówienie execfunkcji w POSIX i podręcznik Linux-aexecve() .

Kiedy proces potomny utworzony przez vfork () wywołuje exit (), czy exit () nie modyfikuje przestrzeni adresowej procesu macierzystego podczas kończenia potomka?

Zwykła exit()potęga - uruchamia haki wyjściowe zainstalowane przez uruchomiony program (w tym jego biblioteki). vfork()jest bardziej restrykcyjny; dlatego w Linuksie nakazuje użycie, _exit()które nie wywołuje funkcji czyszczenia biblioteki C.

vfork()okazało się dość trudne do poprawnego; został usunięty w aktualnych wersjach standardu POSIX i posix_spawn()powinien być używany zamiast tego.

Jednakże, chyba że naprawdę wiesz co robisz, powinieneś nie użyć jednej vfork()lub posix_spawn(); trzymaj się starych, dobrych fork()i exec().

Link do strony Linuksa, o której mowa powyżej, zapewnia większy kontekst:

Jednak w dawnych, złych czasach fork(2) wymagałoby to zrobienia kompletnej kopii przestrzeni danych dzwoniącego, często niepotrzebnie, ponieważ zwykle następuje natychmiast exec(3). Zatem, dla większej wydajności, BSD wprowadziło vfork() wywołanie systemowe, które nie skopiowało w pełni przestrzeni adresowej procesu nadrzędnego, ale pożyczyło pamięć rodzica i wątek kontroli, dopóki nie nastąpiło wywołanie execve(2)lub wyjście. Proces nadrzędny został zawieszony, gdy dziecko korzystało z jego zasobów. Użycie vfork()było trudne: na przykład niezmodyfikowanie danych w procesie nadrzędnym zależało od wiedzy, które zmienne były przechowywane w rejestrze.

Stephen Kitt
źródło
Dzięki. „exec () zapewnia nową przestrzeń adresową dla nowego programu;” Czy normalne zachowanie exec () w celu załadowania programu do przestrzeni adresowej procesu? Nie znalazłem w dwóch linkach, gdzie tworzy nową przestrzeń adresową normalnie lub szczególnie dla vfork ().
Tim
1
Zabawne jest to, że vfork () wygrywa teraz ze wszystkim innym. Jest śmiesznie szybszy niż fork (), gdy masz gigabajt zapisywalnej pamięci.
Joshua
2
Proszę nie mówić innym, aby korzystali posix_spawn. Znacznie trudniej jest napisać poprawny kod używając posix_spawnstarego niż zwykłego fork, a jeśli spróbujesz, możesz natknąć się na ceglaną ścianę, w której nie ma akcji pliku lub atrybutu, który wykonuje czynności pomiędzy forki exec. I nie ma gwarancji, że ma wydajność podobną do vfork, więc nawet nie rozwiązuje problemu, który ludzie chcą rozwiązać.
zwol
1
@zwol: To naprawdę zła rada. Chociaż posix_spawnmoże brakować pożądanej funkcjonalności (możesz to rozwiązać za pomocą pośredniczącego programu pomocniczego, napisanego w C lub skrypcie powłoki inline-on-cmdline), każda próba osiągnięcia tego, co chcesz, vforkwywołuje niebezpieczne niezdefiniowane zachowanie. Specyfikacja parametru vforknie zezwala na wywoływanie funkcji losowych w celu ustawienia stanu, w którym dziecko może odziedziczyć wcześniej execve, i próby takie mogą uszkodzić stan rodzica.
R .. GitHub ZATRZYMAJ LÓD
1
@Joshua: Nowoczesna implementacja posix_spawndziała mniej więcej tak samo, jak vforkw większości warunków. Te, w których występuje różnica, to zwykle przypadki, w których vforkjest to bardzo niebezpieczne: gdzie są zainstalowane programy obsługi sygnałów, które posix_spawnmuszą powstrzymywać się przed uruchomieniem u dziecka przed wykonaniem.
R .. GitHub ZATRZYMAJ LÓD
4

Po wywołaniu vfork()tworzony jest nowy proces, który pożycza obraz procesu nadrzędnego, z wyjątkiem stosu. Proces potomny otrzymuje własną nową gwiazdkę stosu, jednak nie pozwala na returnto funkcja, która go wywołała vfork().

Podczas gdy dziecko jest uruchomione, proces nadrzędny jest blokowany, ponieważ dziecko pożyczyło przestrzeń adresową rodzica.

Niezależnie od tego, co robisz, wszystko, co tylko uzyskuje dostęp do stosu, modyfikuje tylko prywatny stos dziecka. Jeśli jednak zmodyfikujesz dane globalne, zmodyfikuje to wspólne dane, a zatem wpłynie również na element nadrzędny.

Czynnikami modyfikującymi dane globalne są np .:

  • wywoływanie malloc () lub free ()

  • za pomocą stdio

  • modyfikowanie ustawień sygnału

  • modyfikowanie zmiennych, które nie są lokalne dla wywoływanej funkcji vfork().

  • ...

Gdy zadzwonisz _exit()(ważne, nigdy nie dzwonisz exit()), dziecko zostaje zakończone, a kontrola zostaje zwrócona rodzicowi.

Jeśli wywołasz dowolną funkcję z exec*()rodziny, tworzona jest nowa przestrzeń adresowa z nowym kodem programu, nowymi danymi i częścią stosu nadrzędnego (patrz poniżej). Gdy to jest gotowe, dziecko nie pożycza już przestrzeni adresowej od dziecka, ale używa własnej przestrzeni adresowej.

Kontrola jest zwracana rodzicowi, ponieważ jej przestrzeń adresowa nie jest już używana przez inny proces.

Ważne: w systemie Linux nie ma rzeczywistej vfork()implementacji. Linux raczej implementuje się w vfork()oparciu o fork()koncepcję Copy on Write wprowadzoną przez SunOS-4.0 w 1988 roku. Aby użytkownicy uwierzyli, że ich używają vfork(), Linux po prostu konfiguruje wspólne dane i zawiesza rodzica, podczas gdy dziecko nie dzwoniło ani żadnej _exit()z exec*()funkcji.

Linux nie korzysta zatem z faktu, że rzeczywisty vfork()nie musi konfigurować opisu przestrzeni adresowej dla dziecka w jądrze. Powoduje to, vfork()że nie jest szybszy niż fork(). W systemach, które implementują wartość rzeczywistą vfork(), jest ona zazwyczaj 3 razy szybsza fork()i wpływa na wydajność używanych powłok vfork()- ksh93, najnowszej Bourne Shelli csh.

Powodem, dla którego nigdy nie powinieneś dzwonić exit()od vfork()dziecka ed, jest to, że exit()opróżnia stdio w przypadku, gdy dane nie zostały odłączone od czasu przed dzwonieniem vfork(). Może to powodować dziwne wyniki.

BTW: posix_spawn()jest zaimplementowany vfork(), więc vfork()nie będzie usuwany z systemu operacyjnego. Wspomniano już, że Linux nie korzysta vfork()za posix_spawn().

Dla stosu jest niewiele dokumentacji, oto, co mówi strona podręcznika Solaris:

 The vfork() and vforkx() functions can normally be used  the
 same  way  as  fork() and forkx(), respectively. The calling
 procedure, however, should not return while running  in  the
 child's  context,  since the eventual return from vfork() or
 vforkx() in the parent would be to a  stack  frame  that  no
 longer  exists. 

Implementacja może więc robić, co chce. Implementacja Solaris korzysta z pamięci współużytkowanej dla ramki stosu wywołania funkcji vfork(). Żadna implementacja nie zapewnia dostępu do starszych części stosu od rodzica.

schily
źródło
4
Ani biblioteka GNU C, ani biblioteka musl C nie są implementowane posix_spawn()na Linuksie vfork(). Oboje wdrażają go na szczycie __clone().
JdeBP
1
@JdeBP: Wiesz, że vfork()tylko dzwonisz, clone()prawda? Jest dosłownie jednowierszowy w jądrze.
Joshua
1
„Ważne: w systemie Linux nie ma rzeczywistej implementacji vfork ().” <- To nie jest prawda i nie była prawdą przez co najmniej dekadę. Jeśli twój test porównawczy powłoki nie zauważa żadnej różnicy w wydajności pomiędzy Linuksem vforka forkLinuksem, robi coś złego.
zwol
1
Druga połowa tej odpowiedzi rozpoczynająca się od „Ważne: w systemie Linux nie ma rzeczywistej implementacji vfork ()” jest w większości lub całkowicie błędna.
R .. GitHub ZATRZYMAJ LÓD
1
Nie zgłaszaj roszczeń bez weryfikacji. Bieżącą powłokę Bourne'a można skompilować z obsługą vfork i bez niej, więc nawet jeśli uważasz, że funkcje debugowania z Linuksa nie są w stanie dać wiarygodnych wyników, możesz porównać czasy wykonania dla wywołania konfiguracji z vfork w powłoce. Używam skryptu konfiguracyjnego z 800 testami. W systemie Solaris powłoka Bourne'a korzystająca z vfork potrzebuje o 30% mniej czasu procesora w porównaniu do obudowy widelca. W systemie Linux ten sam test skutkuje mniejszym o 10% czasem procesora systemowego. W systemie Solaris nie jest to 3x, ponieważ zawiera wiele wywołań kompilatora.
schily