OSY

Z David Seidl

Přejít na: navigace, hledání

Cvičení z předmětu Operační systémy

Podmínky pro udělení zápočtu:

  • získání minimálně 22 bodů
  • odevzdání uznatelného projektu
  • za projekt je možné získat maximálně 15 bodů přičemž projekt s méně než 8 body není uznatelný
  • během semestru bude probíhat minimálně 10 bodovaných cvičení kde bude možné získat 2 až 4 body
  • cvičení nejsou povinná
  • témata jednotlivých úkolů na cvičení budou probírána na přednášce a na cvičení již nebude látka dále vysvětlována

Informace ke všem cvičením jsou k dispozici na stránkách Ing. Petra Olivky http://poli.cs.vsb.cz/edu/osy/. Na těchto stránkách naleznete i videa s návody do cvičení.

Obsah

1.cvičení (2013)

1.bod
Ve cvičení se máte pouze seznámit s prostředím ve kterém budeme celý semestr pracovat.

Měli by jste umět editovat text v alespoň jednom textovém editoru, který ukládá data bez formátovacích značek. Zopakujte si základní příkazy pro práci v příkazové řádce jako je ls, mkdir, touch, chown, chmod, cd apod. Velice častým problémem je, že na vašem účtu přesáhnete povolenou kvótu, proto si prosím zjistěne velikost vaší kvóty pomocí příkazu quota.

Úkoly

  • vytvořte ve vaší domácí složce podsložku osy a vní další složky pro každé cvičení jednu
  • ve vámi vybraném textovém editoru napište zdrojový kód programu, který na obrazovku vypíše Vaše jméno, příjmení a login
  • soubor se zdrojovým kódem pojmenujte neco.c a přeložte za pomocí řádkového překladače jazyka C "gcc" nebo soubor pojmenujte neco.cpp a přeložte pomocí řádkového překladače jazyka C++ "g++"
  • doporučuji prostudovat (alespoň letmo) manuálové stránky překladače gcc a g++, manuálové stránky si vylistujete pomocí příkazu man gcc, nebo man g++ . Věnujte prosím pozornost parametrům -o, -l, -L a -shared.

2.cvičení (2013)

3.body
Cvičení se bude týkat již pokročilejší práci s procesy v systému Unix, jejich přesouvání na pozadí, opětovné přesouvání na popředí a zastavování. Měli by jste být schopni vypsat běžící procesy a určit jejich PID. Měli by jste si také uvědomit rozdíl mezi jobem a PIDem.

Pro provádění výše jmenovaných akci se v operačním systému Linux používají příkazy:

  • bg -pro přesunutí procesu na pozadí
  • fg -pro přesunutí procesu na popředí
  • kill -pro zaslání signálu procesu
  • ps -pro vypsání procesů
  • & -tohle není přímo příkaz, ale je to paramatr programu který způsobí že se program spustí na pozadí
  • CTRL-Z -klávesová zkratka pro pozastavení programu


Aby jste si vyzkoušeli zdali umíte pracovat s procesy v Linuxu spusťte například program "yes". Ten bude na obrazovku vypisovat písmeno "y". Tento program pozastavte a poté ho zase spusťte. Poté ho opětovně zastavte a přesuňte na pozadí. Nakonec program yes běžící na pozadí ukončete pomocí jobu. Pokuste se program "yes" spustit přímo na pozadí a poté ukončit pomocí PIDu.

V další části cvičení by jste si měli procvičit práci na vzdáleném terminálu. Systémy založené na operačním systému UNIX mají tu výhodu, že již od počátku jsou konstruovány tak, že je možné na nich pracovat vzdáleně. V dnešní době je nejčastější způsob jak se můžete připojit ke vzdálenému Unixovému systému protokol SSH (Security SHel). Kdysi se používat protokol Telnet, ale ten je dnes již nevyhovující z hlediska bezpečnosti.

Připojte se pomocí příkazu ssh na server linedu.vsb.cz. Po zadání hesla budete mít k dispozici stejné terminálové okno jako máte na lokálním počítači, ale všechny příkazy provedené v tomto terminálu bude provádět server lin-edu.cs.vsb.cz. Pomocí příkazu ssh lze spustit na vzdáleném PC nějaký příkaz jeho výstup vypsat na lokální terminál. Můžete to vyzkoušet třeba s příkazem "date", který spustíte na serveru lin-edu.cs.vsb.cz a jeho výstup si necháte vypsat na lokální terminál.

Pomocí protokolu ssh je možné vzdáleně spouštět i grafické aplikace. Takže můžete na serveru lin-edu.cs.vsb.cz spustit třeba i gedit, bluefish, xclock, xeyes a podobné grafické programy. Pokud je správně nastaven Váš ssh klient a server na který se připojujete to umožňuje, budou se pakety které přenášejí grafické informace přenášet v SSH tunelu a vy nebudete muset nic nastavovat. Pokud tomu ale tak nebude je nutné na straně serveru správně nastavit proměnou prostředí DISPLAY a na lokálním PC pomocí příkazu xhost povolit zobrazování grafických aplikací z jiných PC. Vyzkoušejte si spuštění programu "xclock" ze serveru lin-edu.cs.vsb.cz jednak pomocí SSH tunelu a i pomocí proměné DISPLAY.

Aktuální nastavení systémových proměných v OS Linux zjistíte příkazem "export". Proměnou prostředí nastavíte také pomocí příkazu export a parametru. Například export DISPLAY=pca1011d.vsb.cz:0.0

Hodnocení
Pokud si budete jisti, že již rozumíte všem techikám, které byly výše popsány bude Vám zadán jednoduchý úkol, na kterém demonstrujete že problematice rozumíte. Na základě splnění úklu budete ohodnoceni. Můžete si samozřejmě připravit vlastní příklad na které budete demonstrovat porozumění dané problematice. Pro demonstraci si prosím naprogramujte program "soucet", který bude očekávat jako parametry celá kladná čísla. Program bude na obrazovku vypisovat součet těchto čísel. Tedy například:

./soucet 1 2 3
6
6
6
6
6

Výpis bude probíhat tak dlouho dokud program neukončíte.

3.cvičení (2013)

3.body
Dnes se budeme zabývat statickými a dynamickými knihovnami a Makefile.

Pokud chcete opravdu rozumět tomu jak se tvoří větší programové celky v operačních systémech je nutné, aby jste rozuměli technikám, které si v tomto cvičení budeme procvičovat. Představte si, že chcete naprogramovat program, který se bude připojovat na LDAP server. Naprogramovat takový program nebude jistě jednoduché. Pokud ale víte jak se pracuje s knihovnami budete vědět, že můžete použít systémovou knihovnu libldap. Váš program se tak zkrátí na pár řádků a navíc tomu jak funguje připojení na LDAP nemusíte vůbec rozumět. Tedy celá filozofie knihoven je: "Proč programovat to co naprogramovali jiní, a lépe.".

Statická knihovna Pokud tvoříte nějaký větší projekt a jste svědomitý programátor, zajisté si celý program rozsekáte na funkční celky a zdrojový kód pro tyto celky budete psát vždy v samostatných souborech. Takže budete mít prvni.c, druhy.c, treti.c a vysledny.c který již bude obsahovat funkci main, která bude využívat funkce, které byly definovány v ostatních souborech. Výsledný projekt poté vytvoříte tak, že nejdříve vytvoříte z prvni.c, druhy.c, treti.c přemístitelný kod s příponou *.o. To se udělá následovně: gcc -c prvni.c -o prvni.o. Parametrem -c řeknu překladači, že má vytvořit přemístitelný kod a parametem -o říkám jak se má vytvořený soubor jmenovat. Stejným způsobem lze vytvořit i sobory s přemístitelným kodem treti.o a druhy.o . Pokud jsou vytvořeny tyto soubory s přemístitelným kodem, tak je možné je přímo využít při kompilaci výsledného projektu: gcc prvni.o druhy.o treti.o vysledny.c -o vysledny. Tímto příkazem vznikne již výsledný program. Pokud jsou soubory s přemístitelným kódem prvni.o, druhy.o a treti.o součástí nějakého celku a dá se předpokládat že se budou v různých programech tyto soubory používat opakovaně, bylo by vhodné je spojit dohromady. To se dá udětal příkazem ar, který vytvoří takzvaný archiv, který lze pak vkládat jako jeden soubor do různých projektů. Použití je následovné: ar cr static.a prvni.o druhy.o treti.o. Kompilace vysledneho programu by se pak provedla následovně: gcc vysledny.c static.a -o vysledny.
Úkol
Vytvořte projekt, který se bude skládat ze čtyř souborů. První soubor bude obsahovat funkci, která pouze vypíše Vaše křestní jméno. Druhý soubor vypíše Vaše příjmení a třetí funkci, která vypíše Váš login. Čtvrtý soubor bude obsahovat funkci main, ve které použijete všechny Vámi vytvořené funkce pro vypsání Vašeho jménam příjmení a loginu. Nezapomeňte vytvořit i patřičný(é) hlavičkové soubory, kde budou deklarace funkcí a tento hlavičkový soubor poté vložte do souboru s funkcí main. Ze souborů bez funkce main vytvořte přemístitelné kody a tyto kody poté spojte do statické knihovny. Knihovnu poté použijte pro kompilaci souboru s funkcí main a vytvoření výsledného programu.

Dynamická knihovna Použití dynamické knihovny je obdobné jako u statické. Tedy výsledek je více méně tentýž. Rozdílem ale je, že dynamická knihovna se stává součástí programu až při jeho spuštění a nikoli již při kompilaci, jak je tomu u statických knihoven. Uvedu příklad. V úkolu u statické knihovny jste měli za úkol vypsat vaše jméno a příjmení. Kdyby jste ale změnili vaše příjmení (holky vědí :-) ), tak by jste museli zkompilovat celý program. Pokud ale použijete dynamickou knihovnu, stačilo by zkompilovat pouze knihovnu s funkcí, která vypisuje vaše příjmení.
Jak tedy vytvořit dynymickou knihovmu. Základem je parametr programu gcc -shared . Tímto parametrem programu zdělím, že chci z připravených přemístitelných kódů vytvořit dynamickou knihovnu. Přípona dynymických knihoven je stanovena konvencí na *.so a začínat by měly předponou lib. Takže pokud se vrátám k příkladu uvedeném v popisu statických knihoven. Dynamickou knihovnu bychom z přemístitelných kódů vytvořili následovně: gcc -shared prvni.o druhy.o treti.o -o libmoje.so. Pro kompilaci výsledného projektu je zapotřebí zadat překladači dva parametry -l a -L. Parametrem -L zadávám cestu kde je umístěna dynamická knihovna, tedy pokud je knihovna umístěna v aktuálním adresáři použije se parametr -L. pokud je v nadřazeném adresáři tak -L../ nebo lze použít i absolutní cestu -L/home/fei/sei01. Parametrem -l určujeme název dynamické knihovny bez předpoly lib a přípony .so. Tedy kompilaci naše projektu by jsme docílili příkazem gcc -L. -lmoje vysledny.c -o vysledny.
K vašemu velkému údivu by, ale program nefungoval. V operačním systému jsou definovány cesty kde mají programy standardně hledat dynamické knihovny. Když si ale vlastní dynamickou knihovnu umístíte někde v domácím adresáři, programy nemají ani páru o tom že se zde dynamická knihovna nachází. Proto je třeba definovat proměnou prostředí LD_LIBRARY_PATH kterou programům sdělíte, že má dynamické knihovny hledat ne jen v systémových ceatách, ale i v cestě vámi definované. Tedy pro náš případ by jsme definovali proměnou takto export LD_LIBRARY_PATH=.. Nyní již program půjde spustit.
Úkol
Vytvořte dvě složky a v každé dynamickou knihovnu. Obě dynamické knihovny se budou jmenovat stejně a budou obsahovat stejnou funkci. V jedné knihovně funkce příjme dva parametry, první parametr bude řetězec a druhý bude znak. Funkce projde řetězec a nahradí všechny výskyty znaku definovaného jako druhý parametr znakem '_'.
Příklad použití funkce:

char ch[] = "Tohle je zkušební text";
moje_funkce(ch,'e');
printf("%s\n",ch);
Vypsaný text bude vypadat následovně:
Tohle je zkušební t_xt

V druhém souboru bude identická funkce pouze místo znaku '_' bude nahrazovat znakem '-'.

Použijte první dynamickou knihovnu v programu kde demonstrujete její funkci. Pouze za pomocí přepisu proměnné LD_LIBRARY_PATH zaměňte používanou dynamickou knihovnu, bez toho aby jste váš projekt znovu kompilovali.

Makefile Pro tvorbu projektů v příkazovém řádku se velmi často využívá Makefile. Pomocí Makefile lze vytvářet i opravdu "gigantické" projekty. Například při kompilaci jádra operačního systému Linux se také používá Makefile. Jedná se vlastně o skript, který spouští dané akce, které jsou zapotřebí pro kompilaci celého projektu. Takže pokud se Váš projekt skládá z dvaceti souborů, které je zapotřebí zkompilovat pro překlad výsledného programu a Vy jeden z těchto souborů upravíte, Makefile zařídí, že se přeloží jen jeden upravený kod a ostatní kody budou zachovány a nebudou se znova kompilovat.

Syntaxe Makefile

cíl: potřebné soubory k dosažení cíle
   <tab>nástroj jak cíl vytvořit

takže pro příklad jak by mohl Makefile vypadat

vysledny: static.a vysledny.c
gcc vysledny.c static.a -o vysledny

satic.a: prvni.o druhy.o treti.o
ar cr static.a prtni.o druhy.o treti.o

prvni.o: prvni.c
gcc -c prvni.c -o prvni.o

druhy.o: druhy.c
gcc -c druhy.c -o druhy.o

treti.o: treti.c
gcc -c treti.c -o treti.o



Úkol Pro příklad se statickými knihovnami i dynamickými knihovnami vytvořte makefile a projekt přeložte pomocí příkazu make. Připravte si ukázku že Vám program make funguje pro přeložení jen částečně upraveného projektu. Pro získání plného počtu bodů prosím použijte v Makefile patřičné zástupné symboly $@, $^, $< a doplňte Makefile o cíl clean, kterým vymažete projekt a zanecháte pouze zdrojové kódy programu.

4.cvičení (2013)

3.body
Cvičení se týká vytváření nových procesů a komunikace mezi nima. Teorie je velmi dobře zpracovaná ve videomateriálech [1].

Bez zvládnutí těchto technik programování v OS, nebudete schopni napsat a obhájit projekt, proto velmi doporučuji níže probíranou látku nezanedbat.

Následující odkaz Vám urči které zadání máte vypracovat: [2]

Zadání A
Vytvořte program, který si pomocí funkce fork() vytvoří jednoho potomka a tento potomek si vytvoří dalšího potomka a tento potomek dalšího potomka. Vždy po použití funkce fork() a vytvoření potomka vypíše rodičovský proces PID potomka. Třetí potomek odešle svůj PID druhému a druhý potomek odešle svůj PID svému rodiči (prvnímu potomkovi). První potomek příjme PID svého dceřiného procesu a přičte k němu svůj PID. Toto číslo odešle do druhé pipe. Rodič si přečte součet PIDů svého potomka a jeho potomka (vnuka) a vypíše je na standardní výstup.
Schéma:

                                   -------------- 
                                   |   rodič    | 
                                   -------------- 
                                         ^
                                         | PID3+PID2+PID1
                                         |                                  
                                   --------------
                                   |  potomek1  |
                                   --------------
                                         ^
                                         | PID3+PID2
                                         |
                                   --------------
                                   |  potomek2  |
                                   --------------
                                         ^
                                         | PID3
                                         |
                                   --------------
                                   |  potomek3  |
                                   --------------



Zadání B
Vytvořte program, který si pomocí funkce fork() vytvoří tři potomky. Vždy po použití funkce fork() a vytvoření potomka vypíše rodičovský proces PID potomka. Rodičovský proces bude s každým z potomků komunikovat za pomocí pipe. (Bude teda za potřebí tří pipe, pro každého potomka jednu). První ,druhý i třetí potomek pošle svému rodiči svůj pid (funkce getpid()) a rodič vypíše přijaté informace na standardný výstup.
Schéma:

                       PID1        --------------         PID2
                            /----> |   rodič    | <-----\
                            |      --------------       |                      
                            |             |             |
                            |             |             |
                     --------------       |       --------------               
                     |  potomek1  |       |       |  potomek2  | 
                     --------------       |       --------------
                                   --------------
                                   |  potomek3  |
                                   --------------



Zadání C
Vytvořte program, který si pomocí funkce fork() vytvoří tři potomky. Vždy po použití funkce fork() a vytvoření potomka vypíše rodičovský proces PID potomka. První potomek za pomocí pipe pošle druhému potomkovi svúj PID (funkce getpid()). Druhý potomek příjme PID svého kolegy (bratra) a přičte k němu svůj PID. Toto číslo odešle do druhé pipe třetímu potomkovi. Ten opět přičte svůj PID a pošle ho rodiči. Rodič si přečte součet PIDů svých potomků a vypíše je na standardní výstup.
Schéma:

                                   --------------            PID1+PID2+PID3
                                   |   rodič    | <--------------------------------------------------------\
                                   --------------                                                          |
                                                                                                           |
                                                                                                           |
                   --------------      PID1      --------------  PID1+PID2     --------------              |
                   |  potomek1  | -------------> |  potomek2  | -------------> |  potomek3  |--------------/
                   --------------                --------------                --------------



Poznámky:

Pokud vytváříte dva potomky nezapomeňte, že po prvním použití funkce fork() se proces rozdvojí. Při dalším použití funkde fork() musíte podmínkou zařídit, aby jej použil jen rodičovský proces a né potomek vytvořený prvním voláním funkce fork();

V každé instanci vašeho programu nejdříve uzavřete všechny nepotřebné hendly a poté teprve použijte funkce pro zápis nebo čtení z hendlů.

Vytvoření pípy:

   int promena[2];
   pipe(promena);


               ----------------------
      vstup >  | [1]    Pípa    [0] | > výstup
               ----------------------

Nechcete-li používat pro zápis a čtení z pípy funkce write() a read(), můžete se pokusit o přesměrování standardního vystupu nebo výstupu a používat klasické funkce printf() a scanf().

Přesměrování STDIN a STDOUT

Doporučuji vám ve zdrojovém kodu uvést tyto definice

   #define STDIN  0
   #define STDOUT 1
   #define STDERR 2
   
   #define PIPEIN  1
   #define PIPEOUT 0
   

Pokud chci do pipe zapisovat, tedy pouzivat printf platí:

  • presmerovavam STDOUT do vstupu pipy
 close(STDOUT);                        //zavru standardni vystup
 dup(promena[PIPEIN]);                 //zduplikuju vstup pipy
 close(promena[PIPEOUT]);              //puvodni vstup do pipy neni  jiz potreba
 close(promena[PIPEIN]);               //druha strana pipy me nezajima, posilal bych si data sam sobe

Pokud chci z pipe cist, tedy pouzivat scanf platí:

  • presmerovavam STDIN do vystupu pipy
 close(STDIN);                   //zavru standardni vstup
 dup(promena[PIPEOUT]);          //zduplikuju vystup pipy
 close(promena[PIPEIN]);         //puvodni vstup do pipy neni  jiz potreba
 close(promena[PIPEOUT]);        //druha strana pipy me nezajima, posilal bych si data sam sobe

5.cvičení (2013)

K zamyšlení.

Minulý rok jsem si dovolil na těchto řádcích napsat pár slov, probudit tak u vašich kolegům svědomí.
Zda-li se mi to podařilo opravdu nevím. Statistika úspěšnosti minulého roku bohužel nevystoupila z průměru a tak projekt obhájilo opět pouze cca 30% studentů.
Úskalí tohoto předmětu je uvědomit si že boj o zápočet vedete každou hodinu a né jen poslední cvičení kdy odevzdáváte projekt.

3.body
Dnešní zadání tedy bude svou obtížností odpovídat obtížnosti projektu. Nikoli svým rozsahem, ale co do schopnosti programovat a pochopit techniky, které si ve cvičeních procvičujeme. Jelikož většina z vás zvládla práci s rourami naprosto excelentně. Věřím že přidání příkazů exec() a dup() bude pro vás velmi jednoduché.

Zadání
Jako první si prosím zkopírujte následující zdrojový kód do souboru deduct.cpp:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv){
	int i,j;
	scanf("%d",&i);
	j=atoi(argv[1]);
	i-=j;
	printf("%d\n",i);
	return 0;
}

Tento zdrojový kod přeložte a vytvořte program deduct.

g++ deduct.cpp -o deduct

Vaším úkolem bude vytvořit program, který si vytvoří 102 potomků. Vždy dva sousední potomci budou mezi sebou propojeni pípou až na prvního potomka, který bude mít pouze pípu pro výstup a posledního potomka, který bude mít pouze pípu pro vstup. Všichni potomci budou mít standardní vstupy a výstupy přesměrovány do pípy. Opět až na prvního a posledního potomka, kteří budou mít přesměrovány buď jen standardní vstup nebo výstup.

První potomek pomocí funkce printf() vypíše na standardní výstup, který bude přesměrován do pípy, číslo "1000". Druhý potomek příjme toto číslo na standardním vstupu a zašle jej do programu './deduct 1', program příjme číslo, odečte od něj číslo 1 a zapíše ho přesměrovaný standardní výstup.Třetí potomek má identickou funkčnost jako druhý. Tak to bude pokračovat až po potomka 50. 51 potomek bude již odečítat číslo 2, identicky i 52 až stý potomek. Stýprvní potomek příjme číslo a vypíše ho na obrazovku.


Schéma:

                                                ---------------------
                                                |      rodič        |
                                                |                   |
                                                ---------------------                           
                       

     ------------------  1000  --------------  999  --------------  998  -------------- 997                   851 --------------- 850 ---------------
     |  potomek1      | -----> | potomek2   | ----> | potomek3   | ----> | potomek4   |----->  ...  ...  ...  --> | potomek100  | --> | potomek102  |
     | printf("1000") |        | ./deduct 1 |       | ./deduct 1 |       | ./deduct 1 |                           | ./deduct 2  |     | read(0,...) |
     ------------------        --------------       --------------       --------------                           ---------------     ---------------

Váš program se bude chovat stejně jako by jste zapsali do příkazového řádku:

echo 1000 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 |
./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 |
./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 |
./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 |
./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 | ./deduct 1 |
./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 |
./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 |
./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 |
./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 |
./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | ./deduct 2 | cat

Dobré rady pro dobré programátory
Pevně věřím, že vás nenapadne psát mamutí zdrojový kód kde budete tvořit potomky jednoho po druhém. Doporučuji vám vytvořit si nejdříve prvního potomka. Pak v cyklu vytvořit 50 potomků co budou odečítat 1 a poté v cyklu dalších 50 potomků kteří budou odečítat číslo 2. A nakonec posledního potomka.

Píp bude celkem 101. Pro jejich použití doporučuji vytvořit dvourozměrné pole 'int roura[101][2];' a jednotlivé pípy poté vytvářet v cyklu a při práci s pípou se odḱazovat vždy na daný prvek pole.

6.cvičení (2013)

5.bodů
Problematika socketů, kterou se budeme v tomto cvičení zabývat je podstatně složitější než to co jste doposud ve cvičeních z předmětu OSY programovali. Proto Vám velmi doporučuji, aby jste se na cvičení velmi dobře připravili. Pokud budete studovat látku až na cvičení je téměř jisté, že se Vám nepodaří problém v daném čase vyřešit.

Problematika soketů je velmi dobře zpracována na různých serverech na internetu, případně by měla být velmi dobře pochopitelná z příkladů dostupných k tomuto předmětu na stránkách Petra Olivky.
Základní úkony při vytváření serveru

//vytvoření socketu
int sock_listen = socket( AF_INET, SOCK_STREAM, 0 );

//vytvoření a naplnění struktury in_addr addr_any = { INADDR_ANY }; sockaddr_in srv_addr; srv_addr.sin_family = AF_INET; srv_addr.sin_port = htons( 4444 ); srv_addr.sin_addr = addr_any;
//nastavíme případné vlastnosti socketu setsockopt( sock_listen, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof( opt ) ); //svážeme strukturu se socketem bind( sock_listen, (const sockaddr * ) &srv_addr, sizeof( srv_addr ) ); //začneme naslouchat na daném portu listen( sock_listen, 1 );
//proměnná pro vlastnosti příchozích socketů sockaddr_in rsa; int rsa_size = sizeof( rsa );
//accept je blokující funkce, zde se program zastaví a rozběhne se až po navázání spojení //po navázání spojení se vytvoří nový socket na kterém se již komunikuje s klientem int sock_client = accept( sock_listen, ( sockaddr * ) &rsa, ( socklen_t * ) &rsa_size );
//často se zde použije fork() a vytvoří se pro nově vzniklé spojení nový potomek, který bude obsluhovat klienta //rodičovský proces se pak zpět vrátí na accept a bude očekávat další spojení



Základní úkony při vytváření klienta

// vytvoření socketu
int sock_server = socket( AF_INET, SOCK_STREAM, 0 );

// překlad doménových jmen na IP adresu hostent *hostip = gethostbyname( host );
//struktura s vlastnostmi socketu sockaddr_in cl_addr; cl_addr.sin_family = AF_INET; cl_addr.sin_port = htons( port ); cl_addr.sin_addr = * (in_addr * ) hostip->h_addr_list[ 0 ];
//navázání spojení se servererm connect( sock_server, ( sockaddr * ) &cl_addr, sizeof( cl_addr ) )



Zadání
Vaším dnešním úkolem bude naprogramovat jednoduchý program pro komunikaci mezi počítači. Takové jednoduché ICQ.
První část vašeho úkolu je naprogramovat program, který si vytvoří jednoho potomka. Potomek vytvoří na TCP portu 4444 server na kterém bude přijímat spojení. Po přijetí spojení se server zachová tak, že všechny data, která příjme vypíše na standardní výstup. Tuto část si můžete otestovat pomocí programu telnet.

telnet localhost 4444
--------------            --------------
|   Rodič    |  ------->  |  Potomek 1 |  <<-- < port 4444 >
--------------            |            |  -->> standardní výstup
                          --------------  


Až budete mít implementován server, vytvořte ve vašem programu dalšího potomka. Tento potomek si naváže spojení se zvoleným serverem na portu 4444. Potomek bude naslouchat na standardním vstupu a po zadání enter odešle všechna data na otevřený soket na server. Pokud Vám fungovala předchozí část můžete vytvořit spojená sám na sebe, tedy na localhost.

--------------            --------------
|   Rodič    |  ------->  |  Potomek 1 |  <<-- server < port 4444 >
--------------  |         |            |  -->> standardní výstup
                |         --------------  
                | 
                |         --------------
                ------->  |  Potomek 2 |  -->> klient < port 4444 >
                          |            |  <<-- standardní vstup
                          --------------       


Pokuste se rozšířit prvního potomka tak, aby mohl přijímat více spojení. Po každém vytvoření spojení (accept) vytvořte dalšího potomka. Tento potomek bude obsluhovat nově vzniklý socket a bude vypisovat data na standardní výstup. Předchozí proces se vrátí zpět na accept a bude očekávat příchozí spojení. Správné chování si můžete vyzkoušet vícenásobným připojením k sevreru, třeba opět programem telnet.

--------------            --------------  <<-- server < port 4444 >         ----------------
|   Rodič    |  ------->  |  Potomek 1 |  ------------------------------>  |  Potomek 1.1 | <<-- < port 4444 >
--------------  |         |            |  |                                |              | -->> standardní výstup
                |         --------------  |                                ----------------
                |                         |
                |                         |
                |                         |                                ----------------
                |                         ------------------------------>  |  Potomek 1.2 | <<-- < port 4444 >
                |                         |                                |              | -->> standardní výstup
                |                         |                                ----------------
                |                        ...  
                | 
                |         --------------
                ------->  |  Potomek 2 |  -->> klient < port 4444 >
                          |            |  <<-- standardní vstup
                          --------------       


Poslední úkol je doimplementovat možnost připojení druhého potomka na několik serverů. Tedy pokud bude na standardním vstupu zapsán text a je zadán enter odešlou se data na všechny servery se kterými se naváže spojení.

--------------            --------------  <<-- server < port 4444 >         ----------------
|   Rodič    |  ------->  |  Potomek 1 |  ------------------------------>  |  Potomek 1.1 | <<-- < port 4444 >
--------------  |         |            |  |                                |              | -->> standardní výstup
                |         --------------  |                                ----------------
                |                         |
                |                         |
                |                         |                                ----------------
                |                         ------------------------------>  |  Potomek 1.2 | <<-- < port 4444 >
                |                         |                                |              | -->> standardní výstup
                |                         |                                ----------------
                |                        ...  
                | 
                |         --------------
                ------->  |  Potomek 2 |  -->> klient1 < port 4444 >
                          |            |  -->> klient2 < port 4444 >
                          |            |  -->> klient3 < port 4444 >
                          |            |  -->> klient4 < port 4444 >
                          |            |  <<-- standardní vstup
                          --------------

Pokud se Vám podaří dojít až sem, budete mít k dispozici program, kterým budete moci komunikovat v rámci sítě se svými kamarády.

7.cvičení (2013)

4.body
Semafor je věc kterou dobře znáte ze silnic. Už by Vám ale mělo být známo, že samafor je i prostředek, který využívá operační systém. Oba semafory mají společné to, že řídí provoz, a nebo ještě lépe řečeno měli by zaručit to, aby se nám auta(procesy) nesrazily.
V programech které využívají procesy může často vzniknout problém, kdy potřebujete zajistit, aby k nějakému sdílenému prostředku přistupoval pouze jediný z potomků, nebo jen samotný rodič. Může to být například otevřený soubor, nebo třeba sdílená paměť, ale o té se dozvíte až později.
Programově se semafor vytvoří následovně:

 semget( klic, pocet_semaforu, 0666 | IPC_CREAT );
  • klic je jedinečné číslo, které identifikuje samafor
  • pocet_semaforu , tato proměná určuje kolik semaforů se vytváří, v případě že je větší než 1, jedná se vlastně o jakési pole semaforů
  • 0666 | IPC_CREAT zde se nastavují práva a akce kterou chcete provést, v tomto konkrétním případě právo pro zápis a čtení pro všechny a vytvoření semaforu
  • funkce vrací ID semaforu


Pro práci se semafory je dobré si definovat následující funkce:
Funkce pro navýšení hodnoty semaforu

int sem_up( int id ){
   sembuf op = { 0, 1, 0 };
   semop( id, &op, 1 );
}

Funkce pro snížení hodnoty semaforu

int sem_down( int id ){
   sembuf op = { 0, -1, 0 };
   semop( id, &op, 1 );
}

Bližší informace o funkcích pro práci se semafory se dozvíte z manuálových stránek.

Pokud má semafor kladnou hodnotu je to obdoba zelené barvy u klasických semaforů. Když se jeho hodnota sníží na nulu je to opět obdoba oranžové barvy. Pokud se nějaký proces pokusí snížit hodnotu semaforu na zápornou hodnotu, bude tento proces zablokován. Odblokovat ho může jen jiný proces, který zvedne hodnotu semaforu. Často se stává, že několik procesů se postupně zablokuje na semaforu. Když poté jiný proces postupně semafor "zvedá" jsou zablokované procesy odblokovávány a to v tom pořadí v jakém přicházeli.

Zadání:
Vytvořte program, který si vytvoří dvacet potomků. Každý potomek bude nejdříve náhodnou dobu čekat a poté se zablokuje na semaforu. Rodič poté co se všichni potomci zablokují, bude v pravidelných časových intervalech zvedat semafor a vy by jste měli sledovat jak jednotliví potomci budou ožívat, přesně v tom pořadí v jakém se zablokovávali. Běh potomků i rodiče doplňte patřičnými výpisy na obrazovku, ať je možné program dobře sledovat.
Poznámka: Pokud se budete snažit dosáhnout toho, ať rodič zvedá semafor až po zablokování všech potomků, stačí to vyřešit jen vhodně zvoleným zpožděním. Tedy pokud vím, že se každý z potomků zablokuje v náhodném intervalu od 1s do 10s, rodič bude čekat 11s a poté začne zvedat semafor.

8.cvičení (2013)

4.bod
Posledním tématem, které by jste měli znát, aby jste byli schopni úspěšně naprogramovat projekt jsou vlákna. Vlákno je obdoba procesu, ale narozdíl od něj má jistá specifika. Při vytvoření nového procesu pomoci funkce fork, vzniká zcela nový proces, který je identický s procesem ze kterého vznikl. Při vytvoření nového procesu je tedy značně zatěžována paměť počítače. Navíc pokud chceme nějakým způsobem mezi procesy komunikovat musíme využít prostředků operačního systému, jako je pípa, sdílená paměť a podobně. Vlákno je vlastně osamostatněná funkce. Tedy pomocí vláken jste schopni ve vašem programu definovat funkci, která poběží samostatně jakoby bokem vašeho programu. Nicméně takto "rozvlákněná" funkce neztrácí nic z obecných vlastností funkcí. Jednou z takových vlastnbostí je, že funkce může používat globální proměnné v programu. Tedy pokud v programu využíváte vlákna, není třeba využívat sdílené paměti, jelikož vám funkci sdílené paměti nahradí globální proměnná.

Pro vytvoření vlákna se používá funkce pthread_create

 int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg);

Z parametrů je důležitý především třetí, který je ukazatelem na funkci, kterou chcete "rozvláknit" a poslední kterým předáváte funkci parametry. Poslední parametr je typu void, tedy je nahraditelný libovolným datovým typem. Chcete-li funkci předávat více parametrů než jeden, je zapotřebí si definovat strukturu a parametry předávat v ní.

Zadání:

Vytvořte program který bude na obrazovku cyklicky vypisovat Vaše jméno, příjmení a osobní číslo. Jméno a příjmení budou vypisována ve vláknech!! Jméno vypíše první vlákno za 2s po svém spuštění a příjmení druhé vlákno 4s po svém spuštění. Hlavní program bude vypisovat každých 500ms tečku "." do doby než obě vlákna vypíšou jméno a příjmení, poté hlavní program výpis teček ukončí a vypíše na nový řádek váš login. Výstup bude vypadat následovně:

bash# ./test
....David....Seidl
sei01

Snažte se libovolným způsobem využít vlastnosti vláken, tedy především společný paměťový prostor. Pro výpis jména a příjmení používejte jen jednu funkci. Této funkci se jako parametr předá informace co má vypsat a za jakou dobu po svém spuštění to má udělat. Dvě instance této funkce se spustí ve vláknech. Informaci o tom že funkce skončila svou činnost si předávejte pomocí globální proměnné, případně využijte informace o stavu vlákna a čekejte na jeho ukončení (obdobně jako wait u procesů).

Výpis teček v programu prosím dělejte na standardní chybový výstup.


Nyní jsme ve cvičeních procvičili všechny techniky které potřebujete ke zvládnutí projektu.
Projekt můžete odevzdat kdykoli ve cvičení. Pokud projekt obhájíte dřive budoou Vám všechny body za zbývající cvičení doplněny na plný počet.

9.cvičení (2013)

4.body
Když už nyní umíte zařídit pomocí semaforu, aby si programy nepřepisovali sdílená data, bylo by vhodné mít nějakou možnost, aby dva programy mohly sdílet nějaký paměťový prostor. Tento prostor by se využíval pro zápis dat. Systémové prostředky, které nabízejí tuto možnost jsou sdílená paměť a fronta zpráv.
Použití fronty zpráv je velmi podobné jako použití semaforu. Nejdříve je zapotřebí si frontu vytvořit pomocí funkce int msgget(key_t key, int msgflg), jejíž první parametr je klíč, tedy jedinečné číslo a druhý parametr je akce, která se má provést, například 0660 | IPC_CREAT. Pro práci se sdílenou pamětí je poté zapotřebí funkce msgsnd(...) a msgrcv(...).

Zadání:
Vytvořte dva programy. Zapisovatel a Ctenar. Zapisovatel vytvoří frontu zpráv a zapíše do ni zprávu s délkou maximálě 40 znaků a jedno osmibitové číslo. Zprávu i číslo bude program dostávat jako parametr. Ctenar si po svém spuštění vyzvedne zprávu z fronty vypíše ji na obrazovku, přečte i číslo a vypíše znaky zprávy, které se vyskytují na pozici bezezbytku dělitelné tímto číslem. Pokud nebude ve frontě zpráv žádná zpráva bude na to uživatel upozorněn.

Pro kompilaci obou programu vytvořte Makefile.

Příklad:

bash# ./Zapisovatel muj_text_delky_max_40_znaku 2 Zprava odeslana do fronty.

bash# ./Ctenar muj_text_delky_max_40_znaku jtx_ek_a_0zau

bash# ./Ctenar !! Ve fonte zprav se nevyskytuji zadne zpravy. !!

10.cvičení (2013)

4.bod
Každá voda jednou steče dolů a i cvičení z předmětu Operační systémy musí jednou skončit. Ten konec je právě tady a tak vás všechny, kteří si ještě chtějí vylepšit bodový zisk čeká poslední zadání.
Zajisté jste si všimli, že se někdy stane že Vám nejde v Linuxech spustit webový prohližeč Firefox. To je většinou způsobeno tím, že se "nepěkně" odhlásíte a máte přitom Firefox spuštěný. Pokud ho totiž neukončíte korektně, Firefox si po sobě v systému "neuklidí" a ve složce .mozilla si nechá nějaké své dočasné soubory. Jeden s těchto souborů má zabránit vícenásobnému spuštění Firefoxu. V dnešním zadání budu po Vás chtít něco obdobného.

zadání:
Vytvořte program, který si po svém spuštění vytvoří soubor (pokud již neexistuje) a na tomto souboru udělá zámek. Pokud se pokusím spustit program v druhé instanci nebude mi to dovoleno a proces se zastaví a bude pokračovat až poté co první proces ukončí svou činnost a tím odemkne soubor.
Pokud je Váš program správně naprogramován, měl by zámek fungovat i na dvou různých počítačích, které mají sdílený diskový prostor pomocí NFS. Tedy pokud jednu instanci programu spustíte na vašem lokálním PC a druhou na serveru linedu.vsb.cz, bude taktéž zámek souboru fungovat a blokovat druhý proces. Bude se vlastně jednat o takový mezisystémový semafor.

Projekt (2012)

Celé vaše skoro tříměsíční snažení by mělo vyústit v projektu. Jeho konkrétní zadání najdete na stránkách Petra Olivky http://poli.cs.vsb.cz/edu/osy/poz-denni.html. Prosím dejte si velký pozor na to, ať zprávy, které si mezi sebou po socketech předávají jednotliví klienti a server mají správný tvar. Nedodržení správného tvaru je důvod pro neuznání projektu. Velice důležité je, aby jste velice dobře rozuměli každémů řádku. Je to Váš projekt Vaše dílo a tak není nit hloupějšího než když Vám musím vysvětlovat Váš vlastní kód. To paltí především pro ty, kteří si chtěji ušetřit práci a projekt si někde "za pivo" seženou. Veskrze platí, že takový projekt odevzdáváte tak měsíc, než se Vám podaří pochopit to jak to ten člověk co to programoval myslel.
Jednotlivé IPC problémy, které můžete řešit jsou veskrze jednoduché a na internetu i přednáškách velmi dobře popsané, nejvíce práce je s omáčkou kolem, vytvářená semaforů, ošetřování chybových stavů a podobně. Není důležité, aby jste odevzdali superdokonalý program, daleko více mě potěší třeba i jednoduší ale Vaše řešení, za kterým si budete stát. A i když tam třeba budou nějaké nedostatky, budete vědět jak by jste je řešili.

Pro odevzdání projektu (r.2012) je určeno cvičení 3.května 2012. Poté už bude možná jen jedna oprava v případě nedostatků v projektu. Samozřejmě, že odevzdání projektu dříve je možné po domluvě kdykoli i mimo cvičení.

Osobní nástroje