|
Uporaba večopravilnega operacijskega sistema v realnem času Besedilo vaje: V tej vaji bomo spoznali izpopolnjen operacijski sistem v realnem času, ki odpravlja več omejitev preprostega sistema uporabljenega v vajah do sedaj. Laboratorijske vaje, pri katerih smo preprost operacijski sistem uporabili, naredite še enkrat z uporabo izpopolnjenega sistema. Primerjajte razlike v izvedbi in ocenite odstotek procesorskega časa, ki ga prvi in drugi operacijski sistem porabita za režijo, oziroma za svoje delovanje. Razlaga: Programsko opremo napišete v razvojnem okolju, ki teče na osebnem računalniku. Pripravljen delovni prostor (angl. workspace) z vsemi pripadajočimi datotekami najdete tukaj. Izvorna koda izpopolnjenega operacijskega sistema, ki teče v realnem času, se nahaja v datotekah s predpono rtos2 (rtos2*.c in rtos2.h), prirejena pa je tudi zagonska koda v datoteki crt0.s. Po končanem zagonu pristanemo v prazni funkciji start_up(), ki se nahaja v datoteki startup.c in je namenjena inicializacijam. Seveda je prva na vrsti inicializacija mikrokrmilnika, dvig operacijskega sistema ipd. Nadaljujete v datoteki main.c, kjer boste napisali izvorno kodo glavnega programa in opravil. Sledi kratek opis lastnosti izpopolnjenega sistema in njegove uporabe. Lastnosti izpopolnjenega večopravilnega operacijskega sistema v realnem času so naslednje:
Z operacijskim sistemom upravljamo s klici njegovih funkcij. Za njihovo uporabo je potrebno vključiti datoteko rtos2.h, kjer se nahajajo deklaracije. Operacijski sistem inicializiramo s klicem funkcije rtos2init(). Deklaracija funkcije je naslednja: void rtos2init(char *memory, unsigned int size, unsigned int slice);
Sistemski dinamični pomnilnik je kos RAM-a, ki ga uporablja operacijski sistem. Njegova velikost je odvisna od števila ustvarjenih opravil, podatkovnih cevi in njihove velikosti. Definiramo ga kot globalno polje. Po izvršitvi funkcije
rtos2init() operacijski sistem še ne teče. Dvignemo ga s klicem funkcije
void enable_os();
Med inicializacijo se nastavi prekinitev časovnika timer0 in jo operacijski sistem potrebuje za svoje delovanje. Zato po inicializaciji ne smemo več klicati funkcije vic_init(). Oziroma ostale prekinitve je potrebno nastaviti pred klicem funkcije rtos2init(). Prekinitev timer0 je vrste IRQ z najvišjo prioriteto. To pomeni, da ob klicu funkcije vic_init() vektorske prekinitve nič ne moremo uporabiti. Primer uporabe:
Ko je operacijski sistem inicializiran, ustvarimo najprej podatkovne cevi za pretok podatkov med posameznimi opravili in nato sama opravila. Podatkovno cev ustvarimo s klicem funkcije rtos2pipe_create(), ki vrne kazalec na cev. Deklaracija funkcije je naslednja: struct rtos2pipe *rtos2pipe_create(unsigned int size);
V podatkovno cev pišemo s funkcijo rtos2pipe_write(). Iz nje beremo z rtos2pipe_read(). Obe funkciji vračata število uspešno zapisanih, oziroma prebranih bajtov. Podatek se v cev ne more zapisati v primeru, ko v njej ni več prostora. Oziroma ga iz nje ne moremo prebrati, če je cev prazna. unsigned int rtos2pipe_write(struct rtos2pipe *pipe, char *data, unsigned int size);
unsigned int rtos2pipe_read(struct rtos2pipe *pipe, char *buffer, unsigned int size);
Podatkovno cev, ki je ne potrebujemo več, uničimo s funkcijo rtos2pipe_delete(). void rtos2pipe_delete(struct rtos2pipe *pipe);
Primer uporabe:
Cevi lahko uporabljamo v glavnem programu in v opravilih, ne moremo pa jih uporabljati v prekinitvah IRQ, oziroma FIQ. Da cevi lahko uporabljamo tudi v prekinitvah, je potrebno v funkcijah rtos2pipe_...() (glej datoteko rtos2pipe.c) začasno onemogočiti prekinitve. To naredimo s postavitvijo (ob vstopu v funkcijo) in umikom (ob izstopu iz funkcije) zastavice I, oziroma F. Opravila so funkcije tipa rtos2taskptr, oziroma funkcije, ki ničesar ne vračajo, sprejmejo pa kazalec na poljuben tip. Opravilo ustvarimo s klicem funkcije rtos2task_create(), uničimo pa ga s funkcijo rtos2task_delete().
typedef void (* rtos2taskptr)(void *);
Zadnji argument interval podaja pogostnost klicanja opravila. In sicer bo opravilo na vrsti enkrat na časovni interval t = interval * dolžina časovne rezine. Dolžino časovne rezine smo določili ob inicializaciji operacijskega sistema z argumentom slice funkcije rtos2init(). void rtos2task_delete(rtos2taskptr function);
Za vsako opravilo smo določili, na koliko časovnih rezin naj bo na vrsti. Če sta hkrati na vrsti dve ali več opravil, potem se požene tisto z najvišjo prioriteto. Če trenutno teče opravilo z nižjo prioriteto, na vrsti pa je opravilo z višjo prioriteto, potem opravilo z višjo prioriteto prekine opravilo z nižjo prioriteto. Po končanju opravila z višjo prioriteto se nadaljuje izvajanje opravila z nižjo prioriteto. Tako opravila z nižjo prioriteto čakajo na izvajanje takrat, ko ni zahtev za zagon opravil z višjo prioriteto. Zato se lahko zgodi, da opravilo z nižjo prioriteto ne bo na vrsti točno ob poteku časovnega intervala, ampak nekaj časovnih rezin kasneje. Vendar se zaradi tega trenutek naslednje razvrstitve opravila ne zamakne. Frekvenca klicanja ostaja konstantna enkrat na časovni interval. Primer uporabe:
Za dinamične potrebe po pomnilniku si lahko definiramo kos pomnilnika RAM. To naredimo s funkcijo rtos2mem_create(). Definirati je mogoče več med seboj neodvisnih kosov pomnilnika. void rtos2mem_create(struct rtos2mem *region, char *memory, unsigned int size);
Znotraj izbranega kosa pomnilnik dinamično dodeljujemo in sproščamo s funkcijama rtos2mem_allocate() in rtos2mem_free(). Funkcija rtos2mem_allocate() vrne kazalec na dodeljen pomnilnik. V primeru, da dodelitev ne uspe, vrne vrednost 0x00000000. void *rtos2mem_allocate(struct rtos2mem *region, unsigned int size);
void rtos2mem_free(struct rtos2mem *region, void *pointer);
Do posameznega kosa pomnilnika naj načeloma dostopa le eno opravilo (glavni program, prekinitev IRQ ali FIQ). V nasprotnem primeru trčimo ob težavo hkratnega dostopa. Zato moramo biti v takšnem primeru še posebej pazljivi. Potrebno je zagotoviti, da do določenega kosa pomnilnika dostopa le eno opravilo (glavni program, prekinitev) naenkrat. Opravilo (glavni program, prekinitev) med dostopom do kosa pomnilnika (dodeljevanje, uporaba ali sproščanje pomnilnika) ne sme biti prekinjeno z drugim opravilom (prekinitvijo), ki dostopa do istega kosa pomnilnika. Težavo lahko rešimo z zaklepanjem, kar pomeni, da moramo kritične dele kode napisati na poseben način. Lahko pa si pomagamo tudi s funkcijama disable_os() in enable_os() s katerima med kritičnimi deli kode začasno izklapljamo operacijski sistem (preprečimo prekinitev s strani drugega opravila), in s postavljanjem zastavic I in F s katerima začasno onemogočimo prekinitve IRQ in FIQ (preprečimo prekinitev zaradi nove zahteve po prekinitvi). Operacijski sistem ima za svoje potrebe določen svoj kos pomnilnika. Primer uporabe:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||