Díky Jonathanu Lefflerovi za to, že nás nasměroval správným směrem.
Ačkoli váš program pro mě nevytváří stejné neočekávané chování na CentOS 7 / GCC 4.8.5 / GLIBC 2.17, je pravděpodobné, že pozorujete odlišné chování. Chování vašeho programu je ve skutečnosti nedefinováno podle POSIX (na který spoléháte pro fork
). Zde jsou některé výňatky z příslušné sekce (zvýraznění přidáno):
K popisu otevřeného souboru lze přistupovat prostřednictvím deskriptoru souboru, který je vytvořen pomocí funkcí jako
open()
nebopipe()
nebo prostřednictvím streamu, který je vytvořen pomocí funkcí jakofopen()
nebopopen()
.Buď deskriptor souboru nebo proud se nazývá "handle" v popisu openfile, na který odkazuje; otevřený popis souboru může mít několik úchytů.[...]
Výsledek volání funkcí zahrnující jakýkoli jeden handle ("activehandle") je definován jinde v tomto svazku POSIX.1-2017, ale pokud jsou použity dva nebo více handlerů a kterýkoli z nich je stream, aplikace musí zajistit, aby jejich akce jsou koordinovány, jak je popsáno níže. Pokud tak neučiníte, výsledek není definován .
[...]
Aby se rukojeť stala aktivní rukojetí, aplikace musí zajistit, aby níže uvedené akce byly provedeny mezi posledním použitím rukojeti (aktuální aktivní rukojeť) a prvním použitím druhé rukojeti (budoucí aktivní rukojetí). Druhý úchyt se pak stane aktivním úchytem. [...]
Aby tato pravidla platila, nemusí být ovladače ve stejném procesu.
Všimněte si, že po
fork()
, existují dva ovladače tam, kde jeden dříve existoval. Aplikace zajistí, že pokud bude někdy možné získat přístup k oběma ovladačům, oba jsou ve stavu, kdy se druhý může stát aktivním ovladačem jako první. [V případě předchozí kvalifikace] se žádost připraví nafork()
přesně jako by to byla změna aktivní rukojeti. (Pokud je jedinou akcí, kterou provádí jeden z procesů, jedna z funkcí exec nebo_exit()
(nikoliexit()
), k rukojeti se v tomto procesu nikdy nepřistoupí. )Pro první kliku platí první použitelná podmínka níže.[Působivě dlouhý seznam alternativ, které se nevztahují na situaci OP ...]
- Pokud je stream otevřen v režimu, který umožňuje čtení a základní popis otevřeného souboru odkazuje na zařízení, které je schopno vyhledávání, aplikace buď provede
fflush()
nebo bude proud uzavřen.Pro druhou rukojeť:
- Pokud byl některý z předchozích aktivních ovladačů použit funkcí, která explicitně změnila offset souboru, s výjimkou výše uvedených požadavků pro první ovladač, aplikace provede
lseek()
nebofseek()
(podle typu rukojeti) na vhodné místo.
Proto, aby program OP měl přístup ke stejnému streamu v nadřazeném i podřízeném zařízení, POSIX vyžaduje, aby nadřazený fflush()
stdin
před rozvětvením a že dítě fseek()
to po spuštění. Poté, co čekáte na ukončení dítěte, musí rodič fseek()
proud. Vzhledem k tomu, že víme, že správce dítěte selže, lze požadavku na veškeré proplachování a hledání předejít tím, že dítě použije _exit()
(který nemá přístup ke streamu) namísto exit()
.
Splnění ustanovení POSIX vede k následujícímu:
Když jsou tato pravidla dodržována, bez ohledu na posloupnost použitých rukojetí, implementace zajistí, že aplikace, i když sestává z několika procesů, bude poskytovat správné výsledky:při zápisu se žádná data neztratí ani nezduplikují a všechna data budou zapsána v pořádku, s výjimkou vyžádáno hledáním.
Za zmínku však stojí, že
Je definováno implementací, zda a za jakých podmínek jsou všechny vstupy vidět právě jednou.
Oceňuji, že může být poněkud neuspokojivé slyšet pouze to, že vaše očekávání ohledně chování programu nejsou odůvodněna příslušnými standardy, ale to je opravdu vše. Nadřazený a podřízený proces mají některá relevantní sdílená data ve formě společného popisu otevřeného souboru (s kterým jsou spojeny samostatné úchyty), a to se zdá být pravděpodobně vozidlem pro neočekávané (a nedefinované) chování, ale neexistuje žádný základ pro předpovídání konkrétního chování, které vidíte, ani jiného chování, které vidím u stejného programu.