Operační systémy Cvičení 5: Volání jádra, procesy, vlákna. 1
Obsah cvičení Systémová volání Knihovní funkce jazyka C Procesy informace o procesech vytváření, ukončování procesů, signály POSIX vlákna vytváření, ukončování vláken atributy vláken kompilace programu Kde najít další informace Poznámka: uvedené příklady jsou dostupné na www nebo na počítačích v K327 v adresáři: ~trdlicka/os/processes 2
Systémová volání (API) Základní množina funkcí, které tvoří rozraní mezi jádrem a ostatními aplikacemi. Pokud aplikace požaduje nějakou službu od jádra, potom na té nejnižší úrovni použije právě některé systémové volání (např. pro vytvoření nového procesu fork() a exec() ). V Unixu jsou popsány ve 2. sekci manuálu. man -s 2 intro Systémová volání na různých OS se mohou lišit => špatná přenositelnost. 3
Knihovní funkce jazyka C Zjednodušené a standardizované rozhraní k jádru (např. system() versus fork() a exec()). Ve skutečnosti sami uvnitř sebe požívají systémová volání. => méně efektivní, lépe přenositelné, snadnější požívání. V Unixu jsou popsány ve 3. sekci manuálu. man -s 3 intro V některých OS je těžké rozlišit, co je systémové volání a co knihovní funkce. 4
Procesy Proces je v rámci OS jednoznačně identifikován číslem procesu (PID). OS si udržuje navíc informaci o vztahu rodič-potomek => každý proces zná číslo svého rodiče (PPID). 5
Procesy zobrazení informací V shellu ps e ps ef ps e o pid,ppid,user,comm V běžícím procesu getpid() getppid() 6
Příklad: print-pid.c Příklad ilustruje použití funkcí getpid() a getppid(). Přeložte tento příklad a spusťte ho na pozadí. Během 30 sekund po spuštění si ověřte pod jakým PID běží pomocí příkazu (ve stejném terminálu) ps l 7
Procesy - vytváření procesů pomocí knihovní funkce system() snadný způsob jak z procesu spustit jiný proces pomocí fork() a exec() vytvoří podproces, ve kterém spustí shell potom předá zadaný příkaz shellu k vykonání pomocí systémových volání fork() a exec() fork()vytvoří duplicitní kopii aktuálního procesu exec()nahradí program v aktuálním procesu jiným programem pomocí funkce wait() donutíme rodiče čekat na dokončení potomka, jinak by oba procesy běžely nezávisle. 8
Příklad: system.c Příklad ilustruje použití funkce system(). Přeložte tento příklad. Pomocí ps l se podívejte, které procesy běží v daném terminálu. Spusťte přeložený příklad na pozadí a pomocí ps l se podívejte, které procesy přibyly. Ukončete přiklad pomocí příkazu kill -9 %1. Pomocí ps l se podívejte, které procesy běží v daném terminálu. 9
Příklad: fork.c Příklad ilustruje použití funkce fork(). Přeložte tento příklad. Pomocí ps l se podívejte, které procesy běží v daném terminálu. Spusťte příklad na pozadí. Kolik procesů bylo spuštěno? V intervalech 1-30, 30-60 a 60-90 sekund po spuštění si ověřte pod jakým PID a PPID procesy běží (pomocí příkazu ps l). Jak se změnilo PPID u potomka a proč? 10
Příklad: fork-exec.c Příklad ilustruje použití funkcí fork(), execlp(), wait(). Rodič vytvoří nový proces a čeká na jeho dokončení. Potomek provede příkaz sleep 30. Přeložte tento příklad, spusťte ho na pozadí. Které procesy běží v daném terminálu? 11
Úkol Napište program, který přečte z příkazové řádky 11 celých čísel a uložte je do pole a[ ] vytvořítři potomky (procesy), kde 1. potomek sečte pole čísel a[] 2. potomek najde maximální prvek v poli a[] 3. potomek zjistí, zda se číslo a[10] vyskytuje v poli a[] vícekrát Rodičovský proces čeká na dokončení svých potomků. Návod: modifikujte příklad fork.c. 12
Signály Signál je speciální zpráva ("SW přerušení") posílaná jádrem OS procesu, iniciátorem může být i proces (komunikace mezi procesy). Když proces obdrží signál, okamžitě přeruší provádění kódu a začne zpracovávat daný signál. Každý typ signálu je representován svým jménem, které je třeba používat pro přehlednost a přenositelnost programů mezi UNIXy. Je definován jako celé číslo a je pro něj definován implicitní způsob reakce (lze změnit kromě signálů SIGKILL a SIGSTOP). Seznam signálů najdeme např. v manuálu man -s 3HEAD signal 13
Příklad: signal.c Příklad ukazuje jak předefinovat chování procesu na signál pomocí funkce sigaction(). Proměnná sig_count, do které se ukládá informace o tom kolikrát přišel signál SIGQUIT, je speciálního typu sig_atomic_t. Tento typ zaručuje, že operace inkrementace bude atomická. Pokud by přišlo více signálů za sebou, může být funkce definující reakci na signál přerušena uprostřed. => tato funkce by měla být co nejmenší (tak aby se provedla atomicky). 14
POSIX vlákna - vytvoření pthread_create ( thread, attr, start_routine, arg ) Parametry: thread = ukazatel na proměnnou typu pthread_t, kam se uloží ID nového vlákna attr = ukazatel na objekt atributy vlákna (pokud je NULL použijí se implicitní atributy) start_routine = ukazatel na funkci vlákna arg= ukazatel na vstupní data vlákna 15
POSIX vlákna ukončení vlákna Vlákno se ukončí: když se vrátí ze své startovací funkce zavolá funkci pthread_exit() když je ukončeno jiným vláknem pomocí pthread_cancel() když je ukončen celý proces ( např. exit() ) 16
Příklad: hello.c Příklad ilustruje spouštění a ukončování vláken. Přeložte a spusťte tento příklad: gcc o hello hello.c -lpthread 17
POSIX vlákna předávání dat Vstupní data se dají do vlákna předávat pomocí ukazatele na typ void. Můžeme předávat jeden parametr přímo přes tento ukazatel. Více dat můžeme předat přes ukazatel na pole nebo na strukturu. 18
Příklad: hello_arg1.c Příklad ilustruje předání čísla typu int do vlákna. Přeložte a spusťte tento příklad. 19
Příklad: hello_arg1.c Příklad ilustruje předání několika vstupních dat pomocí ukazatele na strukturu. Přeložte a spusťte tento příklad. 20
POSIX vlákna identifikační čísla Každé vlákno má přiřazeno ID. Vlákno může zjistit své ID pomocí funkce pthread_self ( ) Pro porovnávání ID dvou vláken používejte funkci pthread_equal ( thread1, thread2 ) 21
POSIX vlákna spojování vláken Pokud potřebujeme, aby proces nebo některé vlákno počkalo na dokončení jiného vlákna můžeme použít funkci pthread_join ( threadid, status ) threadid je ID vlákna, na které se bude čekat status je ukazatel na proměnnou, do které se uloží návratový kód vlákna (pokud nás nezajímá návratový kód, stačí nastavit NULL). 22
Příklad: join.c Příklad ilustruje čekání na dokončení vláken. Přeložte a spusťte tento příklad. Zkuste zakomentovat funkce pthread_join(), zkompilovat a spustit modifikovaný příklad. Co se změní? 23
POSIX vlákna atributy vláken Definují chování vláken. Pokud ve funkci pthread_create() nezadáme ukazatel na objekt atributy, použijí se implicitní atributy. Jak specifikovat atributy: 1. vytvoří se objekt typu pthread_attr_t 2. zavolá se funkce pthread_attr_init(), které se předá ukazatel na zmíněný objekt (nastaví se implicitní hodnoty) 3. modifikujeme objekt podle potřeby pomocí knihovních funkcí pthread_attr_xyz (viz. např. man pthread_attr_init) 4. ukazatel na objekt se předá funkci pthread_create() 24
Příklad: join1.c Příklad ilustruje změnu atributů. Přeložte a spusťte tento příklad. 25
Úkol Udělejte to samé jako v předchozím úkolu, akorát místo procesů použijte při implementaci vlákna. Zjistěte jak dlouho trvá výpočet procesům a jak dlouho vláknům. Pro informaci o délce výpočtu použijte příkaz time program 26
Kde najít další informace? Viz. odkazy na web stránce cvičení. 27