17
4. Specijalne softverske tehnike Već smo ukazali na razlike u razvoju softvera između embedded sistema i sistema opšte namene. Ukazaćemo sada na nekoliko programskih tehnika koje su tipične za embedded sisteme. 4.1 Manipulisanje hardverom Programeri embedded sistema često treba da kreiraju kôd koji direktno manipiliše radom nekog perifernog uređaja. U zavisnosti od tipa arhitekture, taj uređaj može biti port-preslikani ili memorijsko- preslikani. Ako arhitektura podržava izdvojeni U/I adresni prostor, a uređaj je port-preslikani, tada ne postoji drugi način nego da se kôd "spusti" na asemblerski jezik i na taj način obavi aktuelna manipulacija. Razlog za ovakav pristup je sledeći: Kod C-a ne postoji mogućnost direktne manipulacije portovima. Kod nekih C kompajlera postoji mogućnost korišćenja specijalnih CPU-ovih interno ugrađenih funkcija koje se u trenutku prevođenja zamenjuju CPU-ovim specifičnim asemblersko- jezičkim operacijama. To znači da kod specifičnih mašina, interne funkcije omogućavaju programeru da izbegne in-line asemblerski kôd. Sa druge strane stvari se znatno jednostavnije rešavaju ako je uređaj memorijsko-preslikani. 4.1.1 In-line asembliranje Ako je potrebno da se čita ili upisuje u neki port, tada in-line asembliranje predstavlja verovatno najjednostavnije rešenje. No, in-line adresiranje je uvek jako zavisno od kompajlera. Ilustracije radi, neki proizvođači koriste direktivu #pragma da bi izbegli asemblerske instrukcije, drugi koriste specijalne simbole kakvi su _asm/_endasm, a treći smeštaju asemblerski kôd u nešto što liči na funkcijski poziv kao što je to prikazano na sledećem primeru asm("iskazi na asemblerskom jeziku"); Jedini način da se zna šta se od pojedinog kompajlera očekuje da obezbedi (ili da on dozvoljava in-line asembliranje) je da se konsultuje dokumentacija o kompajleru. S obzirom da je in-line asembliranje zavisno od kompajlera, dobra ideja se sastoji u sledećem: Treba grupisati (wrap) sve asemblerske operacije u posebne funkcije, i memorisati ih u poseban fajl. Tada, ako postoji potreba za promenom kompajlera, pogodnost ovog pristupa je ta što će se promeniti asemblerski kôd samo na jednom mestu. Na primer, ako je potrebno da se upiše ili pročita neki registar- uređaja čija je port adresa 0x42, tada je potrebno kreirati funkcije pristupa na sledeći način int read_reg() { asm ("in acc,0x42"); } void write_reg (int newval) { asm (" mov acc,newval out 0x42 "); } U konkretnom primeru, instrukcije in i out su instrukcije za pristup U/I uređajima, a ne za pristup memorijskim lokacijama radi potreba čitanja i upisa. Treba pri ovome naglasiti da ove instrukcije sadrže neke skrivene pretpostavke koje ne mora da važe za sve kompajlere. Kao prvo, int read_reg() pretpostavlja da povratna vrednost funkcije treba da bude smeštena u akumulatoru. Različiti kompajleri koriste različite konvencije (ponekad zavisne od obima podataka), sve u zavisnosti od toga gde treba smestiti povratnu vrednost. Kao drugo,

4. Specijalne softverske tehnikees.elfak.ni.ac.rs/es/Materijal/Pog.4-Specijalne-softverske-tehnnike.pdfSpecijalne softverske tehnike ... Treba grupisati (wrap) sve asemblerske operacije

  • Upload
    others

  • View
    2

  • Download
    0

Embed Size (px)

Citation preview

4. Specijalne softverske tehnike Već smo ukazali na razlike u razvoju softvera između embedded sistema i sistema opšte namene. Ukazaćemo sada na nekoliko programskih tehnika koje su tipične za embedded sisteme. 4.1 Manipulisanje hardverom Programeri embedded sistema često treba da kreiraju kôd koji direktno manipiliše radom nekog perifernog uređaja. U zavisnosti od tipa arhitekture, taj uređaj može biti port-preslikani ili memorijsko-preslikani. Ako arhitektura podržava izdvojeni U/I adresni prostor, a uređaj je port-preslikani, tada ne postoji drugi način nego da se kôd "spusti" na asemblerski jezik i na taj način obavi aktuelna manipulacija. Razlog za ovakav pristup je sledeći: Kod C-a ne postoji mogućnost direktne manipulacije portovima. Kod nekih C kompajlera postoji mogućnost korišćenja specijalnih CPU-ovih interno ugrađenih funkcija koje se u trenutku prevođenja zamenjuju CPU-ovim specifičnim asemblersko-jezičkim operacijama. To znači da kod specifičnih mašina, interne funkcije omogućavaju programeru da izbegne in-line asemblerski kôd. Sa druge strane stvari se znatno jednostavnije rešavaju ako je uređaj memorijsko-preslikani. 4.1.1 In-line asembliranje Ako je potrebno da se čita ili upisuje u neki port, tada in-line asembliranje predstavlja verovatno najjednostavnije rešenje. No, in-line adresiranje je uvek jako zavisno od kompajlera. Ilustracije radi, neki proizvođači koriste direktivu #pragma da bi izbegli asemblerske instrukcije, drugi koriste specijalne simbole kakvi su _asm/_endasm, a treći smeštaju asemblerski kôd u nešto što liči na funkcijski poziv kao što je to prikazano na sledećem primeru asm("iskazi na asemblerskom jeziku"); Jedini način da se zna šta se od pojedinog kompajlera očekuje da obezbedi (ili da on dozvoljava in-line asembliranje) je da se konsultuje dokumentacija o kompajleru.

S obzirom da je in-line asembliranje zavisno od kompajlera, dobra ideja se sastoji u sledećem: Treba grupisati (wrap) sve asemblerske operacije u posebne funkcije, i memorisati ih u poseban fajl. Tada, ako postoji potreba za promenom kompajlera, pogodnost ovog pristupa je ta što će se promeniti asemblerski kôd samo na jednom mestu. Na primer, ako je potrebno da se upiše ili pročita neki registar-uređaja čija je port adresa 0x42, tada je potrebno kreirati funkcije pristupa na sledeći način int read_reg() { asm ("in acc,0x42"); } void write_reg (int newval) { asm (" mov acc,newval out 0x42 "); } U konkretnom primeru, instrukcije in i out su instrukcije za pristup U/I uređajima, a ne za pristup memorijskim lokacijama radi potreba čitanja i upisa. Treba pri ovome naglasiti da ove instrukcije sadrže neke skrivene pretpostavke koje ne mora da važe za sve kompajlere. Kao prvo, int read_reg() pretpostavlja da povratna vrednost funkcije treba da bude smeštena u akumulatoru. Različiti kompajleri koriste različite konvencije (ponekad zavisne od obima podataka), sve u zavisnosti od toga gde treba smestiti povratnu vrednost. Kao drugo,

write_reg() pretpostavlja da će kompajler prevesti referenciranje na newval, kao odgovarajuće referenciranje preko magacina (napomenimo da se argumenti funkcije prenose preko magacina). No, ne rade svi kompajleri baš tako. Za slučaj da kompajler ne podržava in-line asembliranje, neophodno je kreirati slične Read/Write funkcije na asemblerskom jeziku, a zatim ih povezati sa ostatkom programa. Pisanje funkcija na asemblerskom jeziku je znatno složenije iz razloga što mora biti u skladu sa konvencijama koje koristi kompajler, a odnose se na okvire magacina. Moguće je koristiti šablon za asemblerski jezik, ako se iskompajlira jednostavna funkcija na C-u koja direktno manipuliše sa korektnim brojem argumenata na asemblerskom jeziku. int read_reg_faks() { return 0x7531; } Zamenom operacije čitanja porta sa instrukcijom load-ovanja literala, i izmenom imena funkcije, vrši se direktna konverzija generisanog asemblerskog programa na kompletnu funkciju čitanja porta. 4.1.2 Pristup memorijsko-preslikanim adresama Manipulisanje memorijsko-preslikanim uređajima je relativno jednostavno. Najveći broj okruženja podržava dva metoda: a) bazirani-na-linker-u; i b) bazirani na pokazivačima.

Metod baziran na linker-u koristi kvalifikator tipa extern kako bi informisao kompajler o tome da kompajler koristi resurs koji je definisan van granica programa. Linija extern volatile int device_register; ukazuje kompajleru da resurs obima integer, nazvan device_register, egzistira van granica programa, i nalazi se na mestu koje linker poznaje. Sa ovakvom deklaracijom ostatak programa može da čita i upisuje iz/u uređaj upravo kao da je to globalna promenljiva. Naravno, ovo rešenje ne daje odgovor na pitanje jer ne objašnjava kako linker zna gde je lociran uređaj. Da bi uspešno povezali program sa ovim tipom eksterne deklaracije, neophodno je koristiti komandu linker-a da bi promenljivoj pridružili ime koje je odgovarajuće za tu adresu. Ako registar o kome je reč je lociran na adresi $40000000, tada komanda može biti oblika PUBLIC _device_register = $40000000 Oni koji preferiraju ovaj metod treba da koriste linker kako bi odgovarajuće simbole pridružili fizičkim adresama. Sa druge strane, oni koji za deklaraciju registra-uređaja koriste extern treba da pamte na jednom mestu svu informaciju o sistemsko-memorijskoj mapi koja se nalazi (čuva) u komandnom fajlu linkera. Alternativno, pristup memorijsko-preslikanom hardveru moguće je postići pomoću pokazivača na C-u. Jednostavni cast iskaz može da uslovi pokazivača da adresira bilo koju memorijsku lokaciju. Na primer, program može da manipuliše ASIC-uređajem koji se vidi od strane softvera kao polje od 64, 16-bitnih, memorijsko-preslikanih registara koji počinju od memorijske adrese 0x40000000, koristeći se sledećim kôdom: unsigned short x; /* lokalna promenljiva */ volatile unsigned short *io_regs; /* pokazivač na ASIC */ io_regs = (unsigned short *) 0x40000000; /* pokazivač na ASIC */ x = io_regs [10]; /* čitanje registra 10 */ Ovaj primer deklariše io_regs da bude pokazivač na neoznačenu 16-bitnu (short) promenljivu. Treći iskaz dodele koristi cast sa ciljem da uslovi io_regs da ukaže na memorijsku lokaciju

0x40000000. cast operator usmerava kompajler da ignoriše sve što zna o tipu provere i uradi ono što programer upravo želi. 4.1.3 Pobitne operacije U programima embedded sistema često se javlja potreba za manipulisanje sadržajem pojedinih bitova koji su sastavni delovi hardverskih registara. U najvećem broju slučajeva, sa praktične strane najbolje je da se pročita sadržaj celokupnog registra, promeni vrednost bita, a zatim promenjena vrednost, ako postoji potreba, ponovo upiše u registar uređaja. Tako na primer, da bi se promenio sadržaj trećeg bita sa desne strane, koristi se sledeći kôd: const char status_mask = 0x04; extern volatile char device_register; device_register = device_register | status_mask; // uslovljava se da treći bit sa desne strane bude 1 device_register = device_register & (∼status_mask); // uslovljava se da treći bit sa desne strane bude 0 device_register = device_register ^ status_mask; // menja se stanje trećeg bita sa desne strane Isti efekat se postiže korišćenjem sledećih operatora dodele device_register | = status_mask; device_register & = (∼status_mask); device_register ^ = status_mask; Literal koji odgovara bitu koga treba promeniti naziva se maska. Definisanje konstante koja predstavlja masku (status_mask) izoluje ostatak kôda od nepredvidivih promena u hardveru (ili u nerazumevanju hardvera od strane programera). Konstanta takođe može značajno da poboljša čitljivost ovog tipa kôda. Naglasimo da svi embedded kompajleri podržavaju ANSI C const. Za slučaj da kompajler ne podržava const, neophodno je koristiti preprocesor čiji je zadatak da statusnoj maski dodeli simboličko ime (vidi listing). Forma const se preferira jer ona podržava statički tip provere. #define STATUS_MASK 0x04 device_register = device_register | STATUS_MASK; I pored toga što ovaj read/modify/write metod radi korektno u najvećem broju slučajeva, ipak kod određenih uređaja operacija read može da uzrokuje nepoželjne side-effect-e (kakav je brisanje pending interrupt). Za slučaj da se registar ne može pročitati, a da se pri tome ne stvori novi problem, program mora da koristi shadow registar. Shadow registar je promenljiva koju program koristi da bi sačuvao trag o sadržaju registra. Da bi se promenila vrednost bita potrebno je preduzeti sledeće aktivnosti:

• pročitati shadow registar • modifikovati shadow registar • zapamtiti vrednost shadow registra • upisati novu vrednost u uređaj

U svojoj najkompaktnijoj formi, kôd treba da ima sledeći oblik #define STATUS_MASK 0x04 int shadow; device_register = (shadow | = STATUS_MASK);

4.1.4 Korišćenje storage klase modifikatora volatile

Važan atribut koji se često izostavlja a koristi se za modifikaciju podataka kada se kôd na HLL-u C ili C++ spregne sa hardverom perifernog uređaja je modifikator storage klase nazvan volatile. Najveći broj kompajlera polazi od pretpostavke da je realna memorija u suštini prava memorija, i u cilju optimizacije kôda, čini određene pretpostavke o karakteristikama te memorije. Ključna pretpostavka je sledeća: Vrednost koja se čuva u memoriji neće se promeniti ako se u nju ne upiše novi podatak. Ipak, sadržaj hardversko-periferijskih registara stalno se menja. Ilustracije radi, razmotrimo slučaj UART-a. UART prima serijske podatke od spoljnjeg sveta, izbacuje ekstra bitove (parnost, start bit, stop bitove), i predaje bajt podataka CPU-u radi čitanja. Pri brzini prenosa od 50 kBaud-a, za predaju jednog karaktera potrebno je vreme od 0.2 ms. Neka u procesorskom sistemu magistrala za pristup memoriji radi sa brzinom od 100 MHz, a ciklus pristupa memoriji traje četiri taktna intervala. To znači da je za period od 0.2 ms moguće u UART-ov izlazni registar za podatke (bafer podataka) upisati oko 5000 puta. Ono što je sada najvažnije je sledeće: Neophodno je da postoji neki mehanizam koji će kontrolisati brzinu prenetih podataka ka UART-u.

UART usklađuje brzinu prenosa podataka na osnovu stanja statusnog bita, nazvan TBMT (Transmitter Buffer Empty). U konkretnom primeru, TBMT bit se postavlja na nisko kada se bajt podataka preda UART-u i ostaje na nisko sve dok se vrši serijska predaja. Kôd za ovaj primer je dat sledećim listingom: /* usvojimo da je U/I port lociran na 0x4000, U/I status je lociran na 0x4001, a TMBT =DB0; pri čemu je DB0=1 kada se karakter može poslati */ void main (void) { int *p_status /* pokazivač na statusni port */ int *p_data /* pokazivač na port podataka */ p_status = (int*)0x4001 /* dodeli pokazivač statusnom portu */ p_data = (int*)0x4000 /* dodeli pokazivač portu podataka */ do {} while ((*p_status & 0x01)==0); /* wait */ ...... ...... }

Listing 4.1. Kôd UART-a tipa pooling petlja Pretpostavimo da kompajler na C-u ustanovi da je kreirana pooling petlja u toku čijeg izvršenja se kontinualno čita statusni bit TBMT. U cilju efikasnijeg (pre svega bržeg) izvršenja programa, kompajler može da obavi optimizaciju (iz razloga što se TBMT-u veoma često pristupa) pa se ovaj memorijski podatak (misli se na TBMT) čuva u registru (registru se najbrže pristupa) ili u internom kešu za podatke umesto u glavnoj memoriji. Sa aspekta kôda sve će biti apsolutno korektno, ali se program neće ispravno izvršavati iz razloga što se nova memorijska lokacija podatka, koja odgovara statusnom bitu TBMT, neće više nikada ažurirati. Ključna reč volatile se koristi da ukaže kompajleru da ne napravi bilo kakav pokušaj usmeren ka promeni mesta te memorijske lokacije u memorijskoj hijerarhiji. Time se obezbeđuje da se sadržaj te memorijske lokacije može spontano menjati, i da se uvek direktno čita i upisuje u tu lokaciju. Shodno preporukama, kompajler neće pokušati da optimizira pristup toj promenljivoj, a takođe neće dozvoliti i da se ona čuva u kešu. Neki kompajleri koriste i specijalne ključne reči koje omogućavaju da se promenljive takvog tipa nikada ne nalaze u kešu. 4.1.5 Brzina izvršenja i obim kôda U velikom broju slučajeva u situacijama kada se operacije pristupa memoriji izvršavaju koristeći pokazivače, a ne putem standardnog (direktnog) referenciranja promenljivih, kompajler generiše veoma

efikasan kôd, kako u pogledu obima kôda, tako i u pogledu brzine izvršenja. Tako na primer, kada funkcija manipuliše istom promenljivom po nekoliko puta, ili zaredom pristupa elementima vektora (polja), tada referenciranja koja se baziraju na korišćenju pokazivača rezultiraju efikasnijem kôdu. Sledeća kratka sekvenca na C-u void strcpy2(char dst[] , char const src[]) { int i; for (i=0; src[i]; i+=1) { dst[i]=src[i]; } } prevodi se u sekvencu asemblersko-jezičkih instrukcija koja je oblika void strcpy2(char dst[] , char const src[]) { int i; 00000000: 4E56 0000 link a6,#0 00000004: 226E 0008 movea.l 8(a6),a1 00000008: 206E 000C movea.l 12(a6),a0 for (i=0; src[i]; i+=1) { 0000000C: 7000 moveq #0,d0 0000000E: 6008 bra.s *+10 ; 0x00000018 dst[i]=src[i]; 00000010: 13B0 0800 0800 move.b (a0,d0.l),(a1,d0.l) } 00000016: 5280 addq.l #1,d0 00000010: 4A30 0800 tst.b (a0,d0.l) 0000001C: 66F2 bne.s *-12 ; 0x00000010 0000001E: 4E5E unlk a6 00000020: 4E75 rts 00000022: 8773 7472 6370 dc.b 0x87,'strcpy2' 7932 0000002A: 0000 } Analizom kôda zaključujemo sledeće: Kada se kôd prevede sa subscript referenciranja, tada je obim kôda ove funkcije veličine 34 bajta. Uočimo da se za repetitivno izvršenje tela petlje funkciije (od move.b do bne.s) zahtevaju četiri instrukcije. Kao i veliki broj programskih sekvenci koje manipulišu vektorima, tako se i ova petlja može napisati da zavisi od pokazivača, a ne od subscript referenciranja, kao što je slučaj u sledećem primeru. void strcpy(char *dst, char const *src) { while (( *dst++ = *src++ )) {;} }

Duple male zagrade usmeravaju kompajler da vodi računa o dodeli. Vitičaste zagrade koje zaokružuju simbol ";" usmeravaju kompajler da vodi računa o praznom iskazu. Kod velikog broja kompajlera ova verzija se prevodi u asemblersku sekvencu koja ima sledeći oblik: void strcpy(char *dst, char const *src) { 00000000: 4E56 0000 link a6,#0 00000004: 226E 0008 movea.l 8(a6),a1 00000008: 206E 000C movea.l 12(a6),a0 while (( *dst++ = *src++ )) {;} 0000000C: 12D8 move.b (a0)+,(a1)+ 0000000E: 66FC bne.s *-2 ; 0x00000018 00000010: 4E5E unlk a6 00000012: 4E75 rts 00000014: 8673 7472 6370 dc.b 0x87,'strcpy', 0x00 7900 0000001C: 0000 } U ovom slučaju kompajlirani kôd zauzima 20 bajtova, a telo petlje se redukuje na samo dve instrukcije: move.b i bne.s. 4.2 Prekidi i rutine za obradu prekida Prekidi u suštini predstavljaju "život" svih računarskih sistema. Na osnovu analize strukture kôdova velikog broja tipičnih embedded aplikacija može se zaključiti da u najvećem broju slučajeva znatan deo svog vremena CPU troši na proveru stanja bitova statusa nekog U/I porta u polling petlji što u suštini usporava brzinu izvršenja programa (programska U/I tehnika za unos podataka). Sa druge strane, prekidi kao tehnike koje se koriste za ubrzanje U/I prenosa treba dodatno da prioritiziraju redosled važnosti obrade istovremenih U/I događaja. Tako na primer, voditi računa o pritisnutoj dirci na tastaturi nije tako kritično sa aspekta hitnosti opsluživanja kao što je, na primer, pamćenje podataka u slučaju kada dođe do nestanka napajanja. Konceptualno, ISR predstavlja jednostavnu programsku sekvencu. Spoljni uređaj (kod mikrokontrolera spoljni uređaj može biti interni u odnosu na čip, ali eksterni u odnosu na jezgro CPU-a) aktivira signal za prekid koji se vodi na INTR ulaz CPU-a. Ako je CPU u stanju da prihvati prekid, on prolazi kroz ciklus prihvatanja zahteva za prekid, u toku koga po automatizmu obavlja sledeće aktivnosi:

• smešta povratnu adresu naredne instrukcije u magacin • pribavlja adresu ISR-a (vektor) iz tabele-izuzetaka i skače na tu adresu u memoriji radi izvršenja

naredne instrukcije, nakon ovoga prelazi na izvršenje ISR-a • CPU odlučuje kada da zabrani i ponovo dozvoli rad novim prekidima • sačuva stanje internih resursa (registara) koji se koriste od strane ISR-a • da se odredi koji od uređaja je generisao zahtev za prekid (posebno ako su zahtevi deljivi) • izvrši ISR kôd • resetuje eksterne uređaje koji su generisali zahtev za prekid (ako je to neophodno) • obnovi stanje sistema • dozvoli rad drugim prekidima • obavi povratak iz prekidnog u prekinuti program.

4.2.1 Od polling-petlje do interrupt-driven Jedan tipičan primer embedded aplikacije koja ne zahteva bilo kakve prekide je kućni protivprovalni alarmni sistem. Na slici 4.1 prikazan je dijagram toka protivprovalnog alarmnog algoritma. Uočimo da nakon inicijalizacije sistema procesor kontinualno proverava stanje svakog senzora kako bi

ustanovio da li je on trigerovan (pobuđen) ili nije. Standardno vreme koje je potrebno da se proveri stanje svakog senzora je veoma kratko, tako da potencijalno prosečno vreme kašnjenja od trenutka kada je senzor trigerovan do trenutka kada procesor proverava njegovo stanje je kratko i iznosi nekoliko milisekundi ili kraće. U principu, latencija opsluživanja u najgorem slučaju odgovara vremenu prolaska kroz petlju.

Slika 4.1. Dijagram toka alarmnog sistema

Sada usložnimo malo sistem u tom smislu što ćemo da ugradimo sat realnog vremena, displej panel, a takođe i da se obezbedi telefonska veza sa vlasnikom objekta ili sa milicijom. U principu svaki ugrađeni hardver zahteva dodatno CPU-ovo vreme za opsluživanje. Kada broj U/I periferija postane dovoljno veliki tada vreme odziva na neki događaj kao i njegovo opsluživanje korišćenjem programirane U/I tehnike može da postane kritično pa se zbog toga rešenje traži u korišćenju interrupt-driven tehnike. 4.2.2 Ugnežđeni prekidi i višestruka-ulaznost Kada zadatak višeg prioriteta (nivoa) može da istisne izvršenje zadatka nižeg prioriteta tada stvari postaju značajno složenije. Iz tog razloga, kod jednostavnih sistema, brani se prihvatanje svih ostalih prekida sve dok sistem ne opsluži tekući prekid. U principu, kada tekuća prekidna rutina završi sa izvršenjem, ona ponovo dozvoljava rad (re-enables) novim prekidima. Ako je prekidima dozvoljeno da se gnezde tada programer mora da preuzme specijalne mere kako bi obezbedio svim funkcijama koje se pozivaju u toku opsluživanja rutina za obradu prekida da budu reentrant tipa. Za funkciju koja se poziva od strane thread-ova bez pogleda na sinhronizovani ili mutual metod pristupa kažemo da je reentrant. U principu važe tri pravila koja se odnose na to kada neka funkcija može da bude reentrant:

1. reentrant funkcija ne može da koristi promenljive na ne-atomiziran način sve dok se promenljive ne smeste u magacin zadatka koji je pozvao tu funkciju ili su pak privatne promenljive za taj zadatak (deo kôda je atomiziran ako se ne može prekidati od strane drugog);

2. reentrant funkcija ne može pozivati bilo koje druge funkcije koje same po sebi nisu reentrant; 3. reentrant funkcija ne može koristiti hardver na neatomiziran način.

Kada ISR pozove funkciju koja nije reentrant, program treba da poseduje osobine mutual pristupa, ako ne dolaziće do (postojaće) greške u sinhronizaciji. U opštem slučaju, ova situacija se javlja kada prekidna rutina asinhrono modifikuje kôd koji se koristi od strane drugog zadatka. Pretpostavimo, na

primer, da se real-time takt sistema aktivira svake sekunde i generiše prekid, a ISR ažurira strukturu podataka taktnog sistema (sata) koja se čuva u memoriji. Ako je zadatak na sredini čitanja taktne strukture podataka u trenutku kada se čita prekid od strane taktnog sistema, tada može da se desi promena vrednosti strukture podataka taktnog sistema na takav način da zadatak pročita pola starog vremena i pola novog vremena. Pri tome, vreme koje se raportira na displeju može da se ne složi sa aktuelnim u pogledu dana, meseca ili godine sve u zavisnosti u kom je stanju bilo u trenutku kada se vršilo čitanje. Primer jedne non-reentrant funkcije je prikazan u listingu 4.2. Bool fError; /* neko drugi postavi ovo */ void display ( int j ) { if (( !fError ) { printf ( "\nValue: %d", j ); j = 0; fError = TRUE; } else { printf ( "\nCould not display value"); fError = FALSE; } }

Listing 4.2. No-reentrant funkcija U listigu 4.2 funkcija ne može biti reentrant iz dva razloga: Kao prvo, Bool-ova promenljiva fError se nalazi van funkcije display() i na fiksnoj je lokaciji u memoriji. To znači da se ona može modifikovati pomoću bilo kog zadatka koji poziva funkciju display(). Takođe, korišćenje fError nije atomizirano jer se komutacija zadataka neće javiti između vremenskih trenutaka kada se fError testira i kada se fError postavlja. Zbog ovoga se narušava pravilo 1. Promenljiva „j“ je važeća jer je privatna za display(). Kao drugo, sledeći problem je taj što display() može da naruši pravilo 2 za slučaj da funkcija printf() nije reentrant. Određivanje da li je printf() reentrant tipa zahteva da se pogleda i analizira dokumentacija kompajlera od strane programera. Ako je programer sam kreirao funkcije, tada može biti siguran da li te funkcije ispunjavaju zahteve koji se odnose na reentrant funkciju. Za slučaj da se koriste bibliotečne funkcije koje se nude od strane proizvođača kompajlera ili od nekih drugih izvora, neophodno je da se obave neka ispitivanja i ustanove njihovi tipovi. 4.2.3 Merenje vremena izvršenja Kod velikog broja aplikacija od suštinske važnosti je da programer tačno zna koliko je vremena potrebno da se izvrši rutina. Tako na primer, određena vremensko kritična ISR može da bude logički korektna, a da sistem ne radi ispravno, ako njeno izvršenje traje suviše dugo. Kod rutina kreiranih na asemblerskom jeziku moguće je, u principu, tačno odrediti (ručnim putem, olovkom i papirom) koliko je vremena potrebno za njihovo izvršenje, mada kada se složenost arhitekture povećava (uvođenjem protočnosti, superprotočnosti, keš memorije, i td.) to određivanje postaje sve složenije. Sa druge strane, nije tako lako odrediti, ručnim putem, vreme izvršenja svake rutine koja je kreirana na C-u. Iz nabrojanih razloga, od projektanata embedded sistema do skoro ste mogli da čujete sledeće mišljenje: Sve ISR-ove i vremensko kritične kôdne sekvence, u cilju tačnog određivanja vremena izvršenja, bolje je kreirati na asemblerskom jeziku, nezavisno od toga što je to teži posao za programera. No, današnja moderna razvojna sredstva, kakva su Instrucion Set Simulators i Architectural Simulators, omogućavaju projektantu da kompajlira i izvrši embedded kôd na PC mašini ili radnoj stanici, da sačuva njegov trag izvršenja, i odredi tačan broj mašinskih ciklusa. Pored toga, dobra razvojna sredstva čuvaju trag i o stopi keš pogodaka i promašaja, vremenu pristupa DRAM-ovima, kao i ciklusima

čekanja. To znači da i pored toga što niste neku ISR kreirali na asemblerskom jeziku, moguće je sa velikom preciznošću odrediti koliko je vremena potrebno da se ta ISR izvrši. Za slučaj da projektant softvera ne raspolaže ovakvim sredstvima za razvoj, nego se oslanja na neku svoju intuiciju, u najvećem broju slučajeva za neuspeh optužuje projektante hardvera. Projektanti softvera, sa druge strane, drže se sledećeg pravila: Kada se piše neki program prvo što treba postići je funkcionalnost, a zatim brzina izvršenja. Razlog za ovakav pristup je sledeći: U praksi 80% od problema koji se odnose na brzinu čine 20% kôda, pa zbog toga ima puno smisla prvo učiniti da sistem korektno radi, a nakon toga tražiti gde su njegova uska grla. Na žalost, sistemi u realnom vremenu po svojoj prirodi ne rade korektno ako su spori. Diskusija koja se odnosi na rad u real-time-u kao i interrupt-driven okruženju nedvosmisleno je u vezi sa RTOS-om. Upravljanje radom sistemsko-hardverskim resursima sa čvrstim vremenskim ograničenjima predstavlja složen proces, ali su za obavljanje ove aktivnosti od izuzetne pomoći komercijalno dostupni RTOS-ovi koji u značajnoj meri olakšavaju napore projektanata iz sledećih razloga: Kao prvo, RTOS izoluje jedan zadatak od drugog, omogućavajući svakom projektantu tima da kreira svoj kôd kao da on ima ekskluzivno pravo korišćenja ciljnog sistema kao i pravo korišćenja njegovih resursa. I pored toga što je ovo veliki plus, integralni dizajn mora korektno da adresira stavke koje se odnose na deljive podatke i konkurentnost u radu. Na sličan način, projektanti koji implementiraju taj dizajn moraju dobro razumeti one stavke koje se tiču korektnog korišćenja kritičnih sekcija, semafora, interprocesorske komunikacije, i td. Kao drugo, RTOS mora u određenoj meri da bude ukrojen aplikaciji (customized), tj. da bude podešen ciljnom okruženju koje sistem treba da kontroliše. Obično to znači da je potrebno razviti BSP (board support package) za operativni sistem i ciljnu mašinu. Da bi se ova aktivnost korektno obavila potrebno je vreme od nekoliko nedelja. 4.3 Watchdog tajmeri Kada se u toku rada embedded sistema javi greška koja može da dovede rad sistema u blokadu, tada watchdog (pas čuvar) ima zadatak da ponovo vrati sistem u normalni režim rada, tj. u život. Naglasimo pri ovome da je rad svih visoko-pouzdanih sistema nezamisliv bez watchdog tajmera. Osnovna ideja je jednostavna. Pretpostavimo da je za izvršenje main programske petlje u proseku potrebno 25 ms, a u najgorem slučaju 35 ms. Usvojimo sada da je u sistem instaliran gradivni blok tipa watchdog tajmer koji je povezan na prekid najvišeg nivoa opsluživanja, kakav je, recimo RESET ili NMI. Neka se nakon trigerovanja watchdog tajmera čeka 50 ms, a zatim, ako trigerovanje izostane, neka se aktivira signal na ulazu pina RESET, uslovljavajući da procesor startuje izvršenje programa od početka. Jedini način da se spreči da do reseta sistema ne dođe je da se koristi retriggerable monostabilni multivibrator postavljen na ulazu RESET pina CPU-a koji će se okidati svakih 50 ms i držati pin RESET na neaktivno stanje. U konkretnoj aplikaciji, kôd petlje main se izvršava za 35 ms. Zbog toga, zadnja operacija koja treba da se obavi u petlji main odnosi se na retrigerovanje watchdog tajmera za period od narednih 50 ms. Ako nešto nije u redu, bilo sa hardverom, bilo sa softverom, a posledica je, recimo, smetnji od napajanja (power glitch), program se neće izvršavati korektno pa watchdog tajmer se neće retrigerovati u okviru odgovarajućeg vremenskog slota. Pod ovakvim uslovima watchdog tajmer će završiti svoju kvazistabilnu periodu (isteći će time-out), a sistem će se restartovati. Uobičajeno, projektanti sugerišu da pre resetovanja tajmera, sistem obavi neke provere koje se tiču korektnosti rada i integriteta sistema. Na primer, neophodno je proveriti dubinu magacina, broj alociranih bafera, kao i stanje mehaničkih komponenata u sistemu pre nego što se obavi resetovanje watchdog tajmera. Često puta praksa je i da se postave markeri (flags) u različitim tačkama kôda, koji ukazuju na uspešno izvršenje odgovarajućeg bloka u programu. Pre nego što se tajmer resetuje (često se u stručnoj literaturi ovo naziva "kicking the dog") proverava se stanje svih markera. Ako su svi markeri postavljeni, tajmer se retrigeruje za drugi interval. Ako markeri nisu postavljeni, registruje se greška u radu, a tajmeru se dozvoljava da izađe iz time-out-a (vidi sliku 4.2).

Slika 4.2. Dijagram toka rada watchdog tajmera

4.3.1 Watchdog tajmer: debagiranje ciljnog sistema Naredni problem sa kojim se projektanti sistema suočavaju odnosi se na debagiranje ciljnog sistema. Imajući u vidu da watchdog tajmer radi nezavisno od ostatka sistema, izvršenje programa se može suspendovati korišćenjem debagera. U tom slučaju, kada time-out watchdog tajmera istekne aktivira se RESET sistema. Zbog toga, ako sistem poseduje watchdog tajmer, kakav je slučaj sa najvećim brojem embedded sistema, pribegava se sledećem pristupu: Dozvoljava se ili brani rad tajmera kada je to moguće. Ako je tajmer eksterni blok u odnosu na CPU, tada se postavlja kratkospajač (jumper) na štampanoj ploči u poziciji koja raskida vezu između tajmera i CPU-a. U softveru se to radi tako što se izoluju rutine tajmera tako da se može koristiti uslovna kompilacija (# if DEBUG) sa ciljem da se izbaci (eliminiše) kôd tajmera. Kada se tajmer koristi u kontekstu RTOS-a, mora se voditi računa o kontroli (upravljanju radom) tajmera u odnosu na ostatak sistema. Obično jedan od zadataka koji obavlja RTOS je namenjen radu RTOS-a. Ovaj zadatak ima viši prioritet u odnosu na druge zadatke koje on nadgleda. Pri tome se zadatak RTOS-a budi u regularnim intervalima i proverava "zdravstveno stanje" (tj. status) ostalih zadataka. Ako sve provere ukazuju da sistem korektno radi retrigeruje se watchdog tajmer. 4.4 Fleš memorija Današnji digitalni foto-aparati ili MPEG muzički plejeri nezamislivo je da rade bez fleš memorije. U suštini, kao rezultat brzog napretka u poluprovodničkoj tehnologiji, fleš memorije veoma brzim tempom zamenjuju ostale non-volatile memorijske tehnologije kao projektantsko rešenje za memorisanje firmware-a embedded sistema. Sa tačke gledišta embedded sistema, fleš memorije su atraktivne jer se mogu reprogramirati na licu mesta (in the field). Najstandardniji metod nazvan in-circuit programmibility, znači da se one mogu reprogramirati na samoj štampanoj ploči bez da postoji potreba za dovođenjem specijalnih napona napajanja za programiranje. Korišćenje fleš memorije omogućava projektantima embedded sistema da modifikuju performansne parametre u toku rada sistema čime se ostvaruje optimizacija (optimalna podešenost) u odnosu na tekuće stanje operativnog okruženja. Tako na primer, ako računar koji se ugrađuje u vozilo koristi fleš memoriju, tada je moguće na licu mesta (u toku servisiranja) podesiti kočioni sistem, podesiti ubrizgavanje goriva, kao i druge parametre koji imaju uticaj na paljenje. Znatno komplikovanija i sofisticiranija rešenja su sledeća: Kod fleš baziranih softverskih-arhitektura program mora biti u stanju da se sam modifikuje, ili što je verovatno mnogo značajnije i

restriktivnije, on mora da ispravi grešku u radu sistema na način koji neće dovesti do zastoja. Za realizaciju ove koncepcije koriste se sledeća dva pristupa:

1. embedded programmer - to je deo kôda koji se isporučuje zajedno sa firmware-om uređaja. Prednosti ovog pristupa se ogledaju u sledećem: Kôd se direktno implementira još u fazi izrade uređaja i ne zahteva sofisticirane programe za podršku rada. Nedostatak je taj što kôd mora da bude onaj pravi i da sagleda sve moguće varijante modifikacije još u trenutku kada se uređaj isporučuje kupcu kao i da može da koristi RAM i ROM resurse koji su deo uređaja (instalirani u sistemu kada se uređaj isporučuje). U principu, ovakav pristup je nefleksibilan.

2. microprogrammer - poseduje se samo apsolutni minimum kôda koji postoji na čipu (obično je to bootloader). Bootloader se koristi za download-ovanje programskog algoritma koji se puni u fleš (ovaj bootloader se naziva microprogrammer) kao i za punjenje novih "slika" (verzija) softvera (tj. novih programa). Prednost microprogrammer-a je ta što zahteva ugradnju minimalnog ROM overhead-a. Nedostaci su ti što je veoma teško implementirati ovaj pristup u odnosu na ciljno-rezidentni programski algoritam, i što programi za podršku ovakvog načina punjenja treba da imaju ugrađen znatno viši nivo inteligencije.

Projektanti embedded sistema najčešće predlažu da se u memorijskom prostoru fleša čuvaju sledeća tri nezavisna programa:

a) bootloader - je program koji se izvršava nakon power-up sistema. Ovaj program inicira sistem na rudimentiran režim rada. Nakon iniciranja, sistemu postaje dostupan kako deo RAM tako i deo U/I prostora. Bootloader zatim signalizira korisniku da li on želi da se odluči da sistem radi po već ugrađenom programu koji je projektovan za datu aplikaciju, ili želi da implementira novi algoritam upravljanja putem reprogramiranja fleša. Imajući u vidu da najveći broj korisnika nakon uključenja sistema na napajanje ne želi da da odgovor na ovo pitanje ovo obaveštenje (promts) se realizuje kao opciono i može da se ostvari na sledeća dva načina: a) ako se prekidač koji se nalazi na zadnjoj strani uređaja postavi u stanje ON, ili b) ako se kratkospajač koji se obično nalazi na glavnoj ploči uređaja postavi u drugu poziciju (naglasimo da se stanje kratkospajača testira u toku boot procesa).

b) realni aplikacioni kôd - koji se može reprogramirati pomoću algoritma za reprogramiranje fleša.

c) RAM rezidentni algoritam za reprogramiranje - kojim se obavlja aktuelno reprogramiranje uređaja. Ovaj kôd je lociran u zaštićenom regionu fleša i kopira se u RAM kada se izabere opcija za reprogramiranje.

Dodatak 4 D4.1 Izuzeci Izuzetak (exception) predstavlja naglu promenu toka upravljanja programa kao odziv na neku promenu stanja procesora. Na slici D4.1 prikazana je osnovna ideja.

Slika D4.1. Anatomija izuzetka

Napomena: Promena stanja procesora (događaj - event) trigerovala je naglu promenu toka upravljanja (izuzetak) od aplikacionog programa ka programu za obradu izuzetka (exception handler). Nakon završene obrade, program za obradu izuzetka može da vrati (return) upravljanje prekinutom programu ili da prekine sa daljim radom (abort). Kao što se vidi sa slike D4.1, u toku izvršenja neke tekuće instrukcije, Itek, došlo je do značajne promene stanja-procesora. Ovo stanje se kodira postavljanjem različitih bitova i signala unutar samog procesora. Promena stanja se naziva događaj. Događaj može: 1) da bude u direktnoj vezi sa izvršenjem tekuće instrukcije (kao na primer, javio se aritmetički premašaj u toku izvršenja instrukcije kakav je deljlenje sa nulom, ili greška straničenja kod rada sa virtuelnom memorijom); i 2) da ne bude u vezi sa izvršenjem tekuće instrukcije (signalizacija da je došlo do nestanka napajanja, ili neki ulazno/izlazni uređaj zahteva opsluživanje). U oba slučaja, kada procesor detektuje da se događaj javio on obavlja indirektni poziv na proceduru (izuzetak), koristeći tabelu-grananja nazvanu vektor-tabela (exception table). Pri tome se grananje obavlja na potprogram koji je deo operativnog sistema (exception handler) i koji je specijalno projektovan da procesira taj tip događaja. Kada program za obradu izuzetka završi sa procesiranjem, u zavisnosti od tipa događaja koji je bio uzrok izuzetka može da se desi jedna od sledeće tri stvari:

1. program za obradu izuzetka vrati upravljanje tekućoj instrukciiji, Itek, tj. instrukciji koja se izvršavala u trenutku kada se događaj javio,

2. program za obradu izuzetka vrati upravljanje narednoj instrukciiji, Inar, tj. instrukciji koja je trebalo kao naredna da se izvrši za slučaj da se događaj nije desio,

3. program za obradu izuzetka prekine dalje izvršenje prekinutog programa. Obrada izuzetaka Izuzetke je teško razumeti jer je za njihovu manipulaciju potrebna uska kooperativnost u radu između hardvera i softvera. Sagledajmo zbog toga problem saradnje hardver-softver nešto detaljnije. Svakom tipu mogućeg izuzetka u sistemu dodeljuje se nenegativna celobrojna vrednost (integer) koja se naziva vektor-broj (ili exception number). Neki od ovih brojeva su dodeljeni od strane projektanta procesora, a drugi od strane projektanta kernela (jezgra) operativnog sistema (memorijsko rezidentni deo

operativnog sistema). Tipični primeri iz prve kategorije su deljenje-nulom, greška-straničenja, prekidne-tačke, i dr., a iz druge kategorije su sistemski pozivi i signali od spoljnih U/I uređaja. U trenutku boot-ovanja sistema (kada se računar resetuje ili uključi na napajanje) operativni sistem alocira i inicijalizira vektor-tabelu, na takav način da ulaz k sadrži adresu za program koji obrađuje izuzetak k. Na slici D4.2 prikazan je format vektor-tabele.

012

n-1

. . .

kôd za obradu izuzetka 0

kôd za obradu izuzetka 1

kôd za obradu izuzetka 2

kôd za obradu izuzetka n-1

. . .

exceptiontabela

Slika D4.2. Vektor (exception) tabela

Napomena:Vektor tabela predstavlja tabelu-grananja kod koje ulaz k sadrži adresu kôda za obradu izuzetka k. U trenutku kada sistem izvršava neki program, procesor detektuje da se desio neki događaj i određuje odgovarajući vektor broj k. Procesor zatim trigeruje izuzetak i obavlja indirektni poziv na proceduru. Na slici D4.3 prikazan je način na koji procesor koristi vektor tabelu kako bi formirao adresu odgovarajuće rutine za obradu izuzetka. Vektor broj (exception number) predstavlja indeks u vektor tabeli (exception table), čija se početna adresa čuva u specijalnom CPU-ovom registru nazvanom exception table base register (ETBR).

012

k

. . .

vektor tabela

n-1

. . .

exception tablebase register

- ETBR - adresa ulazaza izuzetak #k

vektor-broj( 4 )∗

Slika D4.3. Generisanje adrese programa za obradu izuzetka

Napomena:Vektor broj je indeks u vektor tabeli. Izuzetak je sličan pozivu procedure, ali ipak postoje sledeće važne razlike:

1. Kao i kod poziva procedure, pre nego što se obavi grananje na rutinu za obradu izuzetka, procesor smešta u magacin (push) adresu povratka. No, zavisno od klase izuzetka povratna adresa može biti adresa Itek ili Inar (vidi sliku 1).

2. Procesor takođe smešta u magacin neku dodatnu informaciju koja je neophodna da se restartuje prekinuti program kada se vrši povratak iz rutine za obradu izuzetka. Tako na primer, sistem baziran na procesorima iz familije IA-32 smešta u magacin EFLAGS registar, koji između ostalog sadrži i sve markere uslova (condition codes).

3. Ako se upravljanje prenosi sa korisničkog programa na kernel, sve stavke se smeštaju u magacin kernela, a ne u magacin korisnika.

4. Program za obradu izuzetka se izvršava u kernel režimu rada što znači da ovaj program ima pravo pristupa svim resursima računara.

Nakon što je hardver trigerovao izuzetak, ostatak rada se obavlja softverski, od strane rutine za obradu izuzetka. Nakon procesiranja događaja opciono se vrši povratak na prekinuti program izvršenjem instrukcije "Return from interrupt". Ovom instrukcijom se izbavlja iz magacina odgovarajuće stanje procesora, tj. obnavlja se stanje upravljačkih registara kao i registara za podatke, i zatim procesor prelazi u korisnički režim rada (user mode). Ovakav scenario važi za slučaj da je izuzetak prekinuo korisnički program, a zatim je povratio upravljanje prekinutom programu. Klase izuzetaka Izuzetke možemo podeliti u sledeće četiri klase: prekidi (interrupts), trapovi (traps), greške (faults) i blokade (aborts). U Tabeli D4.1 sumirani su atributi ovih klasa. Tabela D4.1. Klase izuzetaka

Klasa uzrok asinhroni/sinhroni ponašanje pri povratku prekid signal od U/I uređaja asinhroni uvek se vraća na Inartrap namerni izuzetak sinhroni uvek se vraća na Inargreška potencijalna greška koja

se može ispraviti sinhroni može se vratiti na Itek

blokada greška koja se ne može otkriti

sinhroni nikad se ne vrši povratak

Napomena: Asinhroni izuzeci se javljaju kao rezultat događaja u U/I uređajima koji su spoljni u odnosu na jezgro procesora: Sinhroni izuzeci se javljaju kao direktna posledica izvršenja instrukcije. Prekidi Prekidi (interrupts) se javljaju asinhrono kao rezultat signaliziranja za opsluživanjem U/I uređaja koji su spoljni u odnosu na jezgro procesora. Hardverski prekidi su asinhroni u tom smislu što nisu uzrokovani izvršenjem pojedine instrukcije. Rutine za obradu izuzetaka tipa hardverski prekidi često se nazivaju prekidne U/I rutine (interrupt handlers). Na slici D4.5 prikazano je procesiranje prekida. U/I uređaji kakvi su mrežni adapter, disk kontroler, tajmer i dr., trigeruju prekide signalizirajući te događaje na određenom pinu procesora (INTR) i postavljanjem vektor-broja na sistemskoj magistrali, čime se na jedinstven način identifikuje uređaj koji je zahtevao prekid.

ItekInar

(4) vraca seupravljanjeinstrukciji Inar

(2) nakon završetka Itekupravljanje se prenosi narutinu za obradu prekida

(3) izvršava serutina za obradu

prekida

aplikacioni program

(1) INTR pin sepostavlja visoko utoku izvršenja I tek

Slika D4.5. Rutina za obradu prekida

Napomena: Rutina za obradu prekida vrađa upravljanje instrukciji Inar koja pripada aplikacionom programu.

Nakon što završi instrukcija Itek, procesor uočava da je pin INTR postavljen na visoko, čita vektor-broj sa sistemske magistrale, i nakon toga poziva odgovarajuću rutinu za obradu prekida. Pri povratku iz prekidne rutine, on vraća upravljanje ka instrukciji Inar. Efekat je takav da aplikacioni program produžava sa daljim izvršenjem na način kao da se prekid nikada nije desio. Trapovi Trapovi su namerno izazvani izuzeci koji se javljaju kao rezultat izvršenja instrukcije. Na sličan način kao i rutina za obradu prekida, i rutina za obradu trap-a vraća upravljanje narednoj instrukciji. Najčešća upotreba trap-a je ona koja se odnosi na sledeće: Da obezbedi interfejs tipa poziv-procedure između korisničkih programa i kernel-a poznat pod nazivom sistemski-poziv (system call). Korisnički programi često koriste servise kernela kakvi su čitanje fajla (read), kreiranje novog procesa (fork), punjenje novog programa (execve), ili završetak tekućeg procesa (exit). Da bi obezbedili kontrolisani pristup ovakvim kernel servisima, procesori koriste specijalnu „syscall n“ instrukciju koju korisnički programi mogu da izvrše kada žele da zahtevaju uslugu n. Izvršenjem instrukcije syscall generiše se trap na rutinu za obradu izuzetka koja dekodira argument i poziva odgovarajuću kernel rutinu. Na slici D4.6 prikazan je način procesiranja sistemskog poziva.

syscall

Inar

(4) rutina za obradutrap-a se vraca se vraca nainstrukciju Inar koja sledinakon sistemskog poziva

(2) prenosi se upravljanjerutini za obradu trapa

(3) izvršava setrap rutina

aplikacioni program

(1) aplikacija generišesistemski poziv

Slika D4.6. Obrada trap-a

Napomena: Trap rutina vraća upravljanje instrukciji Inar koja pripada aplikacionom programu. Sa programske tačke gledišta sistemski poziv je identičan regularnom pozivu funkcije, ali je njihova implementacija različita. Regularne funkcije se izvršavaju u korisničkom režimu rada, koji ograničava tipoveinstrukcija koje se mogu koristiti, i koristi isti magacin kao i funkcija koja generiše poziv (calling function). Sa druge strane, sistemski poziv se izvršava u kernel režimu rada, koji dozvoljava izvršenje svih instrukcija (iz repertoara) kao i pristup magacinu definisanom u kernelu. Greške Graške (faults) rezultat su uslova-tipa-otkaz (error conditions) koje program za obradu izuzetka može biti u stanju da ispravi. Ako je program za obradu izuzetka u stanju da ispravi uslov-tipa-otkaz, tada on vraća upravljanje instrukciji koja je bila uzrok da se greška pojavi (faulty instruction) i ponovo je izvršava. U suprotnom rutina za obradu izuzetka predaje upravljanje rutini blokiranja (abort routine) koja je deo kernel-a, čime se završava izvršenje aplikacionog programa koji je uzrokovao grešku. Na slici D4.7 je prikazan scenario procesiranja izuzetka tipa greška.

Itek

(4) rutina za obraduizuzetka izvršava Inar iliblokira rad sistema (abort)

(2) upravljanje se predajerutini za obradu izuzetka

(3) izvršava serutina za obradu

greške

aplikacioni program

(1) Itek uzrokujepojavu greške

abort

Slika D4.7. Rutina za obradu greške

Napomena: U zavisnosti od toga da li se greška može ispraviti ili ne rutina za obradu greške ponovo izvršava instrukciju Inar ili blokira rad sistema (abort). Klasičan primer greške se javlja kod izuzetka tipa greška-usled-straničenja (page fault exception), koja se javlja kada instrukcija Inar referencira virtuelnu adresu čija odgovarajuća fizička stranica nije rezidentna (prisutna) u memoriji pa se zbog toga mora pribaviti sa diska. U konkretnom slučaju, rutina za obradu greške-straničenja puni odgovarajuću stranicu sa diska i vraća upravljanje instrukciji koja je uzrokovala grešku, Itek. Kada se instrukcija Itek ponovo izvrši, odgovarajuća fizička stranica je rezidentna u memoriji, pa je instrukcija Itek u stanju da se izvrši do kraja bez da se generiše greška. Blokade Blokade (aborts) se javljaju kao rezultat neispravljivih fatalnih otkaza – tipično su to hardverski otkazi kakve su greške parnosti koje se javljaju kada dođe do greške u manipulisanju sa DRAM i SRAM bitovima. Rutine za obradu blokade (abort handlers) nikada ne vraćaju upravljanje aplikacionom programu. Kao što je prikazano na slici D4.8, rutina za obradu izuzetka vraća upravljanje abort rutini koja prekida (završava) izvršenje aplikacionog programa.

Itek

(4) rutina za obradu izuzetka prenosiupravljanje abort-kernel rutini

(2) upravljanje se predajerutini za obradu izuzetka

(3) izvršava serutina abort

aplikacioni program

(1) desio se fatalnihardverski otkaz

abort

Slika D4.8. Abort rutina

Napomena: Rutina za obradu blokade (abort) predaje upravljanje abort kernel rutini koja prekida (završava) izvršenje aplikacionog programa. Izuzeci kod Intel-ovih procesora Da bi sagledali prethodno izlaganje u nešto konkretnijoj formi analizirajmo način na koji su definisani izuzeci kod Intel-ovih procesora. Kod procesora Pentium mogu da postoje do 256 različitih tipova izuzetaka. Brojevi od 0-31 odgovaraju izuzecima koji su definisani od strane Pentium arhitekture i identični su kod sistema iz Pentium-ove klase. Brojevi u opsegu od 32-255 odgovaraju prekidima i trapovima koji su definisani od strane operativnog sistema. Na slici D4.9 prikazani su neki primeri.

broj izuzetka opis klasa izuzetka 0 13 14 18

deljenje nulom protekciona greška opšteg tipa greška straničenja provera na nivou mašine

fault fault fault abort

32-127 128 (0x80)

129-255

izuzeci definisani od strane OS-a sistemski poziv izuzeci definisani od strane OS-a

interrupt ili trap trap interrupt ili trap

Slika D4.9. Primeri izuzetaka kod sistema Pentium