410 likes | 549 Views
Оперативни системи 1. В e жбе Lista predmeta: i r2os1@lists.etf.rs. Увод у архитектуру 8086. 16-битни микропроцесор. Адресибилна јединица бајт. Регистри: сегментни општенем e нски индексни базни регистар BP и показивач на стек SP. Сегментни регистри. CS – код сегмент ,
E N D
Оперативни системи 1 Вeжбе Lista predmeta: ir2os1@lists.etf.rs
Увод у архитектуру 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 AL 12h 34h
Индексни и базни регистри • Индексни: • SI i DI, • Сваки по 16 бита, • Користе се при индексном адресирању (приступ елементима низа). • Примeр: mov AX, niz[SI] • Базни регистар за приступ стеку: • BP • 16 бита. • Користи се за приступ стеку (приступ стварним аргументима и локалним промeнљивим процедуре).
Стек • Постоје две поделе (4 врсте) стекова: • да ли расте ка вишим адресама или ка нижим? • да ли показивач на стек показује на заузету локацију на врху стека или на прву слободну локацију изнад врха стека? • Стек процесора 8086: • расте ка нижим адресама • SP указује на заузету локацију на врху стека
Стек позива потпрограма • Са BP Zašto BP? Zar nije dovoljan SP?
Стек позива потпрограма mySub: ; Start of procedure push bp mov bp, sp sub sp, n ; reserve n bytes ; of local storage push reg1 ; save registers push reg2 ; do some processing pop reg2 pop reg1 add sp, n ; just the opposite mov sp, bp pop bp ret ; we are done. Кonvencija pri pozivupotprograma: Napomena: Parametri se stavljaju na stek u obrnutom redosledu od onog navedenog pri deklaraciji f-je.
Потпрограм на 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 Стек: xx xx +7 12виши бајт на вишој адреси +6 34 нижи бајт на нижој адреси +5 retCShh – high byte +4 retCSl l – low byte +3 retPCh +2 retPCl +1 oldBPh +0 oldBPl Како се потпрограму прослеђују параметри? BP АХ = 0х1234 SP
Задатак 1. • Написати програм на програмском језику C који ће помоћу једне функције бесконачно исписивати неки текст.Решење може да буде некоректно у смислу да ће оперативни систем у коначном времену пријавити грешку у извршавању програма и прекинути његово извршавање. Није дозвољено коришћење петљи. #include<stdio.h> void a(){ printf("...\n"); a(); } void main(){ a(); } BP Стек: (у једном реду једна реч – 2B)xx retCSend_mainretPCend_mainoldBP_main retCSend_aretPCend_aoldBP_a1 retCSend_aretPCend_aoldBP_a2 ... Шта је проблем? SP
Задатак 2. • Написати програм на програмском језику C који ће бесконачно понављати исписивање неког текста. Није дозвољено користити петље и програм мора бити исправан, тј. да ОС никада не пријави грешку за тај програм.
#include <stdio.h> int i; void a(){ //cuva pov. adr. asm{ push ax mov ax, [bp]+2 mov i,ax pop ax } } 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; } Решење printf_OFFSET BP_main AX_old return_OFFSET BP_main AX_old i: printf_OFFSET
Задатак 3. • Написати програм за процесор 8086 који треба да изврши неки потпрограм, али тако да се нигдe у коду не види позив тог потпрограма.
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 mov sp, SP_main // restauira sp od main } } void f(){ //kod funkcije //... //kod za izlazak iz funkcije: dispatch2(); } void main(){ stek_f[1023] = FP_OFF(f) SP_f = FP_OFF(stek_f+1022); dispatch1(); } stek_f+1023 stek_f SP_main SP_f На почетку сваке функције: push BP mov BP, SP Решење – варијанта 1 На почетку сваке функције: push BP mov BP, SP На крају сваке функције: pop BP ret SP BP xx PC_main_} BP_main ... f //adresa funkcije f 0 ... ... PC FP_OFF(f) dohvata offset adrese f-je f 0
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 mov sp, SP_main // restauira sp od main } } void f(){ //kod funkcije //... //kod za izlazak iz funkcije: dispatch2(); } void main(){ stek_f[1023] = FP_OFF(f) SP_f = FP_OFF(stek_f+1022); dispatch1(); } stek_f+1023 stek_f SP_main SP_f Решење – варијанта 1 Извршава се код функције PC На почетку сваке функције: push BP mov BP, SP xx PC_main_} BP_main ... f //adresa funkcije f 0 ... ... SP BP 0 На крају сваке функције: pop BP ret PC_f_}
unsigned int temp; void _dispatch1(){ asm { pop temp push f push temp } } void f(){ //kod funkcije //... } void main(){ dispatch1(); } Решење– варијанта 2 temp: BP_main SP xx PC_main_} BP_main BP_main f //adresa funkcije f
Задатак 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; // cuvanjeSP гдe је: • sp поље PCB структуре • SP регистар • На језику С у општем случају није могуће приступити жељеном регистру. • Зато се умећу сегменти асемблерског кода. • Асемблерски еквивалент горе наведеној наредби је (под претпоставком PCB структуре дате на следећем слајду): asm{ mov BX, running // BX = running mov [BX], SP // [BX] = SP - indirektno registarsko adresiranje } -примeтити да се BX користи за адресирање, па се на почетку његов садржај мора привремено сачувати (нпр на стеку)
struct PCB{ unsigned sp; unsigned* stack; }; PCB *p[3]; PCB* running; int nextThread; //makro #define dispatch(x) { nextThread = x; \ _dispatch(); } 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 } } Решење При уласку у функцију dispatch() на стек ће се ставити повратна адреса. То је адреса прве инструкције после позива dispatch() и уједно и адреса од које треба наставити прекинуту нит. По уласку у функцију, наилази се на прве две инструкције сваке функције: push BP mov BP, SP Као резултат прве инструкције на стеку је сачувана вредност BP регистра која је коришћена у прекинутој нити и која ће се користити опет по повратку у ту нит. running је показивач на PCB структуру нити која се тренутно извршава. Да би се умeсто нити, која се извршавала до позива _dispatch() наставила нова нит, неопходно је поставити да running показује на PCB те нове нити. У овом задатку је претпостављено да се нова нит одрeђује уписом одговарајуће вредности у nextThread. Чувају се и 4 регистра (тако је речено у поставци задатка). У општем случају, неопходно је сачувати све регистре осим CS:PC (већ сачувано при уласку у функцију) , BP (већ сачуван првом инструкцијом функције f()) i SS:SP (биће сачувани у PCB). Чува се показивач на стек. Инструкције: mov bx, running mov [bx], sp су еквивалентне са C кодом: running->sp = SP //SP je registar Рестаурација стека нове нити: SP = running->sp; Рестаурација остатка контекста: -прво регистри DX, CX, BX i AX -рестаурација BP са pop BP -рестаурација PC са ret
void a(){ for (int i = 0; i < 3; ++i) printf("U a() %d\n",i); asm { mov ax, 7 } dispatch(2); asm { mov i, ax } printf(" u a() ax = %d\n",i); for (i = 0; i < 3; ++i) printf("U a() %d\n",i); dispatch(2); } void b(){ for (int i = 0; i < 3; ++i) { printf("U b() %d\n",i); } asm { mov ax, 2 } dispatch(1); for (i = 0; i < 3; ++i) { printf("U b() %d\n",i); } dispatch(0); } Решење
void createThread(PCB *newPCB, void (*body)()){ unsigned* st = new unsigned[1024]; unsigned newPC = FP_OFF(body); st[1023] = newPC; //upisuje se rec - int //pocetni kontekst (proizvoljne vrednosti) //st[1023-1018]={PC,BP,AX…DX} newPCB->sp = FP_OFF(st+1018); newPCB->stack = st; } void delete_all(){ delete [] p[1]->stack; delete [] p[2]->stack; delete p[0]; delete p[1]; delete p[2]; } int main(){ p[1] = new PCB(); createThread(p[1],a); printf("napravio a\n"); p[2] = new PCB(); createThread(p[2],b); printf("napravio b\n"); p[0] = new PCB(); running = p[0]; dispatch(1); printf("Kraj.\n"); delete_all(); return 0; } Решење
Задатак 5. • Рeшити претходни задатак, али тако да се сви регистри чувају у PCB. struct PCB{ pomeraj u odnosu na poc. adresu strukture unsigned pc; +0 unsigned sp; +2 unsigned bp; +4 unsigned ax; +6 unsigned bx; +8 unsigned cx; +10 unsigned dx; +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 { mov bx, running mov sp, bx[2] push WORD PTR[bx] //pc push WORD PTR[bx+4] //bp mov ax, [bx+6] push WORD PTR [bx+8] //bx mov cx, [bx+10] mov dx, [bx+12] pop bx } } Решење struct PCB{ pomeraj unsigned pc; +0 unsigned sp; +2 unsigned bp; +4 unsigned ax; +6 unsigned bx; +8 unsigned cx; +10 unsigned dx; +12 ... }; 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 }; struct PCB{ cnt_buf* context; unsigned* stack; }; 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; } Решење Sadržaj steka (u odnosu na BP): +6 i +4 b +2 PC +0 BP -2 BX
Решење Sadržaj steka (u odnosu na BP): +6 i +4 b +2 PC +0 BP 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; } Чест случај је да компајлери генеришу такав код да вредност враћају у неком од регистара. За 8086 углавном важи да се вредност враћа у регистру АХ. Зато се ова линија преводи у: mov ax, [bp+6] pop BP ret Sadržaj Restauriranog Steka (potreban za povratak na setjmp): i b PC 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() коришћењем функције: voidyield(unsigned* oldSP, unsigned* newSP); Прва половина функције чува контекст на текућем стеку и потом памти тренутну врeдност регистра SP у локацији на коју показује oldSP. Друга половина прво у регистар SP уписује садржај локације на коју показује newSP и потом са стека рестаурира контекст.
Решење Проблем: Потребно је познавати имплементацију yield() f-je да би се креирао почетни контекст. struct PCB{ unsigned SP; }; 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() } Чување контекста до сада извршаване нити Рестаурација контекста нити која се наставља На крају се поново дозвољава преузимање процесора.