Download - Datalogi 1F Forår 2003 Multiprogrammering[3]
Datalogi 1F Forår 2003Multiprogrammering[3]
Eksempler på multiprogrammeringskerner
Jørgen Sværke [email protected]
Datalogi 1F: Multiprogrammering[3]
2
Planen for idag• Kerner uden afbrydelser (KB4 kap. 6):
– akernen: kerne med decentralt processkift– bkernen: kerne med centralt processkift
• Kerne med afbrydelser (KB4 kap. 7):– løst koblede drivprogrammer– kerne med tætkoblede drivprogrammer
Datalogi 1F: Multiprogrammering[3]
3
Kerne med decentralt processkift
KInitProc(…) KWaitQKPause() KCurProcKSelectNewProcess()
KInitSem() KReadChar()KWait() KWriteChar()KSignal()
KReadLine()KWriteLine()
readerProcwriterProc
Datalogi 1F: Multiprogrammering[3]
4
Brugerprogrammernevoid reader() {
rsem.Init(1);wsem.Init(0);for(;;) {rsem.Wait();buf.Read();wsem.Signal();}
}
void Writer() { for(;;) { wsem.Wait(); if(buf == ”exit\r”) asm(”call_pal 0xAD”); buf.Write(); rsem.Signal(); }}
Datalogi 1F: Multiprogrammering[3]
5
Processer i akerne
• Proces kontrolblokken i akerne:– CPU registre (gemt på stakken)– Vi gemmer kun stakpeger– Køpegere beregnet for ventekø
struct Process : public Queueable {Registers *sp;
}
Datalogi 1F: Multiprogrammering[3]
6
Processkift• akernen:
– har frivilligt processkift– benytter aktiv venten
• En proces kalder KPause hvis den venter på en hændelse (fungerer som yield() i Java)
• Aktiv venten:
void KGenericProcedure() { while(<hændelse ikke indtruffet>) { KPause(); <udfør aktion efter hændelse er
indtruffet>; }
Datalogi 1F: Multiprogrammering[3]
7
KPause• KPause foretager skiftet fra en proces til en
anden:
void KPause() {<gem registre på stakken>;<gem stakpeger i PKB>;<find ny proces>;<retabler stakpeger fra PKB>;<retabler registre>;
}
Datalogi 1F: Multiprogrammering[3]
8
Ventende processer
AP: KPause
registre P1
AP: KWait
AP: KPause
registre P2
AP:KReadChar
AP: KPause
registre P3
AP: KWriteChar
PCB P1: sp
PCB P2: sp
PCB P3: sp
struct process
AP:KSelectNewP…
AP:KSelectNewP…
AP:KSelectNewP…
Datalogi 1F: Multiprogrammering[3]
9
Semaforoperationervoid KSignal(KSem& sem) {sem++; // Aktivering af ventende
} // sker ved aktiv venten
void KWait(KSem& sem) {while (!sem) // Aktiv venten
KPause();sem--;
}
Datalogi 1F: Multiprogrammering[3]
10
I/O operationerchar KReadChar() {while(!(rdio(com1Lsr) & 0x01))
KPause();return rdio(com1Rbr);
}
void KWriteChar(char ch) {while(!(rdio(com1Lsr) & 0x20))
KPause();wrio(com1Thr, ch);
}
Datalogi 1F: Multiprogrammering[3]
11
Kontrolregistre på UART
Datalogi 1F: Multiprogrammering[3]
12
I/O operationer (2)void KReadLine(char* p, int max){for (int i = 0; i < max-1; i++)
if((*p++ = KReadChar()) == ’\r’)break;
*p = ’\0’;}
void KWriteLine(char *p, int max) {for (int i = 0; (i < max) && *p; i++,p++)
KWriteChar(*p);}
13
Ventende processer: igen
PCB P1: sp
PCB P2: sp
PCB P3: sp
struct process
AP:KReadLine
AP:KWriteLine
AP: KPause
registre P1
AP: KWait
AP: KPause
registre P2
AP:KReadChar
AP: KPause
registre P3
AP: KWriteChar
AP:KSelectNewP…
AP:KSelectNewP…
AP:KSelectNewP…
Datalogi 1F: Multiprogrammering[3]
14
KPause: implementation (klib.s)KPause: lda sp,-STAKRAMME(sp)
stq $0, 0(sp)stq $1, 8(sp)…stq$29, 0xE8(sp)bis sp, 0, a0 // stakpeger er
parameterlda pv, KSelectNewProcessjsr ra, (pv)bis v0, 0, sp // ny stakpeger er
returværdildq $0, 0(sp)ldq $1, 8(sp)…ldq $29, 0xE8(sp)lda sp, STAKRAMME(sp)ret (ra)
SAVE_REGS
REST_REGS
Datalogi 1F: Multiprogrammering[3]
15
KPause(): et par kommentarer
• Tæl stakpeger ned inden registre lægges på stakken:– en afbrydelse vil bruge samme stakpeger
og kan overskrive værdier hvis sp er for høj• Tæl stakpeger op EFTER registre er
fjernet fra stakken• Selve skiftet af KCurProc sker ikke i
KPause men i KSelectNewProcess
Datalogi 1F: Multiprogrammering[3]
16
KSelectNewProcessRegisters* KSelectNewProcess(Registers* sp) {
KCurProc->sp = sp;KWaitQ.Put(KCurProc);KCurProc = KWaitQ.Get();return KCurProc->sp;
}
• Skedulering foregår round-robin• Hvad er det for noget med Registers* ?
Var det ikke stakpegeren???
17
struct Registersstruct Registers {unsigned long r [30];unsigned long ps, pc, gp, a0, a1, a2;
};
r[0]r[1]
r[29]ps
a2
struct registers placering i lageret
r[0]r[1]
r[29]
AP:KPause
en processtak ved kald af KSelectNewProcess
PAL kaldstakramme
Registers*
18
KCurProc KCurProc
PCB P3: sp
PCB P2: sp
PCB P1: sp
void KWriteChar(char ch) { while(!(rdio(com1Lsr) & 0x20)) KPause(); wrio(com1Thr, ch);}
void KWait(KSem& sem) { while (!sem) KPause(); sem--; }
char KReadChar() { while(!(rdio(com1Lsr) & 0x01)) KPause(); return rdio(com1Rbr);}
Ventende processer: igen igen
AP: KPause
registre P1
AP: KWait
AP: KPause
registre P2
AP:KReadChar
AP: KPause
registre P3
AP: KWriteChar
struct process
AP:KReadLine
AP:KWriteLine
KCurProc
AP:KSelectNewP…
AP:KSelectNewP…
AP:KSelectNewP…
19
KCurProc KCurProc
PCB P3: sp
PCB P2: sp
PCB P1: sp
void KWriteChar(char ch) { while(!(rdio(com1Lsr) & 0x20)) KPause(); wrio(com1Thr, ch);}
void KWait(KSem& sem) { while (!sem) KPause(); sem--; }
char KReadChar() { while(!(rdio(com1Lsr) & 0x01)) KPause(); return rdio(com1Rbr);}
Ventende processer: igen igen
AP: KPause
registre P1
AP: KWait
AP: KPause
registre P2
AP:KReadChar
AP: KPause
registre P3
AP: KWriteChar
struct process
AP:KReadLine
AP:KWriteLine
KCurProc
AP:KSelectNewP…
AP:KSelectNewP…
AP:KSelectNewP…
Datalogi 1F: Multiprogrammering[3]
20
OK – nok show:hvordan starter vi akernen?
// Reader processens kernedatastrukturerextern void Reader ();Process readerProc;unsigned long readerStack [stackSize];
void main() {KWait.Init();KInitProc(Reader, readerStack, &readerProc);KInitProc(Writer, writerStack, &writerProc);KCurProc = KWaitQ.Get();KFirst(KCurProc->sp);
}
Datalogi 1F: Multiprogrammering[3]
21
Hvad sker der i KInitProc?
• KInitProc initialiserer en proces’ stak, så det ser ud som om den er afbrudt af KPause():
void KInitProc(void (*startAddr) (), void *Stack, Process *proc) { Stack += (stackSize * sizeof(unsigned long) – sizeof(Registers) ); proc->sp = (Registers *) Stack; proc->sp->r[26] = proc->sp->r[27] = (unsigned long) startAddr; // ra & pv KWaitQ.Put(proc);}
Datalogi 1F: Multiprogrammering[3]
22
Og så skal det hele sættes igangKCurProc = KWaitQ.Get();KFirst(KCurProc->sp);
hvor KFirst er defineret ved:
KFirst: ldgp gp, (pv)ldq pv, 0xD8(a0) // Pop pvaddq a0, 0xF0,a0 // Skip registrebis a0, 0, sp // Sæt spjmp (pv) // Hop til processtart
Datalogi 1F: Multiprogrammering[3]
23
OK, hvad var det for noget med decentralt skift?
• Venteløkkerne for de enkelte I/O og semaforoperationer var spredt ud over kernen:– ineffektivt: registre skal poppes og pushes
hele tiden– svært at vedligeholde:
• hvad nu hvis vi gerne vil ændre vores aktiv venten strategi
– ugennemsigtigt:• vi ved ikke hvilke hændelser de enkelte
processer venter på
Datalogi 1F: Multiprogrammering[3]
24
Kerne med centralt processkift
• I modsætning til akerne.cc ønsker vi at have en central venteløkke og processkift
• Hvordan specificerer en proces hvad den venter på?
• Vi indfører operationen KSleep(), der kan vente på at en hændelse indtræder
Datalogi 1F: Multiprogrammering[3]
25
Hændelser
• En proces kan vente på:– CPU (den er klar til at blive udført)– Ydre enheder (en operation bliver
færdigbehandlet)– Semafor (ankomst af et signal)
• Specifikationen af en hændelse skal kunne omfatte alle ovenstående hændelser
Datalogi 1F: Multiprogrammering[3]
26
struct Eventstruct Event {enum { IO, SEM, CPU } id;union {
struct { int addr; char mask; } io;struct { KSem* addr; } sem;
}Event(int, char); // vent på I/OEvent(KSem&); // vent på semaforEvent(); // vent på CPU
}
Datalogi 1F: Multiprogrammering[3]
27
Ny udgave af semaforoperationNy udgave:void KWait (KSem& sem) {if(!sem)KSleep( Event(sem) );sem--;
}
Gammel udgave:void KWait(KSem& sem) {while (!sem) // Aktiv ventenKPause();sem--;
}
Datalogi 1F: Multiprogrammering[3]
28
I/O operationernechar KReadChar() {if (!rdio(com1Lsr) & 0x01)
KSleep( Event(com1Lsr, 0x01) );return rdio(com1Rbr);
}
void KWriteChar(char ch) {if (!rdio(com1Lsr) & 0x20)
KSleep( Event(com1Lsr, 0x20) );wrio(com1Thr, ch);
}
Datalogi 1F: Multiprogrammering[3]
29
Proceskontrolblokken• Nu bliver processens tilstand udvidet
med hvilken hændelse en proces venter på (hvis nogen):
struct Process {Registers* sp;Event waitsFor;
} *KCurProc;
Datalogi 1F: Multiprogrammering[3]
30
KSleep()• En ”overbygning” til KPause(), der
registerer hvilken hændelse en proces venter på:
void KSleep(Event e) {KCurProc->waitsFor = e;KPause();
}
Datalogi 1F: Multiprogrammering[3]
31
Den centrale venteløkkeRegisters* KSelectNewProcess(Registers* sp) { KCurProc->sp = sp; for(int found = 0; !found; ) { KWaitQ.Put(KCurProc); KCurProc = KWaitQ.Get(); switch(KCurProc->waitsFor.id) { case Event::CPU: found = 1;
break; case Event::IO: if(rdio(KCurProc->waitsFor.io.addr)&
KCurProc->waitsFor.io.mask) found = 1; break;
case Event::SEM: if(*KCurProc->waitsFor.sem.addr) found = 1; break;
} } return KCurProc->sp;}
32
Ventende processer: igen igen igen
AP: KPause
registre P1
AP: KWait
AP: KPause
registre P2
AP:KReadChar
AP: KPause
registre P3
AP: KWriteChar
AP: KSleep AP: KSleep AP: KSleep
PCB P1: sp waitsfor = SEM
struct process
PCB P2: sp waitsfor = IO
PCB P3: sp waitsfor = IO
KCurProc KCurProcKCurProc
AP:KSelectNewP…
AP:KSelectNewP…
AP:KSelectNewP…
void KWriteChar(char ch) { if (!rdio(com1Lsr) & 0x20) KSleep( Event(com1Lsr, 0x20); wrio(com1Thr, ch); }
Datalogi 1F: Multiprogrammering[3]
33
Kerne med centralt processkift
KInitProc(…) KWaitQKSleep() KCurProcKPause()KSelectNewProcess()
KInitSem() KReadChar()KWait() KWriteChar()KSignal()
KReadLine()KWriteLine()
readerProcwriterProc
Datalogi 1F: Multiprogrammering[3]
34
Kerner med aktiv venten• Vi har flere gange diskuteret at aktiv
venten ikke er den mest effektive måde at opdage en hændelse på:– selv med en central venteløkke spilder vi tid
på at undersøge ydre enheders statusregistre når der ikke er behov for det
• forsinker aktivering af processer, der enten venter på CPU eller har modtaget en hændelse
• Men det giver simple kerner:– det hele kan forstås som en sekventiel
proces
Datalogi 1F: Multiprogrammering[3]
35
Kerner med afbrydelser• Slut med aktiv venten• Afbrydelsesprocedure aktiverer
ventende processer• Men alting bliver mere uforudsigeligt,
idet afbrydelser kan indtræde når som helst:– data kan deles mellem
afbrydelsesprocedurer og resten af kernen– brug for udelelig adgang til delt data
Datalogi 1F: Multiprogrammering[3]
36
Processkift• Da vi ikke har aktiv venten, skal vi holde
rede på de ventende processer.• Vi benytter to proceskøer:
– KWaitQ: kø af processer der venter på en hændelse (semaforsignal eller ydre enhed)
– KReadyQ: kø af processer der er klar til at blive udført (venter på at få adgang til CPU)
• Processer bør først forlade KWaitQ når hændelsen de venter på er indtrådt
Datalogi 1F: Multiprogrammering[3]
37
Suspendering af processer• KPause kalder KSelectNewProcess, der
sætter den aktive proces til at vente:
Registers* KSelectNewProcess(Registers* sp) {KCurProc->sp = sp;KWaitQ.Put(KCurProc);KCurProc = KReadyQ.Get(); // Her er forskellenreturn KCurProc->sp;
}
Datalogi 1F: Multiprogrammering[3]
38
Aktivering af processer• Vi aktiverer processerne når vi får en
afbrydelse:
void KInterruptHandler() {while(!KWaitQ.isEmpty())
KReadyQ.Put(KWaitQ.Get());}
• Men hov? alle processerne startes?• Ja, for vi genbruger vores kerne med
decentralt processkift
Datalogi 1F: Multiprogrammering[3]
39
I/O operationerchar KReadChar() {
while(!(rdio(com1Lsr) & 0x01))KPause();return rdio(com1Rbr);
}
void KWriteChar(char ch) {while(!(rdio(com1Lsr) & 0x20))KPause();wrio(com1Thr, ch);
}
• Alle ventende processer aktiveres og udfører check på om hændelse er indtrådt decentralt
Datalogi 1F: Multiprogrammering[3]
40
Afbrydelseshåndtering i ckerne
• Afbrydelse fra ydre enhed aktiverer afbrydelseshåndtering via PAL kode:– først aktiveres ent_int– ent_int kalder KInterruptHandler– derefter returneres til ent_int– ent_int returnerer fra afbrydelse
• ent_int specificeres i ckernens main funktioner via PAL_wrent
Datalogi 1F: Multiprogrammering[3]
41
ent_intent_int: SAVE_REGS
br t0, 1f1: ldgp gp, (t0)
lda pv, KInterruptHandlerjsr ra, (pv)REST_REGScall_pal PAL_rti
Datalogi 1F: Multiprogrammering[3]
42
Stak under afbrydelse
AP:buf.Write()
AP:KWriteLine
procesWrite()
void KWriteLine(char *p, int max) { for (int i=0; (i<max) && *p; i++,p++) KWriteChar(*p);}
PAL stakramme
AP: ent_int
ent_int: SAVE_REGS br t0, 1f
1: ldgp gp, (t0) lda pv, KInterruptHandler jsr ra, (pv) REST_REGS call_pal PAL_rti
void KInterruptHandler() { while(!KWaitQ.isEmpty()) KReadyQ.Put(KWaitQ.Get());} AP:
KInterruptHa…AP:
KReadyQ.Put…void KReadyQ.Put() {…}
AP:KWriteChar
Stak forProces Writer
Datalogi 1F: Multiprogrammering[3]
43
Synkronisering med ydre enhederchar KReadChar() { while(!(rdio(com1lsr) & 0x01)) KPause();// Opdaterer KWaitQ indirekte return rdio(com1Rbr);}
void KInterruptHandler() { while(!KWaitQ.isEmpty()) KReadyQ.Put(KWaitQ.Get());}
• Test på LSR og ventekøoperation i KReadChar skal udføres udeleligt, ellers kan følgende ske …
Datalogi 1F: Multiprogrammering[3]
44
Uheldig rækkefølge<KReadChar>
while(!(rdio(com1lsr) & 0x01))<UART>
sætter ready-bit<KInterruptHandler>
while(!KWaitQ.isEmpty())<KReadChar>
KPause();<KSelectNewProcess>
<sætter proces i ventekø>AAAAARGH: vi opdager ikke at tegnet er læst
Datalogi 1F: Multiprogrammering[3]
45
Implementering af udelelighed• Luk for afbrydelser:char KReadChar() {
forbid(); while(!(rdio(com1lsr) & 0x01))
KPause();char ch = rdio(com1Rbr);permit();return ch;
}• Nu bliver vi ikke afbrudt mellem check af
statusregister og KWaitQ.Put()
Datalogi 1F: Multiprogrammering[3]
46
Køoperationerne skal også beskyttes
• Eksempel:int isEmpty() {
int oldipl = forbid();int b = (size == 0);permit(oldipl);return b;
};
• Gem ipl – så undgår vi at lukke op for afbrydelser ved et uheld
• Vigtigt hvis operationer kan benyttes af afbrydelsesprocedurer
Datalogi 1F: Multiprogrammering[3]
47
Hvad gør vi når klarkøen er tom?
• En tomgangsproces:– en proces som aldrig kommer i ventekøen,
men som hele tiden frivilligt opgiver CPU’en• En tomgangsløkke i KSelectNewProcess:
while( KReadyQ.isEmpty() )/* Do nothing */;
Datalogi 1F: Multiprogrammering[3]
48
Tætkoblede drivprogrammer• I stedet for at vække alle processer ved
en afbrydelse kan vi have en ventekø for hver hændelse:
char KReadChar() {while(!(rdio(com1lsr) & 0x01))
KPause(KReadQ);return rdio(com1Rbr);
}
Datalogi 1F: Multiprogrammering[3]
49
KSelectNewProcess tager en ventekø som argument
Registers* KSelectNewProcess(Registers* sp, Queue<Process>& blockOn) {
KCurProc->sp = sp;blockOn.Put(KCurProc);while( KReadyQ.isEmpty() )
/* Do nothing */;KCurProc = KReadyQ.Get(); return KCurProc->sp;
}
Datalogi 1F: Multiprogrammering[3]
50
Afbrydelsesprocedure ved tæt kobling
void KInterruptHandler() {if( rdio(com1Iir) & 2)while(!KReadQ.isEmpty())KReadyQ.Put(KReadQ.Get());else if( rdio(com1Iir) & 3)while(!KWriteQ.isEmpty())KReadyQ.Put(KWriteQ.Get());
}
Datalogi 1F: Multiprogrammering[3]
51
Opsummering• Kerner uden afbrydelser:
– baseret på aktiv venten• Kerne med decentralt processkift:
– venteløkker i styreprogrammer og semaforoperationer
• Kerne med centralt processkift– processer specificere en hændelse de venter på– én venteløkke der undersøger om hændelser er
sket for alle ventende processer• Kerne med afbrydelser:
– afbrydelser aktiverer ventende processer– tæt koblede drivprogrammer: en kø per hændelse
Datalogi 1F: Multiprogrammering[3]
52
Kilder
• Disse slides er baseret på indholdet i Datalogi 1F kursusbog bind 4, kapitlerne 6 & 7.