1 / 52

Datalogi 1F Forår 2003 Multiprogrammering[3]

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

tilly
Télécharger la présentation

Datalogi 1F Forår 2003 Multiprogrammering[3]

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Datalogi 1F Forår 2003Multiprogrammering[3] Eksempler på multiprogrammeringskerner Jørgen Sværke Hansen cyller@diku.dk

  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 readerProc writerProc KReadLine() KWriteLine() KInitSem() KReadChar() KWait() KWriteChar() KSignal() KInitProc(…) KWaitQ KPause() KCurProc KSelectNewProcess() Datalogi 1F: Multiprogrammering[3]

  4. 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]

  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: 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]

  9. 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]

  10. 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]

  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); } Datalogi 1F: Multiprogrammering[3]

  13. 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

  14. 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]

  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. 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]

  17. 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]

  18. 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

  19. 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

  20. 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]

  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 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]

  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 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]

  27. 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]

  28. 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]

  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ø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]

  32. 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

  33. Kerne med centralt processkift readerProc writerProc KReadLine() KWriteLine() KInitSem() KReadChar() KWait() KWriteChar() KSignal() KInitProc(…) KWaitQ KSleep() KCurProc KPause() KSelectNewProcess() 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 forskellen return 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 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]

  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_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]

  42. 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]

  43. 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]

  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]

More Related