C Programování

Váš první program C pomocí systémového volání vidlice

Váš první program C pomocí systémového volání vidlice
Ve výchozím nastavení nemají programy C žádnou souběžnost ani paralelismus, probíhá pouze jeden úkol najednou, každý řádek kódu se čte postupně. Někdy však musíte přečíst soubor nebo - dokonce nejhorší - zásuvka připojená ke vzdálenému počítači a počítači to trvá opravdu dlouho. Trvá to obvykle méně než sekundu, ale pamatujte, že jedno jádro CPU to dokáže provést 1 nebo 2 miliardy pokynů během této doby.

Tak, jako dobrý vývojář, budete v pokušení instruovat svůj program C, aby během čekání udělal něco užitečnějšího. To je místo, kde je programování souběžnosti pro vaši záchranu - a váš počítač bude nešťastný, protože musí více pracovat.

Zde vám ukážu systémové volání systému Linux fork, jeden z nejbezpečnějších způsobů souběžného programování.

Souběžné programování může být nebezpečné?

Ano, může. Například existuje i jiný způsob volání multithreading. Výhodou je, že je lehčí, ale může opravdu pokazit, pokud jej používáte nesprávně. Pokud váš program omylem načte proměnnou a zapíše do stejná proměnná zároveň se váš program stane nesoudržným a je téměř nezjistitelný - jedna z nejhorších nočních můr vývojářů.

Jak uvidíte níže, vidlice kopíruje paměť, takže s proměnnými není možné mít takové problémy. Vidlice také vytváří nezávislý proces pro každou souběžnou úlohu. Kvůli těmto bezpečnostním opatřením je přibližně 5x pomalejší spuštění nové souběžné úlohy pomocí vidlice než u multithreadingu. Jak vidíte, na výhody, které přináší, to není moc.

Nyní, dost vysvětlení, je čas otestovat svůj první program C pomocí volání vidlice.

Příklad vidlice Linux

Zde je kód:

#zahrnout
#zahrnout
#zahrnout
#zahrnout
#zahrnout
int main ()
pid_t forkStatus;
forkStatus = vidlice ();
/ * Dítě… * /
if (forkStatus == 0)
printf ("Dítě běží, zpracovává se.\ n ");
spánek (5);
printf ("Dítě je hotové, opouští se.\ n ");
/ * Rodič… * /
else if (forkStatus != -1)
printf ("Rodič čeká ... \ n");
počkat (NULL);
printf ("Rodič končí ... \ n");
else
perror ("Chyba při volání funkce vidlice");

návrat 0;

Vyzývám vás, abyste otestovali, zkompilovali a provedli výše uvedený kód, ale pokud chcete zjistit, jak by vypadal výstup, a jste příliš „líní“ na jeho kompilaci - koneckonců jste možná unavený vývojář, který kompiloval programy C celý den - níže naleznete výstup programu C spolu s příkazem, který jsem použil k jeho kompilaci:

$ gcc -std = c89 -Wpedantic -Wall forkSleep.c -o vidlice Spánek -O2
$ ./ forkSleep
Rodič čeká ..
Dítě běží, zpracovává se.
Dítě je hotové, vystupuje.
Rodič opouští…

Nebojte se, pokud výstup není 100% shodný s mým výstupem výše. Pamatujte, že běh věcí současně znamená, že úkoly docházejí mimo pořadí, neexistuje žádné předdefinované řazení. V tomto příkladu můžete vidět, že dítě běží před rodič čeká a na tom není nic špatného. Obecně pořadí závisí na verzi jádra, počtu jader CPU, programech aktuálně spuštěných v počítači atd.

Dobře, teď se vrať ke kódu. Před řádkem s fork () je tento program C naprosto normální: 1 řádek se spouští najednou, pro tento program existuje pouze jeden proces (pokud došlo k malému zpoždění před fork, můžete to potvrdit ve správci úloh).

Po fork () jsou nyní 2 procesy, které mohou běžet paralelně. Nejprve je tu proces dítěte. Tento proces je ten, který byl vytvořen na fork (). Tento podřízený proces je speciální: neprovedl žádný z řádků kódu nad řádkem pomocí fork (). Místo toho, aby hledal hlavní funkci, spustí radku fork ().

A co proměnné deklarované před fork?

Linux fork () je zajímavý, protože chytře odpovídá na tuto otázku. Proměnné a ve skutečnosti je veškerá paměť v programech C zkopírována do podřízeného procesu.

Dovolte mi, abych několika slovy definoval, co dělá vidličku: vytvoří a klon procesu, který jej volá. 2 procesy jsou téměř identické: všechny proměnné budou obsahovat stejné hodnoty a oba procesy provedou řádek hned za fork (). Avšak po procesu klonování, jsou odděleny. Pokud aktualizujete proměnnou v jednom procesu, druhý proces zvyklý nechte si její proměnnou aktualizovat. Je to opravdu klon, kopie, procesy nesdílejí téměř nic. Je to opravdu užitečné: můžete si připravit spoustu dat a poté fork () a použít tato data ve všech klonech.

Oddělení začíná, když fork () vrátí hodnotu. Původní proces (nazývá se rodičovský proces) získá ID procesu klonovaného procesu. Na druhé straně je to klonovaný proces (tenhle se nazývá dětský proces) dostane číslo 0. Nyní byste měli začít chápat, proč jsem za řádek fork () vložil příkazy if / else if. Pomocí návratové hodnoty můžete dítěti dát pokyn, aby udělal něco jiného, ​​než co dělá rodič - a věřte mi, je to užitečné.

Na jedné straně, ve výše uvedeném příkladu kódu, dítě dělá úkol, který trvá 5 sekund a vytiskne zprávu. K napodobení procesu, který trvá dlouho, používám funkci spánku. Potom dítě úspěšně opustí.

Na druhé straně rodič vytiskne zprávu, počkejte, až dítě opustí, a nakonec vytiskne další zprávu. Fakt, že rodič čeká na své dítě, je důležitý. Je to příklad, rodič většinu času čeká na čekání na své dítě. Mohl jsem však rodiči nařídit, aby udělal jakýkoli druh dlouhodobých úkolů, než mu řeknu, aby počkal. Tímto způsobem by místo čekání udělal užitečné úkoly - koneckonců, proto používáme vidlice (), č?

Jak jsem však řekl výše, je to opravdu důležité rodič čeká na své dítě. A je to důležité kvůli zombie procesy.

Jak důležité je čekání

Rodiče obecně chtějí vědět, zda děti dokončily zpracování. Například chcete spouštět úkoly paralelně, ale určitě nechcete rodič by měl odejít dříve, než budou děti hotové, protože pokud by se to stalo, shell by vrátil výzvu, zatímco děti ještě neskončily - což je divné.

Funkce čekání umožňuje čekat, dokud nebude ukončen jeden z podřízených procesů. Pokud rodič zavolá 10krát fork (), bude také muset zavolat 10krát wait (), jednou za každé dítě vytvořeno.

Ale co se stane, když rodič zavolá funkci čekání, zatímco mají všechny děti již natěšený? To je místo, kde jsou potřeba procesy zombie.

Když dítě opustí dříve, než nadřazené volání wait (), jádro Linuxu nechá dítě opustit ale ponechá si lístek sdělením, že dítě vystoupilo. Poté, když nadřízený zavolá wait (), najde lístek, tento lístek smaže a funkce wait () se vrátí ihned protože ví, že rodič potřebuje vědět, kdy dítě skončilo. Tento lístek se nazývá a zombie proces.

Proto je důležité, aby nadřazená volání wait (): pokud tak neučiní, procesy zombie zůstanou v paměti a jádře Linuxu nemůžu uchovávejte v paměti mnoho zombie procesů. Jakmile je dosaženo limitu, váš počítač inení schopen vytvořit žádný nový proces a tak budete v velmi špatný tvar: dokonce pro zabití procesu budete možná muset vytvořit nový proces. Například pokud chcete otevřít správce úloh, aby zabil proces, nemůžete, protože správce úloh bude potřebovat nový proces. Dokonce i nejhorší, nemůžeš zabít zombie proces.

Proto je volání čekání důležité: umožňuje jádro uklidit podřízený proces namísto neustálého hromadění seznamu ukončených procesů. A co když rodič odejde, aniž by kdy volal Počkejte()?

Naštěstí, když je rodič ukončen, nikdo jiný nemůže volat wait () na tyto děti, takže existuje bez důvodu zachovat tyto zombie procesy. Proto, když rodič opustí, všechny zbývající zombie procesy propojen s tímto rodičem jsou odstraněny. Zombie procesy jsou opravdu užitečné pouze k tomu, aby nadřazené procesy mohly zjistit, že dítě bylo ukončeno dříve, než rodič zavolal wait ().

Nyní možná budete chtít znát některá bezpečnostní opatření, která vám umožní nejlepší použití vidlice bez jakýchkoli problémů.

Jednoduchá pravidla, aby vidlice fungovala podle plánu

Nejprve, pokud znáte vícevláknové zpracování, prosím nerozvětvujte program pomocí vláken. Ve skutečnosti se obecně vyhněte míchání více technologií souběžnosti. fork předpokládá, že pracuje v normálních programech C, má v úmyslu naklonovat pouze jeden paralelní úkol, ne více.

Za druhé, vyhněte se otevírání nebo otevírání souborů před fork (). Soubory je jedna z mála věcí sdílené a ne naklonovaný mezi rodičem a dítětem. Pokud čtete 16 bajtů jako rodič, posune čtecí kurzor o 16 bajtů dopředu oba u rodiče a u dítěte. Nejhorší, pokud dítě a rodič zapisují bajty do souboru stejný soubor zároveň mohou být bajty rodiče smíšený s bajty dítěte!

Aby bylo jasno, mimo STDIN, STDOUT a STDERR opravdu nechcete sdílet žádné otevřené soubory s klony.

Za třetí, dávejte pozor na zásuvky. Zásuvky jsou také sdílené mezi rodiči a dětmi. Je užitečné poslouchat port a nechat připravit několik podřízených pracovníků na zpracování nového připojení klienta. nicméně, pokud jej použijete nesprávně, dostanete se do potíží.

Za čtvrté, pokud chcete volat smyčku () ve smyčce, proveďte to pomocí extrémní péče. Vezměme si tento kód:

/ * NEKOMPILUJTE TOTO * /
const int targetFork = 4;
pid_t forkResult
 
pro (int i = 0; i < targetFork; i++)
forkResult = vidlice ();
/ *… * /
 

Pokud si přečtete kód, můžete očekávat, že vytvoří 4 podřízené prvky. Ale spíše to vytvoří 16 dětí. Je to proto, že děti budou taky provést smyčku, takže děti budou zase volat fork (). Když je smyčka nekonečná, říká se jí a vidlicová bomba a je jedním ze způsobů, jak zpomalit systém Linux tolik, že to už nefunguje a bude třeba restartovat. Stručně řečeno, mějte na paměti, že Clone Wars nejsou nebezpečné jen ve Star Wars!

Nyní jste viděli, jak se jednoduchá smyčka může pokazit, jak používat smyčky s vidličkou ()? Pokud potřebujete smyčku, vždy zkontrolujte návratovou hodnotu vidlice:

const int targetFork = 4;
pid_t forkResult;
int i = 0;
dělat
forkResult = vidlice ();
/ *… * /
i ++;
while ((forkResult != 0 && forkResult != -1) && (tj < targetFork));

Závěr

Nyní je čas, abyste provedli vlastní experimenty s vidličkou ()! Vyzkoušejte nové způsoby, jak optimalizovat čas prováděním úkolů napříč více jádry CPU, nebo proveďte nějaké zpracování na pozadí, zatímco budete čekat na načtení souboru!

Neváhejte si přečíst stránky manuálu pomocí příkazu man. Dozvíte se, jak přesně funguje fork (), jaké chyby můžete získat atd. A užívejte si souběžnosti!

Hry Nainstalujte si nejnovější strategickou hru OpenRA na Ubuntu Linux
Nainstalujte si nejnovější strategickou hru OpenRA na Ubuntu Linux
OpenRA je herní engine Libre / Free Real Time Strategy, který obnovuje rané hry Westwood, jako je klasický Command & Conquer: Red Alert. Distribuované...
Hry Nainstalujte si nejnovější Dolphin Emulator pro Gamecube a Wii na Linuxu
Nainstalujte si nejnovější Dolphin Emulator pro Gamecube a Wii na Linuxu
Emulátor Dolphin vám umožní hrát vybrané hry Gamecube a Wii na osobních počítačích se systémem Linux (PC). Jako volně dostupný herní emulátor s otevř...
Hry Jak používat GameConqueror Cheat Engine v Linuxu
Jak používat GameConqueror Cheat Engine v Linuxu
Tento článek popisuje průvodce používáním cheatovacího modulu GameConqueror v systému Linux. Mnoho uživatelů, kteří hrají hry v systému Windows, často...