52
Datalogi 1F Forår 2003 Multiprogrammering[3] Eksempler på multiprogrammeringskerner Jørgen Sværke Hansen [email protected]

Datalogi 1F Forår 2003 Multiprogrammering[3]

  • Upload
    tilly

  • View
    39

  • Download
    4

Embed Size (px)

DESCRIPTION

Datalogi 1F Forår 2003 Multiprogrammering[3]. Eksempler på multiprogrammeringskerner Jørgen Sværke Hansen [email protected]. Planen for idag. Kerner uden afbrydelser (KB4 kap. 6): akernen: kerne med decentralt processkift bkernen: kerne med centralt processkift - PowerPoint PPT Presentation

Citation preview

Page 1: Datalogi 1F Forår 2003 Multiprogrammering[3]

Datalogi 1F Forår 2003Multiprogrammering[3]

Eksempler på multiprogrammeringskerner

Jørgen Sværke [email protected]

Page 2: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 3: Datalogi 1F Forår 2003 Multiprogrammering[3]

Datalogi 1F: Multiprogrammering[3]

3

Kerne med decentralt processkift

KInitProc(…) KWaitQKPause() KCurProcKSelectNewProcess()

KInitSem() KReadChar()KWait() KWriteChar()KSignal()

KReadLine()KWriteLine()

readerProcwriterProc

Page 4: Datalogi 1F Forår 2003 Multiprogrammering[3]

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(); }}

Page 5: Datalogi 1F Forår 2003 Multiprogrammering[3]

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;

}

Page 6: Datalogi 1F Forår 2003 Multiprogrammering[3]

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>; }

Page 7: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

}

Page 8: Datalogi 1F Forår 2003 Multiprogrammering[3]

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…

Page 9: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

}

Page 10: Datalogi 1F Forår 2003 Multiprogrammering[3]

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);

}

Page 11: Datalogi 1F Forår 2003 Multiprogrammering[3]

Datalogi 1F: Multiprogrammering[3]

11

Kontrolregistre på UART

Page 12: Datalogi 1F Forår 2003 Multiprogrammering[3]

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);}

Page 13: Datalogi 1F Forår 2003 Multiprogrammering[3]

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…

Page 14: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 15: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 16: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 17: Datalogi 1F Forår 2003 Multiprogrammering[3]

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*

Page 18: Datalogi 1F Forår 2003 Multiprogrammering[3]

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…

Page 19: Datalogi 1F Forår 2003 Multiprogrammering[3]

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…

Page 20: Datalogi 1F Forår 2003 Multiprogrammering[3]

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);

}

Page 21: Datalogi 1F Forår 2003 Multiprogrammering[3]

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);}

Page 22: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 23: Datalogi 1F Forår 2003 Multiprogrammering[3]

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å

Page 24: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 25: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 26: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

}

Page 27: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

}

Page 28: Datalogi 1F Forår 2003 Multiprogrammering[3]

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);

}

Page 29: Datalogi 1F Forår 2003 Multiprogrammering[3]

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;

Page 30: Datalogi 1F Forår 2003 Multiprogrammering[3]

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();

}

Page 31: Datalogi 1F Forår 2003 Multiprogrammering[3]

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;}

Page 32: Datalogi 1F Forår 2003 Multiprogrammering[3]

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); }

Page 33: Datalogi 1F Forår 2003 Multiprogrammering[3]

Datalogi 1F: Multiprogrammering[3]

33

Kerne med centralt processkift

KInitProc(…) KWaitQKSleep() KCurProcKPause()KSelectNewProcess()

KInitSem() KReadChar()KWait() KWriteChar()KSignal()

KReadLine()KWriteLine()

readerProcwriterProc

Page 34: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 35: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 36: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 37: Datalogi 1F Forår 2003 Multiprogrammering[3]

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;

}

Page 38: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 39: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 40: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 41: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 42: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 43: Datalogi 1F Forår 2003 Multiprogrammering[3]

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 …

Page 44: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 45: Datalogi 1F Forår 2003 Multiprogrammering[3]

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()

Page 46: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 47: Datalogi 1F Forår 2003 Multiprogrammering[3]

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 */;

Page 48: Datalogi 1F Forår 2003 Multiprogrammering[3]

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);

}

Page 49: Datalogi 1F Forår 2003 Multiprogrammering[3]

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;

}

Page 50: Datalogi 1F Forår 2003 Multiprogrammering[3]

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());

}

Page 51: Datalogi 1F Forår 2003 Multiprogrammering[3]

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

Page 52: Datalogi 1F Forår 2003 Multiprogrammering[3]

Datalogi 1F: Multiprogrammering[3]

52

Kilder

• Disse slides er baseret på indholdet i Datalogi 1F kursusbog bind 4, kapitlerne 6 & 7.