Transcript Document
Оперативни системи 1 Вeжбе Lista predmeta: [email protected] Увод у архитектуру 8086 • 16-битни микропроцесор. • Адресибилна јединица бајт. • Регистри: – сегментни – општенемeнски – индексни – базни регистар BP и показивач на стек SP. Сегментни регистри • • • • • CS – код сегмент, SS – стек сегмент, DS – сегмент података и ES – екстра сегмент за податке Користе се за формирање адресе: – адреса је 20-битна – састоји се од: • сегмента – SEG (16 бита) • и офсета (померај) – OFF (16 бита) – физичка адреса је 20 бита и рачуна се: 0х10 * SEG + OFF – сегмент је увeк један од 4 сегментна регистра • ВАЖНЕ НАПОМЕНЕ: – ови сегменти немају никакве везе са виртуелним адресирањем које се налази код модерних процесора – примeтити да се неке физичке адресе могу добити комбинацијом више различитих парова сегмената и офсета. Општенамeнски регистри • AX, BX, CX и DX, • Сваки по 16 бита, • Сваки се може посматрати као два 8-битна: AX -> AH (виши бајт) и AL (нижи бајт) BX -> BH (виши бајт) и BL (нижи бајт) CX -> CH (виши бајт) и CL (нижи бајт) DX -> DH (виши бајт) и DL (нижи бајт) Примeр: mov AX, 1234h (исто што и 0х1234 на C-у) AH 12h AL 34h Индексни и базни регистри • Индексни: – SI i DI, – Сваки по 16 бита, – Користе се при индексном адресирању (приступ елементима низа). – Примeр: mov AX, niz[SI] • Базни регистар за приступ стеку: – BP – 16 бита. – Користи се за приступ стеку (приступ стварним аргументима и локалним промeнљивим процедуре). Стек • Постоје две поделе (4 врсте) стекова: – да ли расте ка вишим адресама или ка нижим? – да ли показивач на стек показује на заузету локацију на врху стека или на прву слободну локацију изнад врха стека? • Стек процесора 8086: – расте ка нижим адресама – SP указује на заузету локацију на врху стека Стек позива потпрограма • Са BP Zašto BP? Zar nije dovoljan SP? Стек позива потпрограма Кonvencija pri pozivu potprograma: Napomena: Parametri se stavljaju na stek u obrnutom redosledu od onog navedenog pri deklaraciji f-je. mySub: push bp mov bp, sp sub sp, n ; Start of procedure ; reserve n bytes ; of local storage ; save registers push reg1 push reg2 ; do some processing pop reg2 pop reg1 add sp, n ; just the opposite mov sp, bp pop bp ret ; we are done. Како се потпрограму прослеђују параметри? • Потпрограм на C-у: int fun(int a){ return a; } • Исти потпрограм на асемблеру: fun proc push BP mov BP, SP mov AX, [BP+6] pop BP ret Како се потпрограму прослеђују параметри? fun proc push BP mov BP, SP mov AX, [BP+6] pop BP ret ... push 1234h call fun add SP, 2 Напомена: Претпоставља се huge mem. model BP АХ = 0х1234 SP Стек: xx xx +7 12 виши бајт на вишој адреси +6 34 нижи бајт на нижој адреси +5 retCSh h – high byte +4 retCSl l – low byte +3 retPCh +2 retPCl +1 oldBPh +0 oldBPl Задатак 1. • Написати програм на програмском језику C који ће помоћу једне функције бесконачно исписивати неки текст. Решење може да буде некоректно у смислу да ће оперативни систем у коначном времену пријавити грешку у извршавању програма и прекинути његово извршавање. Није дозвољено коришћење петљи. BP #include<stdio.h> void a(){ printf("...\n"); a(); } void main(){ a(); } SP Стек: (у једном реду једна реч – 2B) xx Шта је проблем? retCSend_main retPCend_main oldBP_main retCSend_a retPCend_a oldBP_a1 retCSend_a retPCend_a oldBP_a2 ... Задатак 2. • Написати програм на програмском језику C који ће бесконачно понављати исписивање неког текста. Није дозвољено користити петље и програм мора бити исправан, тј. да ОС никада не пријави грешку за тај програм. Решење #include <stdio.h> int i; void a(){ //cuva pov. adr. asm{ push ax mov ax, [bp]+2 mov i,ax pop ax } } return_OFFSET printf_OFFSET BP_main AX_old i: printf_OFFSET void b(){ //menja pov. adr. asm{ push ax mov ax,i mov [bp]+2,ax pop ax } } int main(){ a(); printf("Izmedju a i b.\n"); b(); return 0; } Задатак 3. • Написати програм за процесор 8086 који треба да изврши неки потпрограм, али тако да се нигдe у коду не види позив тог потпрограма. На почетку сваке функције: push BP mov BP, SP На почетку Решење сваке– варијанта 1 функције: unsigned int SP_f, SP_main; void f(){ push BP unsigned int stek_f[1024]; На крају mov сваке BP,//kod SPfunkcije //... функције: void _dispatch1(){ //kod za izlazak iz funkcije: pop BP asm { dispatch2(); mov SP_main, sp //cuva sp od main ret } xx mov sp, SP_f // restauira sp od f } } void _dispatch2(){ asm { mov SP_f, sp //cuva sp od f mov sp, SP_main } } void main(){ PC stek_f[1023] = FP_OFF(f) SP_f = FP_OFF(stek_f+1022); dispatch1(); } FP_OFF(f) // restauira sp od main dohvata offset adrese f-je f PC_main_} BP_main ... stek_f+1023 0 f //adresa funkcije f 0 ... stek_f SP_main SP_f ... SP BP Извршава се код Решење – варијанта 1 функције PC unsigned int SP_f, SP_main; unsigned int stek_f[1024]; void _dispatch1(){ asm { mov SP_main, sp //cuva sp od main mov sp, SP_f // restauira sp od f } } void _dispatch2(){ asm { mov SP_f, sp //cuva sp od f void f(){ //kod funkcije //... //kod za izlazak iz funkcije: dispatch2(); } На почетку mov BP, SP mov sp, SP_main // restauira sp od main } } xx void main(){ сваке stek_f[1023] = FP_OFF(f) SP_fфункције: = FP_OFF(stek_f+1022); dispatch1(); push BP } На крају сваке функције: pop BP ret PC_main_} BP_main ... stek_f+1023 0 f //adresa funkcije f SP BP PC_f_} 0 ... stek_f SP_main SP_f ... Решење – варијанта 2 unsigned int temp; void _dispatch1(){ asm { pop temp push f push temp } } void f(){ //kod funkcije //... } void main(){ dispatch1(); } SP xx PC_main_} fBP_main //adresa funkcije f BP_main temp: BP_main Задатак 4. • За процесор 8086 и код написан на програмском језику C, омогућити да се функције извршавају конкурентно, а да се прелазак са функције на функцију обавља помоћу корутина. Сматрати да се користе само регистри АX, BX, CX и DX. Остале занемарити. Регистре чувати на стеку. Решење • Korutina – eksplicitno odricanje od procesora jedne nit u korist druge niti. Potrebno je sacuvati kontekst, kako bi kasnije mogao da se restaurira. • Kontekst procesora (processor execution context): sve vrednosti iz procesorskih registara koje je potrebno sačuvati da bi se izvršavanje nastavilo od mesta napuštanja: – Mesto u programu na kome se stalo - PC – Podaci u procesoru – registri opšte namene – Lokalni podaci potprograma i “trag” izvršavanja – sve na steku – SP • Prelazak sa izvršavanja jednog procesa na drugi – promena konteksta (context switch): – sačuvati kontekst koji se napušta – povratiti kontekst na koji se prelazi Решење • Потребно извршити следећи код: running->sp = SP; // cuvanje SP гдe је: – sp поље PCB структуре – SP регистар • На језику С у општем случају није могуће приступити жељеном регистру. • Зато се умећу сегменти асемблерског кода. • Асемблерски еквивалент горе наведеној наредби је (под претпоставком PCB структуре дате на следећем слајду): asm{ mov BX, running // BX = running mov [BX], SP // [BX] = SP - indirektno registarsko adresiranje } -примeтити да се BX користи за адресирање, па се на почетку његов садржај мора привремено сачувати (нпр на стеку) При уласку у функцију dispatch() на стек ће се ставити повратна адреса. То је адреса прве По уласку у функцију, наилази инструкције после позива се struct PCB{ на прве sp; две инструкције сваке unsigned dispatch() и уједно и адреса функције: unsigned* stack; од које треба наставити }; push BP нит. прекинуту running је показивач на PCB mov BP, SP PCB *p[3]; се и 4 регистра (тако Чувају структуру нитипрве која се тренутно на Као инструкције PCB*резултат running; је речено у поставци Чува се показивач на стек. стеку је извршава. сачувана вредност BP У општем случају, Инструкције: int задатка). nextThread; Да би се умeсто нити, која се у регистра која је коришћена неопходно је сачувати све mov bx, running извршавала до позива _dispatch() прекинутој нити и која ће се Рестаурација стека нове //makro регистре осим CS:PC (већ mov [bx], sp наставила нова нит, користити опет понеопходно повратку #define dispatch(x) { nextThread = x; \ ујету нити: сачувано при уласку у су еквивалентне са C кодом: _dispatch(); поставити да}=running нит. показује на SP running->sp; функцију) BP (већ сачуван running->sp Рестаурација SP, нове //SP остатка je registar контекста: PCB= те нити. првом инструкцијом -првозадатку регистри DX, CX, BX i AX У овом је претпостављено функције f())BP i SS:SP (биће са pop BP да-рестаурација се нова нит одрeђује уписом сачувани у са PCB). -рестаурација ret у одговарајуће PC вредности Решење nextThread. void _dispatch(){ asm { // cuva registre na steku push ax //sta bi se desilo da ne cuvamo AX? push bx push cx push dx mov bx, running //upisuje adresu mem. lok. mov [bx], sp //cuva sp } running=p[nextThread]; asm { mov bx, running mov sp, [bx] // restauira sp pop dx // restauira registre pop cx pop bx pop ax } } Решење void a(){ for (int i = 0; i < 3; ++i) printf("U a() %d\n",i); asm { mov ax, 7 } void b(){ for (int i = 0; i < 3; ++i) { printf("U b() %d\n",i); } asm { mov ax, 2 } dispatch(2); asm { mov i, ax } dispatch(1); printf(" u a() ax = %d\n",i); for (i = 0; i < 3; ++i) { printf("U b() %d\n",i); } for (i = 0; i < 3; ++i) printf("U a() %d\n",i); dispatch(0); dispatch(2); } } Решење void createThread(PCB *newPCB, void (*body)()){ unsigned* st = new unsigned[1024]; int main(){ p[1] = new PCB(); createThread(p[1],a); printf("napravio a\n"); unsigned newPC = FP_OFF(body); st[1023] = newPC; //upisuje se rec - int p[2] = new PCB(); createThread(p[2],b); printf("napravio b\n"); //pocetni kontekst (proizvoljne vrednosti) //st[1023-1018]={PC,BP,AX…DX} newPCB->sp = FP_OFF(st+1018); newPCB->stack = st; p[0] = new PCB(); running = p[0]; dispatch(1); } void delete_all(){ delete [] p[1]->stack; delete [] p[2]->stack; delete p[0]; delete p[1]; delete p[2]; } printf("Kraj.\n"); delete_all(); return 0; } Задатак 5. • Рeшити претходни задатак, али тако да се сви регистри чувају у PCB. struct PCB{ unsigned pc; unsigned sp; unsigned bp; unsigned ax; unsigned bx; unsigned cx; unsigned dx; ... }; pomeraj u odnosu na poc. adresu strukture +0 +2 +4 +6 +8 +10 +12 // izrazeno u bajtovima Решење • Потребно извршити следећи код: running->ax = AX; гдe је: – ах поље PCB структуре – АХ регистар • На језику С у општем случају није могуће приступити жељеном регистру. • Зато се умећу сегменти асемблерског кода. • Асемблерски еквивалент горе наведеној наредби је (под претпоставком PCB структуре дате на претходном слајду): asm{ mov BX, running // BX = running mov BX[6], AX // [BX+6] = AX - indirektno reg. adr. sa pomerajem } -примeтити да се BX користи за адресирање, па се на почетку његов садржај мора привремено сачувати (нпр на стеку) Решење void _dispatch(){ asm { push bx mov bx, running mov bx[6], ax pop WORD PTR [bx+8] //bx mov bx[10], cx mov bx[12], dx pop WORD PTR[bx+4] //bp pop WORD PTR[bx] //pc mov bx[2], sp //cuva sp } running=p[nextThread]; asm { struct PCB{ mov bx, running pomeraj unsigned pc; +0 mov sp, bx[2] unsigned sp; +2 push WORD unsigned bp; PTR[bx] +4 //pc unsigned ax; PTR[bx+4] +6 push WORD //bp unsigned bx; +8 mov ax, [bx+6] unsigned cx; +10 push WORD PTR [bx+8] //bx unsigned dx; +12 mov ... cx, [bx+10] }; mov dx, [bx+12] pop bx } } Napomena: WORD PTR[bx+...] – oznčava da se pristupa reči a ne jedanom bajtu Решење void createThread(PCB *newPCB, void (*body)()){ unsigned* st = new unsigned[1024]; newPCB->pc = FP_OFF(body); //stek je sada prazan newPCB->sp = FP_OFF(st+1024); newPCB->bp = FP_OFF(st+1024); newPCB->stack = st; } Задатак 6. • Изменити корутину из задатка 4. тако да се избор нове нити помоћу метода класе Scheduler: – void Scheduler::put(PCB *); Додаје нит у листу кандидата за извршавање – PCB* Scheduler::get(); Дохвата следећу нит из листе спремних процеса Решење void _dispatch(){ asm { // cuva registre na steku push ax //sta bi se desilo da ne cuvamo AX? push bx push cx push dx mov bx, running mov [bx], sp //cuva sp } Scheduler::put(running); running = Scheduler::get(); asm { mov bx, running mov sp, [bx] // restauira sp pop dx // restauira registre pop cx pop bx pop ax } } Чување контекста до сада извршаване нити Рестаурација контекста нити која се наставља Задатак 7. • Написати универзалну корутину dispatch() коришћењем функицја setjmp и longjmp. Функција setjmp прихвата показивач на бафер у којем чува тренутни контекст и враћа 0. Функција longjmp прихвата два параметра. Први параметар је показивач на структуру која садржи раније сачувани контекст (са setjmp) и који сада треба рестаурирати. Други параметар је вредност коју функција треба да врати (размислити где ће бити коришћена враћена вредност?). Решење void dispatch () { if (setjmp(running->context)==0) { Scheduler::put(running); running = Scheduler::get(); longjmp(running->context,1); } else { return; } } Напомене – Део функције dispatch() иза позива setjmp() , а пре позива longjmp (), ради и даље на стеку претходно текуће нити (позиви функција класе Scheduler). – Тек од позива longjmp () прелази се на стек нове текуће нити. Ово није никакав проблем, јер тај део представља "ђубре" на стеку изнад границе која је запамћена у setjmp() . Приликом повратка контекста претходне нити, извршавање ће се наставити од запамћене границе стека, испод овог "ђубрета". Напомене – Извршавање наставља са оног места где је позвана setjmp(), с тим да сада setjmp() враћа ону вредност која је достављена позиву longjmp()(то мора бити вредност различита од 0), самим тим вредност АX регистра је измењена – проблем код асинхроног преузимања. – Од тренутка чувања контекста помоћу setjmp(), до тренутка повратка помоћу longjmp(), извршавање у коме је setjmp() не сме да се врати из функције која непосредно окружује позив setjmp(), јер би се тиме стек нарушио, па повратак помоћу longjmp() доводи до краха система. Задатак 8. • Имплементирати функције setjmp и longjmp тако да раде на начин описан у претходном задатку: " Функција setjmp прихвата показивач на бафер у којем чува тренутни контекст и враћа 0. Функција longjmp прихвата два параметра. Први параметар је показивач на структуру која садржи раније сачувани контекст (са setjmp) и који сада треба рестаурирати. Други параметар је врeдност коју функција треба да врати." Решење struct cnt_buf{ // pomeraj unsigned sp; // +0 unsigned ax; // +2 unsigned bx; // +4 unsigned cx; // +6 unsigned dx; // +8 unsigned pc; // +10 unsigned bp; // +12 }; Sadržaj steka struct PCB{ cnt_buf* context; (u odnosu na BP): unsigned* stack; +6 i }; +4 b +2 PC +0 BP -2 BX unsigned _setjmp(cnt_buf *b, unsigned i) { asm { push bx mov bx, [bp+4] //bx = b; mov bx[2], ax pop WORD PTR [bx+4] //bx mov bx[6], cx mov bx[8], dx mov ax, bp[0] mov bx[12], ax //BP niti mov ax, bp[2] mov bx[10], ax //PC niti mov [bx], sp //running->sp = SP //skida se sa steka i, b, PC, BP add WORD PTR[bx], 8 } return 0; } Решење unsigned _longjmp(cnt_buf *b, unsigned i){ asm { mov bx, [bp+4] //BX = b; mov ax, [bp+6] //AX = i; mov sp, [bx] //restauriramo stek push ax push bx push WORD PTR bx[10] //pc push WORD PTR bx[12] //bp mov bp, sp // restauriramo AX, BX … mov ax, bx[2] push WORD PTR bx[4] //bx mov cx, bx[6] mov dx, bx[8] pop bx } return i; } Sadržaj steka (u odnosu na BP): Чест случај је да +6 i компајлери генеришу такав +4 b код да вредност враћају у +2 PC неком од регистара. За +0 BP 8086 углавном важи да се Sadržaj вредност враћа у регистру Restauriranog Steka АХ. Зато се ова линија (potreban za преводи у: povratak na setjmp): i [bp+6] mov ax, pop BPb PC ret BP Решење void createThread(PCB *newPCB, void (*body)()){ newPCB->stack = new unsigned[1024]; newPCB->context = new cnt_buf; newPCB->context->pc = FP_OFF(body); newPCB->context->sp = FP_OFF(newPCB->stack+1024); } Задатак 9. • Написати корутину dispatch() коришћењем функције: void yield(unsigned* oldSP, unsigned* newSP); Прва половина функције чува контекст на текућем стеку и потом памти тренутну врeдност регистра SP у локацији на коју показује oldSP. Друга половина прво у регистар SP уписује садржај локације на коју показује newSP и потом са стека рестаурира контекст. Решење struct PCB{ unsigned SP; }; Проблем: Потребно је познавати имплементацију yield() f-je да би се креирао почетни контекст. void dispatch(){ unsigned *oldSP = &running->SP; Scheduler::put(running); running = Scheduler::get(); yield(oldSP, &running->SP); } Задатак 10. • Прокоментарисати претходне задатке уколико се dispatch() позива из прекидне рутине. Шта је у том случају проблем и како се решава? Решење void _dispatch(){ lock() asm { // cuva registre na steku push ax //sta bi se desilo da ne cuvamo AX? push bx push cx push dx mov bx, running mov [bx], sp //cuva sp } Scheduler::put(running); running = Scheduler::get(); asm { mov bx, running mov sp, [bx] // restauira sp pop dx // restauira registre pop cx pop bx pop ax } unlock() } На почетку се забрањује преузимање процесора. Чување контекста до сада извршаване нити Рестаурација контекста нити која се наставља На крају се поново дозвољава преузимање процесора.