You are on page 1of 21

Projekat iz Operativnih sistema 1

Autor: Aleksa Mitrović 2013/0194

Mail: aleksapsg@gmail.com

Facebook: www.facebook.com/aleksaps

Recenzent: Kristijan Žiža 2013/0044

Mail: me@kristijanziza.com

Facebook: www.facebook.com/kristijan.ziza

Website: www.kristijanziza.com

JUL 2015.
1
SADRŽAJ

Uvod .................................................................. 3
Klase Thread i PCB............................................. 5
Semaphore ...................................................... 11
Event ............................................................... 12
Sistemski pozivi ............................................... 14
Mogudi problemi ............................................. 18
Pitanja za odbranu .......................................... 19
Kako izgleda odbrana ...................................... 21

2
Uvod
Sigurno ste do sada od mnogih stariji studenata čuli za jednu od najtežih,
ako ne i najtežu prepreku na vašem putu studiranja. Zaista, veliki broj studenata
ima problema sa ovim projektom, što zbog apstraktnosti materije, što zbog priča
starijih kolega. U ovom tekstu du vas najpre savetovati kako pristupiti rešavanju,
da vam pomognem da se organizujete, a zatim detaljno objasniti svaki deo
projekta. Odmah da vas upozorim da ovde nedete nadi mnogo koda i moj savet
vam je da ne gledate u tuđe projekte jer de vas to samo nervirati i zbunjivati i
mislidete da je to jedino mogude rešenje i onda rizikujete da vas profesori kazne
nakon provere sličnosti. Mnogi studenti pristupe rešavanju projekta na taj način
što čitaju tuđ kod pa iz njega kapiraju šta treba da rade – nemojte to da radite!
Mislim da de vam ovom uputstvo biti sasvim dovoljno da ukapirate šta se od vas
traži i na koji način da razrešite tražene probleme.

Na početku par opštih informacija – tekst projekta, kada početi, kako raditi i kako
se organizovati.

Tekst projekta se menja iz godine u godinu, ali je u suštini isti – realizacija


malog jezgra operativnog sistema. Pre nego sto počnete, par puta pročitajte tekst
zadatka. Naravno, kao i ja, nedete imati pojma sta ste pročitali, čak i nakon par
čitanja. Ali čisto da razrešite par nepoznatih pojmova i otprilike da vidite šta to
piše na tih 15-ak stranica.
Ako želite da završite projekat u predroku, najbolje bi bilo da krenete
desetak dana nakon što se odmorite od prve kolokvijumske nedelje, jer jako mali
broj studenata reši da brani projekat u predroku pošto mnogi odustaju kad vide
šta se traži i ostavljaju za jul, septembar. Iz razloga što vas ima malo, treba početi
što ranije, jer ako budete imali nekih neobjašnjivih problema, ili sto kaže profesor
Milidev – ako se kosmičke sile okrenu protiv vas, mali broj ljudi de modi da vam
pomogne, jer je mali broj ljudi krenuo da se bavi tim.
Uz ovo uputstvo, ne verujem da de vam biti potrebno više od 15 do 20
dana, osim ako zastanete zbog neke sitne gluposti koju nedeljama niko nede modi
da otkrije, dok vam slučajno u snu ne padne na pamet. Iz tog razloga, moj savet
vam je da ne radite više od 3 do 4 sata dnevno jer nakon toga dete se samo
umoriti i pravidete strašne previde. I ako je mogude, izbegavajte da radite nodu. Iz
ličnog iskustva, čak iako su i mene to savetovali da ne radim, vam kažem da ne
radite nodu.
3
Profesor Milidev de vas takođe savetovati da ne žurite da odmah kucate
kod. Ako budete to poslušali, verovatno de vam trebati i manje od 15 dana da
završite. Najbolje je da svaki deo projekta prvo detaljno razmotrite, razmislite i
zapišete, tj. nacrtate na papiru kako bi trebalo da izgleda. Nakon toga vam nede
trebati više od pet sati da otkucate. Nipošto ne kucajte neki kod ako ne znate
zašto ste to stavili. Nemojte presipati iz šupljeg u prazno, jer ako ne znate što to
radi, vrlo je verovatno da nede uvek raditi. I što je najvažnije – kucajte lep kod!
Nemojte da govorite – samo da otkucam, posle du da ulepšam. Pošto vam
debugger često nede biti od velike koristi, lep kod vas može spasiti. Pišite kratke
komentare na neke složenije delove i između klasa i logičkih celina u kodu
stavljajte razmake od nekoliko redova, da bi vam sve bilo preglednije. I još jedan
savet što se koda tiče, a to de vam i profesor Žorž pričati – kucajte kod ili na
srpskom ili na engleskom, nikako mešovito. I naravno, ovo nije OOP1 – niko vam
nede zameriti ako stavljate gomilu javnih i globalnih podataka. Štaviše, to je
ovde i poželjno raditi.

Pre nego što krenem na objašnjavanje samog projekta, još par napomena.
Što se tiče okruženja, novije generacije na treba da brinu jer su kolege Maja
Vukasovid i Miloš Radid napravili plugin za Eclipse, tako da problemi sa BCC-om i
Paskaloidnim okruženjem su sada prošlost. Ako iz nekog razloga ne gotivite
Eclipse, možete preuzeti od starijih kolega uputstvo za podešavanje Notepad++
gde, ne samo što možete kucati kod, ved i build-ovati i pokretati testove.
Sve to naravno mora da se radi na virtuelnoj mašini 32-bitnog sistema. Da bi vam
virtuelna brže i bolje radila, instalirajte VM tools (samo prevucite neki file/folder
sa vašeg sistema na virtuelnu i ponudide vam instalaciju).
Podsetite se C++, pogotovo što je stari standard iz 1992. godine (mnoge stvari ne
postoje kojih ima u novom standardu, na primer bool, pa demo koristiti int ili
izuzeci, pa demo greške obrađivati drugačije itd) pa da ne gubite vreme na to.

I da, idite na LAB vežbu, ali verujem da vam nede biti baš najjasnije sta da radite
nakon nje (bar meni nije bilo), sem što dete znati kako da podesite okruženje.
Tamo de vam dati par dobrih saveta i pitajte sve što vas interesuje. I najvažnije –
onaj pdf sa lab vežbe sadrži mnogo informacija koje možete iskoristiti u vašem
projektu.

E sad, konačno kad ste pročitali mnogo teksta, što iz ovog uvoda, što gradiva i
zadatka, da konačno krenemo sa realizacijom problema. Podvukao sam najvažnije

4
stvari u uvodu, pa se povremeno vradajte da vidite da li to poštujete dok kucate
projekat.

Klase Thread i PCB

Da bih objasnio klasu Thread, prvo je potrebno objasniti PCB. Šta je uopšte
PCB? Process control block ili PCB je struktura podataka koja opisuje objekat klase
Thread. Ona sadrži sve one podatke potrebne da bi se jedna nit uspešno i efikasno
izvršavala. Pročitajte arhitekturu 8086 da biste znali koji registri postoje u
procesoru. To se nalazi na materijalima sa vežbi, prva prezentacija. Ono što nam,
za sad, treba za PCB su stek (niz unsigned podataka) i registri SS, SP i BP. SS je stek
segment, a SP je stek offset. Stek pointer se dobija kada na segment nalepimo
offset (to procesor sam radi, ne brinite o tome). Za BP takodje pročitajte sa
materijala čemu služi (ima i na lab vežbi), ali za sada – ono što treba da znate da
ove tri stvari se čuvaju. Ukratko, BP se čuva ako imamo lokalnih podataka pa da bi
dohvatali preko njega parametre funkcije, jer SP de se menjati. Bez obzira dal
imate lokalne podatke ili ne, ne škodi da čuvate BP. Jedna važna napomena, stek
raste ka nižim lokacijama i zauzima poslednju zauzetu.
Sve niti (tj PCB-ovi) se prilikom startovanja stavljaju u raspoređivač, tj
Scheduler. Implementacija vam je data, ne treba pisati, iskoristite to.

Zašto uopšte stek?


Poznato vam je još sa Programiranja 1 da svaki program ima program counter i
stack pointer (PC & SP). Kad se poziva funkcija, na stek se stavlja ona adresa na
koju se vradamo prilikom povratka iz funkcije. Ovde je cilj da mi malo
manipulišemo tim vrednostima koje se restauriraju prilikom povratka iz
potprograma – da podmetnemo svoje. Iz tog razloga, svaka nit (Thread) de imati
svoj PCB koji ima svoj stek i potrebne pokazivače koji opisuju slobodna mesta na
tom steku. Koristedi te pokazivače (koji nisu pravi pokazivači u smislu *pointer,
nego obični unsigned) podmetnemo neki drugi stek i program sa njega pročita
vrednosti i ode na potpuno drugačije mesto od očekivanog.

Da rezimiramo:
Thread ima PCB
PCB ima:
 unsigned *stack
5
 unsigned sp
 unsigned ss
 unsigned bp

Promena konteksta ili preuzimanje je upravo gore opisani proces. Jedna nit
je aktivna i izvršava se. Kada dođe red na drugu, ovu sačuvamo i iz Schedulera
uzmemo drugu i njene parametre – podmetnemo njen stek. Promenu konteksta
je potrebno vršiti u metodama sa modifikatorom interrupt. To je jedna olakšica
koju nam omogudava prevodilac. Ako označimo metodu kao interrupt – na ulasku
u metodu se čuvaju svi registri, na izlasku se restauriraju njihove vrednosti. Naš
jedini zadatak je da polja iz PCB-a (SP, SS, BP) prvobitno sačuvamo, a na kraju da
podmetnemo druge.

Promena konteksta se vrši u timeru. Šta je uopšte timer i kako to iskoristiti?


Na svakih 55ms se poziva jedna rutina koja je smeštena u ulazu 8h. Tu se vrše neki
sistemski poslovi koji vas ne interesuju, ali bez toga programi ne mogu da rade.
Pošto nam je u tekstu zadatka rečeno da je jedan time slice (što je jedan
vremenski kvant koliko dugo nit sme da zadrži procesor, pre nego što mora da ga
preda drugoj) 55ms, prirodno se namede da tu rutinu „ukrademo“. Način na koji
se ta rutina „krade“ je opisan na lab vežbi pa ga možete iskoristiti, tj prekopirati,
niko vam nede to zameriti ili pročitajte posle uputstvo za događaje, kako podesiti
rutine, pa možete na taj način. Sasvim je svejedno. Ono što je važno su poslovi
oko stare rutine. Ona se obavezno mora pozivati na svakih 55ms (opisano na labu
kako, dat je ceo kod - iskoristite) da bi se izvršili ti gore opisani sistemski poslovi i
takodje, po završetku programa, mora se vratiti u ulaz 8h (takođe opisano na labu
kako). Sada, pošto smo stavili našu rutinu void interrupt timer() u ulaz 8h, kako
ona treba da izgleda? Naravno, i to je opisano na lab vežbi, iskoristite. Ja du sad
samo ukratko objasniti šta treba raditi.

Pre nego što objasnim kako timer izgleda, da se vratim još malo na PCB. PCB mora
da ima neke flegove koji opisuju nit. Na primer, ako je nit gotova, moramo
obeležiti da je gotova, ako je nit blokirana (bilo na semaforu, bilo da spava ili
slično) moramo obeležiti da je blokirana. U oba slučaja, ne smemo vradati niz u
Scheduler jer u Scheduleru stoje samo niti spremne za izvršavanje! Da li dete
koristiti int kao fleg ili enum pa pisati stanje niti, sasvim je svejedno.

6
Zbog toga, PCB sada izgleda ovako:
 unsigned *stack
 unsigned sp
 unsigned ss
 unsigned bp
 unsigned timeSlice //koliko dugo se nit izvršava
 flegovi ili enum
 ostalo (navedeno kasnije u metodi waitToComplete klase Thread)

Kako demo brojati koliko dugo se nit izvršava? Jedan globalni podatak unsigned
counter rešava problem. Koja nit se izvršava? Opet jedan globalni podatak PCB*
running rešava problem. Da ne bih sad objašnjavao kako se to koristi, najbolje da
pročitate sa laba.

Konačno, timer izgleda ovako:


 Čuvanje konteksta
 Ako je running nit (tj PCB) i dalje spremna stavi je u Scheduler, ako nije,
nikom ništa
 Uzmi novu nit
 Njene parametre SS, SP i BP prepiši u procesorove SS, SP i BP pomodu
asemblerskih instrukcija
 Njen timeSlice prepiši u counter
 Pozovi staru rutinu ako se timer pozvao na 55ms, a ne ako ga je programer
pozvao kao timer();)
 Ostali poslovi (objašnjeno kasnije u metodi sleep klase Thread)

Šta ako su sve niti blokirane ili sve spavaju? Jedna nit (ili samo PCB koji dete
čuvati u globalnom podatku) rešava problem. Telo te niti je while(1); timeSlice joj
je 1. U slučaju da vam Scheduler vrati 0 (null u ovom standardu ne postoji!),
prebacite globalni PCB* running na tu „zaludnu“ nit. Nju nikako ne smete stavljati
u Scheduler, ved čuvajte globalni pokazivač na nju.

E sad ono najvažnije. Kako redi niti (tj PCB) koji je njen zadatak, tj. od koje
linije da počne izvršavanje. Postavlja se pitanje kako inicijalizovati stek PCB-a?
Ako ste pročitali arhitekturu 8086, kao što sam vam napomenuo malopre, znate
koji registri postoje u procesoru. Čim sam rekao „od koje linije počinje

7
izvršavanje“, jasno vam je da treba da promenimo PC. Šta je PC na 8086? Na 8086
PC se sastoji od code segment CS (segment za PC) i instruction pointer IP (offset
za PC). Upisivanjem vrednosti u te registre, manervišemo program counter-om.
Prva linija koda naše niti je prva linija omotačke funkcije wrapper(Thread*
running). Primetite da ona ima argument koji takođe treba staviti na PCB stek. Još
jedna važna stvar – treba podesiti PSW, 16b registar, tako da njegov I fleg, koji
omogudava prekide, bude 1. I naravno, pokazivače na stek. Verujem da vam ovo
opisano rečima ne znači mnogo pa du ovde priložiti malo koda za konstruktor PCB.

Prvo što treba uraditi je za zadati stackSize napraviti stek. Ali obratite pažnju na to
da je zadati stackSize u bajtovima, a unsigned je 16b. Znači, ako nam je dato 4096
bajtova, to je 2048 indeksa za niz unsigned* stack. Kako to srediti? Pa jednostavno
stackSize /= sizeof(unsigned).
Potrebno je paziti na ograničenje veličine niza (tj. steka) – na 8086 je maksimalno
mogude 65535 bajtova, inače de program pucati.
Sada je lako: stack = new unsigned(stackSize);

Posto smo napravili stek, treba ga inicijalizovati po gornjem tekstu. Na vašu sredu,
u zaglavlju <dos.h> su napisani makroi za dohvatanje segmenta i offseta. Pošto
stek raste ka nižim lokacijama, inicijalizujemo s kraja niza.

Postavljamo parametar funkcije:


stack[stackSize – 1] = FP_SEG(myThread)
stack[stackSize – 2] = FP_OFF(myThread)
// U Thread proslediti this konstruktoru PCB da bi za datu nit, dobili njen PCB
// To je upravo ovaj myThread

Postavljamo PSW:
stack[stackSize – 5] = 0x200; //Ovo je 10000…000 – I fleg enable-ovan

Postavljamo PC, tj. CS i IP (segment i offset za PC):


stack[stackSize – 6] = FP_SEF(Thread::wrapper); //segment
stack[stackSize – 7] = FP_OFF(Thread::wrapper); //offset

I ono što nam je ostalo je da podesimo SS, SP i BP:


ss = FP_SEG(stack + stackSize - 16);

8
sp = FP_OFF(stack + stackSize - 16);
bp = sp; //po pravilu BP se na početku stavlja da pokazuje na SP

Ono što vam verovatno nije jasno je, zašto ovi brojevi (-1, -2, -5, -6, -7, -16) i šta je
između. Pa, za početak, na -3 i -4 su segment i offset funkcije callback. To je
funkcija koja se poziva nakon završetka naše metode wrapper. Ali to nas ne
interesuje jer metoda wrapper nikad nede dodi do kraja – poslednja linija de
tražiti promenu konteksta.
Od -8 do -15 su mesta za registre procesora – ax, bx, cx, dx, es, ds, si, di. Na -16 je
bp. Zašto uopšte stavljamo te registre? Pa, prvi put kad nit se izvadi iz Schedulera
u timeru, krenude da restaurira redom vrednosti svih registara (koje je mislio da je
sačuvao na ulasku; on i jeste sačuvao, ali ne za ovu nit nego za onu koja je upravo
izgubila procesor). Zato moramo podmetnuti sve vrednosti. Za ove registre ax, ...,
di vrednosti slobodno mogu da budu random. To je sve u vezi sa inicijalizacijom
PCB-a.

Pošto dete verovatno raditi drugi zadatak, tj onaj zadatak u kom se


sistemski pozivi obavljaju preko posebnog steka, moj savet vam je da odmah
krenete da radite taj zadatak. Ali, ne tako što de odmah sve idi preko kernel steka
(to tek na kraju izmenite), nego zamislite da radite prvi, ali sa izmenama iz
drugog – znači koristite wrapper sa parametrom i umesto kernel objekta (u
slučaju klase Thread: PCB* myPCB) koristite identifikator objekta ID.

Sada, kada smo opisali PCB, možemo predi na Thread.

Thread konstruktor:
Pravi objekat PCB, dodeljuje ID i smešta taj PCB u neku globalnu listu ili niz (u
daljem tekstu du pretpostaviti da ste uzeli niz). Na primer možete napraviti da ID
bude indeks u nizu.

Thread destruktor:
Samo obrisati PCB iz niza i resetovati pokazivač niza na 0 (null). Nema potrebe
zvati ovde waitToComplete jer de se svejedno zvati u izvedenoj klasi.

9
WaitToComplete:
Kada se pozove ova metoda za neki PCB, to znači da trenutna running nit čeka
završetak niti čiju waitToComplete metodu je pozvala. Upravo zbog toga moramo
napraviti i dodati polje u svakom PCB-u, pokazivač na listu svih PCB-ova koji
čekaju na završetak.
Sve što treba uraditi u metodi je ubaciti running PCB u tu listu, staviti mu status na
blokiran i promeniti kontekst.

Sleep:
Ova metoda uspavljuje running nit na određeno vreme (dato parametrom
funkcije, voditi računa o specijalnom slučaju ako se prosledi vreme 0). To radi tako
što smesti running PCB u jednu listu i obeleži ga kao spavajudi. Postavlja se pitanje
kako napraviti tu listu, kako buditi niti i kako znati kad treba da se probude?
Ta lista je opisana na materijalima sa vežbi, vežbe 5. U listi se čuva samo razlika
vremena. Ukratko, ako nekoliko niti spavaju redom 5, 8, 8 i 9 (odnosno n x 55ms),
u listi demo čuvati 5, 3, 0, 1 (čuvamo razliku). Na svakih 55ms, tj u timeru, demo
smanjivati vrednost prvog elementa u listi i kad postane 0, njega i sve susedne sa
vremenom 0, smestiti u Scheduler (naravno prvo ih obeležiti da su spremne za
izvršavanje, tj da više nisu blokirane). Umetanje u listu realizujte sami, ne bi
trebalo da imate problema oko toga. Moj savet vam je da to dobro istestirate, jer
dešava se da vam se sve blokira samo zbog sleep liste jer je niste dobro napravili,
na primer vreme spavanja greškom ode u minus.

Dispatch:
Za sada, kao na labu – one četiri linije koda. Kada uradite sistemske pozive preko
kernel steka, promenidemo.

Start:
Samo obeleži nit kao spremnu i stavi je u Scheduler. Voditi računa ako se više puta
poziva za istu nit - to ne sme da se dogodi.

Wrapper:
U tekstu zadatka je rečeno je da je prva linija thread->run(). Nakon toga sve što
treba da uradite je da probudite sve niti koje su čekale na nju da završe, odnosno
da protrčite kroz onu listu koju smo pomenuli gore, i da sve niti vratite u
Scheduler (naravno, opet prvo im promenite stanje na spremne). Nakon toga,
obeležiti da je nit završila posao da je ne bismo opet stavljali u Scheduler i pozvati

10
dispatch jer smo pominjali da wrapper ne sme da se završi jer mu nismo stavili
callback, pa ko zna gde bi otišlo i kakve bi katastrofalne posledice imalo.

Što se tiče zaključavanja sekcija, to, za sad, isto uradite kao na labu. Al nemojte
mnogo da se mučite oko toga gde staviti, a gde ne – jer, verujte mi, kad uradite
drugi zadatak (onaj sa kernel stekom) bide vam skroz jasno gde to treba staviti –
na jedno jedino mesto. 

Semaphore

Ovo je najlakši deo svim studentima, za koji treba ubedljivo najmanje posla
i razmišljanja. Klasa Semaphore je detaljno objašnjena na slajdovima sa
predavanja, tako da se ja ovde nedu puno zadržavati, samo ukratko objasniti. Za
sad, metode klase Semaphore samo pozivaju metode klase KernelSem preko
objekta sa kojim je u vezi preko identifikatora ID (te dve klase su u odnosu kao
Thread i PCB, takođe čuvati sve KernelSem u globalnom nizu, opet, ID je indeks u
tom nizu), sem konstruktora i destruktora koji treba realizovati na isti način kao i
kod klase Thread (naravno u ovom slučaju koristiti KernelSem objekat umesto
PCB).
U klasi KernelSem dodati i jedan pokazivač na listu blokiranih niti na tom
semaforu. Metode klase KernelSem treba da izgledaju upravo onako kao što
izgledaju na predavanjima. Samo postavlja se pitanje kako blokiranje i
odblokiranje? Pa, ako je nit potrebno blokirati, obeležiti da je blokirana, staviti je
u listu blokiranih na semaforu i tražiti promenu konteksta. Ako je potrebno
odblokirati, uzeti prvu iz liste, promeniti joj status na spremna i staviti u
Scheduler. Ako ste me poslušali pa radite prvi zadatak sa izmenama iz drugog (pa
dete kasnije dodati i obradu sistemskih poziva preko kernel steka), ne morate
mnogo da se mučite oko zaključavanja sekcija – sredide se to posle.
Naravno, zbog testiranja, ono sto mislite da treba da se zaključa – zaključajte,
nemojte baš skroz ignorisati.

11
Event

Pre nego što krenem da objašnjavam događaje, da vam malo približim javni
test. Javni test se sastoji od nekoliko proizvođača i jednog potrošača. Vi pritiskate
po tastaturi i na taj način prozvodite slova. Potrošač ta slova uzima i ispisuje po
konzoli. Ako ništa nije pritisnuto, uzima od svakog proizvođača po znak koji mu je
dodeljen. Konceptom događaja vi dete omoguditi ovo u vezi sa tastaturom.

Šta se inače desi kada se pritisne taster? Generiše se prekid i skoči se na


prekidnu rutinu. U konzolnoj aplikaciji ta prekidna rutina ne radi ništa, ali postoji. I
ovde je, naravno, ideja da se ta prekidna rutina ukrade, da smestimo našu u taj
ulaz, a naša rutina de samo imati posao da nas obavesti da se desio određeni
događaj – u ovom slučaju, taster je pritisnut. Ne treba da vas brine to oko
tastature, to je samo javni test koji je ved napravljen, vaš posao je da napravite tu
rutinu i objekat događaja.

Prvo demo, naravno, objasniti lakši deo, koncept događaja. Klasu Event
treba realizovati kao binarni semafor. Šta to znači? To znači da value na
semaforu, tj. event-u, može biti samo 0 ili 1 – ili se nešto desilo, ili nije. Takođe to
znači da se samo jedna nit (i to ona koja je napravila event) može blokirati na tom
binarnom semaforu, pa nam ne treba lista, ved samo jedan pokazivač.
Naravno, i u ovom slučaju uvezati klase Event i KernelEv kao kod semafora.
Kako onda izgledaju signal i wait?
Pa, umesto sto inkrementiramo i dekrementiramo value, samo demo ga
postavljati na 0 ili 1 i to na slededi način:

KernelEv::signal():
Ako je pokazivač na blokiranu nit jednak 0, postaviti value na 1.
U protivnom, odblokirati nit i postaviti pokazivač na 0.

KernelEv::wait():
Ako je running nit jednaka kreatoru ovog dogadjaja (potrebno je zapamtiti ko je
napravio događaj) raditi sledede:
Ako je value jednak 0 – blokirati nit.
Ako je value jednak 1 – postaviti value na 0.

12
E sad, onaj teži deo u vezi sa rutinom, makroom i klasom IVTEntry.
Šta je posao makroa PREPAREENTRY?
Ovaj makro de nam pripremiti rutinu, staviti je u određeni ulaz i napraviti objekat
klase IVTEntry koji sadrži potrebne podatke. Ovo du vam, na vašu sredu, najlakše
objasniti, ako vam napišem kod za taj makro.

#define PREPAREENTRY(numEntry, callOld)\


void interrupt inter##numEntry(...); \
IVTEntry newEntry##numEntry(numEntry, inter##numEntry); \
void interrupt inter##numEntry(...) {\
newEntry##numEntry.signal();\
if (callOld == 1)\
newEntry##numEntry.callOld();\
}

Šta su ove silne tarabice? Pa pošto za svaki događaj moramo napraviti posebnu
rutinu i poseban objekat klase IVTEntry, moramo ih različito imenovati iako u
suštini rade isti posao. Ove tarabice upravo to omogudavaju. Kada se prilikom
prevođenja izvrši tekstualna zamena, a na primer, pozvan je PREPAREENTRY(9, 0),
ovaj gore makro de izgledati ovako:

void interrupt inter9(...);


IVTEntry newEntry9(9, inter9);
void interrupt inter9(...) {
newEntry9.signal();
if (0 == 1)
newEntry9.callOld();
}

Sada imamo rutinu i objekat, ali šta se radi u konstruktoru objekta klase
IVTEntry?

Potrebno je naravno čuvati staru rutinu da bismo je restaurirali ili zvali ako je
drugi parametar makroa jednak 1.
Tip polja u kom se ta rutina čuva, kao i još neka objašnjenja su data na lab vežbi:
typedef void interrupt (*pInterrupt)(...);

13
Kada imamo taj tip uraditi sledede:

oldRout = getvect(numEntry);
//oldRout je upravo tipa pInterrupt
setvect(numEntry, interruptRoutine);
//Ova dva parametra su prosleđenja konstruktoru i predstavljaju broj ulaza i samu
rutinu koja se upisuje u taj ulaz

Takođe je potrebno uvezati objekat klase KernelEv i objekat klase IVTEntry jer
onaj signal koji se poziva u gore napisanoj rutini, de pozivati signal klase KernelEv,
da mu signalizira da se nešto desilo.
Metoda callOld() samo poziva staru rutinu: oldRout();

U destruktoru obavezno restaurirati rutinu koju ste prethodno sačuvali u oldRout.

Sistemski pozivi

Po mom mišljenju, ovaj projekat je mnogo lakši ako se na ovakav način radi
obrada sistemskih poziva, jer kao što sam ved napomenuo, ovde ne morate da
razmišljate o zaključavanju sekcija. Obavezno još par puta pročitajte tekst zadatka
i nekoliko puta dok budete čitali ovo uputstvo se vradajte i čitajte onaj deo teksta
koji opisuje ovu obradu. Prvo se postavlja pitanje, šta su uopšte u našem slučaju
sistemski pozivi, tj. šta potpada pod sistemski poziv? To su upravo one metode
koje su dostupne korisniku, tj. nekom ko bude koristio naše malo jezgro. Znači svi
konstruktori, destruktori i javne metode iz interfejsa iz teksta zadatka.

Prvo treba napisati deklaraciju metode koja de predstavljati rutinu koja obrađuje
sistemske pozive:
void interrupt SysCallRout(...) i postaviti je u neki slobodan ulaz (slobodni ulazi su
od 60h) pomodu metode setvect.

Obradu sistemskih poziva demo podeliti u nekoliko koraka:


1. Pakovanje argumenata u registre procesora
2. Skok na prekidnu rutinu
3. Dohvatanje parametara

14
4. Prelazak na kernel stek
5. Prepoznavanje šta se desilo
6. Izvršavanje operacije
7. Povratak iz sistemskog poziva

Imademo jednu strukturu koja de nam čuvati sve podatke potrebne za obradu.
Ona de, pored ostalih podataka, imati i jedan enum koji de nam govoriti koji
sistemski poziv se desio.

Potrebno je sad sve javne metode izmeniti tako da rade na slededi način (a njihov
kod sačuvati, to nam je svejedno potrebno, samo de se kasnije izvršiti):

SystemData sysData;
sysData.name = ….
sysData.param1 = ….
sysData.param2 = ….
....
sysData.paramX = ....

Napravili smo lokalnu strukturu koja de enkapsulirati sve podatke potrebne za


obradu sistemskog poziva. Šta znače ova polja?
Name polje predstavlja upravo gore navedeni enum, koji nam govori koji
sistemski poziv treba da obradimo. Ako smo u metodi sleep, name de biti SLEEP,
ako smo u metodi Semaphore::wait, name de biti SemWait (na vama je da
odlučite kako dete nazvati) itd.
param1, param2, ...., paramX su podaci potrebni za određeni sistemski poziv.
Nede nam svi biti potrebni u svakom sistemskom pozivu, ali na primer, u
konstruktoru klase Thread nam treba StackSize, TimeSlice i Thread*, u
destruktoru klase Semaphore nam treba ID da bismo znali koji da obrišemo iz
niza, u sleep metodi nam treba Time da bismo znali koliko de nit spavati itd...

Slededi korak je da zapakujemo taj argument u registre procesora. Pošto se


registri cx i dx ne koriste za instrukcije (nego samo ax i bx), koristidemo njih.
Naravno, prvobitno ih za svaki slučaj sačuvajte na steku, sa dve push instrukcije.
Imamo podatak sysData (koji sadrži sve potrebne informacije), pa demo njegov
segment i offset staviti u registre cx i dx.

15
unsigned tcx = FP_SEG(&newData); //Saljemo adresu sa &
unsigned tdx = FP_OFF(&newData);
asm{
mov cx, tcx;
mov dx, tdx;
}

Kada smo sve zapakovali, zovemo prekidnu rutinu kao: asm int brojUlaza, gde je
brojUlaza onaj ulaz gde ste smestili rutinu. I na kraju, skidamo sa steka prethodno
sačuvane vrednosti registara cx i dx (paziti na redosled kojim ih skidate!).

Sta se desi kada pozovemo prekidnu rutinu?


Prvo što treba da uradimo je da snimimo kontekst running niti. Imademo jedan
globalni (obavezno globalni, nije preporučljivo imati nikakve lokalne podatke u
rutinama) podatak SysData u koji demo dočekati onaj podatak poslat kroz registre
procesora. Učitati vrednosti registara cx i dx u neke unsigned promenljive, a zatim
pomodu makroa MK_FP dobiti pokazivač (makro prima dva parametra, segment i
offset podatka i na osnovu njih rekonstruiše pokazivač).

globalSysData = (SysData*)MK_FP(tcx, tdx);


//pod pretpostavkom da je u tcx segment pokazivača, a u tdx offset.

Slededi korak je prelazak na kernel stek. Taj stek je ništa drugo nego običan niz,
koji nije potrebno inicijalizovati kao PCB, samo mu treba staviti SS, SP i BP na
stackSize (ne na stackSize – 1 jer na 8086 stack pointer ukazuje na poslednju
zauzetu lokaciju). Jednostavno, pomodu asemblerskih instrukcija promenite stek.

Kada smo prešli na kernel stek, preko parametra name iz našeg pokazivača demo
preko jednog ogromnog switcha (ili if else if else if, ....) prepoznati sta se desilo.
Kada nađemo koja operacija je zahtevana, jednostavno je izvršimo. Možete
napraviti neke kernel metode pa ih pozivati, npr. za sleep napravite _sleep i slično
i one de imati jako sličan (ako ne i isti) kod koji su imale javne metode, dostupne
korisniku, pre nego što smo izmenili projekat.

Na kraju, restauriramo kontekst running niti. Kontekst kernel niti (tj kernel steka,
kernel nit nije prava nit) nije potrebno snimati.

16
Napomena: Kako uraditi Semaphore::value? To je jedina metoda koja ima
povratnu vrednost. Jednostavno upišete tu vrednost u pokazivač (je l’ vam sad
jasnije što je bolje da bude globalni?), pročitate je u metodi Semaphore::value i
vratite.

Ono što je jako važno je da, dok smo na kernel steku, nikako ne sme dodi do
promene konteksta. Kako onda uraditi blokirajude pozive (pozivi koji oduzimaju
procesor niti – sleep, dispatch, wait, ...)? Jednostavno, blokirajudi pozivi nede
menjati kontekst, nego de samo postavljati neki fleg da je zahtevana promena
konteksta. Na kraju rutine, pre nego što restauriramo running nit, prvo demo
pitati da li je potrebna promena konteksta. Ako jeste, onda pitamo da li smemo da
stavimo running nit u Scheduler, uzmemo novu nit iz Schedulera i promenimo
kontekst (slično kao u timeru – nemojte pozivati timer ovde!).
Na ovaj način smo regulisali da se timer nikad ne poziva kao timer(); ved samo po
pravilu, na svakih 55ms.

I sad onaj deo što se tiče zaključavanja sekcija. Jedan lock kao prva linija u rutini,
jedan unlock kao poslednja linija u rutini. To je sve. Taj lock samo brani promenu
konteksta softverskim flegom, ne brani prekide. Prekide je potrebno dozvoliti
asemblerskom instrukcijom asm sti dok smo u kernel niti.

Zašto je potrebno dozvoliti prekide? Moramo ih eksplicitno dozvoliti jer interrupt


metode na ulazu automatski brišu I fleg i ne dozvoljavaju prekide. Prekidi moraju
da nam budu omogudeni dok obrađujemo sistemski poziv da bismo dozvolili
sistemu da diše – da reaguje na timer i tim smanjuje vreme spavanja ostalim
nitima koje spavaju i da reaguje na događaje (u ovom slučaju na događaje od
tastature).

Nemojte da vam sad pada koncentracija dok ovo čitate, ovaj deo je jako važan. 
U tekstu zadatka je rečeno da nakon svakog događaja je potrebno menjati
kontekst. Takođe, timer menja kontekst kada je counter == 0. Ali šta dok smo na
kernel steku? Kontekst nikako ne sme da se promeni! Zbog toga, u timeru (samo
ako je potrebna promena konteksta, tj ako je counter == 0) i u KernelEv::signal
pitati da li sistem lock-ovan, pa ako jeste, postaviti fleg koji zahteva promenu

17
konteksta (isti onaj koji smo postavljali za blokirajude pozive), a ako nije sistem
lockovan, onda uraditi promenu konteksta.
I da, ovaj KernelEv::signal nema potrebe (staviše ako ste radili ovako ko što sam
naveo gore i ne sme) raditi preko kernel steka, nego jednostavno, samo neka
odradi ono što i treba, tj. što smo napisali u sekciji Event.

Za one koji žele efikasnije da urade


Niko vam nede zameriti ako uradite sistemske pozive kao što smo gore napisali.
Ali, postoji efikasnije rešenje. Umesto džinovskog switcha, možemo slati segment i
offset metode koja treba da se pozove, pa postavljati na stek (koji sad treba
drugačije inicijalizovati). Ili jos efikasnije, napraviti niz pokazivača na funkcije, pa
prosleđivati indeks one koja treba da se pozove (rešenje osmislio Bojan Roško).
Ali, pošto su ovo naprednije obrade, ostavljam vam, ako želite, da sami razmislite
kako to može da se izvede.

Mogući problemi

Ved vam u tekstu zadatka piše koji su najčešdi problem. Imajte ono u vidu dok
kucate projekat. U ovoj sekciji du navesti one probleme koje smo mi imali dok smo
radili projekat. Kada projekat počne da vam neobjašnjivo puca ili vam kompajler
izbacuje neobjašnjivu grešku, pokušajte rešenje nadi ovde.

 Ime fajla ne sme biti duže od 8 karaktera


 Stavljati ; na kraju definicije klase i strukture
 Error function is now obsolete – ovo se najčešde javlja kod makroa,
verovatno niste u javni projekat uključili file koji ga sadrži
 Error kod asemblerskih instrukcija: asm i { moraju biti u istom redu
 Ako asembler iz neobjašnjivog razloga prijavljuje error, probajte da
promenite imena promenljivih koje koristite u asm (npr. ne može seg ili
offset da se koristi)
 60h je heksa vrednost, tako da ili navedite 0x60 kada podešavate rutine, ili
pretvorite u decimalnu

18
 Pazite da slučajno dve iste rutine ne upišete u isti ulaz (npr. i za timer i za
event upišete u 60h)
 Marko PREPAREENTRY se poziva pre svih vaših inicijalizacija, tako da ako
objekat IVTEntry smeštate u dinamički niz, pravite grešku, jer taj niz još nije
napravljen
 Greškom ste negde zaboravili da podesite fleg (ili enum) koji opisuje stanje
niti (spremna, blokirana, gotova)
 Obavezno oslobađati memoriju (nemojte to ignorisati, bcc nede to sam
uraditi pa može dodi do velikih problema)
 Restaurirajte rutine za timer i za događaje
 Ako vam javni test puca kada pritisnete ESC, vrlo verovatno da vam se
rutina za događaj (tj. za tastaturu) ne restaurira dobro
 Zbog greške u javnom testu (koja nisam siguran da je ispravljena) prilikom
pokretanja javnog testa, dešava se da tastatura prestane da reaguje nakon
drugog pokretanja u konzoli – rešenje – pre nego što u destruktoru
restaurirate rutinu, samo je još jednom pozovite. Naravno ovo uradite tek
nakon što vam se javi ovakva situacija, ali prvobitno kontaktirajte asistenta.

Pitanja za odbranu

Ovo su samo neka od pitanja koja vas mogu pitati na odbrani. Uglavnom se
pitanja baziraju na tome šta i kako ste radili, ali mogu biti i neka što se tiče teorije.
Na ova pitanja možete odgovore nadi u celom ovom tekstu, tako da ih nedu
odgovarati.

1) Opisati obradu sistemskih poziva.


2) Početne vrednosti registara - zašto se stavljaju i sta znači koje mesto.
3) Kako se radi sleep (specijalan slučaj - sleep(0).
4) Kako se postavlja ID u kojoj klasi (specijalan slucaj - KernelEv nije friend class)?
 KernelEv nema razloga da ne bude friend class, tako da to slobodno dodajte
5) Makro prepareentry - kako radi i sta znači bukvalno svaka linija i zasto je on
potreban?
6) Kako se obrađuju blokirajudi pozivi (Sta su blokirajudi pozivi?)?

19
7) Sta je preuzimanje?
8) Brojna pitanja za efikasniju obradu (kako efikasnije su mogli sistemski pozivi na
primer).
9) Zašto je potrebno da budu dozvoljeni prekidi dok obrađujemo sistemski poziv?
10) Da li, dok smo na kernelu (tj dok obrađujemo sistemski poziv), mogu da se
ugnezde prekidi?
 Ne mogu jer prvi put kad dođe prekid od timera ili događaja, interrupt fleg
de se obrisati
11) Pošto su prekidi na kernelu dozvoljeni, a potrebno je nakon svakog događaja
promeniti kontekst, zar to nede napraviti problem i ako hode, kako ga razrešiti?
12) Zašto nipošto ne smemo vršiti promenu konteksta dok smo na kernel steku?
13) Brojna pitanja koja bukvalno stoje u tekstu zadatka (tipa parametri
prepareentry i slično).
14) Promena konteksta - Šta je SS, SP i kad mora da se menja i BP?
15) Šta je PC, tj kako na se 8086 "pravi"?
16) Šta je 8086?
17) Zašto na 8086 je moguce uraditi Type* pointer = 0; pointer->callMethod();
 8086 nema virtuelnu memoriju pa je mogude dereferencirati čak i null
18) Šta se dešava na ulazu u interrupt metodu, a sta na izlasku?
 Na ulasku se implicitno zabranjuju prekidi, a na izlasku se implicitno
dozvoljavaju
19) Sta označava modifikator interrupt (odgovor prekidna rutina se ne uvažava)?
20) Metoda/funkcija koja menja kontekst, koji modifikator mora da sadrži I zašto i
šta bismo morali da radimo ako ne bi sadržala taj modifikator?
21) Šta je volatile i na promenljivu na koju ste ga stavili, zašto ste to stavili?
22) Šta de se desiti ako obrišemo aktivnu nit, a nije pozvan waitToComplete u
destruktoru?
23) Da li nit može sama za sebe da pozove waitToComplete()?
24) Da li je potrebno konstruktor zaštititi od promene konteksta i zašto?
 Gradivo OOP2
25) Prilikom promene konteksta potrebno je čuvati podatke iz pitanja 15). Zašto
samo to? Ako smo mi u metodi method() i desi se prekid od timera, u trenutku
kad sačuvamo kontekst u timeru, zar nas slededi put nede vratiti na to mesto gde
je urađeno running->sp = tsp, ....; a ne na deo koda u method() u kom se desio
prekid od timera?
 PC je sačuvan na ulazu u metodu jer smo je obeležili kao interrupt. On se
menja dok idemo kroz metodu, ali je sačuvana potrebna vrednost

20
Kako izgleda odbrana

Od julskog roka 2015. su izmenjene odbrane. Više se projekat ne brani kod


demonstratora nego kod profesora. Da li de se nešto opet menjati, ne znam, ali du
vam opisati kako izgleda odbrana i u jednom i u drugom slučaju.
Pre nego što predate projekat, moj savet je da obavezno odete do 26-ice i da ga
dobro istestirate. Čak ga i build-ujte tamo.

Stari način odbrane:


Profesor vas pozove da uđete i smesti vas kod demonstratora kod kog on odluči.
Demonstrator pokrene javni test i malo testira sa različitim parametrima. S
kolikim entuzijazmom demonstrator lupa po tastaturi, zavisi od osobe do osobe i
koliko je raspoložen. Neki samo pritisnu par dugmida (i obavezno strelice jer one
generišu prekide i kad ih držite dok ostali tasteri samo kad ih pustite), a neki se
baš iživljavaju nad tastaturom. Kada sve to lepo prođe, onda vam postavljaju par
pitanja, da opišete šta i kako ste radili. I eventualno neko pitanje iz teorije. Mnogo
vole ono za PCB i za makro. Kada preživite demonstratora i ako ste ostavili dobar
utisak, asistent vas nede mnogo propitivati. Jedno jednostavno pitanje, uglavnom
da opišete kako ste radili sistemske pozive. Ali, ako niste ostavili dobar utisak kod
demonstratora, vrlo je verovatno da de vam asistent dati modifikaciju, koju ako ne
odradite, padate odbranu.

Nov način odbrane:


Pošto je ovo još uvek u beta verziji, opisadu vam kako je to izgledalo (tj kako su mi
preneli, ja sam imao srede pa odbranio projekat u predroku po starom načinu
odbrane). Nema više demonstratora, samo asistent, koji vas propituje i sada je
modifikacija obavezna. Modifikacija je, sad u julu, bila da se u jedan ulaz može
smestiti više događaja. Rešenje je jednostavno – stavite sve u niz i ceo niz
pozovete. Ali nije problem u rešenju, nego je problem u ostatku projekta. Ako
vam radi javni test, ne mora da znači da vam je projekat dobar. Može da se desi
da ste projekat toliko nabudžili da i jedna sitna promena kao što je ; može dovesti
do pada, a kamoli velika kao što je modifikacija. Zato vam je moj savet da dobro
istestirate svaki deo projekta pojedinačno da ne biste imali problema. Profesor
nosi te modifikacije kudi i onda ih pokrede i ako ne radi, automatski padate.

21

You might also like