Download as pdf or txt
Download as pdf or txt
You are on page 1of 76

OPERATIVNI SISTEMI

PRATEĆI MATERIJAL ZA VEŽBE


Gorana Vučić

AŽURIRANO
1.5.2017

1
2
SADRŽAJ

VEŽBE 1..............................................................................................................................................7
1. Komandna linija..........................................................................................................................7
Kako je organizovan sistem i fajlovi gde se mi krećemo?..........................................................7
Preimenovanje, kopiranje i neke osnovne operacije...................................................................8
Na koje sve načine možemo da pokrenemo program na linuxu?................................................9
Vlasništva, prava, privilegije …................................................................................................10
Kakve privilegije možemo da imamo nad fajlovima?..............................................................10
Postoji 7 tipova fajlova u UNIX-u............................................................................................12
Kako se menjaju i postavljaju vlasnik fajla i korisnička grupa?...............................................13
2. Makefile.....................................................................................................................................13
Zasto pišemo Makefile?............................................................................................................13
3. Debugger....................................................................................................................................15
Neke dodatne komande:............................................................................................................16
Naredbe za rad sa datotekama i tekstom:..................................................................................17
VEŽBE 2............................................................................................................................................19
skelet.c.......................................................................................................................................19
Obrada grešaka..............................................................................................................................20
Makroi............................................................................................................................................20
mkfile.c......................................................................................................................................20
Open funkcija:................................................................................................................................21
Close funkcija................................................................................................................................22
mkdir.c.......................................................................................................................................22
filesize.c.....................................................................................................................................22
Lseek funkcija................................................................................................................................22
Primitivni sistemski tipovi.............................................................................................................23
cpfile.c.......................................................................................................................................24
catfile.c......................................................................................................................................26
rmfile.c......................................................................................................................................27
VEŽBE 3............................................................................................................................................29
userinfo.c...................................................................................................................................29
listuser.c.....................................................................................................................................30
fileinfo.c.....................................................................................................................................31
stat, fstat i lstat funkcije da bi prikupili informacije o fajlu...........................................................31
Veličina fajla..................................................................................................................................35
Vremena fajlova.............................................................................................................................35
truncate_undetected.c...............................................................................................................36
Funkcija utime...............................................................................................................................36
VEŽBE 4............................................................................................................................................37
umask_demonstration.c.............................................................................................................37
Funkcija umask..............................................................................................................................37
chmod.c.....................................................................................................................................38
sizeof_directory.c.......................................................................................................................39
Čitanje direktorijuma.....................................................................................................................39
sizeof_directory2.c....................................................................................................................41
filter_by_chtime.c......................................................................................................................42
filter_by_extension.c.................................................................................................................44
VEŽBE 5............................................................................................................................................45
VEŽBE 6............................................................................................................................................51

3
Peocesi...........................................................................................................................................51
Identifikatori procesa.....................................................................................................................52
Fork................................................................................................................................................52
hello_process.c..........................................................................................................................54
hello_process2.c........................................................................................................................54
Exit funckije...................................................................................................................................55
Wait i waitpid funkcije..................................................................................................................55
processes_and_vars.c................................................................................................................56
Jednostavna komunikacija između deteta i roditelja - PIPE..........................................................57
simple_pipe.c.............................................................................................................................58
Exec funckija.................................................................................................................................59
hello_exec.c...............................................................................................................................60
create_process.c........................................................................................................................60
VEŽBE 7............................................................................................................................................63
Koncept prekida i signala..............................................................................................................63
Slanje signala.................................................................................................................................64
signals.c.....................................................................................................................................65
Imenovani pipe-ovi........................................................................................................................66
fifo_client.c................................................................................................................................67
fifo_server.c...............................................................................................................................68
Deljena Memorija..........................................................................................................................69
shared_memory_writer.c...........................................................................................................70
shared_memory_reader.c..........................................................................................................71
Semafori.........................................................................................................................................72
log_server.c...............................................................................................................................74
log_client.c................................................................................................................................75

4
NAPOMENA: Iz kodova sa časa su izvučene samo najbitnije funkcije i
iskomentarisane. Funkcije koje su se obrađivale ranije su preskočene. Neophodno
je gledati cele kodove da biste mogli da razumete sve kako treba. Ukoliko primetite
bilo kakvu grešku javite.

5
6
VEŽBE 1

Teme:
1. Komandna linija
2. Makefile
3. Debugger

1. Komandna linija

Kada otvorimo terminal sa leve strane kursora piše:


nalog@nalog-Lenovo-G580:~$ → ime korisnika koji je ulogovan @ ime računara : putanja gde se
tranutno nalazimo i $ za kraj
~ → označava naš home direktorijum

Kako da saznamo gde se nalazimo na sistemu?

pwd → path to working directory → putanja do tekućeg direktorijuma


gde se nalazimo

ls → izlistava sadržaj direktorijuma


ls -a → osim običnih prikazuje i skrivene fajlove koji počinju sa tačkom
ls -1 → izlistava sadržaj formatirano po redovima
ls -t → izlistava sadržaj po vremenu
ls -r → izlistava sadržaj u obrnutom redosledu
ls -rt → možemo spajati opcije ovo znači izlistaj obrnuto po vremenu
ls -l → izlistava dodatne informacije o sadržaju direktorijuma
ls -R → rekurzivna varijanta ls-a, ona će proći kroz sve poddirektorijume
počevši od onog iz kog pozivamo i izlistati sve fajlove

cd → change directory → promena direktorijuma (sa leve strane


kursora menja se putanja)

Kako je organizovan sistem i fajlovi gde se mi krećemo?


Na Unix zasnovanim operativnim sistemima (Linux, Mac OS, FreeBSD su danas
najpopularniji) organizacija je hijerarhijska → kao obično stablo, na vrhu imamo root koreni
direktorijum (/), ispod tog direktorijuma postoje razni direktorijumi čija je svrha različite prirode,
npr. home kao korisnički direktorijum, direktorijum opt… Svaki direktorijum može imati samo
jedan roditeljski direktorijum, ali može imati više poddirektorijuma.
/
slika: organizacija direktorijuma

Home bin opt ...

kor1 kor2

Desktop

1.c
7
Postoje dve vrste putanja:

1) apsolutna → puna putanja do nekog fajla, ona je ista gde god da se nalazimo na
sistemu (u ovom primeru to je npr. /home/kor2/desktop/1.c)
2) relativna → u pitanju je putanja dokumenta ili direktorijuma u odnosu na
trenutni radni direktorijum (npr. nalazimo se u kor2 a želimo da
uđemo na desktop korišćenjem koncepta relativne putanje to ćemo
uraditi sa cd Desktop)

Imamo specijalne oznake:

.. → to je naddirektorijum odnosno roditeljski direktorijum


~ → home direktorijum za trenutnog korisnika
. → trenutni direktorijum u kome se nalazimo

touch name → komanda za pravljenje fajla koji je prazan


mkdir directory → komanda za pravljenje direktorijuma

Ako želimo da napravimo više direktorijuma gde se jedan nalazi unutar drugog:

mkdir -p a/b/c/d → opcija -p označava parent, kaže da treba napraviti


roditeljski direktorijum a potom direktorijume unutar njega

mkdir -v → skraćeno za verbose, štampa poruku za svaki kreirani direktorijum


mkdir -pv ime_dir → ispisuje poruku za svaki kreirani direktorijum

Preimenovanje, kopiranje i neke osnovne operacije

Ako želimo da preimenujemo neki fajl ne treba da koristimo komandu rename, već mv.

mv → move, komanda sa kojom vršimo preimenovanje fajlova


mv 1.txt 2.txt → preimenovanje fajla 1.txt u 2.txt
mv p/ s → preimenovanje p direktorijuma da se zove s direktorijum
rm → remove, komanda za brisanje
rm 1.txt → brisanje fajla 1.txt
rm /a/b/c/d/e/{2.txt, 3.c} → želimo da obrišemo samo navedene fajlove koji se nalaze u e
direktorijumu
rmdir s → komanda za brisanje praznog direktorijuma
rm -r s/ → komanda za brisanje direktorijuma koji nije prazan -r je skraćenica
za rekurzivno, rekurzivno prolazi kroz direktorijum i briše sve što
se nalazi u njemu

cp 1.txt 2.txt →kopiranje sadržaja iz prvog fajla u drugi fajl


cat 2.txt →prikaz sadržaja iz fajla u terminalu
touch a/{1.txt,1.c} →pravimo fajlove 1.txt i 1.c u direktorijumu a

Ako želimo da kopiramo fajlove iz nekog direktorijuma u neki drugi direktorijum gde već
postoje fajlovi sa istim imenima koje mi želimo da prekopiramo, fajlovi u direktorijumu će se
promeniti bez ikakvog upozorenja, što nije dobro:

8
cp 1.txt 1.c a/ → prethodno napravljeni fajlovi u direktorijumu a koji se isto zovu bi
bili obrisani bez upozorenja

Zbog toga koristimo opciju -i koja nam se postavlja pitanje da li želimo da menjamo sadržaj
nekog fajla koji se kopira iz nekog drugog direktorijuma:

cp -i 1.txt 1.c a/ → opcija -i gde nas terminal pojedinačno pita za svaki fajl da li
želimo da ga obrišemo
cp -r a/ b → rekurzivno kopira jedan direktorijum u drugi, u ovom slučaju
direktorijum a u b

Imamo neki fajl i znamo da se nalazi negde na računaru ali ne znamo tačno gde. Postoje dve
komande koje nam mogu pomoći da ih nađemo:

locate specijalan.txt → ispisuje putanju tog fajla gde god da se nalazi, funckioniše tako što
ima malu bazu podataka koja indeksira sve putanje. Pre nego što
krenemo da tražimo naš fajl moramo da inicijalizujemo tu bazu
koristeći komandu update db za to nam trebaju root privilegije.

locate --regex "" → služi za pronalaženje fajlova samo uz pomoć regularnog izraza
find / → traži tamo gde mu mi zadamo. Užasno sporo, ako otprilike znamo
gde se nalazi fajl onda je ok, inače pretražuje ceo sistem
find . -name 'specijalan*' → u tekućem direktorijumu rekurzivno pretražuje sve fajlove koji
imaju npr. ime specijalan

Zašto je find komanda korisna→ u nekoj hijerarhiji direktorijuma želimo da prebrišemo sve
fajlove (npr. hoćemo rekurzivno da prebrišemo tilda fajlove (back up fajlove)) to bi uradili na
sledeći način:

find . -name '*~' -exec rm {} \; → nad tim fajlom koji si našao primeni komandu koja je zadata
(exec), umesto imena fajla stoje vitičaste zagrade jer ne znamo
tačno ime tog fajla i na kraju moramo da stavimo \;
u ovom primeru pronalazi sve back up fajlove i briše ih

Na koje sve načine možemo da pokrenemo program na linuxu?

1. navođenjem relativne putanje (npr. ./a.out)


2. program može da se pokrene navođenjem apsolutne putanje na računaru koja je ista sa bilo koje
putanje
3. navođenjem samo imena kao kada izvšimo komandu ls koja je zapravo program napsan u c-u
prekompajliran i postavljen na specijalno mesto u sistemu (PATH putanja)

echo "neka_poruka" → ispisuje u terminal poruku koju smo napisali između navodnika
which ls → kaže gde se nalazi neka komanda, samo prolazi kroz PATH
putanju i traži da li se tu nalazi program koji nama treba

Postoji specijalna promenljiva koja se zove PATH, ona sadrži putanje do direktorijuma
razdvojene dvotačkom, da bi pristupili putanji u terminalu ispred putanje stavljamo znak $

9
echo $PATH → sadržaj putanje PATH (ispod je prikazana jedna PATH putanja)

(/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/
home/nalog/llvm/build/Release+Asserts/bin:/home/nalog/projects/git-commands)
Šta se dešava kada pozovemo ls?

LS se nalazi u bin direktorijumu. Terminal se pita da li je ovo ls relativna putanja, pošto ne počinje
ni tačkom ni ~, znači nije. Da li je ovo ls apsolutna putanja? Ne počinje kosom crtom znači nije.
Dobro onda ću da probam da nađem ls na podrazumevanoj putanji. Podrazumevana putanja je
sastavljena od svih direktorijuma koji su dodati u PATH putanju i ide redom i traži da li se nalazi u
nekom od tih direktorijuma, ukoliko je pronađe izvršava tu komandu odnosno program.

► komanda which prolazi kroz ove direktorijume i traži da li se tu nalazi ls

Vlasništva, prava, privilegije …

Računari su pravljeni da može da ih koristi više korisnika. Osnovna stvar je uvođenje


korisničkih naloga. Korisnički nalog na UNiX sistemima je home direktorijum gde korisnik smešta
svoje fajlove. Da bi to funkcionisalo kako treba, neki korisnik ne može da pristupa fajlovima
drugog korisnika.

Uvođenje korisnickih naloga → za jedan korisnički nalog postoji home direktorijum gde se
smeštaju obični lični podaci korisnika
ACL → access control list za svakog korsinka možemo da kažemo šta
sme a šta ne sme da uradi

Jedan fajl ima jednog korisnika koji je vlasnik i ima grupu kojoj pripada (npr. Pera može
samo da čita neki fajl, Mika može da ga čita i da ga modifikuje, a Ana ne može ništa). Ovaj sistem
je dobar ali može biti zamoran.
Unix sistemi uvode pojam grupe. Jedan fajl može imati samo jednog vlasnika, npr. Miloš i
Petar treba da vide neki fajl koji je Miloš napravio. To se rešava upotrebom grupa, jedan fajl ima
jednog vlasnika i grupu kojoj pripada. Miloš napravi grupu koju čine Petar i Miloš i ta grupa ima
neka određena prava, ostali korisnici ne mogu da pristupe toj grupi i ne mogu da vide ili menjaju
fajlove, jedino njih dvojica mogu.
Grupa ima određena prava, dok ostali korisnici ne mogu da pristupe tom fajlu jer nisu
članovi te grupe. Korisničke grupe definišu prava pristupa za nešto. Grupe služe da regulišu resurse.

sudo grupa → grupa gde su administratori (potrebne root privilegije)

Tri bitne komponente su:

1. vlasnik fajla
2. korisnička grupa
3. ostali

Dakle korisničke grupe služe da regulišu prava pristupa za neki resurs (npr. ko sme da
koristi štampač ukoliko ga više računara deli). Ne moraju uvek da imaju logično ime, osnovna ideja
je da se zaštiti neki resurs.

10
Kakve privilegije možemo da imamo nad fajlovima?

Kada koristimo komandu ls -l za svaki fajl na početku ispisa stoje ispisana prava u vidu
karaktera. Prva tri slova su prava za vlasnika fajla, druga tri slova su za vlasničku grupu a treća tri
su za ostale.

Na primer:

-rwxrwxrwx
-123456789

1 → vlasnik može da čita fajl


2 → vlasnik može da piše u fajl
3 → vlasnik može da izvršava taj fajl

4 → grupa može da čita fajl


5 → grupa može da piše u fajl
6 → grupa ima pravo izvršavanja tog fajla

7 → ostali mogu da čitaju fajl


8 → ostali mogu da pišu u fajl
9 → ostali mogu da izvršavaju taj fajl

Menjanje prava:

chmod → komanda za menjanje privilegija, nakon koje ide slovo kome se menjaju
privilegije
chmod -u → menjanje prava za vlasnika
chmod -g → menjanje prava za grupu
chmod -o → menjanje prava za ostale

Nakon toga pisemo + ili – u zavisnosti da li dodajemo ili oduzimamo privilegije.

npr: chmod +w 1.c → za fajl 1.c dajemo svima privilegije da mogu da ga čitaju

Pošto ovo moze da bude zamorno uvodimo numerički sitem privilegija:

Pozicija nam određuje za šta je to pravo. Ako bismo formirali binarno, 1 je ako je
postavljena privilegija 0 ako nije postavljena. Na početku se piše nula, biće objašnjeno kasnije.

Primer: -rw-rw-r-- → kada izvršimo ls -l na početku nam stoje privilegije


-110110100 → binarni sistem za privilegije
0664 → oktalni sistem za privilegije sa kojim ćemo raditi

0777 → postavljena su sva prava

binarni oktalni
sistem: sistem:
0100 → 4 → oznaka za čitanje
0010 → 2 → oznaka za pisanje
0001 → 1 → oznaka za izvršavanje

11
-rw-rw-r--
-rw- rw- r--
-110 110 100
06 6 4

Prva crtica kod privilegija → označava kog tipa je fajl (- ako je regularan, d ako je
direktorijum…)
Postoji 7 tipova fajlova u UNIX-u:

1. Obični (regularni) fajlovi, najčešći tip fajlova, sadrže podatke u nekoj formi. Kernel ne
razlikuje da li su ovi podaci tekstualni ili binarni (izuzev kada se radi o izvršnim fajlovima).

2. Direktorijumi – mogu se smatrati nekom vrstom tabela. Direktorijum sadrži imena drugih
fajlova, kao i pokazivače na informacije o ovim fajlovima (pokazivač na i-nod).
U Unix-u postoje dve vrste uređaja, jedni sa kojima se mora raditi sekvencijalno, i drugi sa kojima
se može kretati i unapred i unazad. Svi uređaji se predstavljaju jednim od dva naredna tipa fajlova:

3. Blok specijalni fajl – tip fajla koji obezbeđuje baferisani U/I u blokovima fiksne dužine prema
uređajima kao što su hard disk, cd-rom, memorija. Naš hard disk je fajl kada izlistamo sadržaj ls
/dev/sda imamo npr. 6 particija konkretnog diska ako napišemo ls -l /dev/sda na početku će postojati
slovo b to je oznaka za blokovski fajl (i terminal je fajl, njega možemo naći u dev-u).

4. Karakterski specijalni fajl – obezbeđuje nebaferisani U/I prema uređajima. - radi se


sekvencijalno sa njima. Prenos jedan po jedan karakter (miš, tastatura, terminal, modemi). Kada
napisemo ls-l /dev/tt0 biće ispisano c. Poslednja 3 tipa fajlova su:

5. FIFO - Koristi se za komunikaciju među procesima. Nekada se naziva pipe (cev).


fifo – pipe ako se vratimo na veb server, jedan od načina na koji je implementiran je da se pokrene
jedan program koji pokrene više drugih programa. Dođe http zahtev i ovaj program samo preusmeri
na neki od programa koji već rade, obradi zahtev i vraća klijentu. Postavlja se pitanje kako izvršiti
komunikaciju dva fajla. Postoji jedan bafer u memoriji kome mogu da pristupaju oba programa
program 1 i program 2 . Program 1 piše u bafer, a program 2 čita iz tog bafera (first in first out).
Komunikacija nije dvosmerna, postoji ali kao posebna vrsta, mi je nećemo raditi.

6. Soketi - Koristi se za mrežnu komunikaciju među procesima, a može se koristiti i za


komunikaciju na istom hostu. Oznaka je s. Kad pošaljemo nešto preko mreže imamo TCP/IP
protokol. Postoji nešto što se zove soket. i on se ponaša kao fajl. Ako šaljemo http zahtev serveru
ispišemo to u soket, pošaljemo server pročita isti takav soket i on nam odgovori i pošalje
informacije isto u okviru soketa.

7. Simbolički linkovi - Tip fajla koji pokazuje na druge fajlove. To je pokazivač na već postojeći
fajl. Ako imamo 1.c i pravimo simbolički link od 1.c u 2.c kada izlistamo 2.c dobićemo isti sadržaj
kao i u 1.c. Oni pokazuju na isti sadržaj, ako obrišemo simbolički link 1.c će postojati sasvim
normalno, ali ako obrišemo originalni fajl, simbolički link ne pokazuje ni na šta i neće raditi.

r → regular file
b → block device
c → character device
d → directory
s → socekt file
l → symbolic link
f → fifo, pipe

12
(Mrežni transport → soket on se ponaša kao fajl, ispišemo neki zahtev, ode do servera server otvori
pročita to što je stiglo obradi zahtev i vrati nam odgovarajuću stranicu.
Fifo →ili pipe imamo server, pokrene se jedan program dođe http zahtev on samo
preusmei na neki od programa koji aktivno rade, taj program obradi zahtev i
vrati klijetnu odgovor. To je bafer u memoriji kome pristupaju dva fajla →
učita se nešto i prosledi drugom fajlu, uglavnom jednosmerna komunikacija)
Kako se menjaju i postavljaju vlasnik fajla i korisnička grupa?
Niko od administratora ne može da menja vlasnika fajla, ili korisničku grupu.
Pitanje je da li mi kao vlasnik fajla možemo da promenimo da vlasnik fajla bude neko drugi. Ne
možemo jer može doći do zloupotrebe.

chown korsinik:ime_grupe → menjamo vlasnika fajla, odnosno korisničku grupu koja je


vlasnik fajla
chgrp ime_grupe 1.c → menja vlasničku grupu nekog fajla

2. Makefile

Zasto pišemo Makefile?


Source kod androinda ima 40GB. Kompajliranje od nule na jako dobroj mašini traje 40 min.
i ako želimo nešto da promenimo i testiramo trajalo bi toliko dugo. Zbog toga koristimo Makefile.
Makefile služi da ubrza razvoj softvera, detektuje razlike, smanji vreme kompilacije. Danas
je u razvoju softvera najskuplji programer, tako da se trudimo da optimizujemo programersko
vreme.

Kompilacija se može podeliti u 4 faze:

1. pretprocesiranje
2. kompajliranje
3. asembliranje
4. linkovanje

Za ono što mi posmatramo grupisaćemo prve tri faze kao jednu. One proizvode .o fajl
(imamo objektni fajl) i imamo drugu fazu linkovanje. Kad promenimo jedan fajl promeni se
objektni fajl koji odgovara .c fajlu koji smo menjali i uradi se linkovanje. Nije potrebno
kompajlirati sve ostale objektne fajlove, već samo te koji su izmenjeni i možemo mnogo brže da
testiramo program. Umesto da čekamo 40min čekamo 40s da se to sve lepo izlinkuje.

Osnovni Makefile koji smo ranije radili:

CC = gcc
CFLAGS = -Wall -Wextra

1: 1.o
$(CC) -o 1 1.o $(CFLAGS)
1.o: 1.c
$(CC) -c 1.c

13
Možemo da olakšamo sebi, tako što ćemo da napišemo nešto što menja levu i desnu stranu
pravila. Možemo da uradimo sledeće:

$@ → označava levu stranu pravila


$^ → označava desnu stranu pravila

CC = gcc
CFLAGS = -Wall -Wextra

1: 1.o
$(CC) -o $@ $^ $(CFLAGS)
1.o: 1.c
$(CC) -c $^

Ukoliko imamo više programa koje treba da kompajliramo možemo da koristimo % koji
menja ime tog programa koji kompajliramo, na taj način nećemo gubiti vreme da na svakom mestu
mejamo npr 1, 2 … već će to kompajler sam da uradi za nas. Kada pozivamo Makefile kucamo
komandu make -name (make 1, na svim mestima gde se nalazi % će zameniti 1, tako da mi ne
moramo to ručno da radimo)

CC = gcc
CFLAGS = -Wall -Wextra

%: %.o
$(CC) -o $@ $^ $(CFLAGS)
%.o: %.c
$(CC) -c $^

make 1 → Ako u folderu imamo datoteke 1.c 2.c 3.c da bi kompajlirali samo prvi kucamo make 1,
isto vazi za 2.c i 3.c...

Deo u Makefile-u koji služi za brisanje onoga što smo naveli da želimo da obrišemo, kao
npr. objektne fajlove, back up fajlove…

.PHONY: clean

clean:
rm -f *.o

Pisanje shell skripte:

Shell skripte imaju ekstenziju .sh (npr 1.sh). Shell skripte su datoteke koje mogu obavljati
različite zadatke i omogućavaju nam da automatski obavljamo niz radnji. Za pisanje shell skripte
može se koristiti proizvoljan tekst editor. Bitno je da imamo pravo izvršavanja na datoteci u kojoj je
shell skripta. To se može postići jednom od sledeće dve naredbe:
chmod +x ime_skripte
chmod 755 ime_skripte

14
U shell skripti na početku uvek moramo da napišemo:
#!/bin/bash → putanja do interpretera
Npr. Imamo skriptu 1.sh u njoj je napisano:

#!/bin/bash
echo 'Zdravo'

chmod +x 1.sh → Da bi skriptu mogli da pokrenemo moramo da postavimo executable


privilegije na skriptu

Skripta se pokrece sa ./1.sh i u terminalu će biti ispisano Zdravo

file komanda → ispisuje tip fajla za svaki fajl

Tri kontrolne strukture koje ćemo obraditi: kolekcijaska for petlja, obična for petlja i if else.

Za sve fajlove u tekućem direktorijumu treba ispisati koji su to fajlovi.


Ovde ćemo koristiti kolekcijusku for petlju. Kada stavimo $(nešto) to nešto se tumači kao komanda
koju bash treba da izvrši. U ovom primeru za f u listi fajlova pokreni komadu file za svaki fajl u listi
fajlova.

for f in $(ls)
do
file $f
done

If else kontrolna struktura:

Argumetni komande linije u bash-u kreću od jedinice. Prvom argumetu pristuamo sa $1,
drugom sa $2… $# označava broj argumenata. Možemo da vršimo poređenje sa:

-g → greater
-e → equal
-ne → not equal…

Ako broj argumenata nije 1 biće ispisano “Usage ./prva.sh num”

if [[ $# -ne 1 ]]
then
echo "Usage: ./prva.sh num"
exit 1
fi

Obična for petlja:

for ((i=1; i<=$1; i++))


do
touch $i.txt
done

15
3. Debugger

Imamo program segfault koji ima u sebi grešku koja dovodi do segmentation fault-a.
Prevodimo program da dobijemo izvršni fajl. Potom sa gdb komandom pokrećemo debugger. Kada
se debugger pokrene na početku sa leve strane kursora stoji (gdb). U njemu kucamo komande. Da bi
pokrenuli naš program izvršavamo komandu run i prosleđujemo argumente komandne linije. U
ovom slučaju pokrećemo sa run a.

gcc -g -o segfault segfault.c


gdb ./segfault
(gdb) run a

Izlaz nakon komande run u terminalu će izgledati npr. ovako:

Program received signal SIGSEGV, Segmentation fault.


0x000000000040058f in main (argc=2, argv=0x7fffffffdc98) at segfault.c:18
18 str[-1] = replacement;

Terminal izbacuje grešku i govori u kojoj liniji se nalazi greška, ne govori zbog čega je
došlo do greške, to moramo sami da otkrijemo.

Drugi program je worng_value koji isto komapjliramo i pokrećemo debugger, program


treba da dobije dva broja iz komandne linije i da ih uporedi. Ako mu prosledimo npr -13 i 15 vratiće
da je -13 veći broj. Kako da otkrijemo uzrok tome. Možemo da korstimo break point. Break point
je tačka gde program treba da se zaustavi i da ne izvršava tu liniju do koje je došao.

gdb ./wrong_value
(gdb) b wrong_value.c: 14

Da bi postavili break point u debbugger-u pozivamo opciju b kao oznaku za break point,
potom ime programa u kome postavljamo break point, zatim dve tačke i broj koji označava gde
program treba da se zaustavi.
Da bi videli trenutne vrednosti promenljivih koristimo opciju p. što je oznaka za print.
Štampa se vrednost promenljive.

bt → back trace
px → p je komanda ze print kaže da treba da oštampa vrednost promenljive

Terminal će nam ispisati trenutne vrednosti za x i y.


Videćemo da je x neki ogroman broj i da smo napisali unsinged umesto int, iz tog razloga je vraćao
negativan broj kao veći.

n → kada želimo da izvršavamo program naredbu po naredbu počevši od break pointa


c → ako želimo da izvršavamo normalno od break pointa

Nekad želimo da saznamo šta se dešava u nekoj funkciji, da bi ušli u funkciju koja se zove
npr. f umesto da je preskočimo koristimo komandu s, što znači da treba da zakorači u tu funkciju.

16
Neke dodatne komande:

Dobijanje informacija o korisnicima:

users → spisak korisnika ulogovanih na sistem


w → izveštaj u vidu spiska o trenutnom statusu sistema i spisak korisnika ulogovanih
na sistem zajedno sa procesima koji im pripadaju
w user → kao i prethodno, ali se prikazuju samo podaci za zadatog korisnika
who → spisak korisnika ulogovanih na sistem
who am I → ispis ličnih podataka za tekućeg korisnika
finger → traži zadate podatke o korisniku i prikazuje informacije iz inicijalne konfiguracije
korisnikovog naloga, kao i ostale sadržaje iz korisnikovih .plan i .project fajlova
(ukoliko postoje). Prikazuje se i da li je korisnik prijavljen i koliko dugo traje
sesija korisnika.
finger user → kao i prethodno, ali se prikazuju samo podaci za zadatog korisnika
last → lista ko je kad radio na serveru počev od nekog datuma.
last user → isto to samo sa određenu osobu.
groups user → prikazuje grupe kojima pripada korisnik

Naredbe za rad sa datotekama i tekstom:


stat fajl → prikazuje informacije o navedenoj datoteci
emacs r.c& → pokretanje editora emacs; & označava da se proces pokreće u pozadini, tj.
shell se može koristiti za druge namene dok se proces izvršava
less → pregled teksta stranu po stranu. Akcije u komandi less:
/rec osvetljava sve pojave niske rec. Ako se rec ne navede, to znači find next.
q → Izlaz.
cat → povezuje fajlove i koristi se za ispis sadržaja fajlova
cat -b lista_fajlova → numerisanje svih ne-blanko linija izlaza naredbe cat i to počev od 1
cat -n lista_fajlova → numerisanje svih linija izlaza naredbe cat i to počev od 1
cat -s lista_fajlova →zamena svih višestrukih blanko linija jednom linijom
cat > fajl → upis karaktera koji se unose sa tastature u fajl do pritiska Ctrl+C, ali
se pritom prethodni sadržaj fajla gubi
cat >> → fajl dopisivanje karaktera koji se unose sa tastature na kraj fajla

wc lista_fajlova → brojač redova, reči i karaktera u fajlovima ili standardnom ulazu (ako
nije navedena lista)
wc -L → ispisuje dužinu najduže linije
wc -l → broji prelaske u novi red
wc -w → broji reči
wc -c → broji samo karaktere
head fajl → ispisuje početak fajla (prvih 10 redova)
head -num fajl → ispisuje prvih num redova fajla
tail fajl → ispisuje kraj fajla (poslednjih 10 redova)
tail -num fajl → ispisuje poslednjih num redova
tail -f fajl → ispisuje kraj fajla, kao i redove koji se naknadno (npr. iz drugog
procesa) dopisuju u fajl
sort lista_fajlova → prikazuje sortirane redove jednog ili više fajlova; ako nema zadatih
fajlova, onda se sortiraju podaci sa standardnog ulaza i prikazuju se na
standardni izlaz
sort f lista_fajlova → pri sortiranju se ne razlikuju velika i mala slova (ignore case)

17
18
VEŽBE 2

Da bi programer mogao da pristupa kernelu obezbeđeni su sistemski pozivi. Npr. funkcije


koje koristimo u C-u su implementirane tako da koriste sistemske pozive. U Unix-u za svaki
sistemski poziv postoji funkcija istog imena u C standardnoj biblioteci. Proces poziva ovu funkciju,
koja poziva odgovarajući servis kernela. Pri pisanju programa možemo sistemske pozive koristiti
kao i ostale C funkcije. Funkcije standardne biblioteke možemo zameniti sistemskim pozivima, dok
je obratno obično nemoguće uraditi. Sistemski poziv koji se koristi za alokaciju memorije u UNIX-
u je sbrk. Funkcija malloc koristi ovaj sistemski poziv.

U Unix-u postoji više sekcija za pomoćne (man) stranice. Sekcije su:


1) Opšte naredbe (komande)
2) Sistemski pozivi
3) Funkcije C standardne biblioteke
4) Specijali fajlovi i drajveri
5) Formati datoteka
6) Igre i skrinsejveri
7) Razno
8) Komande za administriranje sistema

skelet.c
Napisaćemo skelet programa koji ćemo kasnije koristiti u svim zadacima. Ono što je
neophodno jeste obrada grešaka. Za sve funkcije koje nam trebaju njihov opis možemo da nađemo
u man stranicama. Postoji 7 man strana, kao što je prethodno navedeno. Mi uglavnom koristimo
man 2 i man 3. Na početku su nam potrebna zaglavlja. Osnovni mehanizmi za obradu grešaka, u
proceduralnoj paradigmi imamo error kode. U c-u to je errno greška. Kada dođe do greške funkcija
postavi grešku u error kode. Sve greške pišemo na stderr, zbog automatskog ocenjivača.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <errno.h>
#define osErrorFatal(userMsg) osErrorFatalImpl((userMsg), __FILE__, __func__, __LINE__)
#define osAssert(expr, userMsg) \
do { \
if(!(expr)) \
osErrorFatal(userMsg); \
} while(0)

static const char * osUsage = "./programName [argsList]";


void osErrorFatalImpl(const char *userMsg, const char *fileName, const char *functionName, const int lineNum){
perror(userMsg);
fprintf(stderr, "File: '%s'\nFunction name: '%s'\nLine: '%d'\n",fileName,functionName,lineNum);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv){

return 0;
}

19
Obrada grešaka

Kada dođe do greške, najčešće funkcija vraća negativnu vrednost a promenljiva errno
definisana u errno.h se postavlja na vrednost koja daje dodatne informacije. (U knjizi APUE na više
mesta se nalazi greška, kada se navodi da funkcija vraća vrednost 1 umesto da se navede da vraća -1
u slučaju greške). Svaka od tih vrednosti je definisana kao konstanta koja počinje slovom E. Npr.
errno može uzeti vrednost EACCES (to je najčešće vrednost 13) što je znak za problem pristupanja
fajlu (najčešće zbog nedovoljnih prava pristupa). Sa man 3 errno može se dobiti spisak svih
ovakvih konstanti.
Bitno: Vrednost promenljive errno se ne resetuje kada se neka funkcija uspešno izvrši. Zato njenu
vrednost treba proveravati samo u slučaju kada se zna da je došlo do greške. Dve funkcije C
standardne biblioteke se koriste za štampanje poruka o greškama. Prva se zove strerror i u
zavisnosti od vrednosti konstante koju prima vraća opis greške u obliku niske koja se odnosi na tu
konstantu. Druga se zove perror i na osnovu vrednosti promenljive errno štampa poruku koju prima
kao argument zajedno sa opisom greške na standardni izlaz za greške.

Makroi
Kada god imamo komplikovane makroe uokvirujemo ih sa do while petljom, da bi nam se
izvršile sve naredbe. Makroi su samo simboličke zamene. Kada uokvirimo sa do while petljom, ne
interesuje nas stil pisanja korisnika, jer šta god da napiše biće zamenjeno odgovarajućim blokom
koji nešto radi.

if (condition) if (condition)

→ do {
statement_1; statement_1;
statement_2; statement_2;
} while (0);

if naredba će izvršiti samo prvu narebu. Zbog toga ovo treba da izgleda kao što je prikazano
na desnom primeru da bi sve naredbe bile izvršene. Isto važi i za makroe.

#define foo \ #define foo \

statement_1; \ → do { \
statement_2 statement_1; \
statement_2; \

} while (0)

__FILE__, __fun__ i __LINE__ su makroi definisani u okviru kompajlera, oni se obrađuju kada
program pukne i daju nam informacije o tome u kom fajlu je došlo do greške, u kojoj funkciji i u
kojoj liniji.

Kada god obrađujemo greške moramo da gledamo da li je korisnik uneo dovoljan broj
argumenata. Imaćemo jedan string gde ćemo ispisivati kako program treba da se poziva.

static const char * osUsage = "./programName [argsList]";


mkfile.c
Program treba da napravi fajl. Korisnik pošalje putanju kao argument komandne linije i
program treba na toj putanji da napravi fajl.

20
Open funkcija:
Koristićemo funkciju open. Koju možemo da vidimo u man stranama sa man 2 open.

int open(const char *pathname, int flags);


int open(const char *pathname, int flags, mode_t mode);

open → prima putanju do fajla, neke flegove i mod. Flegove koje može da primi su fleg za čitanje,
fleg za pisanje… Mod se odnosi na privilegije. Mi joj zadajemo oktalni zapis za privilegije
u ovom slučaju je to 0644. Open nam vraća fajl deskriptor to jest jedan broj. Postoje dve verzije
funkcije, jedna sa 2 a druga sa 3 argumenta. Dakle prvi argument je putanja do datoteke koju
otvaramo (bilo apsolutna ili relativna). Drugi argument mora da sadrži neku od opcija:

O_RDONLY → otvoriti samo za čitanje


O_WRONLY → otvoriti samo za pisanje
O_RDWR → otvoriti i za čitanje i za pisanje

Fajl deksriptor je broj koji je pridružen našem fajlu.


Pišemo funkciju koja otvora fajl (osFileOpen). Kada otvorimo neki novi fajl vraća nam se
broj (fajl deskriptor) inače u slučaju greške vraća nam -1. Da bi vratili fajl deskriptor koristićemo
pokazivač. Na kraju je potrebno da se fajl zatvori funkcijom close.
int main(int argc, char **argv){

osAssert(2 == argc, "Argument missing");


/* Fajl deskriptor */
int fd;
/* Pozivamo funkciju za otvaranje fajla kojoj prosleđujemo putanju, mod za čitanje
i fajl deskriptor koji smo prethodno definisali */
osAssert(osFileOpen(argv[1], "w", &fd),"File create");
close(fd);
return 0;
}
bool osFileOpen(const char *filePath, const char *mode, int *fd){
/* Mode u kome otvaramo fajl u ovom slučaju je -rw-r--r-- i imamo flegove koji su postavljeni na 0 */
static mode_t defaultMode = 0644;
int flags = 0;
/* U zavisnosti koji je mod u pitanju (čitanje, pisanje ili nadovezivanje postavljamo flagove na odg. vrednost) */
switch(mode[0]){
case 'r':
flags |= '+' == mode[1] ? O_RDWR : O_RDONLY;
break;
case 'w':
flags |= '+' == mode[1] ? O_RDWR : O_WRONLY;
flags |= O_TRUNC;
flags |= O_CREAT;
break;
case 'a':
flags |= '+' == mode[1] ? O_RDWR : O_WRONLY;
flags |= O_APPEND;
flags |= O_CREAT;
break;
default:
return false;
}
/* Open funkcija nam vraća odgovarajući fajl deskriptor koji na kraju vraćamo kao rezultat funkcije ukoliko
je on veći od nule, inače je došlo do greške */
*fd = open(filePath, flags, defaultMode);
return *fd >= 0;
}

21
Neke od dodatnih opcija funckije open su:

O_APPEND → svakim pozivom write upisivati na kraj fajla


O_CREAT → kreirati fajl ako ne postoji, zahteva se i treći argument mod koji definiše
prava pristupa novog fajla
O_EXCL → generiše grešku ako je opcija O_CREAT navedena i fajl već postoji
O_TRUNC →ako fajl postoji i uspešno je otvoren za pisanje, smanjiti njegovu dužinu na 0

Close funkcija
Funkcija kao jedini argument prima fajl deskriptor. Ovom funkcijom se zatvara taj fajl deskriptor,
tako da se više ne odnosi na fajl. Kada se proces završi svi fajl deskriptori se automatski zatvaraju.

mkdir.c
Program treba da napravi neki direktorijum. Korisnik prosleđuje putanju programu.

Treba da definišemo default mode. Mi postavljamo drwxr-xr-x, odnosno 0755 je mode za


direktorijum.

w → menja sadržaj direktorijuma


r → listanje sadržaja direktorijuma
x → pravo da otvori direktorijum

Za pravljenje direktorijuma imamo funkciju mkdir → man 2 mkdir (za više informacija)
U slučaju uspeha vraća nula inače -1 ako je došlo do greške. Mkdir neće da nam da da napravimo
direktorijum koji već postoji, ali može da pravi poddirektorijume.
int main(int argc,char **argv){
/* Proveravamo broj argumenata i pozivamo funkciju osCreateDirectory kojoj prosleđujemo putanju
na kojoj želimo da napravimo novi direktorijum */
osAssert(2 <= argc, "Argument missing");
osAssert(osCreateDirectory(argv[1]),"Create directory failed");
return 0;
}
bool osCreateDirectory(const char *dirPath){
/* Postavljamo mode za direktorijum i potom pravimo direktorijum na putanji koja nam je data i sa modom
koji smo postavili */
static mode_t mode = 0755;
return mkdir(dirPath , mode) >=0;
}

filesize.c
U zadatku treba da odredimo veličinu fajla. Prvo je potrebno da otvrimo fajl, potrebna nam
je putanja do fajla i mode u kome fajl otvaramo, kao i promenljiva u koju ćemo da smestimo fajl
deskriptor.

Lseek funkcija
Svaki otvoreni fajl u procesu ima pridružen trenutni offset fajla (current file offset),
uobičajeno nenegativan broj koji sadrži broj bajtova od početka fajla. Ovaj broj se postavlja na 0
kada se fajl otvara, izuzev kada je uključena opcija O_APPEND (u tom slučaju postavlja se na

22
veličinu fajla). Read i write pri čitanju povećavaju ovu vrednost. TOF se može postaviti funkcijom
lseek a deklaracija ove funkcije je:

off_t lseek(int filedes, off_t offset, int whence);

Prvi argument je fajl deskriptor. Drugi je offset, a treći argument može biti jedna od 3 vrednosti:

SEEK_SET → offset fajla se postavlja na offset bajtova od početka fajla


SEEK_CUR → offset fajla se postavlja na zbir TOF i offset. Offset može biti pozitivan ili negativan
SEEK_END → offset fajla se postavlja na zbir veličine fajla i offset. Offset može biti pozitivan ili
negativan

offset u lseek-u je broj karaktera za koji se pomeramo levo ili desno u odnosu na to kako smo
odabrali neki od prethodno navedenih flegova za računanje.

Objašnjenje zadatka:
Prvo nam trebaju prava pristupa u kome otvaramo fajl a to je 0644. Nakon toga potrebni su
nam flegovi. Mode može da bude za pisanje, čitanje, nadovezivanje… Ako je u pitanju mod za
pisanje treba da proverimo da li se radi o kombinovanom modu “w+”. Onda je naš fleg postavljen
na O_RDWR. Kada prisutpamo mode[1] program neće pući ukoliko nemamo +, jer na kraju
imamo terminirajuću nulu. Isto tako proveravamo da li imamo kombinovani mod za čitanje “r+”.
Na kraju nam ostaje da otvorimo fajl pomoću funckije open.
U main-u gledamo da li je broj argumenata odg. Treba nam fajl dekriptor. Treba da
proverimo da li je fajl otvoren. Treba da pozovemo funkciju osFileOpen kojoj prosleđujemo
putanju do fajla, mod za čitanje (da fajl ne bi bio obrisan) i fajl deskriptor u koji želimo da nam
bude upisana vrednost.
Nakon toga treba da odredimo veličinu fajla, to možemo da uradimo sa sistemskim pozivom
lseek, on radi sa fajl deskriptorima. Lseek-u treba proslediti fajl deskriptor, potom gde želimo da
postavimo offset i u odnosu na šta želimo da računamo taj offset.
U ovom slučaju offset postavljamo na 0 i biramo fleg SEEK_END. Tako da će se offset naći na
samom kraju fajla. Pa možemo da izračunamo veličinu fajla. Ukoliko se funkcija uspešno izvršila
vraća veličinu fajla u suprotnom nam vraća -1. Treba da ispišemo tu veličinu na stdout. Offset može
da bude mnogo veliki broj, da bi mogli da ga ispišemo kastujemo ga sa (intmax_t).

Primitivni sistemski tipovi

Postoji mnogo tipova podataka na Unixu koji predstavljaju suženje celobrojnih tipova na
određeni skup vrednosti. To su npr. tipovi off_t, clock_t, mode_t, size_t, ssize_t, itd. Veći spisak
ovih tipova može se pronaći u knjizi APUE, sekcija 2.8. Promenljive ovih tipova se mogu porediti
sa celobrojnim konstantama bez problema, ali pri štampanju vrednosti ovih promenljivih kompajler
može javiti upozorenja. Da bi se izbegla upozorenja pri štampanju treba uključiti zaglavlje stdint.h,
koristiti specifikator %jd u printf funkcijama i eksplictno konvertovati podatak u intmax_t (za
celobrojne tipove) ili uintmax_t (za celobrojne nenegativne tipove) pri štampanju. Primeri:

printf (“%jd“, (intmax_t)promenljiva);


printf (“%ju“, (uintmax_t)promenljiva);

23
int main(int argc, char **argv){

/* Proveravamo broj argumenata na početku, inicijalizujemo fajl deskriptor na nula i pozivamo funckciju
koja nam otvara fajl na datoj putanji u modu za čitanje i koja nam upisuje vrednosti u fajl deskr. */
osAssert( 2==argc, "Argument missing");
int fd = 0;
osAssert(osFileOpen(argv[1],"r",&fd),"File open failed");

/* Veličinu fajla ćemo da odredimo funkcijom lseek tako što ćemo offset fajla da postavimo na kraj fajla */
off_t fileSize = lseek(fd, 0, SEEK_END);
osAssert(fileSize >= 0, "lseek failed");
close(fd); /* Zatvaramo taj fajl koji smo prethodno otvorili */

printf("%jd\n", (intmax_t)fileSize); /* Ispisujemo vrednost veličine fajla na standardni izlaz*/


return 0;
}
bool osFileOpen(const char *filePath, const char *mode, int *fd){

/* Postavljamo mode na -rw-r--r-- i flegove inicijalizujemo na nulu */


static mode_t defaultMode = 0644;
int flags = 0;

/* Construct flags. mode[1] ne dobijamo segmentation fault kada nemamo nula zbog ‘\0’ */
switch(mode[0]){
case 'r':
flags |= '+' == mode[1] ? O_RDWR : O_RDONLY;
break;
case 'w':
flags |= '+' == mode[1] ? O_RDWR : O_WRONLY;
flags |= O_TRUNC;
flags |= O_CREAT;
break;
case 'a':
flags |= '+' == mode[1] ? O_RDWR : O_WRONLY;
flags |= O_APPEND;
flags |= O_CREAT;
break;
default:
return false;
}
*fd = open(filePath, flags, defaultMode);
return *fd >= 0;
}
cpfile.c
Treba da prekopiramo sadržaj jednog fajla u drugi fajl. Za to ćemo koristiti sistemske pozive
read i write.

READ & WRITE

Sistemski pozivi read i write se koriste za čitanje i pisanje na Unix-u. Svaka funkcija
standardne biblioteke koja čita (piše) poziva read (write). Funkcije napisane u C-u su komfornije za
rad i one pokušavaju da učitaju što veću količinu podataka i smeste u svoj interni bafer. Mana
standardne biblioteke je nedostatak efikasnosti. Kod sistemskog poziva vrši se prebacivanje u
kernel režim rada, kod funkcija standardne biblioteke, deo vremena se provodi u korisničkom
režimu rada. Funkcija read odjedanput učita 4kb podataka, dok getc pri prvom pozivu smešta
podatke u bafer i onda 4096 puta pristupa tom baferu. Kod čitanja sa diska to ne mora biti bitno, jer
najviše vremena protekne na pozicioniranje glave na disku, pa je vreme koje se potroši za funkciju
getc zanemarljivo. U nekim drugim slučajevima može postojati veća razlika u efikasnosti.

24
ssize_t read (int fd, void *buf, size_t count);

ssize_t i size_t su primitivni sistemski tipovi. Prvi može biti i pozitivan i negativan ceo broj
a drugi može biti samo pozitivan ceo broj. Možemo ih koristi kao i druge cele brojeve i umesto njih
koristiti tip int.
Funkcija read čita sa fajl deskriptora fd maksimalno count bajtova i smešta ih u prostor na
koji pokazuje pokazivač buf. Ukoliko se pokušava sa čitanjem 100 bajtova a 30 je ostalo u fajlu – u
prvom pozivu read vraća se 30, u drugom 0. Read čita od trenutno postavljenog offseta fajla, a pre
uspešnog povratka offset se povećava za broj pročitanih bajtova.

ssize_t write (int fd, const void *buf, size_t count);

Funkcija write pokušava da u fajl povezan sa fajl deskriptorom fd upiše count bajtova
bafera buf. Ukoliko se povratna vrednost ne poklapa sa vrednošću trećeg argumenta, došlo je do
greške. Najčešći uzroci greške su popunjavanje prostora na disku ili prekoračenje dozvoljene
veličine fajla u datom procesu. Upisivanje kreće od trenutnog offseta fajla izuzev u slučaju kada je
uključena opcija O_APPEND pri otvaranju fajla. U tom slučaju se fajl offset postavlja na kraj fajla
pre svakog pisanja. Posle svakog uspešnog write poziva, offset fajla se povećava za br. upisanih
bajtova.

Objašnjenje zadatka:
funkcija osCopyFileFd:
Kao argumente prima dva fajl deskriptora, jedan iz kog kopiramo sadržaj fajla i drugi u koji
upisujemo taj sadržaj. Treba da definišemo neki bafer koji ćemo da koristimo. Ideja je da napravimo
bafer koji je jednak veličini bloka na hard disku zbog brzine.
static const uint32_t memBufSize = 1U << 13; //8kb
Bafer pravimo tako što dinamički alociramo memoriju za karaktere. Naravno obrada greške
je neophodna. Treba nam neka promenljiva (readBytes) koja će da pamti koliko bajtova smo
pročitali. Dok čitamo sistemskim pozivom read, mi ćemo taj sadržaj da pišemo u neki drugi fajl
deskriptor. Da bi se to ispisalo koristimo sistemski poziv write, za koji nam je potreban fajl
deskriptor na koji štampamo sadžaj, potom bafer iz kog štampamo i koliko bajtova želimo da
odštampamo. Ako je broj koji se vraća veći od nule onda je sve u redu inače se dogodila greška i
vraćeno je -1, u tom slučaju treba da oslobodimo bafer koji smo koristili i da signaliziramo gde je
došlo do greške. Ostalo je da uporedimo da li je readBytes == 0, ukoliko jeste znači da je sve
pročitano iz datoteke i funkcija vraća true, a pre toga treba osloboditi memoriju za bafer;

funkcija osCopyFilePath:
Ona ima dva argumenta, jedan je putanja do fajla u koji upisujemo sadržaj a drugi je putanja
do fajla iz koga prepisujemo sadržaj. Ona otvara fajlove sa zadatih putanja i vraća fajl deskriptore
tih fajlova i potom poziva prethodnu funkciju osCopyFileFd, rezultat ove funkcije se smešta u
result promenljivu.
int saveErrno = errno;
close(fdDest);
close(fdSrc);
errno = saveErrno;
return result;

Errno je error kod koji dele sve naše funkcije u programu. Kada dolazi do pucanja programa
tu se napiše razlog pucanja programa. Ako se dogodi neka greška u osCopyFileFd imamo garanciju
da će errno da bude postavljen na tu grešku, ali problem je close koji se uvek završava sa success i
može da se desi da nam pukne program, a da mi ne možemo da uočimo šta je tačno greška. Zbog
toga koristimo maskiranje, sačuvamo errno u jednu promenljivu pre nego što zatvorimo deskriptore
i onda samo na kraju ispišemo to u errno.

25
int main(int argc, char **argv){

osAssert(3 == argc, "Argument missing, usage: ./cpfile source destination"); /* Provera br. argumenata */
osAssert(osCopyFilePath(argv[2],argv[1]),"File write to console");
return 0;
}

bool osCopyFilePath(const char *filePathDest, const char * filePathSrc){

/* Deklarišemo file deskriptor za fajl u koji upisujemo, i fajl deskriptor za fajl iz kojeg prepisujemo */
int fdDest, fdSrc;
/* Otvaramo fajl u koji upisujemo u modu za pisanje, i otvaramo fajl iz koga prepisujemo u modu za čitanje*/
if(!osFileOpen(filePathDest, "w", &fdDest) || !osFileOpen(filePathSrc,"r",&fdSrc))
return false;
/* Kupimo rezultat koju vraća funkcija osCopyFileFd i pamtimo errno vrednost, jer će close da upiše success*/
bool result = osCopyFileFd(fdDest, fdSrc);
int saveErrno = errno;
close(fdDest);
close(fdSrc);

errno = saveErrno;
return result;
}

bool osCopyFileFd(const int fdDest, const int fdSrc){

/* Kreiramo memorijski bafer i alociramo memoriju */


static const uint32_t memBufSize = 1U << 13; //8kb
char *memBuf = malloc(memBufSize);
if(NULL == memBuf)
return false;

int32_t readBytes;
while((readBytes = read(fdSrc, memBuf, memBufSize)) > 0) /* Čitamo iz fajla, i upisujemo u memBuf*/
if(write(fdDest, memBuf, memBufSize) < 0) /* Upisujemo u fajl sadržaj iz memBuf */
{
free(memBuf);
return false;
}
free(memBuf);
return 0 == readBytes;
}

0→0 4 → 16 8 → 256 12 → 4096


1→2 5 → 32 9 → 512 13 → 8192
2→4 6 → 64 10→ 1024 14 → 16384
3→8 7 → 128 11 → 2048 15 → 32756

catfile.c
Treba da ispišemo sadržaj fajla u terminal. Da bi to uradili moramo prvo da otvorimo taj
fajl, a potom sa sistemskim pozivima read i write da prepišemo sadržaj fajla u terminal.

ssize_t read(int fd, void *buf, size_t count);


ssize_t write(int fd, const void *buf, size_t count);

26
int main(int argc, char **argv){

/* Provera br. argumenata; definisanje fajl deskriptora i poziv funkcije da se otvori fajl na datoj putanji.
Potom poziv funkcije koja kopira sadržaj fajla u terminal */
osAssert(2 == argc, "Argument missing");
int fd;
osAssert(osFileOpen(argv[1],"r",&fd),"File create");
osAssert(osCopyToTerminal(fd),"File write to console");
close(fd); /* Zatvaramo fajl */
return 0;
}
bool osCopyToTerminal(const int fd){

static const uint32_t memBuffSize = 1U << 13; //8KB veličina memorije koju ćemo alocirati za naš bafer
char *memBuf = malloc(memBuffSize); /* Alokacija memorije */
if(NULL == memBuf)
return false;

int32_t readBytes; /* Koliko bajtova smo pročitali iz fajla */


while((readBytes = read(fd, memBuf, memBuffSize)) > 0){
if(write(STDOUT_FILENO, memBuf, memBuffSize)<0){
free(memBuf);
return false;
}
}
free(memBuf);
return 0 == readBytes; /* Ukoliko je readBytes ==0 znači da je sve pročitano iz našeg fajla, vraća se true*/
}

rmfile.c
Brisanje fajla ili direktorijuma. Link na postojeći fajl se kreira pomoću link funkcije.
Kreiranje nove direktorijumske odrednice i povećanje broja linkova je atomična operacija.
Kreiranje hard linkova za direktorijume je dozvoljeno samo superuseru. Razlog je što bi zlonameran
korisnik mogao da napravi kružne petlje u sistemu, koje većina alata koji rade sa fajl sistemom ne
mogu da otkriju.
Komanda unlink briše direktorijumsku odrednicu i smanjuje broj linkova fajla koji se prima
kao argument. Ukoliko postoji još linkova na fajl, fajl je i dalje dostupan. Samo smanjivanjem broja
linkova na 0 sadržaj fajla se briše. Ukoliko proces ima fajl otvoren on se ne može obrisati tokom
njegovog izvršavanja. Tek kada se fajl zatvori, kernel proverava broj procesa koji imaju ovaj fajl
otvoren, ukoliko je on 0, sadržaj fajla se briše.
Funkcija unlink se često koristi da se privremeni fajl koji kreira program obriše ako dođe do
pucanja programa. Proces kreira fajl pomoću open ili creat i odmah poziva unlink.
Za unlink-ovanje fajla može se koristiti i remove funkcija. Za fajl, remove je identična unlink, a za
direktorijum je identična rmdir.

int main(int argc, char **argv) {


osAssert(3 == argc, "Argument missing. Usage: ./rmfile -[fd] path/to/file/or/directory");

osAssert('-' == argv[1][0], "Not a command line option");


if ('f' == argv[1][1]) /* Ukoliko je u pitanju regularan fajl brišemo ga komandom unlink. */
osAssert(unlink(argv[2]) >= 0, "Removing file failed");
else if ('d' == argv[1][1]) /*Ukoliko je u pitanju direktorijum, brišemo ga komandom rmdir. */
osAssert(rmdir(argv[2]) >= 0, "Removing directory failed");
else
osAssert(false, "Wrong option, valid are 'f' and 'd'");
return 0;
}

27
28
VEŽBE 3

userinfo.c
Treba da ispišemo neke informacije o korisniku na osnovu njegovog imena. To možemo da
uradimo pozivom funkcije getpwnam(ime_korisnika). Ova funkcija informacije o korisniku smešta
u strukturu koja se zove passwd. Za detaljnije informacije ukucati man 5 passwd u terminalu.

Za svakog korisnika (user account) upisana je jedna linija koja sadrži 7 polja, ona su
odvojena dvotačkama. I tu su redom postavljeni korisničko ime, ID korisnika, ID grupe, neke
dodatne informacije o korsiniku, potom informacija o home direktorijumu, izabrani bash...

· login name
· optional encrypted password →passwd je dostupan svim programima koji postoje na
sistemu i njega svako može da čita. Kompletne informacije o
šiframa je izdvojena u shadow fajl. Razlika između passwd i
shadow fajla, shadow fajlu mogu da pristupe samo neki
programi, dok passwd može da pristupa bilo ko;
· numerical user ID
· numerical group ID
· user name or comment field
· user home directory
· optional user command interpreter

Sve što se piše u passwd piše se u statički alociranu strukturu i to može da predstavlja
problem. Da bi koristili funkciju getpwnam potrebno je da ukljucimo <pwd.h>. Ova funckija nam
vraća statički alociranu strukturu. Ta struktura sadrži sledeća polja:

struct passwd {
char *pw_name; /* username * /
char *pw_passwd; /* user password */
uid_t pw_uid; /* user ID */
gid_t pw_gid; /* group ID * /
char pw_gecos; /* user information */
char *pw_dir; /* home directory * /
char *pw_shell; /* shell program */
};

Ukoliko dođe do greške funckija nam vraća NULL i postavlja neku vrednost u errno.
Za nas je dovoljno da pogledamo da nije slučajno vratila NULL, jer je u tom slučaju došlo do
greške.

struct passwd *getpwnam(const char *name);


struct passwd *getpwuid(uid_t uid);

Funkciju getpwuid koristi ls komanda da mapira numerički user ID koji se nalazi u i-nodu
za korisničko login ime. Funkcija getpwnam koristi program login kada unosimo naše korisničko
ime. Obe funkcije vraćaju pokazivač na struct passwd koju pune. Ova struktura je inače statička
varijabla u funkciji, pa se presnimava svaki put kada pozovemo neku od ovih funkcija. Prethodne
funkcije nam daju informacije o korisniku ako znamo njegov broj/ime korisnika.

29
int main(int argc, char **argv){

/* Proveravamo broj argumenata i pozivamo funkciju koja treba da ispiše info. o korisniku */
osAssert(2 == argc, os_Usage);
osAssert(osWriteUserInfoToStreamUsername(argv[1],stdout),"Writing user info to stream failed");
return 0;
}
bool osWriteUserInfoToStreamUsername(const char *username, FILE* out){

/* Koristimo funkciju getpwnam koja prima kao argument ime korisnika i potom info. o korisniku smešta
u strukturu passwd, pošto ona može da nam vrati null ukoliko je došlo do greške to treba da proverimo
i da vratimo false u tom slučaju */
struct passwd *userInfo = getpwnam(username);
if(NULL == userInfo)
return false;

fprintf(out, "Username: %s\n", username); /* Ispisujemo ime korisnika */


fprintf(out, "Home directory: %s\n", userInfo->pw_dir); /* Home direktorijum korisnika */
fprintf(out, "Basic information: %s\n", userInfo→pw_gecos); /* Neke informacije tog korisnika */
fprintf(out, "User numeric id: %jd\n", (intmax_t)userInfo→pw_uid); /* User ID */
fprintf(out, "Group numeric id: %jd\n", (intmax_t)userInfo→pw_gid); /* Group ID */

return !ferror(out);
}

listuser.c
Program treba da isčita sve korisnike iz passwd fajla. Pitanje je kako to možemo da
uradimo. Za to postoji određeni mehanizam. Ukoliko je potrebno da prođemo kroz ceo password
fajl, koristimo sledeće 3 funkcije:

void setpwent(void); → premotova passwd na početak


struct passwd *getpwent(void); → dobijemo informaciju o jednom korisniku na sisitemu tako
što nam razbije informacije o korisniku u strukturu passwd
void endpwent(void); → služi za zatvaranje passwd-a

Funkcija getpwent učitava red iz password fajla i smešta ga u odgovarajuću statičku


strukturu.Funkcijom setpwent vraćamo se na početak fajla, dok funkcija endpwent zatvara
password fajl.
Napomena: kao i readdir i ove funkcije vraćaju pokazivače na statičku memoriju, pa nije potrebno
alocirati/dealocirati memoriju već samo deklarisati pokazivač.

Implementacija funkcije getpwnam:

struct passwd *getpwnam(const char *name) {


struct passwd *ptr;
setpwent();
while ((ptr = getpwent()) != NULL)
if (strcmp(name, ptr->pw_name) == 0)
break; /* found a match */
endpwent();
return(ptr); /*a ptr is NULL if no match found */
}

Da bi ovo radilo moramo da korstimo odgovarajući makro #define _XOPEN_SOURCE 700

30
Za funkcije setpwnet(), getpwent() i endpwent() ne moramo da obrađujemo greške. Ako getpwent
dođe do null-a znači da je završio sa isčitavanjem korisnika.

#define UNUSED_VAR(x) ((void)x)


static const char* os_Usage = "Usage: ./listuders";
int main(){
UNUSED_VAR(os_Usage); /* Pošto nećemo koristiti os_Usage kastujemo ga u void da komapjler
osListUsers(); ne prijavljuje warning-e */
return 0;
}

bool osWriteUserInfoToStreamPwd(const struct passwd* userInfo, FILE* out){


/* Kao i u prethodnom zadatku za svakog korisnika pišemo informacije o tom korisniku */
fprintf(out, "################################################\n");
fprintf(out, "Username: %s\n", userInfo→pw_name);
fprintf(out, "Home directory: %s\n", userInfo→pw_dir);
fprintf(out, "User type: %s user\n", userInfo→pw_uid <1000 ? "system" : "regular");
fprintf(out, "Basic information: %s\n", userInfo→pw_gecos);
fprintf(out, "User numeric id: %jd\n", (intmax_t)userInfo→pw_uid);
fprintf(out, "Group numeric id: %jd\n", (intmax_t)userInfo→pw_gid);
return !ferror(out);
}

void osListUsers(){
struct passwd* currentUserInfo = NULL; /* Deklarišemo strukturu passwd u koju ćemo ispisivati info. */

/* Pre nego što krenemo da koristimo passwd fajl potrebno je da ga premotamo na početak,
zato prvo korisitmo funkciju setpwent(), da bi dobili informacije o korisniku koristimo funkciju
getpwent() koja redom uzima podatke o korisniku i smešta u passwd strukturu sve dok ne dođemo do NULL.
Da bi zatvorli passwd fajl na pravi način kucamo na kraju endpwent() */
setpwent();
while(NULL != (currentUserInfo = getpwent()))
osAssert(osWriteUserInfoToStreamPwd(currentUserInfo,stdout),
"Writing user information to stream failed!");
endpwent();
}

fileinfo.c
Napomena: Ovaj zadatak je bitan, jer će sigurno nešto od ovoga biti na kolokvijumu. Zbog toga je
postavljena većina koda i kroz komentare je objašnjeno kako se radi zadatak.
U zadatku treba da se ispišu informacije o fajlu u formatu sličnom kao kada se pozove ls -l
komanda (npr.: -rw-rw-r-- 1 nalog nalog 9483 Wed Mar 8 19:59:59 2017 fileinfo.c → koji tip fajla
je u pitnju, prava pristupa, broj linkova, ime korisnika, ime grupe, veličina fajla, vreme kada je fajl
modifikovan, i putanja do fajla). Koristićemo funkcije:
stat, fstat i lstat funkcije da bi prikupili informacije o fajlu
int stat(const char *path, struct stat *buf); → prosleđujemo joj putanju, prati simboličke linkove
int fstat(int fd, struct stat *buf); → prosleđujemo joj fajl deskriptor
int lstat(const char *path, struct stat *buf); → prosleđujemo joj putanju, ne prati simboličke
linkove

Ove 3 funkcije smeštaju informacije o navedenom fajlu u strukturu na koju pokazuje buf.
Funkcija fstat kao prvi argument prima otvoreni fajl deskriptor dok ostale 2 funkcije primaju
putanju do fajla. Funkcija lstat se razlikuje od stat jer kada je u pitanju simbolički link, lstat vraća
informaciju o samom linku a ne o fajlu na koji pokazuje link. Komanda ls -l koristi stat funkciju za
davanje informacija o fajlu. Ukucati man 2 stat za više informacija.

31
Struktura stat izgleda:

struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */

}
U ovom slučaju funkcija stat treba da napuni strukturu stat informacijama o fajlu. Ukoliko
stat funkcija pukne vraća se -1 kao greška.
Simbolički linkovi:
To su indirektni pokazivači na fajlove. Uvedeni su da se prevaziđu nedostaci hard linkova.
Hard linkovi zahtevaju da se link i fajl nalaze na istom fajl sistemu. Samo superuser moze da kreira
hard link za direktorijum. Postoje funkcije koje prate simboličke
linkove i one koje to ne rade. Možemo da napravimo petlje u fajl
sistemu koristeći simboličke linkove. Većina funkcija postavlja
vrednost errno na ELOOP kada dođe do ovoga.(pr. kreiranja
simboličkog l.)
mkdir foo
touch foo/a
ln -s ../foo foo/testdir
ls -l foo
int main(int argc, char **argv){

osAssert(argc == 2, os_Usage);
/* Prvo deklarišemo string fileInfoStr u koji ćemo da smeštamo sve informaicje o fajlu, njega ćemo
inicijalizovati na NULL jer ćemo kasnije za njega alocirati memoriju u funk. OsGetFileInfo */
char *fileInfoStr = NULL;
osGetFileInfo(argv[1],&fileInfoStr); /* Pozivamo funkciju osGetFileInfo kojoj prosleđujemo putanju do
printf("%s\n", fileInfoStr); fajla o kome želimo da saznamo informaciju, i adresu fileInfoStr */
free(fileInfoStr);
return 0;
}
void osGetFileInfo(const char *filePath, char** fileInfoStr){

/* Inicijalizovaćemo konstantan string koji će sadržati tip fajla i privilegije za pristup fajlu
NOTE: Extra space at the end is convinient for further processing*/
static const char* filePriviledges = "-rwxrwxrwx ";

/* Pitamo da li je fileInfoStr inicijalizovan na NULL, ako nije onda moramo da prijavimo grešku
jer mi treba da alociramo memoriju za nejga*/
errno = EINVAL;
osAssert(NULL == *fileInfoStr, "Given pointer must be NULL initialized");
Errno = 0;

/* Alociramo memoriju za file info string */


*fileInfoStr = malloc(START_FINFO_STR_LEN);
/* Da bi nam bilo lakše pravimo lokalnu kopiju pokazivaca fileInfoStr sa kojom možemo da radimo */
char* finfoStr = *fileInfoStr;
osAssert(NULL != finfoStr, "Alloctaion of output string failed"); ...

32
/* Da bismo pročitali informaciju o fajlu potrebno je da korstimo funciju stat().
Informacija se smešta u strukturu stat. Ukoliko je -1 vraćeno znači da stat nije uspeo i vratio je grešku */
struct stat finfo;
osAssert(-1 != stat(filePath,&finfo), "Getting file information failed");

/* Sada ćemo prvo da pročitamo informaciju o tipu fajla. Da li je fajl regularan, soket, link… To radimo uz
pomoć predefinisanih makroa. Funkcije koje koristimo su u stvari makroi. Na prvom mestu u stringu finfoStr
gde pišemo koji je tip fajla u pitanju upisujemo jedan karakter kao oznaku (npr. d za direktorijum,
s za soket…). Pre nego što smo počeli da ispitujemo tip fajla, prekopirali smo string filePriviledges u
string finfoStr, jer je finfoStr naš konačan string u koji smeštamo sve informacije o fajlu. st_mode sadrži sve
informacije o fajlu koji nama trebaju. Treba pogledati u man strani sve vrednosti. Određivanje tipa fajla
možemo da uradimo na dva načina:

1. stat(pathname, &sb);
if ((sb.st_mode & S_IFMT) == S_IFREG) {
/* Handle regular file */
}
S_IFMT 0170000 bit mask for the file type bit field S_IFBLK 0060000 block device
S_IFSOCK 0140000 socket S_IFDIR 0040000 directory
S_IFLNK 0120000 symbolic link S_IFCHR 0020000 character device
S_IFREG 0100000 regular file S_IFIFO 0010000 FIFO

2. stat(pathname, &sb);
if (S_ISREG(sb.st_mode)) {
/* Handle regular file */
}*/
strcpy(finfoStr, filePriviledges);
switch(finfo.st_mode & S_IFMT){
case S_IFSOCK:
finfoStr[0] = 's'; /* Ispitujemo da li je fajl soket */
break;
case S_IFLNK:
finfoStr[0] = 'l'; /* Ispitujemo da li je fajl link */
break;
case S_IFREG:
FinfoStr[0] = '-'; /* Ispitujemo da li je regularan fajl */
break;
case S_IFBLK:
finfoStr[0] = 'b'; /* Ispitujemo da li je blok fajlt */
break;
case S_IFDIR:
finfoStr[0] = 'd'; /* Ispitujemo da li je fajl direktorijum */
break;
case S_IFCHR:
finfoStr[0] = 'c'; /* Ispitujemo da li je karakter fajl */
break;
case S_IFIFO:
finfoStr[0] = 'f'; /* Ispitujemo da li je fifo fajl */
break;
}

/* Postavljanje privilegija:
Na početku zadatka smo postavili sva prava pristupa. Sada ćemo ispitivati jedno po jedno i ukoliko to pravo pristupa
ne postoji ukinućemo ga tako što ćemo upisati karakter ‘-’ na tom mestu. Prvo proveravamo da li korisnik ima pravo
čitanja, ukoliko nema ukidamo ga, potom proveravamo da li ima pravo pisanja, ako nema ukidamo ga i tako redom
ispitujemo sva prava za grupu i ostale. (prva tri su za korisnkia, druga tri za grupu i ostala tri za ostale)
*/
...

33
if(!(S_IRUSR & finfo.st_mode)) finfoStr[1] = '-'; /* Ispitujemo pravo čitanja za korisnika */
if(!(S_IWUSR & finfo.st_mode)) finfoStr[2] = '-'; /* Ispitujemo pravo pisanja za korisnika */
if(!(S_IXUSR & finfo.st_mode)) finfoStr[3] = '-'; /* Ispitujemo pravo izvršavanja za korisnika */

if(!(S_IRGRP & finfo.st_mode)) finfoStr[4] = '-'; /* Ispitujemo pravo čitanja za grupu */


if(!(S_IWGRP & finfo.st_mode)) finfoStr[5] = '-'; /* Ispitujemo pravo pisanja za grupu */
if(!(S_IXGRP & finfo.st_mode)) finfoStr[6] = '-'; /* Ispitujemo pravo izvršavanja za grupu */

if(!(S_IROTH & finfo.st_mode)) finfoStr[7] = '-'; /* Ispitujemo pravo čitanja za ostale */


if(!(S_IWOTH & finfo.st_mode)) finfoStr[8] = '-'; /* Ispitujemo pravo pisanja za ostale */
if(!(S_IXOTH & finfo.st_mode)) finfoStr[9] = '-'; /* Ispitujemo pravo izvršavanja za ostale */

/* Do sada smo ispitali tip fajla i prava pristupa, naš string izgleda npr. ovako drwxr--r-- , sada hoćemo na to
da nadovežemo ostale informacije o fajlu. Npr. hoćemo da napišemo broj linkova. Prvo ćemo odrediti za koliko
karaktera od početka stringa treba da se pomerimo da bismo upisali novu informaciju. U ovom slučaju to je za
11 karaktera. Koristićemo funkciju sprintf koja vraća broj upisanih bajtova, što znači da možemo da vodimo
računa gde nam se trenutno nalazi offset. Koristićemo promenljivu pos koja će pamtiti taj offset i koja će se
uvećavati svaki put kada nešto upišemo u naš string.
int sprintf(char *str, const char *format, ...);
Da bi ispisali broj linkova, pristupamo broju linkova u strukturi finfo. */

unsigned pos = 11;


pos += sprintf(finfoStr + pos, "%ju ", (uintmax_t)finfo.st_nlink);

/* U naš string dodajemo ime korisnika koji dobijamo na osnovu user ID koji prosleđujemo funkciji
OsGetUsernameFromUid */

char *username = osGetUsernameFromUid(finfo.st_uid);


osAssert(NULL != username,"Getting username failed");
pos+= sprintf(finfoStr + pos, "%s ", username);
free(username);

/* U naš string dodajemo ime grupe koji dobijamo na osnovu group ID koji prosleđujemo funkciji
OsGeGrpnameFromGid */
char *grpname = osGetGrpnameFromGid(finfo.st_gid);
osAssert( NULL != grpname, "Getting username failed");
pos += sprintf(finfoStr + pos, "%s ",grpname);
free(grpname);

/* U naš string dopisujemo veličinu fajla iz strukture finfo */


pos+=sprintf(finfoStr + pos,"%jd ",(intmax_t)finfo.st_size);

/* U naš string dodajemo vreme modifikacije fajla. Ne možemo samo da ispišemo finfo.st_mtime jer je to broj
Sekundi od epohe 1.1.1970, već moramo da ispišemo formatirano vreme. Zbog toga koristimo funkciju ctime().
char *ctime(const time_t *timep); */
time_t seconds = finfo.st_mtime;
pos += sprintf(finfoStr + pos,"%s",ctime(&seconds));

/* U naš string dopisujemo putanju fajla koju smo prosledili kao argument komandne linije */
sprintf(finfoStr + pos-1," %s",filePath);

}
char *osGetUsernameFromUid(uid_t id){
/* Uz pomoć funkcije getpwuid koja kao argument prima user ID, informacije o korisniku smeštamo u
strukturu passwd*/
struct passwd *userInfo = getpwuid(id);
if(NULL == userInfo)
return false;
...

34
/* Alociramo memoriju za string koji ćemo vratiti kao rezultat funkcije */
char *username = malloc(strlen(userInfo->pw_name)+1);
if(NULL == username)
return NULL;
/* U prethodno alociranu string upisujemo korisničko ime */
strcpy(username, userInfo->pw_name);
return username;
/* Zbog čega alociramo username i onda pišemo u njega ime korisnika?
Zbog toga što je ova struktura statički alocirana, i ako dođe do promene konteksta, pristupimo joj iz neke
druge funkcije možemo da obrišemo prethodni sadržaj te strukture. Zbog toga je neophodno kopiranje sadržaja
i potom vraćanje kao rezultata funkcije. (Nikako se ne sme vratiti userInfo→pw_name kao rezultat funkcije) */
}

char *osGetGrpnameFromGid(gid_t id){

/* Dobijamo informaciju o grupi na osnovu njenog id-a. Postoji i funkcija koja vraća informacije na
osnovu imena. Za više informacija o group strukturi ukucati man 5 group.
Svaka linija group fajla sadrži ime grupe, opciono šifru grupe, ID grupe i listu korisnika koji se nalaze u grupi*/

struct group *gprInfo = getgrgid(id);


if(NULL == gprInfo)
return NULL;

char *grpname = malloc(strlen(gprInfo->gr_name)+1);


if(NULL == grpname)
return NULL;

strcpy(grpname,gprInfo->gr_name);
return grpname;

Veličina fajla
Polje st_size u strukturi stat sadrži veličinu fajla u bajtovima. Ovo polje ima smisla samo
kod regularnih fajlova, direktorijuma i simboličkih linkova. Kod običnih fajlova dozvoljena je
veličina 0; kod simboličkih linkova veličina fajla je broj bajtova u imenu. U strukturi stat postoje i
polja st_blksize i st_blocks. Prvo predstavlja preferiranu veličinu bloka za I/O operacije na fajlu,
dok drugo predstavlja broj 512-bajt blokova koji su alocirani.
Vremena fajlova
Polja za vremena se čuvaju za svaki fajl.

Vreme modifikacije (st_mtime) označava kada je poslednji put menjan sadržaj fajla. Vreme
promene statusa (st_ctime) označava kada je poslednji put menjan i-nod (promena prava pristupa,
vlasnika, broja linkova). Vreme posledjeg pristupa (st_atime) može iskoristiti administrator sistema
da obriše fajlove kojima se nije skorije pristupalo (npr. a.out fajlovi kojima se nije pristupalo u
prethodnoj nedelji).
ls -l →vreme modifikacije ls -lu →vreme pristupa ls -lc →vreme promene statusa

35
truncate_undetected.c
U zadatku treba da postavimo vreme pristupa i modifikacije fajla, na ono koje je bilo pre
nego što smo mu pristupili i izmenili ga.
Funkcija utime
Vremena pristupa i modifikacije mogu se promeniti pomoću utime funkcije. Vremena u
strukturi struct utimbuf u koju se smeštaju ova dva vremena, su predstavljena sekundama od Epohe
1.1.1970. godine. Effective user ID mora biti jednak vlasniku fajla ili moramo imati prava pisanja
za fajl.
1. Ukoliko je times argument null pokazivač, oba vremena se postavljaju na trenutno vreme.
2. Inače su vremena postavljena na vrednosti u strukturi na koju pokazuje times.
Vreme promene i-noda se automatski menja kada se pozove funkcija utime. Za dobijanje trenutnog
vremena može se koristiti funkcija time. Ona vraća broj sekundi od Epohe u trenutku u kome se
poziva.
time_t time(time_t *t);

int main(int argc, char **argv){


osAssert(argc == 2, os_Usage); /* proveravamo broj argumenata u suprotnom ispisujemo poruku*/
osTruncateUndetected(argv[1]); /* pozivamo funkciju kojoj prosleđujemo putanju do fajla*/
return 0;
}

void osTruncateUndetected(const char *filePath){

/* Kako mozemo da sakrijemo da smo pristupali fajlu?


Prvo pokupimo vreme kada mu se pristupalo i kada je modifikovan poslednji put, to radimo sa stat funkcijom,
potom pristupimo fajlu i možemo da ga modifikujemo i na kraju vratimo vreme pristupa i modifikacije
na ono koje je bilo pre nego što smo ga menjali */
struct stat finfo;
osAssert(-1 != stat(filePath,&finfo),"Getting file info failed");

/* Fajl kojem pristupamo smo otvorili u modu za pisanje tako da je fajl prebrisan. */
FILE* fp = fopen(filePath, "w");
osAssert(NULL != fp, "Truncating file failed");
fclose(fp); /* fajl zatvaramo */

/* Sada je potrebno da vratimo vreme pristupa i modifikacije na ono koje je bilo pre nego što smo prebrisali
fajl. Deklarišemo utimbuf strukturu koja sadrži sledeća polja:
struct utimbuf {
time_t actime; access time
time_t modtime; modification time
};
U strukturu times upisujemo vreme modifikacije i pristupa. I sa funkcijom utime postavljamo ta vremena
za dati fajl
int utime(const char *filename, const struct utimbuf *times);
Kad u terminalu ukucamo stat za isti ovaj fajl, videćemo da je fajl prazan i da mu je vreme pristupa
i modifikacije isto kao i ono kada je fajl kreiran */
struct utimbuf times;
times.actime = finfo.st_atime;
times.modtime = finfo.st_mtime;
osAssert(-1 != utime(filePath,&times),"Restoring times failed");
}

36
VEŽBE 4

umask_demonstration.c
U zadatku treba da se napravi fajl sa određenim pravima pristupa. Ta prava pristupa mogu
biti egzotična :) . Npr. Treba da napravimo fajl sa pravima pristupa 0777. Za to ćemo koristi umask.
Kucati man umask za više informacija. umask nikada ne puca nego uvek vraća staru vrednost koja
je bila postavljena. Ako hoćemo da saznamo vrednost umaska treba u terminal da ukucamo umask.
Ako hoćemo da promenimo vrednost umaska kucamo umask broj (prava koja želimo da ukinemo).

Funkcija umask

mode_t umask(mode_t mask);

Umask → su prava koja hoćemo da ukinemo.


Kako računamo koja prava pristupa će nam biti postavljena ukoliko pozivamo umask?
Imamo neki mode koji nam je već zadat, zatim imamo umask koji invertujemo i onda koristimo
AND za te dve vrednosti.

mode & (~umask)

Na primer:
mode: 110110110 0666 110110110 0666
umask: 000010010 0022 →
~ umask: 111101101 111101101 & ~0022
dobijamo: 110100100 0644

Ako treba da postavimo prava pristupa na 0777. Potrebno je da prvo postavimo umask na 0
potom napravimo taj fajl sa tim pravima pristupa i na kraju da povratimo vrednost za umask koja je
bila prethodno postavljenja.
Ako ovaj naš program pozovemo sa ./umask_demonstration 1.txt 0644 i potom ukucamo ls
-l, u terminalu će pistati:
---x-wx-wx 1 nalog nalog 0 mar 15 14:37 1.txt
Dakle vidimo da je ukinuto sve što smo postavili (0644).
Funkcija umask postavlja masku za prava pristupa pri kreiranju fajla, i vraća prethodnu
masku. Ovo je jedna od retkih funkcija koje nemaju kao mogućnost vraćanje greške. Maska se
koristi kad god proces kreira novi fajl ili direktorijum. Svi bitovi koji su postavljeni u maski,
isključeni su pri kreiranju fajla.
Kada želimo da obezbedimo da su pojedini bitovi za pristup postavljeni moramo
modifikovati umask vrednost dok se proces izvršava. Menjanje maske u procesu ne utiče na masku
u shell-u. Vrednost umask se reprezentuje kao oktalni broj. Za razliku od ispisa prava pristupa kao
kod ls -l komande, kod oktalnog zapisa prava pristupa su predstavljena oktalnom trojkom. Slovo r
nosi vrednost 4, w nosi vrednost 2, x nosi vrednost 1. Inicijalna prava pristupa određuje open, kada
je navedena opcija O_CREAT.
Neke od komandi koje se mogu uneti u shell-u su:

umask → prikazuje koja su prava onemogućena pri kreiranju fajlova


umask -S → prikazuje koja su prava omogućena
umask 027 → postavlja masku na 027

37
int main(int argc, char **argv) {

osAssert(3 == argc, os_Usage);


/* Da bi preveli broj koji je predstavljen stringom, koristimo funkciju strtol. Ona prevodi broj iz string
reprezentacije u long integer. Postavljamo da baza koju korisnik unosi bude 8, i dobijamo
dekadnu reprezentaciju tog broja.
NAPOMENA: Umask se koristi samo prilikom kreiranja novih fajlova, ako imamo fajlove koji postoje
za to se koristi chmod */

long newUmaskVal = strtol(argv[2], NULL, 8);


mode_t previousUmask = umask(newUmaskVal); /* Pamtimo staru vrednost umask-a i postavljamo novu */

/* Kreiramo fajl */
int fd=0;
osAssert(osFileOpen(argv[1], "w", &fd), "File create");
close (fd);

/* Nakon što smo postavili prava pristupa za naš fajl, potrebno je da vratimo staru vrednost umask-a */
umask(previousUmask);
exit(EXIT_FAILURE);
}

bool osFileOpen(const char *filePath, const char *mode, int *fd) {


/* Postavljamo privilegije na -rwxrwxrwx */
static mode_t defaultMode = 0777;

/* Deklarišemo flegove, i u zavisnosti koji mod je prosleđen lepimo flegove */


int flags = 0;
switch (mode[0]) {
case 'r':
flags |= '+' == mode[1] ? O_RDWR : O_RDONLY; /* Ukoliko je postavljen r+ onda postavljamo O_RDWR
break; inače postavljamo O_RDONLY fleg, slično važi i za
case 'w': w+ kao i a+*/
flags |= '+' == mode[1] ? O_RDWR : O_WRONLY;
flags |= O_TRUNC;
flags |= O_CREAT;
break;
case 'a':
flags |= '+' == mode[1] ? O_RDWR : O_WRONLY;
flags |= O_APPEND;
flags |= O_CREAT;
break;
default:
return false;
}
/* Koristimo funkciju open da bi otvorili fajl, i vraćamo true ukoliko je fajl uspešno otvoren */
*fd = open(filePath, flags, defaultMode);
return *fd >= 0;
}

chmod.c

U zadatku treba da promenimo prava pritupa fajla koji već postoji. To ćemo raditi uz pomoć
funkcije chmod. Kucati man 2 chmod za detaljnije informacije.

chmod i fchmod funkcije

int chmod(const char *path, mode_t mode);


int fchmod(int fd, mode_t mode);

38
Ove funkcije omogućavaju nam da promenimo bitove pristupa postojećeg fajla. Prednost
Unix-a je što možemo u jednoj for petlji da prođemo kroz ceo sistem i promenimo prava
pristupa.
Napomena: umask vrednost ne utiče na promenu prava pristupa fajlu, već utiče na prava samo pri
kreiranju fajla!
Ako funkcija chmod pukne vraća -1 i postavlja neku vrednost u errno. Za postojeće fajlove umask
nema smisla, ako imamo fajlove koji postoje da bi promenili prava pristupa koristimo chmod.

int main(int argc, char **argv) {


osAssert(3 == argc, os_Usage);

/* Get octal number decade value (e.g. 644 octal is 6*8^2 + 4*8^1 + 4*8^0 = 420).
Da bi postavili prava pristupa moramo string sa ulaza da konvertujemo u oktalni broj */

long mode = strtol(argv[1], NULL, 8);

/* Menjamo mode, u onaj koji korisnik želi */


osAssert(-1 != chmod(argv[2], mode), "Changing mode for existing file");
return 0;
}

sizeof_directory.c
Ideja zadatka je da prosledimo putanju do nekog direktorijuma i da odredimo ukupnu
veličinu tog direktorijuma. Treba da obiđemo sve fajlove i poddirektorijume i da njihovu veličinu
dodajemo na ukupan zbir svih veličina.
Treba nekako da izlistamo direktorijum. Pitanje je šta se sve nalazi u tom direktorijumu? U
njemu se nalazi spisak i-nodova koji sadrži putanje do nekih fajlova. Direktorijumi kao tip fajla
nemaju nikakvu strukturu koja je sortirana. Kada listamo direktorijume, ne možemo da garantujemo
da ćemo prvo naići na sve regularne fajlove, pa na sve poddirektorijume… Da bi izlistali
direktorijum, to moramo da radimo rekurzivno. Kada naiđemo na neki fajl treba prvo da pitamo šta
je taj fajl. Ako je regularan fajl u pitanju kupimo njegovu veličinu dodajemo na ukupan zbir i
nastavljamo dalje da ispitujemo za ostale fajlove. Ako je u pitanju direktorijum onda moramo
rekurzivno da ga obiđemo. Obilaženje direktorijuma možemo da radimo u širinu i u dubinu. Mi
ovde obrađujemo obilaženje direktorijuma u dubinu, jer kada naiđemo na direktorjum mi uđemo u
njega i obradimo sve iz njega, potom izađemo i obrađujemo ostale fajlove.

Čitanje direktorijuma

Direktorijume može da čita svako ko ima prava čitanja za taj direktorijum. Na različitim
sistemima različito su predstavljene direktorijumske odrednice. Zbog toga u mnogim Unix-ima nije
dozvoljeno da programi koriste read funkciju pri pristupanju direktorijumima. Za prolazak kroz
direktorijume koriste se funkcije opendir i readdir. Readdir ono što pročita smešta u dirent struktru.
DIR *opendir(const char *name);
struct dirent *readdir(DiR *dirp);

struct dirent { /* dirent struktura mora imati bar dva polja */


ino_t d_ino; /* i-node number */
char d_name[NAME_MAX + 1]; /* null-terminated filename */
}
Pomoću opendir funkcije pristupa se direktorijumskim odrednicama, a redosled elemenata
zavisi od implementacije.

39
bool osSizeOfDirectory(const char *filePath, unsigned *pDirSize) {

/* Kupimo informacije na putanji koja nam je prosleđena, ukoliko je stat uspeo treba da dodamo veličinu tog fajla
na ukupnu veličinu */
struct stat finfo;
if (-1 == stat(filePath, &finfo))
return false;
/* Dodajemo veličinu tog fajla na ukupnu veličinu */
*pDirSize += finfo.st_size;

/* Pošto rekurzivno obilazimo direktorijum, ukoliko se u tom direktorijumu nalazi regularan fajl a ne direktorijum
da bi rekurzija nastavila dalje sa izvršavanjem moramo da vratimo true */
if (!S_ISDIR(finfo.st_mode)) { /* Na ovaj način ispitujemo da li fajl nije direktorijum */
return true;
}

/* Ukoliko je fajl direktorijum, onda je potrebno da obiđemo taj direktorijum. Da bi obišli direktorijum prvo
moramo da ga otvorimo, to radimo uz pomoć funkcije opendir. Da bi funkcija radila moramo da uključimo
zaglavlje <dirent.h> . Opendir funkciji je potrebno da prosledimo putanju do direktorijuma, ona nam vraća
strukturu DIR čiji sadržaj možemo da listamo. Kao setpwent funckija kod passwd fajla, opendir funkcija
postavlja nas na početak ove strukture, u slučaju da opendir nije uspela vraća NULL */

DIR *dir = opendir(filePath);


if (NULL == dir)
return false;

/* Ukoliko prethodno nije došlo do greške sada moramo da listamo sadržaj direktorijuma, to radimo uz pomoć
funkcije readdir. Njoj prosleđujemo DIR struktru, i mi listamo taj direktorijum sve dok nam se ne vrati NULL. */

struct dirent *entry = NULL;


while (NULL != (entry = readdir(dir))) {
/* Da bi rekurzivno obišli direktorijum moramo da napravimo novu putanju, alociraćemo memoriju za novu putanju,
pošto dir struktura čuva samo ime trenutnog fajla. Naravno treba proveriti da li je alokacija bila uspešna. */

char *newFilePath = malloc(strlen(filePath) + 1/*for '/'*/ + strlen(entry->d_name) + 1/*for '\0'*/);


if (NULL == newFilePath)
return false; /* U slučaju da je došlo do greške vratimo false. */

/* Nakon što smo alocirali memoriju sada možemo da kreiramo putanju, tako što ćemo prvo prekopirati u našu novu
putanju trenutnu putanju na kojoj se nalazimo, potom dodati na to ‘/’ i ime fajla za koji rekurzivno pozivamo našu
funkciju. */
strcpy(newFilePath, filePath);
strcat(newFilePath, "/");
strcat(newFilePath, entry->d_name);
/* Pri izlistavanju direktorijuma, treba da preskočimo . i .. direktorijume, ali da dodamo njohove veličine na ukupan
zbir svih veličina fajlova. Ove direktorijume preskačemo da ne bismo ušli u beskonačnu rekurziju.*/

if (!strcmp(".", entry->d_name) || !strcmp("..", entry->d_name)) {


if (-1 == stat(newFilePath, &finfo)) {
int saveErrno = errno;
free(newFilePath);
errno = saveErrno;
/* Kada se dogodi greška, treba da sačuvamo grešku koju nam je vratio stat i da tu grešku od stata
ostavimo da bude zapamćena u errno-u */
return false;
}
*pDirSize += finfo.st_size; /* Ukoliko se stat uspešno izvršio dodajemo njihove veličine na ukupnu */
free(newFilePath); /* Oslobađamo memoriju */
continue; /* Treba da pređe na naredni element */
}

40
/* Pozivamo rekurzivno našu funkciju za trenutni fajl */
bool success = osSizeOfDirectory(newFilePath, pDirSize);

/* Treba da oslobodimo memoriju za newFilePath, ali pošto će free() da postavi errno na success, pre toga moramo
da zapamtimo vrednost errno, ukoliko je došlo do greške pozivom funkcije vraćamo false*/
int saveErrno = errno;
free(newFilePath);
errno = saveErrno;
if (!success)
return false;
}

/* Zatvaramo trenutni direktorijum, ako je vraćeno -1 došlo je do greške */


if (-1 == closedir(dir))
return false;
return true;
}

int main(int argc, char **argv) {

/* Prvo proveravamo broj argumenata u suprotnom obaveštavamo korisnika kako se koristi program.
Kupimo informacije o fajlu, proveravamo da li je taj fajl direktorijum */
osAssert(2 == argc, os_Usage);
struct stat finfo;

osAssert(-1 != stat(argv[1], &finfo), "Failed to stat directory");


osAssert(S_ISDIR(finfo.st_mode), "Checking that file directory");
/* Pozivamo funkciju osSizeOfDirectory kojoj prosleđujemo putanju i promenljivu u koju ćemo da smeštamo
veličinu direktorijuma. */
unsigned dirSize = 0;
/* Ako ovo jeste direktorijum treba da obiđemo putanju, inače je obilazak direktorijuma pukao */
osAssert(osSizeOfDirectory(argv[1], &dirSize), "Failed to calculate size of directory");
printf("%uB\n", dirSize); /*vrednost velicine direktorijuma*/
return 0;
}

VAŽNO: Primetiti da su promenljive directory i entry u gornjem primeru samo pokazivači, tj. da
nije alocirana memorija za njih! To je zbog toga što funkcije opendir i readdir vraćaju pokazivače
na statički alociranu memoriju, pa korisnik ne mora voditi računa o alokaciji/dealokaciji memorije.
NAPOMENA: Nećemo proveravati greške za readdir funkciju, to bi se radilo tako što se zapamti
vrednost errno pre korišćenja ove funkcije, a po završetku korišćenja proverilo da li je ta vrednost
izmenjena. Ako jeste, došlo je do greške.

sizeof_directory2.c
Zadatak je isti kao prethodni, treba da odredimo ukupnu veličinu svih fajlova direktorijuma
koji nam je prosleđen. Ali u ovom primeru nećemo praviti putanje kao u prethodnom nego ćemo
koristi funkciju chdir. Pozicioniraćemo se u direktorijum isčitati šta nam treba a potom se vratiti u
roditeljski direktorijum.

int chdir(const char *path);

41
bool osSizeOfDirectory(const char *filePath, unsigned *pDirSize) {
/* Kupimo informacije o fajlu */
struct stat finfo;
if (-1 == stat(filePath, &finfo))
return false;

/* Uvećavamo ukupnu veličinu, dodajemo na naš ukupan zbir veličinu trenutnog fajla */
*pDirSize += finfo.st_size;
/* Potom šaljemo putanju koja je relativna, koristimo realpath funkciju koja nam vraća celu putanju */

char *absolutePath = realpath(filePath, NULL);


printf("%-80s: %jd\n", absolutePath, (intmax_t)finfo.st_size); /* Ispisujemo za svaki fajl njegovu putanju
free(absolutePath); i velčinu tog fajla */

/* Ukoliko fajl nije direktorijum, vraćamo true da bi rekurzija nastavila da radi */


if (!S_ISDIR(finfo.st_mode)) {
return true;
}
/* Obilazimo direktorijum, prvo ga otvorimo uz pomoć opendir funkcije, u slučaju greške vraćamo false*/
DIR *dir = opendir(filePath);
if (NULL == dir)
return false;

/* Menjamo radni direktorijum, na onaj koji smo prethodno otvorili */


if (-1 == chdir(filePath))
return false;
/* Čitamo šta se nalazi u direktorijumu */
struct dirent *entry = NULL;
while (NULL != (entry = readdir(dir))) {
/* Preskačemo . i .. direktorijume, ali računamo njihovu veličinu. Njih preskačemo zbog beskonačne rekurzije*/
if (!strcmp(".", entry->d_name) || !strcmp("..", entry->d_name)) {
if (-1 == stat(entry->d_name, &finfo))
return false;
*pDirSize += finfo.st_size;

absolutePath = realpath(entry->d_name, NULL); /* Od relativne pravimo apsolutnu putanju */


printf("%-80s: %jd (%s)\n", absolutePath, (intmax_t)finfo.st_size, entry->d_name);
free(absolutePath);
continue;
}

/* Rekurzivno pozivamo našu funkciju za trenutni fajl, i prosleđujemo ukupnu veličinu */


if (!osSizeOfDirectory(entry->d_name, pDirSize))
return false;
}

/* Kada završimo sa tim direktorijumom, vraćamo se u roditeljski i zatvaramo taj direktorijum */


if (-1 == chdir(".."))
return false;
if (-1 == closedir(dir))
return false;
return true;
}

filter_by_chtime.c
U zadatku je potrebno da se ispišu putanje svih fajlova koji su modifikovani u proteklih n
dana, koje korisnik prosleđuje. Treba opet da obiđemo direktorijum, ali ovaj put koristimo gotove
funkcije za to ftw() i nftw().

42
Obezbeđena je funkcija ftw(3) (file tree walk) koja prolazi kroz hijerarhiju i zove funkciju
koju je definisao korisnik za svaki fajl. Ova funkcija prati simboličke linkove (pa će se neki fajlovi
brojati dva puta), postoji i funkcija nftw(3) koja poseduje opciju za nepraćenje simboličkih linkova.
Da bi ovo radilo moramo da ukljucimo zaglavlje <ftw.h>. Za detaljne informacije kucati man ftw ili
man nftw.

int nftw(const char *dirpath, int (*fn) (const char *fpath, const struct stat *sb, int typeflag, struct
FTW *ftwbuf), int nopenfd, int flags);

Kada pozovemo nftw prvi argument je putanja na kojoj se nalazi naš direktorijum, zatim
naredni argument je pokazivač na funkciju koja rekurzivno obilazi taj direktorijum, potom
maksimalan broj fajl deskriptora koji mogu biti otvoreni i poslednji su flegovi, oni označavaju
ponašanje nftw da li hoćemo da obilazi u širinu, dubinu…Ako je povratna vrednost -1 vraća grešku.
Funkcija (*fn) koja vrši obilazak direktorijuma, ima kao argumente putanju do trenutnog
fajla, struktru stat koju ova funkcija unapred popuni za trenutni fajl. Imamo informacije o tome kog
tipa je naš fajl za nas su najbitniji FTW_F koji nam govori da li je fajl regularan i FTW_D koji
nam govori da li je fajl direktorijum. Struktura FTW nam govori koliko je daleko samo ime fajla od
početka putanje. Ako imamo putanju obliku 1/2/3.c polje base name govori koliko daleko se 3.c
nalazi u odnosu na početak putanje, drugo polje broji dubinu rekurzije, i to polje se zove level.

struct FTW {
int base;
int level;
};

/* Služi da kastujemo promenljive koje nećemo koristiti u void, da nam kompajler ne izbacuje warning-e*/
#define onUnusedFuncArg(x) ((void)x)
#define SECS_PER_DAY (60*60*24)

/* Funkcija koja rekurzivno obilazi zadati direktorijum */


int osPrintFilesChInDays(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf) {
osUnusedFuncArg(tflag);
osUnusedFuncArg(ftwbuf);
/* Povratna vrednost ove funkcije je bitna, ukoliko želimo da rekurzija nastavi sa radom moramo da vratimo nula.
Ukoliko se vrati nešto drugo, ne samo da će biti vraćena greška već i resursi koje je ova funkcija za
nas alocirala neće biti oslobođeni */

time_t currentTimeInSec = time(NULL); /* Određujemo trenutno vreme */


time_t diffInSec = currentTimeInSec – sb→st_ctime;/ *Od trenutnog vremena oduzimamo vreme kad je fajl modif.*/
if (diffInSec / SECS_PER_DAY < os_Days) /* Proveravamo da li je broj dana manji od zadatog */
printf("%-80s\n", fpath); /* Ukoliko jeste ispisujemo informaciju na stdout */
return 0;
}

int main(int argc, char **argv) {


osAssert(3 == argc, os_Usage);
os_Days = atoi(argv[2]); /* Sa standardnog ulaza pamtimo broj dana koji je korisnik zadao */

/* Pozivamo funkciju nftw koja će rekurzivno da obilazi direktorijum. Njoj se kao argumenti prosleđuju
putanja do direktorijuma, pokazivač na funkciju koja vrši obilazak, koliki je broj otvorenih fajl deskriptora
i na kraju flegovi za koje ćemo mi uglavnom stavljati 0*/

osAssert(-1 != nftw(argv[1], osPrintFilesChInDays, 50, 0), "Traversing file system failed");


return 0;
}

43
filter_by_extension.c

U ovom primeru treba da filtriramo fajlove po ekstenzijama. Korisnik zada neku ekstenziju i
putanju do direktorijuma. Mi treba da ispišemo sve fajlove na standardni izlaz, koje smo pronašli sa
tom ekstenzijom rekurzivno obilazeći direktorijum.

#define onUnusedFuncArg(x) ((void)x)


#define SECS_PER_DAY (60*60*24)
int osPrintFilesWithExt(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf) {
osUnusedFuncArg(sb); /* Kastujemo u void promenljivu koju ne koristimo */

If (FTW_F == tflag) {
char fileExt = strrchr(fpath + ftwbuf->base, '.'); /* Tražimo ekstenziju u putanji od pozadi */
if (NULL != fileExt && !strcmp(fileExt, os_Ext)) { /* Ukoliko ekstenzija odgovara onoj koju je korisnik
printf("%-80s\n", fpath); prosledio, ispisujemo tu putanju na stdout */
}
}
return 0;
}

int main(int argc, char **argv) {


osAssert(3 == argc, os_Usage);
os_Ext = argv[2];
/* Pozivamo funkciju nftw koja će rekurzivno da obiđe direktorjum na zadatoj putanji */
osAssert(-1 != nftw(argv[1], osPrintFilesWithExt, 50, 0), "Traversing file system failed");
return 0;
}

44
VEŽBE 5
PRIPREMA ZA KOLOKVIJUM
KOLOKVIJUM 2016

#define osErrorFatal(userMsg) osErrorFatalImpl((userMsg), __FILE__, __func__, __LINE__)


#define osAssert(expr, userMsg) \
do { \
if (!(expr)) \
osErrorFatal(userMsg); \
} while (0)

static const char* osUsage = "Usage: ./1 filePath";


void osErrorFatalImpl(const char* userMsg, const char* fileName, const char* functionName, const int lineNumber);
bool osIsPublicFile(const char* fpath); /* Funkcija koja vraća da li ostali imaju rw prava pristupa */

int main(int argc, char** argv) {

osAssert(argc == 2, osUsage); /* Provera broja argumenata */

bool result = osIsPublicFile(argv[1]); /* U result upisujemo povratnu vrednost naše funkcije i rezultat
fprintf(stdout, "%s\n", (result == true) ? "true" : "false"); ispisujemo na standardni izlaz */

exit(EXIT_SUCCESS);
}

bool osIsPublicFile(const char* fpath) {

struct stat fInfo; /* Struktura koja sakuplja informacije o fajlu */


osAssert(stat(fpath, &fInfo) != -1, "stat failed"); /* Informacije sakuplajmo pozivom funkcije stat */

osAssert(S_ISREG(fInfo.st_mode), "File not regular"); /* Ukoliko fajl nije regularan prekidamo program */

if ((fInfo.st_mode & S_IROTH) && (fInfo.st_mode & S_IWOTH)) /* Proveravamo da li ostali imaju prava za r i w */
return true; /* Ukoliko imaju vraćamo true inače false */
else
return false;
}
/* Funckija za obradu grešaka */
void osErrorFatalImpl(const char* userMsg, const char* fileName, const char* functionName, const int lineNumber) {
perror(userMsg);
fprintf(stderr, "File name: %s\nFunction name: %s\nLine number: %d\n", fileName, functionName, lineNumber);
exit(EXIT_FAILURE);
}

45
/* Makori za ispis greške i prekidanja programa u slučaju greške */
#define osErrorFatal(userMsg) osErrorFatalImpl((userMsg), __FILE__, __func__, __LINE__)
#define osAssert(expr, userMsg) \
do { \
if (!(expr)) \
osErrorFatal(userMsg); \
} while (0)

static const char* osUsage = "Usage: ./1 filePath"; /* Promenljiva koja objašnjava kako se poziva program */
void osErrorFatalImpl(const char* userMsg, const char* fileName, const char* functionName, const int lineNumber);

void osMakePublicDir(const char* fpath); /* Funckija za pravljenje direktorijuma */


void osMakePublicDir2(const char* fpath); /* Funkcija za pravljenje direktorijuma na drugi način */

int main(int argc, char** argv) {

osAssert(argc == 2, osUsage); /* Provera broja argumenata */

osMakePublicDir(argv[1]); /* Pozivanje funk. za pravljenje direktorijuma, kojoj prosleđujemo


//osMakePublicDir2(argv[1]); putanju do direktorijuma koji želimo da napravimo */

exit(EXIT_SUCCESS);
}

/* Prvi način pravljenja direktorijuma: pomoću umask */


void osMakePublicDir(const char* fpath) {

mode_t oldMask = umask(0); /* Umask postavljamo na 0 da bi mogli da postavimo naša prava


pristupa, i pri tom pamtimo stari umask kako bi mogli da ga vratimo */

osAssert(mkdir(fpath, 0777) != -1, "dir creation failed"); /* Pravljenje direktorijuma uz pomoć funkcije mkdir */
umask(oldMask); /* Vraćamo stari umask */
}

/* Drugi način pravljenja direktorijuma: pomoću chmod */


void osMakePublicDir2(const char* fpath) {

osAssert(mkdir(fpath, 0777) != -1, "dir creation failed"); /* Pravimo direktorijum sa određenim pravima pristupa */

osAssert(chmod(fpath, 0777) != -1, "chmod failed"); /* I uz pomoć chmod menjamo prava pristupa ukoliko se direktorijum
nije pravilno napravio sa našim pravima pristupa*/
}

void osErrorFatalImpl(const char* userMsg, const char* fileName, const char* functionName, const int lineNumber) {

perror(userMsg);
fprintf(stderr, "File name: %s\nFunction name: %s\nLine number: %d\n", fileName, functionName, lineNumber);

exit(EXIT_FAILURE);
}

46
#define osErrorFatal(userMsg) osErrorFatalImpl((userMsg), __FILE__, __func__, __LINE__)
#define osAssert(expr, userMsg) \
do { \
if (!(expr)) \
osErrorFatal(userMsg); \
} while (0)

#define SECS_PER_DAY (24*60*60) /* Ovaj makro će nam služiti da prevedemo sekunde u dane */
static const char* osUsage = "Usage: ./1 filePath"; /* Objašnjenje kako poziva program */

void osErrorFatalImpl(const char* userMsg, const char* fileName, const char* functionName, const int lineNumber);

unsigned osNumOfDaysFIleModified(const char* fpath); /* Funkcija koja vrća pre koliko dana je sadržaj fajla menjan*/

int main(int argc, char** argv) {

osAssert(argc == 2, osUsage); /* Provera broja argumenata */

unsigned days = osNumOfDaysFIleModified(argv[1]); /* U ovu promenljivu upisujemo povratnu vrednost ove funk*/
printf("%u\n", days); /* Ispisujemo broj dana na standardni izlalz */

exit(EXIT_SUCCESS);
}

unsigned osNumOfDaysFIleModified(const char* fpath) {

struct stat fInfo; /* U strukturu stat uz pomoć funkcije stat kupimo


osAssert(stat(fpath, &fInfo) != -1, "stat failed"); informacije o fajlu */

time_t now = time(NULL); /* Kupimo trenutno vreme */


osAssert(now != -1, "time failed"); /* Ukoliko je došlo do greške izlazimo iz programa */

unsigned days = (now – fInfo.st_mtime)/SECS_PER_DAY; /* Računamo broj dana kada je poslednji put fajl mod.*/
return days;
}

void osErrorFatalImpl(const char* userMsg, const char* fileName, const char* functionName, const int lineNumber) {

perror(userMsg);
fprintf(stderr, "File name: %s\nFunction name: %s\nLine number: %d\n", fileName, functionName, lineNumber);

exit(EXIT_FAILURE);
}

47
static const char* osUsage = "Usage: ./1 srcPath destPath"; /* Objašnjenje kako se poziva zadatak */
void osErrorFatalImpl(const char* userMsg, const char* fileName, const char* functionName, const int lineNumber);

void osMoveFile(const char* srcPath, const char* destPath); /* Funckija koja premešta fajl sa jedne na drugu putanju */
void osMoveFile2(const char* srcPath, const char* destPath); /* Drugi način da se uradi ova funkcija */
void osMoveFile3(const char* srcPath, const char* destPath); /* Treći način da se uradi funnkcija */

int main(int argc, char** argv) {

osAssert(argc == 3, osUsage); /* Provera broja argumenata */


osMoveFile3(argv[1], argv[2]); /* Poziv funkcije za premeštanje fajla */
exit(EXIT_SUCCESS);
}

void osMoveFile(const char* srcPath, const char* destPath) {

char *sPath = realpath(srcPath, NULL); /* Od relativne pravimo apsolutnu putanju za source path */
char *dPath = realpath(destPath, NULL); /* Od relativne pravimo apsolutnu putanju za desti. path */

osAssert(sPath != NULL, "source file must exist"); /* Ukoliko source putanja ne postoji prekidamo program */

/* Pre pozivanja strcmp moramo da se uverimo da nijedna putanja nije NULL. U suprotnom, može doći do segmentation fault-a */
if (sPath != NULL && dPath != NULL) {
osAssert(strcmp(sPath, dPath) != 0, "File paths must be different");
}

/* Oslobađanje dinamički alociranih promenljivih */


free(sPath);
free(dPath);

struct stat fInfo; /* Kupimo informacije o fajlu uz pomoć funkcije stat


osAssert(stat(srcPath, &fInfo) != -1, "stat failed"); i smeštamo informacije u strukturu stat */

int srcFd = open(srcPath, O_RDONLY); /* Otvaranje source path za čitanje */


mode_t oldMask = umask(0); /* Postavljamo umask na 0 i pamtimo staru vrednost */
int destFd = open(destPath, O_CREAT | O_WRONLY | O_TRUNC, fInfo.st_mode); /* Otvaramo dest path sa istim pravima
umask(oldMask); pristupa kao i source path, i vraćamo umask na staru
vrednost*/
int bufSize = 1<<13u; /* Definišemo veličinu bafera */
char* buf = malloc(bufSize); /* Alociramo memoriju za bafer */
osAssert(buf != NULL, "allocation failed"); /* Ukoliko alokacija nije bila uspešna prekidamo program */

int bytesRead = 0;
while ((bytesRead = read(srcFd, buf, bufSize)) > 0) { /* Čitamo iz source fajla, i prepisujemo na destination fajl*/
osAssert(write(destFd, buf, bytesRead) == bytesRead, "write failed");
}
osAssert(bytesRead == 0, "read failed");
free(buf);
close(srcFd);
close(destFd);

osAssert(unlink(srcPath) != -1, "unlink failed"); /* Na kraju brišemo source fajl sa unlink-om */


}

48
/* Drugi način kako možemo da prekopiramo fajl sa jedne na drugu putanju */
void osMoveFile2(const char* srcPath, const char* destPath) {

char *sPath = realpath(srcPath, NULL); /* Od relativne putanje pravimo apsolutnu za source putanju */
char *dPath = realpath(destPath, NULL); /* Od relativne putanje pravimo apsolutnu za destination putanju */

osAssert(sPath != NULL, "source file must exist");

/* Pre pozivanja strcmp moramo da se uverimo da nijedna putanja nije NULL. U suprotnom, može doći do segmentation fault-a*/
if (sPath != NULL && dPath != NULL) {
osAssert(strcmp(sPath, dPath) != 0, "File paths must be different");
}

/* Oslobađanje dinamički alociranih promenljivih */


free(sPath);
free(dPath);

/* U slučaju da fajl na destPath postoji */


if (access(destPath, F_OK) == 0){

/* Fajl mora da se obriše pre poziva link funkcije */


osAssert(unlink(destPath)!= -1, "unlink failed");;
}

/* Linkuje se nova putanja */


osAssert(link(srcPath, destPath)!= -1, "unlink failed");

/* Briše se fajl sa srcPath */


osAssert(unlink(srcPath) != -1, "unlink failed");
}
/*Treći način kako može da se prekopira fajl sa jedne na drugu putanju */
void osMoveFile3(const char* srcPath, const char* destPath) {

char *sPath = realpath(srcPath, NULL); /* Od relativne putanje pravimo apsolutnu za source putanju */
char *dPath = realpath(destPath, NULL); /* Od relativne putanje pravimo apsolutnu za destination putanju */

osAssert(sPath != NULL, "source file must exist");

/* Pre pozivanja strcmp moramo da se uverimo da nijedna putanja nije NULL. U suprotnom, može doći do segmentation fault-a*/
if (sPath != NULL && dPath != NULL) {
osAssert(strcmp(sPath, dPath) != 0, "File paths must be different");
}

/* Oslobađanje dinamički alociranih promenljivih */


free(sPath);
free(dPath);

/* Kreira se fajl na destPath */


osAssert(rename(srcPath, destPath) != -1, "rename failed");
}

49
Ovaj zadatak se radi pozivom funkcija iz prethodnih zadataka, tako da će bit navedene samo deklaracije
funkcija. Biće prikazano kako se implementira funkcija koja za nftw obilazi fajl rekurzivno.

#define SECS_PER_DAY (24*60*60)


#define UNUSED(x) ((void)x)

static const char* osUsage = "Usage: ./1 dirPath destPath";


static char* dirPath = NULL;

void osErrorFatalImpl(const char* userMsg, const char* fileName, const char* functionName, const int lineNumber);

unsigned osNumOfDaysFIleModified(const char* fpath); /* Računa pre koliko dana je modifikovan fajl */
void osMoveFile(const char* srcPath, const char* destPath); /* Premešta fajl sa jedne na drugu putanju */
void osMakePublicDir(const char* fpath); /* Pravi direktorijum */
bool osIsPublicFile(const char* fpath); /* Proverava da li ostali imaju prava pristupa za r i w */
/* Funkcija koja za nftw obilazi rekurzivno direktorijum*/
int processFile(const char* fpath, const struct stat *sb, int typeflag, struct FTW* ftwbuf);

int main(int argc, char** argv) {

osAssert(argc == 3, osUsage);
dirPath = argv[2];
osMakePublicDir(argv[2]);
osAssert(nftw(argv[1], processFile, 50, 0) != -1, "nftw failed");
exit(EXIT_SUCCESS);
}

int processFile(const char* fpath, const struct stat *sb, int typeflag, struct FTW* ftwbuf) {

UNUSED(sb);
if (typeflag == FTW_F) { /* Ukoliko je fajl reg. nastavljamo dalje sa izvrašavanjem */

if (osIsPublicFile(fpath)) { /* Ukoliko ostali imaju pravo r I w radimo sledeće */

unsigned days = osNumOfDaysFIleModified(fpath); /* Računamo kada je fajl poslednji put modifikovan */

if (days < 30) { /* Ukoliko je modifikovan u poslednjih 30 dana */

char* newPath = malloc(strlen(dirPath) + 2 + strlen(fpath + ftwbuf->base)); /* Alociramo prostor za novu putanju */


strcpy(newPath, dirPath); /* Pravimo novu putanju */
strcat(newPath,"/");
strcat(newPath, fpath + ftwbuf->base);

osMoveFile(fpath, newPath); /* Premeštamo dati fajl na tu novu putanju */

free(newPath); /* Oslobađamo memoriju */


}
else {

osAssert(unlink(fpath) != -1, "unlink failed"); /* Ukoliko je broj dana kada je fajl modifikovan veći od 30
brišemo taj fajl funkcijom unlink */
}
}
}

return 0;
}

50
VEŽBE 6

PROCESI

Proces je program u izvršavanju. Pitanje je šta svaki proces ima u memoriji, i kako se on
prikazuju. Svaki proces poseduje 4 segmenta:

1. kod segment → mašinske instrukcije koje procesor izvršava. Obično se ovaj deo često
izvršavanih programa kao što su tekstualni editori, C kompajler, shell nalazi u memoriji. U
ovom segmentu je obično dozvoljeno samo čitanje, da se ne bi desilo da program slučajno
modifikuje instrukcije.

2. Inicijalizovani segment podataka → obično samo segment podataka, sadrži promenljive koje su
inicijalizovane u programu, npr: int i = 0; a da se pritom inicijlizacija nalazi van funkcija
Neinicijalizovani segment podataka→ obično se naziva bss segment (dobio ime po asemblerskom
operatoru “block started by symbol”). Podaci u ovom segmentu su inicijalizovani na 0 ili na
null pokazivače pre izvršavanja programa. C deklaracija koja se nalazi van funkcija:
int s[10]; smešta podatke u ovaj segment.

3. Stek → gde se smeštaju automatske promenljive, zajedno sa informacijama koje se čuvaju svaki
put kada se pozove funkcija. Svaki put kada se funkcija pozove, adresa na koju treba da se
izvrši povratak i određene informacije o okruženju pozivaoca (npr. neki mašinski registri) se
čuvaju na steku. Nova funkcija zatim na steku alocira prostor za njene automatske i privremene
promenljive. Ovako funkcionišu rekurzivne funkcije u C-u. Svaki put kada rekurzivna funkcija
pozove samu sebe, novi stek okvir (stack frame) se koristi, tako da se skupovi promenljivih
različitih instanci funkcije ne mešaju.

4. Hip → u kome se vrši dinamička alokacija memorije.

51
U izvršnoj verziji programa na disku se čuvaju samo tekst segment i inicijalizovani segment
podataka.
Identifikatori procesa

Svaki proces ima jedinstven proces ID, nenegativan ceo broj. Kada se procesi završe,
njihovi ID-ovi postaju kandidati za ponovnu upotrebu. Većina UNIX sistema implementiraju
algoritme da odlože ponovnu upotrebu, tako da novokreirani procesi dobijaju ID-ove različite od
skoro završenih procesa. Ovo smanjuje mogućnost pojave grešaka.
Postoje neki specijalni procesi. Proces koji ima proces ID 0 je planer proces (scheduler)
i obično se naziva razmenjivač (swapper). Ovaj proces je deo kernela. ID 1 je proces koji se zove
init i pokreće ga kernel pri kraju procedure startovanja sistema. Program za ovaj proces je /sbin/init.
Ovaj proces se ne prekida i on je običan korisnički proces a ne sistemski proces u kernelu, iako
ima superuser privilegije. On kreira druge procese koje pokrećemo. Svaki proces osim init procesa
ima roditeljski proces.
Proces init pripada grupi procesa koje zovemo demoni. To su procesi koji se izvršavaju u
pozadini, a ne pod direktnom kontrolom korisnika. Najčešće se pokreću pri startovanju sistema, i
služe da konfigurišu hardver, reaguju na zahteve preko mreže itd. Svi procesi na sistemu su
organizovani kao stablo (nije binarno). Ispis drvolike strukture procesa se može dobiti
komandom pstree (man 1 pstree).

#include <unistd.h>

pid_t getpid(void); → vraća ID procesa


pid_t getppid(void); → vraća ID roditeljskog procesa

uid_t getuid(void); → vraća real user ID


uid_t geteuid(void); → vraća effective user ID

gid_t getgid(void); → vraća real group ID


gid_t getegid(void); → vraća effective group ID

FORK

Postojeći proces može da kreira novi proces pozivom fork funkcije:

pid_t fork(void);

Novi proces se zove dete proces, a proces koji ga je kreirao roditeljski proces. Kaže se da
se ova funkcija poziva jednom a vraća dvaput. To u stvari znači da se kod programa počev od prve
naredbe posle fork izvršava dva puta – u detetu procesu i u roditeljskom procesu. Jedina
razlika je da je povratna vrednost funkcije fork u detetu 0, a u roditeljskom procesu je proces
ID deteta. Razlog za ovo je što roditelj može imati više od jednog deteta, a ne postoji funkcija koja
omogućava procesu da dobije proces ID-ove svoje dece. U detetu se vraća 0 jer dete može imati
samo jednog roditelja, i uvek može ID tog roditelja dobiti pomocu getppid funkcije.

I roditelj i dete nastavljaju izvršavanje od instrukcije koja sledi iza fork. Dete je kopija
roditelja. Dete dobija kopiju roditeljskog prostora za podatke, hip i stek dok dele isti
tekst segment. (Današnje implementacije ne rade potpuno kopiranje već omogućavaju samo
čitanje na zajedničkoj memoriji, a ukoliko je potrebno izmeniti neke podatke, onda se pravi kopija).

52
Ako je memorija dinamički alocirana pre poziva fork, tada i dete i roditelj imaju kopije
dinamički alocirane memorije, pa je potrebno uraditi oslobađanje memorije u oba slučaja.

U opštem slučaju, nikada ne znamo da li se prvo izvršava roditelj ili dete. Ovo zavisi od
algoritma koji koristi kernel. Neophodno je da roditelj čeka nekoliko sekundi da bi bili sigurni da će
se dete završiti pre roditelja. Standardna U/I biblioteka je baferisana. Standardni izlaz je linijski
baferisan ukoliko je povezan sa terminalom, a inače je puno baferisan. U slučaju preusmeravanja,
printf se poziva jednom, pre fork, ali linija ostaje u baferu i kopira se u dete proces. Funkcija printf
na kraju programa nadovezuje novi sadržaj na sadržaj bafera, i kada se procesi završe njihovi baferi
se konačno prazne. Tako da se pri preusmeravanju tekst koji se ispisuje pre fork poziva prikazuje 2
puta.

Pri forkovanju svi fajl deskriptori koji su otvoreni kod roditelja dupliraju se za dete. Važno
je da roditelj i dete dele isti offset fajla. To omogućava da roditelj sačeka dete, dete nešto ispiše i
posle toga roditelj nadoveže ispis na detetov. Ukoliko roditelj i dete pišu po istom deskriptoru, bez
sinhronizacije, njihov izlaz će biti izmešan.

Dva glavna razloga da fork ne uspe su:

a) već ima previše procesa na sistemu


b) maksimalan broj procesa za ovaj real user ID je prekoračen.

Funkcija fork se koristi u dva slučaja:

a) Kada proces hoće da duplira sebe kako bi roditelj i dete obavljali različite sekcije koda u
isto vreme – ovako funkcionišu serveri.
b) Kada proces hoće da izvrši drugi program – ovako funkcioniše shell.

53
hello_process.c

int main() {

/* Pravimo novi proces, tako što koristimo sistemski poziv fork ako želimo da saznamo više kucati man 2 fork */
pid_t childPid = fork();

/* Kada god koristimo fork moramo da proverimo povratnu vrednost. Ako je vraćeno -1 došlo je do greške.
Nakon što pozovemo fork sve naredbe nakon toga su zajedničke za oba procesa */
osAssert(-1 != childPid, "Creating child process failed");

/* Ako želimo da radvojimo izvršavanje da se nešto izvršava u jednom procesu, a nešto u drugom moramo da koristimo if else
granu. Ukoliko je childPid > 0 znači da se nalazimo u parent grani i ispisujemo ID od child procesa i ID od roditeljskog procesa.
Dok u else grani štampamo poruku da se nalazimo u detetu i štampamo njegov ID a potom i roditeljski ID uz pomoć funkcije
getppid(). Pid-ovi se prave tako što operativni sistem zna koji je poslednji pid kome nikome nije dodeljen, i taj pid dodeljuje
novom procesu. Ako se na primer neki proces završi i ima npr. ID = 1124, ne dodeljuje se odmah taj ID nekom novom procesu,
jer pre toga tabela procesa mora da se osveži. Tek nakon što se tabela procesa osveži taj ID može biti dodeljen novom procesu */

if (childPid > 0) { /* Ukoliko je uslov ispunjen roditelj izvršava ovu granu. */


printf("Hello from parent, child pid is %jd and my pid is %jd.\n", (intmax_t)childPid, (intmax_t)getpid());
sleep(2);
} else { /* Dete izvršava ovu granu */
printf("Hello from child, my pid is %jd and parent has pid %jd.\n", (intmax_t)getpid(), (intmax_t)getppid());
}

/* I jedan i drugi će nakon ovoga ispisati ovaj printf. Child će izvršiti sve instrukcije u svojoj grani i nakon toga će izvršiti
sve instrukcije ispod te grane. Isto važi i za parent-a */
printf("We both execute this instruction!\n");
return 0;
}

hello_process2.c

int main() {

/* Pravimo novi proces, tako što koristimo sistemski poziv fork, ako želimo da saznamo više kucati man 2 fork */
pid_t childPid = fork();

/* Kada god koristimo fork moramo da proverimo povratnu vrednost. Ako je vraćeno -1 došlo je do greške.
Nakon što pozovemo fork sve naredbe nakon toga su zajedničke za oba procesa */

osAssert(-1 != childPid, "Creating child process failed");

/* Ako želimo da radvojimo izvršavanje da se nešto izvršava u jednom procesu, a nešto u drugom moramo da koristimo if else
granu. Ukoliko je childPid > 0 znači da se nalazimo u parent grani i ispisujemo ID od child procesa i ID od roditeljskog procesa.
Dok u else grani štampamo poruku da se nalazimo u detetu i štampamo njegov ID a potom i roditeljski ID uz pomoć funkcije
getppid().
Ukoliko želimo da prekinemo neki proces, to možemo da uradimo sa exit(). Ako pozovemo exit() za neki od ova dva procesa,
taj proces se prekida. Problem nastaje ukoliko se child završi, a roditelj ga nije sačekao. Njegov kontrolni blok je i dalje alociran
i on postaje zombi proces. Zombi procesi zauzimaju sve moguće ID-jeve i mi ne možemo da forkujemo nove procese, zbog toga
pozivamo funkciju wait koja će da sačeka izvršavanje child-a. Wait je blokirajuća funkcija, naš proces će da stoji zaglavljen
sve dok se neki child ne završi.*/

if (childPid > 0) {/* Roditelj izvršava ovu granu */


printf("Hello from parent, child pid is %jd and my pid is %jd.\n", (intmax_t)childPid, (intmax_t)getpid());
osAssert(-1 != wait(NULL), "Waiting for child to finish");
} else { /* Dete izvršava ovu granu */
printf("Hello from child, my pid is %jd and parent has pid %jd.\n", (intmax_t)getpid(), (intmax_t)getppid());
exit(0);
}

/* Ovo će ispisati samo parent, jer smo child proces prekinuli sa exit() */
printf("Only parent executes this instruction!\n");
return 0;
}

54
EXIT funckije

Nezavisno od toga kako se proces završi, u kernelu se izvršava isti kod. Prvo se zatvaraju svi
otvoreni deskriptori za proces, oslobađa memorija koja je korišćena i slično. Roditelj se obaveštava
o načinu prekida deteta procesa pomoću exit statusa, a ukoliko je došlo do neuobičajenog prekida,
kernel generiše status prekida koji roditelj može dobiti pomoću wait ili waitpid funkcija.

Šta se dešava ukoliko se roditelj završi pre deteta? Proces init nasleđuje to dete (siroče), tj.
postaje njegov novi roditelj. Ono što se dešava je da bilo kad da se proces završi, kernel prolazi kroz
sve aktivne procese da vidi da li je proces koji se završio roditelj nekog procesa koji još postoji.
Ukoliko takav dete proces postoji, njegov roditeljski proces ID se postavlja na 1. Ovako se
garantuje da svaki proces ima roditelja.

Kada se dete završi pre roditelja, pošto kernel čuva informacije o svakom procesu koji se
završava u tabeli procesa, roditelj može da dobije ove informacije pozivom wait ili waitpid. Proces
koji se završio, ali čiji roditelj još nije čekao na njega (nije pozvao wait ili waitpid) se zove
zombi (neformalno, postojanje zombija označava “nezainteresovanost” roditelja da sazna kako je
završen dete proces). Iako se taj proces završio, informacije o njemu još uvek će postojati u tabeli
procesa. Ako neki zombi postoji duži period vremena, onda najverovatnije postoji bag u
roditeljskom programu. Ako bi se roditeljski proces završio a zombi i dalje postojao, to bi
značilo da postoji greška u operativnom sistemu. Trebalo bi uvek obezbediti da roditeljski
proces čeka na dete proces. Prisustvo par zombija ne ugrožava sistem, ali prisustvo većeg broja
može uzrokovati da ne postoje slobodni proces ID-ovi za nove procese (do problema sa memorijom
ne dolazi jer je jedini prostor koji je potreban onaj u tabeli procesa).

wait i waitpid funkcije

Kada se proces završi, bilo regularno ili neregularno, kernel šalje SIGCHLD signal
njegovom roditelju. Podrazumevana akcija za ovaj signal je ignorisanje. Proces koji poziva wait ili
waitpid funkciju može da:
a) blokira, ako su sva deca i dalje aktivna
b) se vrati momentalno sa statusom prekida deteta, kada je dete završilo sa radom
c) se vrati momentalno sa greškom, ukoliko proces nema nijedan dete proces
Ukoliko pozivamo wait jer dolazi do SIGCHLD signala, očekujemo da se wait momentalno vrati.
Ali ukoliko je zovemo u proizvoljnom trenutku vremena, ona može da blokira dalje izvršavanje
programa.

pid_t wait(int *statloc);


pid_t waitpid(pid_t pid, int *statloc, int options);

Razlike između ove dve funkcije su:


a) wait funkcija može da blokira pozivaoca dok se dete proces ne završi, dok waitpid ima
opciju koja sprečava blokiranje
b) waitpid ne čeka dete koje se prvo završava, već opcije određuju decu na koje čeka
Ukoliko se dete već završilo i postalo zombi, wait se momentalno vraća sa statusom deteta. Inače,
blokira pozivaoca dok se dete ne završi. Ukoliko ima više dece, wait se vraća kada se bilo koje od
njih završi.

Za obe funkcije se u argument statloc smešta status prekida procesa. Pojedinačni bitovi označavaju
različite stvari, a razloge prekida određujemo preko makroa.

55
Napomena: Ukoliko funkcija exit vrati 0,onda kažemo da se taj proces uspešno završio. Vraćanje
neke druge vrednosti označava neuspeh (najčešće 1). Ovo treba razlikovati od normalnog i
nenormalnog završetka procesa: svaki završetak procesa pozivom exit funkcije smatra se
normalnim završetkom, dok se nenormalnim smatra prekid procesa usled primanja nekog signala.

U zavisnosti od argumenta pid waitpid funkcija čeka na različite procese:


a) pid == 1 → čeka na bilo koje dete proces. U ovom slučaju ekvivalentna je wait funkciji
b) pid > 0 → čeka na dete proces čiji ID je jednak pid
c) pid == 0 → čeka na bilo koje dete proces čiji je proces group ID jednak onom od pozivajućeg
procesa
d) pid < 1 → čeka na bilo koje dete čiji je proces group ID jednak apsolutnoj vrednosti
pidArgument options dalje kontroliše waitpid. On je ili 0 ili se dobija bitskim ili
konstanti iz tabele.

Funkcija waitpid omogućava neblokirajuću verziju wait funkcije.

Da li je potrebno da se kopira kompletan adresni prostor? Kod segment i segment podataka ne


moramo da kopiramo. Kod segment treba u memoriji da označimo kao read only i onda programi
ne mogu da menjaju sami sebe. Treba da se uverimo da zaista dobijaju kompletnu kopiju sebe.

processes_and_vars.c

int main() {

/* Promenljiva postoji jedino u parent-u jer child još uvek nije definisan */
int var = 17;
printf("Before: var= %d\n", var);

/* Kreiramo novi proces */


pid_t childPid = fork();

/* Proveravamo da li je fork uspeo */


osAssert(-1 != childPid, "Creating child process failed");
if (childPid > 0) { /* Parent izvršava ovu granu */
/* U ovoj grani nećemo dirati promenljivu var, sačekaćemo da se child završi */
osAssert(-1 != wait(NULL), "Waiting for child to finish");
} else { /* Child proces izvršava ovu granu */
/* U ovoj grani ćemo da modifikujemo var promenljivu pomnožićemo je sa dva i ispisati njenu vrednost na standardni izlaz,
i potom ćemo da prekinemo izvršavanje child procesa */
var *= 2;
printf("Child: var= %d\n", var);
exit(0);
}

/* Parent će izvršavati narednu naredbu, nakon što je child modifikovao. Izlaz nam pokazuje da child dobija svoju kopiju
promeljive */
printf("Parent: var= %d\n", var);
return 0;
}

Možemo da kreiramo dete proces i da se taj proces npr. normalno završi ili da ga nasilno
prekinemo, ili npr. da delimo sa nulom. Potom možemo da proverimo informacije o završetku tog
deteta procesa na sledeći način:

int main(int argc, char **argv){


pid_t pid; /* identifikator procesa */
int status; /* informacija o završetku dete procesa */

/* kreitra se dete proces koji se normalno završava */

56
if((pid = fork())<0)
error_fatal(argv[0]);
else if(pid==0)
exit(42);
if(wait(&status) != pid)
error_fatal(argv[0]);
status_print(status);

return 0;
}
static void status_print(int status){
if(WIFEXITED(status)) /* da li se proces završio normalno */
printf("normal termination, exit status = %d\n",WEXITSTATUS(status) );
else if(WIFSIGNALED(status)) /* da li se završio nekim signalom */
printf("abnormal termination, signal number = %d\n",WTERMSIG(status) );
else if(WIFSTOPPED(status)) /* da li je proces stopiran */
printf("child stopped, signal number=%d\n",WSTOPSIG(status) );
}

Jednostavna komunikacija između deteta i roditelja - PIPE

Roditeljski i dete proces često moraju da razmenjuju informacije – zgodno je koristiti PIPE.
PIPE možete zamisliti kao JEDNOSMERNU ulicu ograničenog kapaciteta (može stati X
automobila). Smer ulice može biti i na jednu i na drugu stranu, međutim kada se odlučimo nema
menjanja smerova.
Automobili odgovaraju bajtovima koji se razmenjuju, smer odgovara toku komunikacije
1) dete → roditelj ili
2) roditelj → dete

Pipe-ovi se koriste za razmenu poruka među procesima.

Npr. Da bi izvršili komandu $ ls | wc – l shell kreira dva procesa, jedan izvršava ls komandu
a drugi wc komandu. Pipe možemo da zamislimo kao jednu cev koja propušta podatke da idu od
jednog procesa ka drugom u jednom smeru.

Korišćenje pipe-a da poveže dva procesa

Da bi ovo radilo svaki od procesa mora da ukloni stranu koja mu nije potrebna. ls će u ovom
slučaju zatvoriti pipe za čitanje, jer ona koristi pipe za pisanje. Dok će sa druge strane wc komanda
da zatvori stranu za pisanje, jer će koristiti pipe za čitanje onoga što joj je ls komanda poslala. I na
taj način je napravljena komunikacija u jednom smeru između procesa. Nakon što svaki proces
završi sa radom dužan je da zatvori onu stranu koju je koristio.

Pipovi su predstavljeni kao fajl deskriptori. Hoćemo da koristimo dva fajl deskriptora koje
mi napravimo. Kažemo fork, pošto su to fajl deskriptori oba dobijaju kopiju, mi sami treba da
odredimo koja strana će da bude ulazna a koja izlazna. Jedna strana će da zatvori pipe za čitanje, a
druga za pisanje. Potom oni nešto rade prosleđuju i na kraju treba da zatvore onu stranu koju su
koristili. Pipe je uvek jednosmerna komunikacija. Jednu stranu moramo da ubijemo. Prvo

57
napravimo pipe i jedan i drugi imaju oba fajl deskriptora. Pošto izvršimo fork i potrebno je da
uklonimo onu stranu koja nam ne treba. Tako da naša poruka može da putuje u jednom smeru.

Ideja zadatka je da parent čita neku poruku prosleđuje je detetu i ono tu poruku štampana standardni
izlaz.

simple_pipe.c

#define PIPE_RD_END (0)


#define PIPE_WR_END (1)
#define MAX_LINE_LEN (128)

int main() {
/* Na početku moramo prvo da kreiramo pipe. I roditelj i dete će dobiti kopije fajl deskriptora. Ukoliko pipe kreiramo
nakon fork()-a samo će jedan od njih imati pristup fajl deskriptorima tog pipe-a. Da ne bismo pomešali koja nam je koja strana
definisaćemo makroe. PIPE_RD_END (0) i PIPE_WR_END (1) */

/* Potreban nam je niz od dva int-a, pipe pravimo pozivom funkcije pipe kojoj prosleđujemo ovaj niz. Pošto je pipe sistemski
poziv moramo da proverimo da nije slučajno došlo do greške, ukoliko je vraćeno -1 */
Int pipeFds[2];
osAssert(-1 != pipe(pipeFds), "Pipe creation failed");

/* Pozivamo fork(). Kreiramo dete proces. Nakon toga je potrebno da proverimo da li se dogodila greška prilikom forkovanja.
Razdvajamo slučaj za parenta i slučaj za child. Pošto svako od njih dobija kopije fajl deskriptora ovog pipe, moramo da
zatvorimo odgovarajuću stranu koja nam nije potrebna. */
pid_t childPid = fork();
osAssert(-1 != childPid, "Creating child process failed");

/* U roditeljskoj grani čitamo liniju sa ulaza i šaljemo je kroz pipe našem detetu.*/
if (childPid > 0) { /* Roditelj izvršava ovu granu */
/* Roditelj ništa ne čita iz pipe, zato zatvaramo stranu za čitanje od pipe-a.*/
close(pipeFds[PIPE_RD_END]);

/* Ovde ćemo da čitamo neku liniju sa standardnog ulaza i nju treba da prosleđujemo našem detetu */
char *line = NULL;
size_t lineLen = 0;
osAssert(-1 != getline(&line, &lineLen, stdin), "Reading line failed");

/* Upisujemo tu liniju u pipe, i čekamo da se dete izvrši */


osAssert(lineLen <= MAX_LINE_LEN, "Line longer then allowed");
osAssert(-1 != write(pipeFds[PIPE_WR_END], line, lineLen), "Write failed"); /* Pišemo u pipe, liniju koju smo pročitali */
osAssert(-1 != wait(NULL), "Wait failed");

/* Nakon što smo završili sve što je bilo potrebno zatvaramo onu stranu koju smo koristili */
close(pipeFds[PIPE_WR_END]); /* Zatvaramo file descriptor za pisanje */
free(line);

} else { /* Dete izvršava ovu granu */


/* Čitamo liniju iz pipe-a i ispisujemo je na standardni izlaz. Pošto nam nije potreban write end strana od pipe zatvaramo je*/
close(pipeFds[PIPE_WR_END]);
char buf[MAX_LINE_LEN];
osAssert(-1 != read(pipeFds[PIPE_RD_END], buf, sizeof buf), "Reading line from pipe failed");
close(pipeFds[PIPE_RD_END]); /* Zatvaramo file descriptor za pisanje */

/* NAPOMENA: Sistemski pozivi read() i write() rade sa bajtovima. To znači da ukoliko se poruka šalje sistemskim pozivom
write nema terminirajuću nulu na samom kraju, naš bafer neće imati terminirajuću nulu. U ovom primeru šaljemo liniju
sa sve '\n' i '\0'. U nekom drugom primeru možda neće biti isti slučaj tako da o tome treba voditi računa */
printf("The line:\n%s", buf);
exit(0);
}

return 0;
}

58
EXEC funckija

Ukoliko želimo da pozovemo neki program koji je neko drugi napisao to nećemo moći da
pokrenemo kao što smo pokretali na prethodne načine. Da bi se to izbeglo koristi se familija
funkcija koja se zove exec().

Kada pokrećemo neki drugi proces exec() drugačije funkcioniše. Ako imamo neki proces u
kome smo nešto radili i dođe poziv exec() Šta se događa? Kompletan adresni prostor se menja
novim adresnim prostorom tog novog procesa koji smo pokrenuli. Npr. ls ili gcc …

Postoji 6 razlicitih exec funkcija, kada kažemo exec mislimo na bilo koju od ovih funkcija:

int execl(const char *pathname, const char *arg0, ... /* (char *)0 */ );
int execv(const char *pathname, char *const argv []);
int execle(const char *pathname, const char *arg0, … /* (char *)0, char *const envp[] */ );
int execve(const char *pathname, char *const argv[], char *const envp []);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );
int execvp(const char *filename, char *const argv []);

Funkcija exec zamenjuje proces iz koga je pozvana, izvršavanjem programa čije je ime
navedeno kao prvi argument te funkcije. Kad neki proces pozove exec, kernel odbacuje njegov kod
segment, data segment, stek. Pronalazi se odgovarajući izvršni fajl koji je prvi argument exec
funkcije, kernel čita njegove segmente, pravi nove za naš proces i vrši pripreme za nastavljanje
novog procesa. Kod koji se exec-uje nasleđuje puno stvari od prethodnog procesa, id procesa, id
roditeljskog procesa, radni direktorijum, itd.

Prve 4 exec funkcije uzimaju argument pathname dok poslednje 2 uzimaju argument
filename. U slučaju da filename sadrži karakter '/', uzima se kao pathname . Inače se izvršni fajl
traži u direktorijumima koji se nalaze u PATH promenljivoj(echo $PATH). Najčešće biramo
funkciju iz ove dve grupacije na sledeći način:
1) ako izvršavamo neki program koji smo mi napisali koristimo neku od prve 4 funkcije
2) ako izvršavamo neku ugrađenu komandu koristimo neku od posledje 2 funkcije

Druga razlika je u prenosu argumenata. Funkcije execl, execlp i execle zahtevaju da se svaki
od argumenata prenese kao poseban argument. Kraj se označava null pokazivačem, tj. sa (char *)0.
Za ostale 3 funkcije mora da se napravi niz pokazivača na argumente, i adresa ovog niza je
argument ovim trima funkcijama.

Treća razlika je u prosleđivanju environment liste promenljivih programu. Funkcije koje se


završavaju sa 'e', omogućavaju nam da prenesemo pokazivač na niz pokazivača na environment
stringove. Ostale 4 funkcije koriste environ promenljivu.

Teško je zapamtiti šta koja funkcija od ovih radi. Slova u imenima funkcija mogu pomoći.
Slovo ‘p’ označava da funkcija uzima argument filename i koristi PATH promenljivu da nađe
izvršni fajl. Slovo ‘l’ označava da funkcija uzima listu argumenata, a slovo ‘v’ označava da je
argument vektor argv[]. Slovo ‘e’ označava da funkcija uzima envp[] niz umesto da koristi trenutni
environment.

U većini UNIX implementacija, samo je jedna od ovih 6 funkcija, execve, sistemski poziv.
Ostalih 5 su bibliotečke funkcije koje koriste ovaj sistemski poziv.
Funkcija exec se jednom poziva i ne vraća se. Dakle, nema povratka u kod iz koga je pozvan
(povratak je jedino moguć ako exec nije uspeo).

59
hello_exec.c

int main() {
/* Exec funkcija zamenjuje kompletni adresni prostor trenutnog procesa, procesom koji pokrećemo – u ovom slučaju 'ls'.
NAPOMENA: funkcija iz exec familije koje sadrže 'p' pretražuju program u PATH promenljivi i iz tog razloga će ls u ovom
slučaju biti pronađen. Probajte da zamenite liniju ispod sa:
osAssert(-1 != execl("ls", "ls", "-la", NULL), "Starting ls failed"); da vidite šta će se desiti. */

osAssert(-1 != execlp("ls", "ls", "-la", NULL), "Starting ls failed");


/* Adresni prostor je zamenjen tako da se naredna linija neće izvršiti */
printf("Not executed message instruction!\n");
return 0;
}

create_process.c
Ideja zadatka je da korisnik prosledi preko komandne linije koju komandu on želi da izvrši,
child treba tu komandu da izvrši.

int main(int argc, char **argv) {


/* Proveravamo broj argumenata */
osAssert(argc >= 2, os_Usage);
pid_t processHandle = osCreateProcessFromArgv(argc, argv); /* Pozivamo funkciju koja treba da izvrši */

int status; /* Ovde treba da sačekamo da se dete proces izvrši i to radimo sa waitpid funkcijom */
osAssert(-1 != waitpid(processHandle, &status, 0), "Waiting for child process failed");
/* Treba da proverimo da li se naš proces izvršio kako treba, odnosno treba da proveravamo status završetka */
printf("Program %s exited with code %d.\n", argv[1], WEXITSTATUS(status));
return 0;
}

pid_t osCreateProcess(char **programNameAndArgs) {


/* Kreiramo dete proces – ono će izvršiti program koji nam je poslao korisnik */
pid_t childPid = fork();
if (-1 == childPid)
return -1;
/* Child adresni prostor menja se sa adresnim programom koji treba da se izvrši */
if (0 == childPid) { /* Grana koju izvršava dete */
osAssert(-1 != execvp(programNameAndArgs[0], programNameAndArgs+1), "Starting program failed");
return 0; /* Ova linija se nikada neće izvršiti */
} else { /* Grana koju izvršava roditelj */
return childPid;
}
}

60
pid_t osCreateProcessFromArgv(const int argc, char **argv) {

/* Treba da alociramo memoriju za niz stringova */


char **programNameAndArgs = malloc((argc + 1)* sizeof(char*));
osAssert(NULL != programNameAndArgs, "Allocation of programNameAndArgs failed");

/* Treba da sačuvamo putanju do fajla i ime tog fajla u nizu, tako da ćemo prvo alocirati memoriju za prvi član niza na kome
će da se nalazi putanja do fajla.*/
programNameAndArgs[0] = malloc(strlen(argv[1]) + 1);

osAssert(NULL != programNameAndArgs[0], "Allocating storage for program path failed");

strcpy(programNameAndArgs[0], argv[1]);

/* Drugi član niza biće ime programa koji treba da se pokrene */


char *fileNamePtrCandidate = strrchr(argv[1], '/');
char *fileNamePtr = fileNamePtrCandidate ? fileNamePtrCandidate+1 : argv[1];

programNameAndArgs[1] = malloc(strlen(fileNamePtr) + 1);

osAssert(NULL != programNameAndArgs[1], "Allocate storage for program name failed");

strcpy(programNameAndArgs[1], fileNamePtr);

/* Pošto smo sačuvali putanju i ime programa, neophodno je da sačuvamo u nizu i ostale argumnte koji nam je korisnik
posalao*/

for (int i = 2; i < argc; ++i) {

programNameAndArgs[i] = malloc(strlen(argv[i]) + 1/*'\0'*/);


osAssert(NULL != programNameAndArgs[i], "Allocation of programNameAndArgs[i] failed");
strcpy(programNameAndArgs[i], argv[i]);

programNameAndArgs[argc] = NULL;

/* Pozivamo funkciju koja kreira dete koje će izvršiti execv funkciju. Ovoj funkciji prosleđujemo niz sa svim argumntima */

pid_t processHandle = osCreateProcess(programNameAndArgs);


osAssert(-1 != processHandle, "Creation of process failed");

/* Oslobađamo memoriju i vraćamo pid */

for (int i = 0; i <= argc; ++i)


free(programNameAndArgs[i]);

free(programNameAndArgs);

return processHandle;
}

61
62
VEŽBE 7

Na ovim vežbama nastavljamo da se bavimo interprocesnom komunikacijom. Jednu vrstu


interprocesne komunikacije čine pipe-ovi. To je interprocesna komunikacija između roditelja i
deteta. Mana pipe-a je što možemo da ga koristimo samo u relaciji roditelj dete. Zbog toga uvodimo
prekide i signale.
Koncept prekida i signala

Osnovni primer gde se koristi ovaj koncept jeste primer sa mišem i tastaturom. Kada
pritisnemo taster na tastaturi ili pomeramo miš to odgovara događaju koji možemo da pratimo na
ekranu.

Procesori savremenih računarskih sistema imaju mogućnost rada u bar dva režima rada:
korisničkom (user mod) i sistemskom (supervisor, kernel mod). U sistemskom režimu moguće je
izvršiti sve instrukcije. Kada se dogodi neki prekid, naš procesor treba da pređe u sistemski režim
rada. Postoje prekidi na hardverskom nivou (ovim se nećemo baviti) i na softverskom. U primeru
koji ćemo raditi definisaćemo neku funkciju koja se aktivria samo kada se pošalje neki signal.

Signali su obaveštenja procesu da se neki događaj desio. Mogu se opisati i kao softverski
prekidi. Analogni su hardverskim prekidima, jer prekidaju noramalan tok izvršavanja programa. U
većini slučajeva nije moguće predvideti kada će se desiti neki signal.

Jedan proces može da pošalje signal drugom procesu. Signali se na ovaj način mogu koristiti
za tehnike sinhronizacije ili za interprocesnu komunikaciju. Takođe je moguće da proces pošalje
signal samom sebi. Ali obično većina signala koji su poslati nekom procesu dolaze od strane
kernela. (npr. ukoliko se deli nulom, ili se pristupa memoriji kojoj ne sme da se pristupa, ukoliko
korisnik naglo prekine izvršavanje nekog programa…)

Svaki signal se definiše kao unique (small) integer, koji počinje od 1. Ovi integer-i su
definisani u <signal.h> datoteci, čije ime počinje sa SIGXXXX. Npr. kada korisnik želi da prekine
izvršavanja programa, ispaljuje se signal SIGINT (signal br. 2).

Signale delimo u dve katergorije:


1. standardni signali → koje koristi kernel kako bi obaveštavao procese događaja (num. 1 do 31 )
2. realtime signali

Nakon što se signal ispali, proces može da reaguje na različite načine u zavisnosti koji signal je u
pianju:

1. signal se ignoriše, nema nikakav efekat na proces


2. proces se prekida (abnormal process termination)
3. dogodi se core dump, a proces prekida sa izvršavanjem
4. proces se stopira, izvršavanje procesa je suspendovano
5. izvršavanje procesa se nastavlja nakon što je prethodno bio zaustavljen

Program može da reaguje, tako što će se ponašati drugačije nakon što je signal ispaljen. Na
rapolaganju su nam neke akcije koje mogu da se izvršavaju nakon što se ispali signal:

1. default action → treba da se dogodi


2. signal se ignoriše → ovo je korisno kod signala koji će da prouzrokuju prekidanje procesa
3. signal handler → se izvršava

63
Signal hadler je funkcija pisana od strane programera, koja na odgovarajući način reaguje
kada se ispali neki signal. Npr. shell ima handler za SIGINT signal (Contol + C) koji treba da
prekine ono što se trenutno izvšava i da kontrolu preda main input loop-u.

Postoje različiti tipovi signala: SIGABRT, SIGCHLD, SIGINFO, SIGINT, SIGKILL, SIGPIPE,
SIGSTOP… (detaljnije o tipovima signala i njihovim objašnjenjima možete pronaći u TLPI
polavlje 20.2.).

Kada se dogodi neki signal, ukoliko postoje handler funkcije koje treba da reaguju na te
signale one se pozivaju u tom trenutku. Pozivanje handler funkcija može da prekine tok izvršavanja
main funkcije u nekom određenom trenutku kada je signal ispaljen. Kernel poziva handler funkicju
koja treba da se izvrši. Nakon što handler funkcija završi sa svojim izvršavanjem, izvršavanje
programa se nastavlja od tačke gde je handler prekinuo tok izvršavanja main funkcije. Sledeća slika
to najbolje ilustruje:

Slanje signala

Jedan proces može da pošalje signal drugom procesu koristeci kill() sistemski poziv, koji je
analogan kill shell komandi. Za rad sa signalima potrebno je da uključimo zaglavlje #include
<signal.h>. Signale iz komandne linije šaljemo komandom kill(). Ovaj sistemski poziv vraća 0
ukoliko je uspeo, inače -1 ukoliko je došlo do greške.

Za više informacija kucati man 2 kill.

int kill(pid_t pid , int sig );

Da bi videli sve signale i njihove vrednosti potrebno je u terminalu kucati kill -l.

Ideja zadatka je da napišemo handler funkcije koje će hvatati neke određene signale. U
ovom slučaju obrađujemo signale SIGINT i SIGTERM. Potrebno je da pokrenemo program
./signal. Na početku programa ispisaće nam se PID tog procesa koji je upravo pokrenut. Ukoliko
pozovemo Control + C biće poslat signal SIGINT i naš program će reagovati na taj signal. Ukoliko
želimo da ubijemo ovaj proces potrebno je iz drugog terminala da kucamo komandu kill -15
pid_procesa, čime se šalje signal SIGTERM i naš program će biti prekinut.

64
signals.c

/* Da bi mogli da korsitimo signale moramo da uključimo datoteku <signal.h> */


#include <signal.h>

void osErrorFatalImpl(const char* userMsg, const char* fileName, const char* functionName, const int lineNumber);

bool osShouldTerminate = false;


/* handler za SIGINT signal */
void osHandleSIGINT(int signum);
/* handler za SIGTERM signal */
void osHandleSIGTERM(int signum);
/* handler za obrađivanje oba signala*/
void HandleSig(int signum);

int main(int argc, char** argv) {

/* Moguće je postaviti različite signal handler-e za svaki signal ponaosob. */


//osAssert(signal(SIGINT, osHandleSIGINT) != SIG_ERR, "SIGINT handler setup failed");
//osAssert(signal(SIGTERM, osHandleSIGTERM) != SIG_ERR, "SIGTERM handler setup failed");

/* Ali, moguće je i da isti handler obrađuje više signala.


Kao prvi argument se šalje ime signala koji šaljemo i onda prosleđujemo handler koji će ga obrađivati */

osAssert(signal(SIGINT, HandleSig) != SIG_ERR, "SIGINT handler setup failed");


osAssert(signal(SIGTERM, HandleSig) != SIG_ERR, "SIGTERM handler setup failed");

/* Štampamo pid procesa da bismo znali kome da šaljemo signale. Signale iz komandne linije šaljemo alatkom kill,
da bi videli spisak svih signala i njihovih vrednosti potrebno je u terminalu da kucamo kill -l. Da bi uhvatili SIGTERM
neophodno je iz drugog terminala da unesemo komandu kill ime_signala pid.
BITNO: Ekvivalentan sistemski poziv postoji i u C-u, kucati man 2 kill */

printf("PID is: %jd\n",(intmax_t)getpid());


do{
printf("waiting to signal!\n");
/* pause se koristi za čekanje na signal
BITNO: čekanje sa pause() nije aktivno. Pause blokira naš proces i on čeka u redu blokiranih procesa */
pause();
printf("signal caught\n");
}while(!osShouldTerminate);
exit(EXIT_SUCCESS);
}
/* Handler funkcija koja reaguje na signal SIGINT */
void osHandleSIGINT(int signum){
printf("SIGINT caught\n");
}
/* Handler funkcija koja reaguje na signal SIGTERM */
void osHandleSIGTERM(int signum){
printf("SIGTERM caught\n");
osShouldTerminate = true;
}
void HandleSig(int signum){

/* Možemo da postavimo da nam jedan signal hendler obrađuje oba signala:


Argument funkcije je redni broj signala i može se koristiti za otkrivanje o kom signalu je reč
BITNO: U signal handler-ima moraju da se koriste funkcije koje su reentrant safe, tj. one koje garantuju korektan
nastavak izvršavanja nakon prekida.
Napisani primer koristi printf koji nije reentrant safe i služi samo kao ilustracija koncepta. U realnim aplikacijama
nikada ne koristi nešto što nije reentrant safe funkcija.*/
switch (signum) {
case SIGINT:
printf("SIGINT caught\n"); break;
case SIGTERM:
printf("SIGTERM caught\n");
osShouldTerminate = true; break;
default:
break;
}
}

65
Gde je dominatna upotreba ovog mehanizma sa signalima? Kada smo radili sa zombi
procesima, koristili smo funkciju wait(). Svaki put smo blokirali naš proces dok god se neki child
proces ne završi. To je jedan od retkih mehanizama gde možemo da koristimo signale, jer svaki od
child procesa kada se završi šalje SIGCHLD signal i naravno kada god se dogodi SIGCHLD tada
bi trebalo da ga sačekamo i obradimo. Naš program onda ne mora da čeka blokiran dok se neki
child proces ne završi. To je jedna od upotreba gde se mogu koristiti signali.

Naš proces kada pređe u sistemski režim rada obrađuje signale, nakon izlaska iz njega se
uvek poziva sheduler koji će po nekom svom unutrašnjem algoritmu da odredi koji proces iz
sistema će se naredni izvršavati. To može da bude naš proces ili neki drugi proces. Kada radimo sa
signalima ideja je da se uvek koriste funkcije koje su reentrant safe. Odnosno kada se ta funkcija
prekine nastavak izvršavanja će biti korekran. U slučaju printf() funkcije to nije tako. printf()
koristi standardnu biblioteku. U standardnoj biblioteci baferi su statički alocirani. Kada se dogodi
promena konteksta neki drugi proces može da koristi printf() funkciju i da preko našeg bafera nešto
prepiše. Kada se vrati kontrola našem procesu dobićemo ispisivanje nečega što nije ono što smo mi
želeli. Tako da ovde upotreba printf() funkcije u signalu je samo za ilustraciju, u realnom svetu se to
nikada ne koristi.

Imenovani pipe-ovi

Razlika između običnih i imenovanih pipe-ova je što nam imenovani pipe-ovi omogućavaju
da imamo komunikaciju između nezavisnih procesa. Imenovani pipe je fizički fajl koji postoji na
hard disku i koristi se za interprocesnu komunikaciju. Imamo jednu cev u koju nešto pišemo i sa
druge strane to nešto čitamo. Da bi napravili file potrebno je da koristimo funkciju koja se zove
mkfifo. Za više informacija kucati man 3 mkfifo.

int mkfifo(const char *pathname, mode_t mode);

Fifo-vi su po definiciji blokirajući. Ako imamo dva procesa p1 i p2 koja su nezavisna i


hoćemo da oni komuniciraju tako što će koristiti jedan pipe. Kada pozovemo funkciju mkfile() mi
samo pravimo fifo fajl. Da bismo ga pokrenuli, potrebno je da jedan proces otvori taj fifo fajl u
read modu a drugi u write modu. Kada prvi proces otvori fifo fajl u read modu on stoji blokiran
dok drugi proces ne otvori fifo fajl u write modu. Tak nakon toga izvršavanje može da se nastavi.
Uvek treba da budemo osigurani da je jedan proces otvorio fifo fajl u read a drugi u write modu
inače će stajati blokirani.

Kada napravimo fifo fajl on obično ima veličinu od 64 KB. Kada pozovemo komandu ls -l u
terminalu videćemo da u pravima pristupa za naš fajl kao prvi karakter stoji slovo p što označava da
je to fifo file. Kada jedan proces zatvori svoju stranu fifo-a, automatski se zatvara i druga strana.

mkfifo()
write read

P1 process P2 process

66
Kada se napravi fifo fajl i otvori, koristimo iste I/O sistemske pozive kao kod pipe-a i drugih
fajlova. FIFO ima mod za čitanje i mod za pisanje. Drugi naziv za fifo su imenovani pipe-ovi.

Iz shell-a možemo da kreiramo fifo uz pomoć komande mkfifo:

$ mkfifo [-m mode] pathname

pathname je ime fifo fajla koji treba da se kreira, a -m se koristi za dodelu prava kao i kod chmod
komande.

Ideja zadatka je da imamo jednog klijenta i server. Klijent generiše neki random broj i šalje
serveru. Server treba da pročita taj broj i da ga ispiše na stdout. Dok god korisnik za nastavak
programa upisuje “yes” program će nastavljati sa izvršavanjem. Neophodno je prvo pokrenuti
server iz jednog terminala, a potom pokrenuti klijenata iz drugog terminala.

fifo_client.c

#define MAX_SIZE (20)


#define osErrorFatal(userMsg) osErrorFatalImpl((userMsg), __FILE__, __func__, __LINE__)
#define osAssert(expr, userMsg) \
do {\
if (!(expr)) \
osErrorFatal(userMsg); \
} while (0)

void osErrorFatalImpl(const char* userMsg, const char* fileName, const char* functionName, const int lineNumber);
static char* osUsage = "./fifo_client pathToFifo";

int main(int argc, char** argv) {


fifo_client.c
osAssert(argc == 2, osUsage);

/* Otvaranje fifo fajla u suprotnom modu u odnosu na fifo_server */


int fd = open(argv[1], O_WRONLY);
osAssert(fd != -1, "open failed");

/* Postavljanje sita za generisanje slučajnih brojeva */


srand(time(NULL));
char localBuf[MAX_SIZE];

/* Sve dok korisnik unosi “yes” šalje se novi slučajni broj serveru */
do{
int broj = rand();
sprintf(localBuf, "%d",broj);
printf("Sent number: %d\n", broj);

osAssert(write(fd, localBuf, MAX_SIZE) != -1, "write failed");


printf("Continue sending: \n");
scanf("%s",localBuf);

}while(!strcasecmp("yes", localBuf));

/* BITNO: zatvaranjem jedne strane fifo fajla automatski se zatvara i druga strana */
close(fd);

exit(EXIT_SUCCESS);
}

67
fifo_server.c

#define MAX_SIZE (20)


#define osErrorFatal(userMsg) osErrorFatalImpl((userMsg), __FILE__, __func__, __LINE__)
#define osAssert(expr, userMsg) \
do {\
if (!(expr)) \
osErrorFatal(userMsg); \
} while (0)

/* Server strana koja samo vrši čitanje iz fifo-a */


void osErrorFatalImpl(const char* userMsg, const char* fileName, const char* functionName, const int lineNumber);
static char* osUsage = "./fifo_server pathToFifo";

int main(int argc, char** argv) {

osAssert(argc == 2, osUsage);

/* Pravljenje fifo fajla: Ovakav poziv bi pukao ukoliko već postoji fajl na putanji argv[1]
osAssert(mkfifo(argv[1], 0600) != -1, "mkfifo failed");
BITNO: Samo jedna strana u komunikaciji pravi fifo. Druga strana samo otvara fifo fajl u suprotnom modu */

/* Ako je potrebno da se obradi provera postojanja fifo fajla na putanji jedno od mogućih rešenja je sledeće */
int result = mkfifo(argv[1], 0600);
if (result == -1) {

/* Ako uzrok pucanja nije postojanje fajla već nešto drugo neophodno je prekinuti izvršavanje */
osAssert(errno == EEXIST, "mkfifo failed");

/* Provera da li se radi o fifo fajlu. Ako nije fifo, potrebno je prekinuti izvršavanje */
struct stat fInfo;
osAssert(stat(argv[1], &fInfo) != -1, "stat failed");
fifo_server.c osAssert(S_ISFIFO(fInfo.st_mode), "Not a fifo.");
}

/* Otvaranje fifo fajla je blokirajuća operacija. Proces ostaje blokiran u open pozivu sve dok neki drugi proces ne
otvori fifo fajl u suprotnom modu */
int fd = open(argv[1], O_RDONLY);
osAssert(fd != -1, "open failed");

char buf[MAX_SIZE];
int readBytes = 0;

/* Dok god je otvoren drugi kraj fifo fajla, redom se čitaju brojevi koje generiše klijent */
while ((readBytes = read(fd, buf, MAX_SIZE)) > 0) {

int broj;
sscanf(buf, "%d", &broj);
printf("Received number: '%d'\n", broj);
}

/* Provera da li je read uspešno završen */


osAssert(readBytes != -1, "read failed");

/* Zatvaranje fajla */
close(fd);

exit(EXIT_SUCCESS);
}

68
Deljena Memorija

I/O operacije možemo da radimo na neki od sledećih načina:

1. način → korišćenjem printf/scanf funkcija


2. način → korišćenjem read/write
3. način → korišćenjem memorijskog mapiranja

Ideja je da mi na hard disku imamo neki fajl. Imamo RAM memoriju. Memorijsko
mapiranje radi tako što deo fajla ili ceo fajl upiše u memoriju i složi. Mi dobijamo adresu početka
tog bloka, tako da kompletne I/O operacije koje vršimo, radimo u RAM memoriji. Na taj način I/O
operacije znatno ubrzavamo. Npr. Ako radimo sa slikama, možemo da primetimo da je rad sa
pikselima mnogo spor. Da bi to ubrzali moramo tu sliku memorijski da mapiramo.

Ako imamo neki fajl, da bi koristili deljenu memoriju neophodno je da ga mapiramo,


odnosno da njegov sadržaj prenesemo u RAM memoriju. To se postiže funkcijom mmap(). Za više
informacija o ovoj funkciji kucati u terminalu man 2 mmap.

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

Prvi argument ove funkcije predstavlja adresu kojom sugerišemo kompajleru gde hoćemo da
postavi naš memorijski blok, to se uglavnom ne koristi tako da ćemo mi tu postavljati nula. Drugi
argument je veličina memorijskog bloka koju želimo. Treći argument su zaštite koje želimo da
postavimo na tom memorijskom bloku, to mogu da budu PROT_EXEC, PROT_READ,
PROT_WRITE, PROT_NONE. Četvrti argument može da bude MAP_SHARED ili MAP_PRIVATE,
biće objašnjeno kasnije. Peti argument je fajl deskriptor. Poslednji argument je offset koliko želimo
da se pomerimo od početka adresnog bloka.

Naš fajl nije običan fajl nego niz bajtova ili neka struktura koju smo mi napravili. Sada sve
što radimo sa fajlom događa se isključivo u RAM memoriji, kada smo završili sa I/O operacijama i
to što smo izmenili želimo da vratimo nazad u fajl to se postiže funkcijom munmap(). Za više
informacija o ovoj funkciji kucati u terminalu man 2 munmap.

int munmap(void *addr, size_t length);

Memorijsko mapiranje kao takvo je jedan od oblika I/O operacija. Pitanje je zašto je to u
vezi sa deljenom memorijom. To je u vezi sa deljenom meorijom zato što ako imamo veći broj
procesa, među njima možemo da uspostavimo komunikaciju tako što fajlove ne otvaramo sa open()
već sa shm_open() sistemskim pozivom. Za više informacija kucati u terminalu man 3 shm_open.

int shm_open(const char *name, int oflag, mode_t mode);

Ti fajlovi koje otvaramo, nisu fajlovi koji se otvaraju negde u našem direktorijumu već na
nekom određenom mestu u operativnom sistemu (/proc dir). Šta se dešava? Jedan od ovih procesa
koji želi da uspostavi komunikaciju sa ostalim procesima preko deljene memorije treba da napravi
ovaj fajl sa shm_open() i kada ga napravi potrebno je da ga mapira. Svaki naredni proces koji
otvara fajl i mapira ga, operativni sistem je dovoljno pametan da za sve njih izmapira isti prostor u
memoriji, i na taj način ako neki od procesa nešto izmeni ostali procesi mogu to da vide. Tako se
uspotavlja interprocesna komunikacija kada se koristi deljena memorija.

Kada god koristimo deljenu memoriju treba da vodimo računa o sledećim stvarima:

69
1. prava pristupa → onako kako smo otvorili fajl sa shm_open() funkcijom na taj isti način sa istim
pravima pristupa moramo i da mapiramo taj fajl, ako koristimo neka druga prava pristupa mmap()
funkcija će da nam pukne
2. Ako nešto forkujemo, moramo da imamo u vidu da forkovanjem svi oni zadržavaju isti
memorijski prostor
3. mmap() funkcija nam omogućava da postavimo flegove:
MAP_SHARED → u tom slučaju svi procesi dele isti adresni prostor u memoriji
MAP_PRIVATE → ako hoćemo da svako ima svoj prostor onda postavljamo ovaj fleg, ideja
je da će na početku svi procesi imati isti adresni prostor ali u momentu
kada neko nešto od njih doda u fajl, tada se kompletan adresni prostor
premešta i njemu će biti dodeljena kopija kao mapa tog fajla.
4. koliko god procesa da imamo samo jedan kreira deljenu memoriju svi ostali je samo otvaraju

Ukratko: Otvaramo fajl sa shm_open(), taj fajl izmapiramo. Kada ga izmapiramo koristimo
ista prava pristupa kao kada smo ga otvarali inače u suprotnom mmap() funkcija puca. Koliko god
procesa da imamo koji otvaraju isti fajl, za nas će svi oni biti izmapirani u isti adresni prostor što je
obezbeđeno sa MAP_SHARED flegom. U sprotnom postavljamo MAP_PRIVATE ukoliko želimo
da svaki od njih, ukoliko želi da menja fajl, ima svoj adresni prostor.
Ideja zadatka je da imamo jedan proces koji će da upisuje u fajl niz brojeva i drugi proces
koji treba da otvori taj isti fajl i da pročita taj niz brojeva.

shared_memory_writer.c

int main(int argc, char** argv) {


osAssert(argc >= 3, osUsage);
/* Kreira se blok deljene memorije dovoljne veličine za smeštanje niza */
int n = argc - 2;
int size = (argc-2)*sizeof(int);
int *niz = osCreateMemoryBlock(argv[1], size);
/* Niz brojeva se smešta u deljenu memoriju */
int i;
for (i = 0; i<n; i++)
niz[i] = atoi(argv[i+2]);
/* Izmene memorijskog bloka se trajno snimaju na disku, tj. u fajlu koji predstavlja deljenu memoriju */
osAssert(munmap(niz, size) != -1, "unmap failed");
exit(EXIT_SUCCESS);
}

void* osCreateMemoryBlock(const char* filePath, unsigned size) {

/* Kreira se blok deljene memorije:


BITNO 1: Kreiranje objekta deljene memorije radi samo jedan proces ostali procesi samo otvaraju objekat u
odgovarajućem modu
BITNO 2: mod u kome je otvoren fajl deljene memorije mora biti jednak modu u kome se mapira memorija */
int memFd = shm_open(filePath, O_RDWR | O_CREAT, 0600);
osAssert(memFd != -1, "shm_open failed");

/* Veličina fajla se postavlja na željenu veličinu.


BITNO: Ovu operaciju radi samo onaj proces koji kreira objekat deljene memorije */
osAssert(ftruncate(memFd, size) != -1, "ftruncate failed");

void* addr;
/* Mapiranje objekta deljene memorije u RAM memoriju:
BITNO: prava pristupa u memorijski mapiranom delu moraju da budu jednaka modu u kome je fajl deljene memorije
otvoren */
osAssert((addr = mmap(0, size, PROT_READ | PROT_WRITE, \
MAP_SHARED, memFd, 0)) != MAP_FAILED, "mmap failed");
/* Zatvara se fajl deljene memorije jer je njegov sadržaj u memoriji, i sam fajl nam više nije potreban */
close(memFd);
/* Vraćamo adresu memorijski mapiranog bloka */
return addr;
}

70
shared_memory_reader.c

int main(int argc, char** argv) {

osAssert(argc == 2, osUsage);

/* Učitavamo parče deljene memorije:


BITNO: ovde samo učitavamo sadržaj objekta deljene memorije, ne kreiramo ga. Kreiranje radi samo jedan proces */
unsigned size = 0;
int *niz = osGetMemoryBlock(argv[1], &size);
int brojEl = size/sizeof(int);

/* Čitamo sadržaj niza iz mapiranog bloka */


int i = 0;
for (i = 0; i < brojEl; i++)
printf("%d ", niz[i]);

printf("\n");

/* Izmene memorijskog bloka se trajno snimaju na disku, tj. u fajlu koji predstavlja deljenu memoriju */
osAssert(munmap(niz, size) != -1, "munmap failed");
/* Brišemo objekat deljene memorije, jer nam više ne treba unlink radi poslednji proces koji koristi objekat deljene
memorije */
osAssert(shm_unlink(argv[1]) != -1, "shm_unlink failed");

exit(EXIT_SUCCESS);
}

void *osGetMemoryBlock(const char* filePath, unsigned* size) {

/* Otvara se fajl koji predstavlja deljenu memoriju */


int memFd = shm_open(filePath, O_RDONLY, 0);
osAssert(memFd != -1, "shm_open failed");

/* Uz pomoć fstata se dobija veličina fajla:


BITNO: nikada ne koristiti običan stat, već uvek fstat
JOŠ BITNIJE: samo jedan proces radi ftruncate i podešava veličinu fajla. SVI ostali pomoću fstat otkrivaju tu veličinu*/
struct stat fInfo;
osAssert(fstat(memFd, &fInfo) != -1, "fstat failed");
*size = fInfo.st_size;

void* addr;

/* Mapiranje objekta deljene memorije u RAM memoriju:


BITNO: prava pristupa u memorijski mapiranom delu moraju da budu jednaka modu u kome je fajl deljene memorije
otvoren*/
osAssert((addr = mmap(0, *size, PROT_READ, MAP_SHARED, memFd, 0)) != MAP_FAILED, "mmap failed");

close(memFd);

return addr;
}

Ovaj primer koji smo sada napravili je krajne jednostavan. Ideja interprocesne komunikacije
je takva da procesi dok rade treba da šalju poruku jedni drugima što ne možemo da uradimo uz
pomoć ovog primera ali se to može implementirati uz pomoć semafora.

71
SEMAFORI

Semafor je apstraktni tip podataka, odnosno struktura koja može da blokira proces na neko
vreme i da ga propusti kada se steknu određeni uslovi. Semafor se može definisati kao celobrojna,
nenegativna promenljiva nad kojom se, pored standardnih operacija kreiranja i oslobađanja, mogu
izvesti samo još dve operacije: P i V. Operacija P(S) pri čemu je S semafor testira da li je vrednost
promenljive S pozitivna i u slučaju da jeste proces koji je izvršio tu operaciju se propušta dalje
(obično u kritičnu sekciju), a vrednost semafora se umanjuje za jedan. U suprotnom se proces
blokira i stavlja u red za čekanje. Analogno, operacija V(S), ako postoje procesi koji čekaju
blokirani na semaforu S (vrednost semafora nije pozitivna), propušta tačno jedan od procesa da
nastavi sa izvršavanjem (uđe u kritičnu sekciju), odnosno prekida njegovo blokiranje. U supotnom
se vrednost semafora povećava za jedan.

P
V

DA DA Ponovo pokretanje
S>0 Blokiranje procesa S=0 jednog procesa koji
je bio blokiran

NE NE

S=S-1 S=S+1

KRAJ KRAJ

Dijagram P operacije Dijagram V operacije

Ideja semofara za sinhronizaciju procesa je da možemo da lociramo resurse. Na primer ako


imamo jedan štampač i 15 procesa koji žele da štampaju. Ukoliko ih pustimo da rade, štampaće se
jedan po jedan dokument i na kraju će svi dokumenti biti lepo odštampani. To se jednostavno
postiže uz pomoć semafora. Na našem sistemu kada neki proces hoće da štampa nešto on prvo mora
da izvrši sistemski poziv sem_wait() odnosno mora da pita da li ima pristup da koristi taj štampač.
Operativni sistem zna koliko ima resursa (štampača za štampanje) i to čuva neki brojač resursa koji
nam govori da li je neki resurs slobodan ili nije. Resurs za štampač postavljen na 1 znači da je taj
resurs slobodan, to znači da operativni sistem može da dodeli taj resurs nekom procesu i nakon što
ga dodeli brojač resursa postavlja na nula. Taj proces koji je dobio štampač, on nešto štampa i kada
završi sa štampanjem on pozove sem_post(), odnosno on oslobađa resurs tako što brojač resursa
povećava za 1. U ovom slučaju pošto imamo samo jedan resurs sa 0 ga postavlja na 1. Svaki
naredni proces koji želi da koristi štampač, prvo pita da li sme da ga koristi, ako sme on zauzme taj
resurs i koristi ga neko vreme, kada je završio sa korišćenjem on oslobađa taj resurs.

Ako imamo 5 dokumenata. Svaki od njih mora da pita da li može da koristi štampač
(resurs). Neko od njih uzme resurs i radi nešto sa njim i smanji brojač resursa na nula. Kada dođe
do promene konteksta i pojavi se neki novi proces i pita da li može da koristi resurs, ako ne može
on se blokira i čeka. Tek kada prvi proces završi sa radom on oslobađa resurs i povećava mu brojač.

72
Neko od ostalih procesa koji su se blokirali i čekaju na taj resurs, će dobiti štampac. U tom
trenutku mora da postoji neka politika kako se bira koji proces treba da dobije resurs. Npr. Ako je
implementirana politika na FIFO principu to znači ko je prvi stigao i blokirao se on treba da dobije
resurs. Kada on završi sa radom sledeći koji je na redu dobija resurs i tako redom dok ga svi ne
iskoriste. Dakle kada uzimamo resurs smanjujemo brojač, kada vraćamo resurs uvećavamo brojač.

U okviru sistema postoje lokalni i procesni semafori. Lokalni se koriste za sinhronizaciju


tredova koji se nalaze u istom adresnom prostoru. A procesni semafori kada vršimo sinhronizaciju
procesa koji uopšte nisu povezani.

Pitanje je kako treba da se sinhronizuju dva procesa koja treba da šalju jedan drugom neke
komande preko objekta deljene memorije a da pri tome naše poruke ne budu izmešane. Jedan
proces treba da čita a drugi da piše. Dakle imamo dve operacije čitanje i pisanje. To znači da nam
trebaju dva resursa koja prikazujemo sa dva odvojena semafora. Jedan semafor koji zovemo P za
pisanje i drugi Č za čitanje.

Proces koj želi da piše nešto on prvo mora da pozove funkciju sem_wait(p), to jest da pita da
li on u tom trenutku može da piše. Ukoliko ne može on će se blokirati i čekati u redu neko vreme.
Kada bude bio u mogućnosti da piše on će neko vreme nešto pisati i nakon toga potrebno je da
pozove funkciju sem_post(č) tako da drugi proces zna da može da vrši čitanje. Proces koji treba da
čita prvo poziva funkciju sem_wait(č), jer mora da pita da li on u tom trenutku može da čita. On
zavisi od prvog procesa koji nešto piše. Proces koji čita mora da sačeka proces koji nešto piše da
mu da znak kada može nešto da pročita. Nakon što je pročitao to što je trebalo, potrebno je da da
drugom procesu da znak da može da piše tako što će pozvati funkciju sem_post(p).

Na početku je potrebno da se inicijalizuju resursi. Resurs za pisanje inicijalizovaćemo na 1


jer on je taj koji treba prvo nešto da napiše da bi drugi proces to mogao da pročita. Dok ćemo drugi
resurs za čitanje da inicijalizujemo na 0. Sledeći dijagram najbolje objašnjava sve ovo.

DM
sem P
sem Č
P Č

SEM P = 1
SEM Č = 0

sem_wait(P) sem_wait(Č)

sem_post(Č) sem_post(P)

Da bi procesi za pisanje i čitanje bili sinhronizovani moramo da koristimo objekat deljene


memorije.
Ideja zadatka je da imamo server i jednog klijenta. Server treba nešto da piše a klijent to što
je server napisao treba da pročita i ispiše na stdout. Za to će nam trebati dva semafora, semafor za
čitanje koji inicijalizujemo na 0, i semafor za pisanje koji inicijalizujemo na 1. Samo jedan proces
vrši inicijalizaciju. I na samom kraju samo jedan od njih poziva shm_unlink(). Semafor za pisanje
prvo mora da pita da li može da piše, ukoliko može on nešto upiše i onda pozove sem_post(č), kako
bi drugi proces mogao da čita. Analogno je za proces koji vrši čitanje. Ovde ćemo koristiti deljenu
memoriju. Imaćemo jednu strukturu u jednom i u drugom procesu. U oba procesa argumenti u
strukturi moraju da budu navedeni istim redom inače nam program neće raditi.

73
log_server.c

#define MSG_LEN (256) /* maksimalna veličina poruke u bloku memorije */


#define LOCAL_SEM (0) /* lokalni semafor - koristi se za sync tredova */
#define PROC_SEM (!LOCAL_SEM) /* globalni semafor - koristi se za sync procesa */

/* Struktura koja opisuje sadržaj fajla deljene memorije:


IZUZETNO VAŽNO: radosled polja u svim procesima koji koriste ISTI semafor za sync ISTOG resursa MORA biti jednak
u svim procesima, inače program neće raditi kako treba */

typedef struct {
char buf[MSG_LEN];
sem_t safeToRead;
sem_t safeToWrite;
} osMemoryBlock;

int main(int argc, char** argv) {

osAssert(argc == 2, osUsage);
/* Kreira se memorijski blok */
osMemoryBlock* pMsgBuf = osCreateMemoryBlock(argv[1], sizeof(osMemoryBlock));

/* Inicijalizacija semafora:
IZUZETNO VAŽNO: samo JEDAN proces radi inicijalizaciju semafora svi ostali procesi samo koriste semafore.
Broj kojim se inicijalizuje semafor treba da bude jednak ukupnom broju dostupnih resursa. Ako nema dostupnih resursa
semafor se inicijalizuje nulom */
osAssert(sem_init(&pMsgBuf->safeToRead, PROC_SEM, 0) != -1, "read sem init failed");
osAssert(sem_init(&pMsgBuf->safeToWrite, PROC_SEM, 1) != -1, "write sem init failed");

/* Lokalni bafer za učitavanje poruka */


char localBuf[MSG_LEN];

do {
/* Učitavamo poruku od korisnika */
scanf("%s", localBuf);
/* Čekamo na semaforu koji štiti operaciju pisanja i na taj način sinhronišemo pristup baferu za razmenu
poruka u deljenoj memoriji.
log_server.c IZUZETNO VAŽNO: sinhronišu se SAMO i ISKLJUČIVO deljeni resursi i promenljive.
Sve promenljive koje nisu deljene se NIKADA ne sinhronišu i njihovo pojavljivanje u kritičnoj sekciji je
ozbiljna greška, jer značajno umanjuje ili u potpunosti poništava ubrzanje koje se dobija paralelizacijom */
osAssert(sem_wait(&pMsgBuf->safeToWrite)!=-1, "sem wait failed");

/* BITNO: samo pristup, tj. upotreba deljene promenljive se sinhroniše */


strcpy(pMsgBuf->buf, localBuf);

/* Obaveštavamo drugi proces da je postavljena nova poruka u deljenoj memoriji */


osAssert(sem_post(&pMsgBuf->safeToRead) != -1, "sem post failed");

} while(strcasecmp(localBuf, "quit"));

/* Izmene memorijskog bloka se trajno snimaju na disku, tj. u fajlu koji predstavlja deljenu memoriju */
osAssert(munmap(pMsgBuf, sizeof(osMemoryBlock)) != -1, "munmap failed");
/* Briše se objekat deljene memorije
BITNO: samo jedan proces ovo radi */
osAssert(shm_unlink(argv[1]) != -1, "shm unlink failed");

exit(EXIT_SUCCESS);
}
void *osCreateMemoryBlock(const char* filePath, unsigned size) {

int memFd = shm_open(filePath, O_RDWR | O_CREAT, 0600);


osAssert(memFd != -1, "shm_open failed");
osAssert(ftruncate(memFd, size) != -1, "ftruncate failed");
void* addr;
osAssert((addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, memFd, 0)) != MAP_FAILED,
"mmap failed");
close(memFd);

return addr;
}

74
log_client.c

#define MSG_LEN (256)


#define LOCAL_SEM (0)
#define PROC_SEM (!(LOCAL_SEM))

typedef struct {

char buf[MSG_LEN];
sem_t safeToRead;
sem_t safeToWrite;

} osMemoryBlock;

int main(int argc, char** argv) {

osAssert(argc == 2, osUsage);

/* Čita se objekat deljene memorije */


unsigned size;
osMemoryBlock *pMsgBuf = osGetMemoryBlock(argv[1], &size);

/* Lokalni bafer */
char localBuf[MSG_LEN];
do {

/* Čekamo na semaforu koji štiti operaciju čitanja i na taj način sinhronišu pristup baferu
za razmenu poruka u deljenoj memoriji.
IZUZETNO VAŽNO: Sinhronišu se SAMO i ISKLJUČIVO deljeni resursi i promenljive.
Sve promenljive koje nisu deljene se NIKADA ne sinhronišu i njihovo pojavljivanje u kritičnoj sekciji
je ozbiljna greška, jer značajno umanjuje ili u potpunosti poništava ubrzanje koje se dobija paralelizacijom */
osAssert(sem_wait(&pMsgBuf->safeToRead) != -1, "sem wait failed");

/* BITNO: samo pristup, tj. upotreba deljene promenljive se sinhroniše */


strcpy(localBuf, pMsgBuf->buf);

/* Obaveštavamo drugi proces da je bezbedno da upiše novu poruku u deljenu memoriju */


osAssert(sem_post(&pMsgBuf->safeToWrite) != -1, "sem post failed");

/* BITNO: lokalna operacija se nikada ne sinhroniše */


printf("Received message: '%s'\n", localBuf);

} while(strcasecmp(localBuf, "quit"));

/* Izmene memorijskog bloka se trajno snimaju na disku, tj. u fajlu koji predstavlja deljenu memoriju */
osAssert(munmap(pMsgBuf, size) != -1, "munmap failed");

exit(EXIT_SUCCESS);
}

void* osGetMemoryBlock(const char* filePath, unsigned* size) {

int memFd = shm_open(filePath, O_RDWR, 0);


osAssert(memFd != -1, "shm open failed");

struct stat fInfo;


osAssert(fstat(memFd, &fInfo) != -1, "stat failed");
*size = fInfo.st_size;

void *addr;
osAssert((addr = mmap(0, *size, PROT_READ | PROT_WRITE, MAP_SHARED, memFd, 0)) != MAP_FAILED,
"mmap failed");

close(memFd);

return addr;
}

75
To što dobijemo memorijskim mapiranjem moramo da ukastujemo u neki tip, to mora da
bude isto u oba procesu. Samo jedan proces pravi fajl, i samo jedan vrši unlink-ovanje na kraju kada
završe sa radom.

76

You might also like