Nový proces bude potomkem hlavního vlákna, které vlákno vytvořilo. Myslím.
fork
vytváří nový proces. Rodič procesu je jiný proces, nikoli vlákno. Rodičem nového procesu je tedy starý proces.
Všimněte si, že podřízený proces bude mít pouze jedno vlákno, protože fork
pouze duplikuje (zásobník pro) vlákno, které volá fork
. (To není tak úplně pravda:celá paměť je duplikovaná, ale podřízený proces bude mít pouze jedno aktivní vlákno.)
Pokud jeho rodič skončí jako první, bude nový proces připojen k procesu init.
Pokud rodič skončí jako první, SIGHUP
signál je vyslán dítěti. Pokud dítě neustoupí v důsledku SIGHUP
dostane init
jako jeho nový rodič. Viz také manuálové stránky pro nohup
a signal(7)
pro trochu více informací o SIGHUP
.
A jeho rodič je hlavní vlákno, nikoli vlákno, které jej vytvořilo.
Rodič procesu je proces, nikoli konkrétní vlákno, takže nemá smysl říkat, že hlavní nebo podřízené vlákno je rodič. Celý proces je rodič.
Jedna poznámka na závěr:Míchání nití a vidličky je třeba provádět opatrně. Některá úskalí jsou diskutována zde.
Opravte mě, pokud se mýlím.
Postačí :)
Jako fork()
je systémové volání POSIX, jeho chování je dobře definováno:
Proces se vytvoří s jedním vláknem . Pokud vícevláknový proces volá fork(), nový proces musí obsahovat repliku volajícího vlákna a celý jeho adresní prostor, případně včetně stavů mutexů a dalších zdrojů. V důsledku toho, aby se předešlo chybám, může podřízený proces provádět operace async-signal-safe pouze do doby, než bude zavolána jedna z funkcí exec.
https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html
Rozvětvený potomek je přesný duplikát svého rodiče, ale pouze vlákno s názvem fork()
v nadřazeném prvku, stále existuje v potomkovi a je novým hlavním vláknem tohoto potomka, dokud nezavoláte exec()
.
Popis POSIX „bude vytvořen s jedním vláknem“ je zavádějící, protože ve skutečnosti většina implementací skutečně vytvoří přesný duplikát nadřazeného procesu, takže všechna ostatní vlákna a jejich paměť jsou duplikovány také, což znamená, že vlákna tam ve skutečnosti jsou. , prostě už nemohou běžet, protože jim systém nikdy nepřiřadí žádný CPU čas; ve skutečnosti chybí v tabulce plánovače vláken jádra.
Jednodušší mentální obraz je následující:
Když rodič zavolá fork, celý proces se na okamžik zmrazí, atomicky zduplikuje, a pak se rodič rozmrazí jako celek, ale v podřízeném se rozmrazí pouze jedno vlákno, které volalo fork, vše ostatní zůstane zmrazeno.
Proto není bezpečné provádět určitá systémová volání mezi fork()
a exec()
jak také upozorňuje standard POSIX. V ideálním případě byste neměli dělat nic víc, než možná zavírat nebo duplikovat deskriptory souborů, nastavovat nebo obnovovat obslužné programy signálů a pak volat exec()
.
Co se však stane, když vlákno vytvoří nový proces pomocí fork()?
Nový proces bude vytvořen zkopírováním volajícího vlákna adresní prostor (nikoli celý adresní prostor procesu ). Obecně je to považováno za špatný nápad, protože je velmi těžké jej uvést do pořádku. POSIX říká, že podřízený proces (vytvořený ve vícevláknovém programu) může volat pouze funkce async-signal-safe, dokud nezavolá jednu z exec*
funkce.
Pokud jeho rodič skončí jako první, bude nový proces připojen k initprocess.
Podřízený proces je obvykle zděděn procesem init. Pokud je nadřazený proces řídící proces (např. shell), pak POSIX vyžaduje:
Je-li procesem řídící proces, signál SIGHUP musí být odeslán každému procesu ve skupině procesů v popředí řídícího terminálu, který patří k volajícímu procesu.
To však neplatí pro většinu procesů, protože většina procesů procesy neřídí.
A jeho rodič je hlavní vlákno, nikoli vlákno, které jej vytvořilo.
Rodičem rozvětveného potomka bude vždy proces, který volal fork(). Takže PPID je podřízený proces bude PID vašeho programu.
problém pramení z chování samotného fork(2). Kdykoli je pomocí fork(2) vytvořen proces newchild, nový proces získá adresní prostor newmemory, ale vše v paměti se zkopíruje ze starého procesu (při kopírování při zápisu to není 100% pravda, ale sémantika je stejná).
Pokud zavoláme fork(2) v prostředí s více vlákny, vlákno provádějící volání je nyní hlavním vláknem v novém procesu a všechna ostatní vlákna, která běžela v nadřazeném procesu, jsou mrtvá. A vše, co udělali, bylo ponecháno přesně tak, jak to bylo těsně před voláním fork(2).
Nyní si představte, že tato další vlákna šťastně dělala svou práci před voláním fork(2) a o několik milisekund později jsou mrtvá. Co když něco, co tato nyní mrtvá vlákna udělala, nemělo zůstat přesně tak, jak to bylo?
Dovolte mi uvést příklad. Řekněme, že naše hlavní vlákno (to, které bude volat fork(2)) spalo, zatímco my jsme měli spoustu jiných vláken, které šťastně dělaly nějakou práci. Přidělování paměti, zápis do ní, kopírování z ní, zápis do souborů, zápis do databáze a tak dále. Pravděpodobně přidělovali paměť něčím jako malloc(3). No, ukázalo se, že malloc(3) používá interně mutex aby byla zaručena bezpečnost závitu. A přesně to je ten problém.
Co když jedno z těchto vláken používalo malloc(3) a získalo zámek mutexu přesně ve stejném okamžiku, kdy hlavní vlákno volalo fork(2)? V novém podřízeném procesu je zámek stále držen – nyní mrtvým vláknem, které jej nikdy nevrátí.
Nový podřízený proces nebude mít tušení, zda je bezpečné používat malloc(3) nebo ne. V nejhorším případě bude volat malloc(3) a blokovat, dokud nezíská zámek, což se nikdy nestane, protože vlákno, které ho má vrátit, je mrtvé. A to je jen malloc(3). Zamyslete se nad všemi dalšími možnými mutexy a zámky v ovladačích databází, knihovnách pro práci se soubory, síťových knihovnách a tak dále.
pro úplné vysvětlení můžete přejít na tento odkaz.