520 likes | 618 Views
Datalogi 1F Forår 2003 Multiprogrammering[3]. Eksempler på multiprogrammeringskerner Jørgen Sværke Hansen cyller@diku.dk. Planen for idag. Kerner uden afbrydelser (KB4 kap. 6): akernen: kerne med decentralt processkift bkernen: kerne med centralt processkift
E N D
Datalogi 1F Forår 2003Multiprogrammering[3] Eksempler på multiprogrammeringskerner Jørgen Sværke Hansen cyller@diku.dk
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]
Kerne med decentralt processkift readerProc writerProc KReadLine() KWriteLine() KInitSem() KReadChar() KWait() KWriteChar() KSignal() KInitProc(…) KWaitQ KPause() KCurProc KSelectNewProcess() Datalogi 1F: Multiprogrammering[3]
void 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(); } } Brugerprogrammerne Datalogi 1F: Multiprogrammering[3]
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]
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]
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]
Ventende processer AP: KWait AP: KReadChar AP: KWriteChar AP: KPause AP: KPause AP: KPause registre P1 registre P2 registre P3 AP: KSelectNewP… AP: KSelectNewP… AP: KSelectNewP… struct process PCB P1: sp PCB P2: sp PCB P3: sp Datalogi 1F: Multiprogrammering[3]
Semaforoperationer void 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]
I/O operationer char 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]
Kontrolregistre på UART Datalogi 1F: Multiprogrammering[3]
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); } Datalogi 1F: Multiprogrammering[3]
Ventende processer: igen AP: KReadLine AP: KWriteLine AP: KWait AP: KReadChar AP: KWriteChar AP: KPause AP: KPause AP: KPause registre P1 registre P2 registre P3 AP: KSelectNewP… AP: KSelectNewP… AP: KSelectNewP… struct process PCB P1: sp PCB P2: sp PCB P3: sp
KPause: implementation (klib.s) SAVE_REGS KPause: lda sp,-STAKRAMME(sp) stq $0, 0(sp) stq $1, 8(sp) … stq$29, 0xE8(sp) bis sp, 0, a0 // stakpeger er parameter lda pv, KSelectNewProcess jsr ra, (pv) bis v0, 0, sp // ny stakpeger er returværdi ldq $0, 0(sp) ldq $1, 8(sp) … ldq $29, 0xE8(sp) lda sp, STAKRAMME(sp) ret (ra) REST_REGS Datalogi 1F: Multiprogrammering[3]
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]
KSelectNewProcess Registers* 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??? Datalogi 1F: Multiprogrammering[3]
en processtak ved kald af KSelectNewProcess PAL kald stakramme AP: KPause r[29] r[1] r[0] struct Registers struct Registers { unsigned long r [30]; unsigned long ps, pc, gp, a0, a1, a2; }; struct registers placering i lageret a2 ps r[29] Registers* r[1] r[0]
Ventende processer: igen igen AP: KReadLine AP: KWriteLine AP: KWait AP: KReadChar AP: KWriteChar AP: KPause AP: KPause AP: KPause registre P1 registre P2 registre P3 AP: KSelectNewP… AP: KSelectNewP… AP: KSelectNewP… void KWriteChar(char ch) { while(!(rdio(com1Lsr) & 0x20)) KPause(); wrio(com1Thr, ch); } char KReadChar() { while(!(rdio(com1Lsr) & 0x01)) KPause(); return rdio(com1Rbr); } void KWait(KSem& sem) { while (!sem) KPause(); sem--; } struct process KCurProc KCurProc KCurProc PCB P1: sp PCB P2: sp PCB P3: sp
Ventende processer: igen igen AP: KReadLine AP: KWriteLine AP: KWait AP: KReadChar AP: KWriteChar AP: KPause AP: KPause AP: KPause registre P1 registre P2 registre P3 AP: KSelectNewP… AP: KSelectNewP… AP: KSelectNewP… void KWriteChar(char ch) { while(!(rdio(com1Lsr) & 0x20)) KPause(); wrio(com1Thr, ch); } char KReadChar() { while(!(rdio(com1Lsr) & 0x01)) KPause(); return rdio(com1Rbr); } void KWait(KSem& sem) { while (!sem) KPause(); sem--; } struct process KCurProc KCurProc KCurProc PCB P1: sp PCB P2: sp PCB P3: sp
OK – nok show:hvordan starter vi akernen? // Reader processens kernedatastrukturer extern 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]
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]
Og så skal det hele sættes igang KCurProc = KWaitQ.Get(); KFirst(KCurProc->sp); hvor KFirst er defineret ved: KFirst: ldgp gp, (pv) ldq pv, 0xD8(a0) // Pop pv addq a0, 0xF0,a0 // Skip registre bis a0, 0, sp // Sæt sp jmp (pv) // Hop til processtart Datalogi 1F: Multiprogrammering[3]
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]
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]
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]
struct Event struct Event { enum { IO, SEM, CPU } id; union { struct { int addr; char mask; } io; struct { KSem* addr; } sem; } Event(int, char); // vent på I/O Event(KSem&); // vent på semafor Event(); // vent på CPU } Datalogi 1F: Multiprogrammering[3]
Ny udgave af semaforoperation Ny udgave: void KWait (KSem& sem) { if(!sem) KSleep( Event(sem) ); sem--; } Gammel udgave: void KWait(KSem& sem) { while (!sem) // Aktiv venten KPause(); sem--; } Datalogi 1F: Multiprogrammering[3]
I/O operationerne char 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]
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]
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]
Den centrale venteløkke Registers* 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; } Datalogi 1F: Multiprogrammering[3]
Ventende processer: igen igen igen void KWriteChar(char ch) { if (!rdio(com1Lsr) & 0x20) KSleep( Event(com1Lsr, 0x20); wrio(com1Thr, ch); } AP: KWait AP: KReadChar AP: KWriteChar AP: KSleep AP: KSleep AP: KSleep AP: KPause AP: KPause AP: KPause registre P1 registre P2 registre P3 AP: KSelectNewP… AP: KSelectNewP… AP: KSelectNewP… struct process KCurProc KCurProc KCurProc PCB P1: sp waitsfor = SEM PCB P2: sp waitsfor = IO PCB P3: sp waitsfor = IO
Kerne med centralt processkift readerProc writerProc KReadLine() KWriteLine() KInitSem() KReadChar() KWait() KWriteChar() KSignal() KInitProc(…) KWaitQ KSleep() KCurProc KPause() KSelectNewProcess() Datalogi 1F: Multiprogrammering[3]
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]
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]
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]
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 forskellen return KCurProc->sp; } Datalogi 1F: Multiprogrammering[3]
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]
I/O operationer char 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]
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]
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 Datalogi 1F: Multiprogrammering[3]
Stak under afbrydelse Stak for Proces Writer void KWriteLine(char *p, int max) { for (int i=0; (i<max) && *p; i++,p++) KWriteChar(*p); } ent_int: SAVE_REGS br t0, 1f 1: ldgp gp, (t0) lda pv, KInterruptHandler jsr ra, (pv) REST_REGS call_pal PAL_rti proces Write() AP: buf.Write() AP: KWriteLine PAL stakramme AP: KWriteChar void KInterruptHandler() { while(!KWaitQ.isEmpty()) KReadyQ.Put(KWaitQ.Get()); } AP: ent_int AP: KInterruptHa… void KReadyQ.Put() {…} AP: KReadyQ.Put… Datalogi 1F: Multiprogrammering[3]
Synkronisering med ydre enheder char 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]
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]
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]
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]
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]
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]
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]
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]