Løsningsforslag til eksamen i IN 17 og IN 17A Dag Langmyhr Øystein Gran Larsen Våren 1996 1 Oversettelse I vårt forslag har vi lagt større vekt på at oversettelsen skal vært «rett frem» og lett forståelig enn rask. 1 #include <regdef.h> 3.text.globl sort 5 6 # Navn: sort. 7 # Synopsis: Sorterer en vektor med n elementer. 8 # Signatur i C: void sort(short a[], int n). 9 # Registre: a0: a. 10 # a1: n. 11 # t0: any_change. 1 # t1: ix. 13 # t: &a[ix]. 1 # t3: a[ix]. 15 # t: a[ix+1]. 16 17 sort: li t0,1 # any_change = TRUE; 18 while: beqz t0,exit # while (any_change) { 19 addi a1,-1 # n--; 0 move t0,zero # any_change = FALSE; 1 move t1,zero # for (ix=0; move t,a0 # 3 for: bge t1,a1,for_x # ix<n;...) { lh t3,0(t) # 5 lh t,(t) # 6 ble t3,t,if_x # if (a[ix]>a[ix+1]) { 7 sh t3,(t) # /* Bytt a[ix] og 8 sh t,0(t) # a[ix+1]. */ 9 li t0,1 # any_change = TRUE; } 30 if_x: addi t1,t1,1 # for(...;...; ix++) 31 addi t,t, # 3 b for # } 33 for_x: b while # } 3 exit: jr ra # return; 1
Spesielt å merke seg er at adressene øker med i vektoren siden hvert element er en short. Følgende instruksjoner vil gi problemer for pipelinen i MIPS-prosessoren: Alle hopp. Dette bør fortrinnsvis løses ved ombytting av instruksjoner, så linje 31 og 3 må bytte plass. Slik ombytting er ikke mulig for de andre hoppene, så vi må i stedet legge en nop bak linjene 18, 3, 6, 33 og 3. Bruken av register T i linjene 5 og 6. Dette bør også løses ved å bytte om rekkefølgen instruksjonene utføres i, men er i vårt tilfelle ikke mulig uten fullstendig omstrukturering av løsningen. Da velger vi heller den nest beste løsningen, nemlig å sette inn en nop mellom de to linjene. Den største utnyttelsen av instruksjonscachen får vi når all koden som gjentas ligger der, det vil si all koden i den ytre løkken. Dette omfatter 16 instruksjoner pluss de 5 nop-ene som ble lagt inn i forrige deloppgave, totalt 1 ord eller 8 byte. Boolsk logikk Sannhetsverditabellen er vist i tabell 1. Tabell 1: Sannhetsverditabellen fra oppgave -a A B C f F F F T F F T T F T F F F T T T T F F F T F T F T T F F T T T T Siden A B C + A B C = A B(C + C) = A B og kan f forenkles til A B C + A B C = (A + A)B C = B C; f = A B + B C: En implementasjon med NAND-porter er vist i figur 1 på neste side. 3 Lister Datastrukturen kan deklareres slik:
A B C Figur 1: En implementasjon med NAND-porter 1 struct line_info { unsigned line_no; 3 struct line_info *next; }; 5 6 struct id_info { 7 unsigned char *id_word; 8 struct id_info *next; 9 struct line_info *first, *last; 10 }; 11 1 struct id_info *id_p; Innsettingsfunksjonen og dens hjelpefunksjoner kan skrives på denne måten: 1 #include <stdlib.h> #include <string.h> 3 #include "liste.h" 5 struct line_info *new_line(unsigned line) 6 { 7 /* Allocate and initialize a new `struct line_info' object: */ 8 9 struct line_info *lp; 10 11 lp = (struct line_info*)malloc(sizeof(struct line_info)); 1 lp->line_no = line; lp->next = (struct line_info*)0; 13 return lp; 1 } 15 16 static struct id_info *new_word(unsigned char *word, unsigned line) 17 { 18 /* Allocate and initialize a new `struct id_info' object: */ 19 0 struct id_info *p = (struct id_info*)malloc(sizeof(struct id_info)); 1 unsigned char *c = (unsigned char*) malloc(strlen(word)+1); 3
3 p->id_word = strcpy(c,word); /* Save a copy of `word'. */ p->next = (struct id_info*)0; /* Clear `next'. */ 5 p->first = p->last = new_line(line); 6 return p; 7 } 8 9 void insert_w(unsigned char *word, unsigned line) 30 { 31 struct id_info *p; 3 33 if (! id_p) { 3 /* Første ord i listen: */ 35 id_p = new_word(word,line); return; 36 } 37 38 /* Check whether `word' has occurred previously: */ 39 p = id_p; 0 while (p) { 1 if (strcmp(p->id_word,word)==0) break; /* Yes, it has. */ p = p->next; 3 } 5 if (p) { 6 /* We need only a new `struct line_info' object: 7 Insert it as the last in the list. */ 8 p->last = p->last->next = new_line(line); return; 9 } 50 51 /* We need a new `struct id_info' object: */ 5 p = new_word(word,line); 53 p->next = id_p; id_p = p; /* Insert new object first in list. */ 5 return; 55 } Parallellstyring (Ikke IN 17A) Til løsningen med semaforer trenger vi fire semaforer: én til hver av prosessene P 1, P og K siden de skal kunne blokkeres uavhengig av hverandre, og en «mutex»- semafor. Vi trenger dessuten en teller P_n som forteller hvor mange elementer det er i hver buffer slik at K kan velge riktig. 1 1 #define N 10 3 semaphore P[] = {N, N}, K = 0, Mutex = 1; int *P_buf[], 5 P_n[] = {0, 0}; 1 I denne oppgaven benyttes konstruksjonen a[x -1] diverse ganger. Dette skyldes at vektorer i C alltid har 0 som nedre grense mens vi egentlig ønsket 1.
6 7 void producer(int x) 8 { 9 int item; 10 11 P_buf[x-1] = (int*)malloc(n*sizeof(int)); 1 while (1) { 13 produce_item(&item); 1 down(&p[x-1]); down(&mutex); 15 enter_item(p_buf[x-1], item); ++P_n[x-1]; 16 up(&mutex); up(&k); 17 } 18 } 19 0 void consumer(void) 1 { int item, ix; 3 while (1) { 5 down(&k); down(&mutex); 6 ix = P_n[1-1]>0? 1 : ; 7 remove_item(p_buf[ix-1], &item); --P_n[ix-1]; 8 up(&mutex); up(&p[ix-1]); 9 consume_item(&item); 30 } 31 } Linda-løsningen er nesten en blåkopi av semaforløsningen derdown er byttet ut med in og up med out. 1 #define N 10 3 int *P_buf[]; 5 void producer(int x) 6 { 7 int item; 8 9 P_buf[x-1] = (int*)malloc(n*sizeof(int)); 10 while (1) { 11 produce_item(&item); 1 in("p", x); in("mutex"); 13 enter_item(p_buf[x-1], item); ++P_n[x-1]; 1 out("mutex"); out("k"); 15 } 16 } 17 18 void consumer(void) 19 { 0 int item, ix; 1 5
while (1) { 3 in("k"); in("mutex"); ix = P_n[1-1]>0? 1 : ; 5 remove_item(p_buf[ix-1], &item); --P_n[ix-1]; 6 out("mutex"); out("p", ix); 7 consume_item(&item); 8 } 9 } 30 31 void main(void) 3 { 33 : 3 for (ix = 1; ix <= N; ++ix) { 35 out("p", 1); out("p", ); 36 } 37 out("mutex"); 38 : 39 } Løsningen med meldinger benytter to nye prosesser buffer1 og buffer som håndterer hver sin produsentbuffer. 1 #define N 10 #define FALSE 0 3 #define TRUE 1 5 typedef enum { PUT, GET } op_code; 6 7 typedef struct { 8 op_code op; 9 int item, from; 10 } message; 11 1 void producer1(void) 13 { 1 message m; 15 16 while (1) { 17 produce_item(&m.item); 18 m.op = PUT; m.from = 1; send(buffer1, &m); 19 } 0 } 1 void buffer1(void) 3 { message m; 5 int *buf, n = 0, c_ready = TRUE; Denne løsningen er enkel og oversiktlig, men utnytter ikke parallelliteten fullt ut. Av og til vilbufferx vente på å få sende sin melding til consumer mens den i stedet kunne gitt klarsignal til producerx. For å oppnå maksimal parallellitet, måtte vi hatt enda to prosesser til å kontrollere overføringen av data til consumer. 6
6 7 buf = (int*)malloc(n*sizeof(int)); 8 while (1) { 9 if (n == 0) receive(producer1, &m); 30 if (n < N) receive(any, &m); 31 else receive(consumer, &m); 3 33 if (m.op == PUT) { 3 enter_item(buf, &m.item); ++n; 35 } else c_ready = TRUE; 36 37 if (c_ready && n>0) 38 remove_item(buf, &m.item); --n; 39 m.from = 1; send(consumer, &m); 0 } 1 } } 3 void consumer(void) 5 { 6 message m; 7 8 while (1) { 9 receive(any, &m); consume_item(&m.item); 50 m.op = GET; send(m.from==1? buffer1 : buffer, &m); 51 } 5 } (producer og buffer er ikke vist; de er helt analoge med henholdsvis producer1 og buffer1.) De styrer de hele ved å velge hvilke prosesser de vil ta imot meldinger fra. Semaforer i en SISD-maskin (altså en vanlig énprosessormaskin) vil innebære at en prosess som ikke kommer direkte inn i kritisk region vil overlate kontrollen til operativsystemet. På denne måten kan operativsystemet starte andre prosesser som kan bli ferdige i kritisk region slik at ventende prosesser kan fortsette. En MIMDmaskin med felles hukommelse (altså en maskin med flere prosessorer) vil derimot komme til å tillate aktiv venting for å slippe prosessen inn i kritisk region. Hvorfor er aktiv venting uønsket i en SISD-maskin? Hovedsakelig fordi det er bortkastet tid. En annen grunn er at prosessen som venter aktivt ikke vil komme inn i kritisk region før en annen prosess som besitter kritisk region får kjørt og frigjort den kritiske regionen. Siden det bare er én prosessor i systemet så vil den ventende prosessen ikke komme noen vei med aktiv venting. Dermed er det viktig at en prosess som ikke slipper til i kritisk region medfører prosessbytte. Hvilke forutsetninger må ligge til grunn for at aktiv venting skal fungere i en MIMD-maskin? De prosessene som benytter de samme kritiske regioner må kjøre samtidig, dvs. på hver sin prosessor. Dessuten, siden en kritisk region gjerne er kort, vil tiden tapt til aktiv venting være mindre enn kostnaden ved prosessbytte. 7