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