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()
}
На почетку се
забрањује
преузимање
процесора.
Чување контекста
до сада извршаване
нити
Рестаурација контекста
нити која се наставља
На крају се поново
дозвољава
преузимање
процесора.