Když je podřízený proces vytvořen
vfork()
voláníexec()
, neexec()
upravit adresový prostor nadřazeného procesu načtením nového programu?
Ne, exec()
poskytuje nový adresní prostor pro nový program; nemění nadřazený adresní prostor. Viz například diskuse o exec
funkce v POSIX a Linux execve()
manuálová stránka.
Když podřízený proces vytvořený pomocí vfork() zavolá exit(), nezmění exit() při ukončení podřízeného procesu adresní prostor nadřazeného procesu?
Obyčejný exit()
možná – spouští výstupní háky nainstalované běžícím programem (včetně jeho knihoven). vfork()
je více omezující; proto na Linuxu vyžaduje použití _exit()
což není zavolejte čisticí funkce knihovny C.
vfork()
ukázalo se, že je docela obtížné dostat se správně; byl odstraněn v aktuálních verzích standardu POSIX a posix_spawn()
by měl být použit místo toho.
Pokud však skutečně vědět, co děláte, neměli byste použijte buď vfork()
nebo posix_spawn()
; držte se starého dobrého fork()
a exec()
.
Manuálová stránka Linuxu, na kterou odkazuje výše, poskytuje další kontext:
Nicméně, za starých časů
fork(2)
by vyžadovalo vytvoření úplné kopie datového prostoru volajícího, často zbytečně, protože obvykle bezprostředně potéexec(3)
je hotovo. Pro větší efektivitu tedy BSD zavedlovfork()
systémové volání, které plně nezkopírovalo adresní prostor nadřazeného procesu, ale vypůjčilo si paměť rodiče a řídicí vlákno až do voláníexecve(2)
nebo došlo k odchodu. Nadřazený proces byl pozastaven, zatímco dítě využívalo své prostředky. Použitívfork()
byl ošemetný:například neupravování dat v rodičovském procesu záviselo na znalosti, které proměnné jsou uloženy v registru.
Když zavoláte vfork()
, je vytvořen nový proces a tento nový proces si vypůjčí obraz procesu nadřazeného procesu s výjimkou zásobníku. Podřízenému procesu je přidělena vlastní nová hvězdička zásobníku, ale neumožňuje return
z funkce, která volala vfork()
.
Když je podřízený proces spuštěn, nadřazený proces je zablokován, protože si podřízený vypůjčil adresní prostor nadřízeného.
Bez ohledu na to, co děláte, vše, co právě přistupuje k zásobníku, upravuje pouze soukromý zásobník dítěte. Pokud však změníte globální data, změní se společná data a ovlivní to také nadřazená data.
Věci, které mění globální data, jsou např.:
-
volání malloc() nebo free()
-
pomocí stdio
-
úprava nastavení signálu
-
modifikace proměnných, které nejsou lokální pro funkci volající
vfork()
. -
...
Jakmile zavoláte _exit()
(důležité, nikdy nevolejte exit()
), dítě je ukončeno a kontrola je předána zpět rodiči.
Pokud zavoláte jakoukoli funkci z exec*()
rodina, je vytvořen nový adresní prostor s novým programovým kódem, novými daty a částí zásobníku z rodiče (viz níže). Jakmile je toto připraveno, dítě si již nevypůjčuje adresní prostor od dítěte, ale používá vlastní adresní prostor.
Ovládací prvek je vrácen nadřazenému prvku, protože jeho adresní prostor již nepoužívá jiný proces.
Důležité:V systému Linux neexistuje žádný skutečný vfork()
implementace. Linux spíše implementuje vfork()
na základě Copy on Write fork()
koncept představený SunOS-4.0 v roce 1988. Aby uživatelé věřili, že používají vfork()
, Linux pouze nastaví sdílená data a pozastaví rodiče, zatímco dítě nezavolá _exit()
nebo jeden z exec*()
funkce.
Linux proto nevyužívá faktu, že skutečný vfork()
nepotřebuje nastavit popis adresního prostoru pro dítě v jádře. Výsledkem je vfork()
která není rychlejší než fork()
. Na systémech, které implementují skutečný vfork()
, je obvykle 3x rychlejší než fork()
a ovlivňuje výkon shellů, které používají vfork()
- ksh93
, poslední Bourne Shell
a csh
.
Důvod, proč byste nikdy neměli volat exit()
z vfork()
ed child je to exit()
vyprázdní stdio v případě, že existují nevyprázdněná data z doby před voláním vfork()
. To by mohlo způsobit podivné výsledky.
BTW:posix_spawn()
je implementován nad vfork()
, tedy vfork()
nebude odstraněn z operačního systému. Bylo zmíněno, že Linux nepoužívá vfork()
pro posix_spawn()
.
Pro zásobník existuje jen málo dokumentace, zde je to, co říká manuálová stránka 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.
Implementace si tedy může dělat, co chce. Implementace Solaris používá sdílenou paměť pro zásobníkový rámec funkce volající vfork()
. Žádná implementace neuděluje přístup ke starším částem zásobníku od rodiče.