Увод у архитектуру 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 pozivu potprograma:
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 procpush BPmov BP, SPmov AX, [BP+6]pop BPret...push 1234hcall funadd SP, 2
Напомена: Претпоставља се huge mem. model
Стек:xxxx+7 12 виши бајт на вишој
адреси+6 34 нижи бајт на нижој адреси+5 retCSh h – high byte+4 retCSl l – low byte+3 retPCh+2 retPCl+1 oldBPh+0 oldBPl
SP
BPАХ = 0х1234
Задатак 1.• Написати програм на програмском језику C који ће помоћу једне
функције бесконачно исписивати неки текст. Решење може да буде некоректно у смислу да ће оперативни систем у коначном времену пријавити грешку у извршавању програма и прекинути његово извршавање. Није дозвољено коришћење петљи.
#include<stdio.h>void a(){ printf("...\n"); a();}
void main(){ a();}
Шта је проблем?
Стек: (у једном реду једна реч – 2B)xx
retCSend_mainretPCend_mainoldBP_main
retCSend_aretPCend_aoldBP_a1
retCSend_aretPCend_aoldBP_a2
...
SP
BP
Задатак 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_OFFSETBP_mainAX_old
i:printf_OFFSET
return_OFFSETBP_mainAX_old
Задатак 3.
• Написати програм за процесор 8086 који треба да изврши неки потпрограм, али тако да се нигдe у коду не види позив тог потпрограма.
Решење – варијанта 1unsigned 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
xx
PC_main_}
BP_main
...
f //adresa funkcije f
0
...
...
SP BP
PC
0
На почетку сваке
функције:push BP
mov BP, SP
На почетку сваке
функције:push BP
mov BP, SPНа крају сваке
функције:pop BP
ret
FP_OFF(f) dohvata offset adrese f-je f
Решење – варијанта 1unsigned 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
xx
PC_main_}
BP_main
...
f //adresa funkcije f
0
...
...
SP BP
PC
0
PC_f_}
Извршава се код функције
На крају сваке функције:
pop BPret
На почетку сваке
функције:push BP
mov BP, SP
Решење – варијанта 2unsigned int temp;
void _dispatch1(){ asm { pop temp
push fpush temp
}}
void f(){//kod funkcije//...}
void main(){ dispatch1();}
xx
PC_main_}
BP_main
BP_main
SPtemp:
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; // cuvanje SPгдe је:– sp поље PCB структуре– SP регистар
• На језику С у општем случају није могуће приступити жељеном регистру.
• Зато се умећу сегменти асемблерског кода.• Асемблерски еквивалент горе наведеној наредби је (под
претпоставком PCB структуре дате на следећем слајду):asm{
mov BX, running // BX = runningmov [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 bxpush cxpush dxmov bx, running //upisuje adresu mem. lok.
mov [bx], sp //cuva sp } running=p[nextThread]; asm {
mov bx, runningmov sp, [bx] // restauira sp
pop dx // restauira registre pop cx pop bx pop ax
}}
При уласку у функцију dispatch() на стек ће се
ставити повратна адреса. То је адреса прве
инструкције после позива dispatch() и уједно и адреса
од које треба наставити прекинуту нит.
Чувају се и 4 регистра (тако је речено у поставци
задатка). У општем случају, неопходно је сачувати све регистре осим CS:PC (већ
сачувано при уласку у функцију) , BP (већ сачуван
првом инструкцијом функције f()) i SS:SP (биће
сачувани у PCB).
Чува се показивач на стек.Инструкције:
mov bx, runningmov [bx], sp
су еквивалентне са C кодом:running->sp = SP //SP je registar
Рестаурација стека нове нити:
SP = running->sp;Рестаурација остатка контекста:-прво регистри DX, CX, BX i AX-рестаурација BP са pop BP-рестаурација PC са ret
running је показивач на PCB структуру нити која се тренутно
извршава. Да би се умeсто нити, која се
извршавала до позива _dispatch() наставила нова нит, неопходно је поставити да running показује на
PCB те нове нити. У овом задатку је претпостављено
да се нова нит одрeђује уписом одговарајуће вредности у
nextThread.
По уласку у функцију, наилази се на прве две инструкције сваке
функције:push BP
mov BP, SPКао резултат прве инструкције на
стеку је сачувана вредност BP регистра која је коришћена у прекинутој нити и која ће се
користити опет по повратку у ту нит.
Решење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 = runningmov BX[6], AX // [BX+6] = AX - indirektno reg. adr. sa pomerajem
}-примeтити да се BX користи за адресирање, па се на почетку његов
садржај мора привремено сачувати (нпр на стеку)
Решењеvoid _dispatch(){ asm {
push bxmov bx, runningmov bx[6], axpop WORD PTR [bx+8] //bxmov bx[10], cxmov bx[12], dxpop WORD PTR[bx+4] //bppop WORD PTR[bx] //pc
mov bx[2], sp //cuva sp }
running=p[nextThread]; asm {
mov bx, runningmov sp, bx[2]
push WORD PTR[bx] //pcpush WORD PTR[bx+4] //bpmov ax, [bx+6]push WORD PTR [bx+8] //bxmov 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 bxpush cxpush dxmov bx, running
mov [bx], sp //cuva sp } Scheduler::put(running); running = Scheduler::get(); asm {
mov bx, runningmov 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; // +0unsigned ax; // +2unsigned bx; // +4unsigned cx; // +6unsigned dx; // +8unsigned pc; // +10unsigned bp; // +12
};
struct PCB{ cnt_buf* context; unsigned* stack;};
unsigned _setjmp(cnt_buf *b, unsigned i) {asm { push bxmov bx, [bp+4] //bx = b; mov bx[2], axpop WORD PTR [bx+4] //bxmov bx[6], cxmov bx[8], dxmov ax, bp[0]mov bx[12], ax //BP nitimov ax, bp[2]mov bx[10], ax //PC nitimov [bx], sp //running->sp = SP//skida se sa steka i, b, PC, BPadd WORD PTR[bx], 8 }return 0;
}
Sadržaj steka(u odnosu na BP):
+6 i+4 b+2 PC+0 BP-2 BX
Решењеunsigned _longjmp(cnt_buf *b, unsigned i){ asm {
mov bx, [bp+4] //BX = b;mov ax, [bp+6] //AX = i;mov sp, [bx] //restauriramo stekpush axpush bxpush WORD PTR bx[10] //pcpush WORD PTR bx[12] //bpmov bp, sp// restauriramo AX, BX …mov ax, bx[2]push WORD PTR bx[4] //bxmov cx, bx[6]mov dx, bx[8]pop bx}return i;
}
Чест случај је да компајлери генеришу такав код да вредност враћају у
неком од регистара. За 8086 углавном важи да се вредност враћа у регистру
АХ. Зато се ова линија преводи у:
mov ax, [bp+6]pop BPret
Sadržaj Restauriranog Steka (potreban za povratak na setjmp):ibPCBP
Sadržaj steka(u odnosu na BP):
+6 i+4 b+2 PC+0 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;
};
void dispatch(){unsigned *oldSP = &running->SP;Scheduler::put(running);running = Scheduler::get();yield(oldSP, &running->SP);
}
Проблем: Потребно је познавати
имплементацију yield() f-je да би се креирао почетни
контекст.
Задатак 10.
• Прокоментарисати претходне задатке уколико се dispatch() позива из прекидне рутине. Шта је у том случају проблем и како се решава?
Решењеvoid _dispatch(){lock() asm { // cuva registre na steku
push ax //sta bi se desilo da ne cuvamo AX?push bxpush cxpush dxmov bx, running
mov [bx], sp //cuva sp } Scheduler::put(running); running = Scheduler::get(); asm {
mov bx, runningmov sp, [bx] // restauira sp
pop dx // restauira registre pop cx pop bx pop ax
}unlock()}
Чување контекстадо сада извршаване
нити
Рестаурација контекстанити која се наставља
На почетку се забрањује
преузимање процесора.
На крају се поново дозвољава
преузимање процесора.