Løsningsforslag til eksamen i INF103 Dag Langmyhr (oppgave 2 og 5) Olav Lysne (oppgave 3 og 6) 15. desember 2001 Sigbjørn Næss (oppgave 1 og 4) 1 Boolsk algebra Sannhetsverditabellen i deloppgave a er vist i tabell 1. a 2 a 1 a 0 F 1 F 2 0 0 0 0 0 0 0 1 1 0 0 1 0 1 0 0 1 1 0 1 1 0 0 1 0 1 0 1 0 1 1 1 0 0 1 1 1 1 0 0 Tabell 1: Sannhetsverditabell for oppgave 1a Ut fra denne tabellen ser man at F 1 = a 2 a 1 a 0 + a 2 a 1a 0 + a 2a 1 a 0 og at F 2 = a 2 a 1a 0 + a 2 a 1 a 0 + a 2 a 1 a 0. Skjemaet er rett frem gitt disse to ligningene. Kall to stykker av kretsen fra oppgave 1a for henholdsvis K 1 og K 2,og utgangene fra de to kretsene K F 1 1, KF 2 1, KF 1 2 og KF 2 2. G vil være 1 når enten K F 1 1 = KF 1 2 = 1 eller K F 2 1 = 1 og a 3 = a 4 = a 5 = 0 eller K F 2 2 = 1 og a 0 = a 1 = a 2 = 0. For å implementere G trenger man dermed to stykker av kretsen fra oppgave a, seks invertere, tre AND-porter og én OR-port, som vist i figur 1 på neste side. (Denne kretsen kan lett forenkles.) 2 Oversettelse Jeg har skrevet funksjonen litt anderledes (men den fungerer likt!) for å gjøre den enklere å oversette. Dette er ingen forutsetning for å få en god karakter; en oversettelse «rett frem» vil bli bedømt som like god. 1 ;;; Rutinenavn: maxa 2 ;;; Synopsis: Finner største element i en vektor. 3 ;;; Signatur i C: short maxa (short a[], short n) 4 ;;; Inn-parametre: R0: adressen til a 5 ;;; R1: n 6 ;;; Ut-parametre: R0: den største verdien 7 ;;; 8 ;;; Teknikk: 1
a 0 a 1 a 2 K 1 F 1 F 2 G a 3 F 1 a 4 a 5 K 2 F 2 Figur 1: Løsning på oppgave 1b 9 ;;; Følgende C-kode er oversatt: 10 ;;; 11 ;;; short maxa (short *a, short n); 12 ;;; { 13 ;;; short max = *a; 14 ;;; 15 ;;; while (--n > 0) { 16 ;;; ++a; 17 ;;; if (max - *a < 0) max = *a; 18 ;;; } 19 ;;; 20 ;;; return max; 21 ;;; } 22 ;;; 23 ;;; Denne versjonen vil alltid gi samme resultat som den som er gitt 24 ;;; i oppgaven, men den egner seg bedre for oversettelse til LC2. 25 ;;; 26 ;;; Registre: R0: a 27 ;;; R1: n 28 ;;; R2: *a 29 ;;; R3: max 30 31 maxa: st R2,saveR2 ; /* Gjem unna R2 32 st R3,saveR3 ; og R3. */ 33 ldr R3,R0,#0 ; max = *a; 34 35 loop: add R1,R1,#-1 ; --n 36 brnz exit ; while ( > 0) { 37 38 add R0,R0,#1 ; ++a; 39 ldr R2,R0,#0 ; t = - *a; 40 not R2,R2 ; /* -*a beregnes som 41 add R2,R2,#1 ; (not *a)+1. */ 42 add R2,R2,R3 ; if (max + t 43 brzp loop ; < 0) { 44 ldr R3,R0,#0 ; max = *a; 45 br loop ; } } 46 2
47 exit: add R0,R3,#0 ; R0 = max; 48 ld R2,saveR2 ; /* Hent tilbake R2 49 ld R3,saveR3 ; og R3. */ 50 ret ; return R0; 51 52 saver2:.fill 0 ; Gjemmested for R2 53 saver3:.fill 0 ; og R3. 3 Socket-programmering Det viktige i deloppgave a er at studentene viser at de har forstått hva som må til for å få den skisserte kommunikasjonsløsningen til å fungere. Det dreier se altså om å få overført de riktige dataene, samt å håndtere det at enkelte deler av det de må overføre har en udefinert lengde. Mitt forslag er følgende: 1. Oppkobling 2. Overføring av to tegn som angir lengde på filnavnet. Her antas det at ingen filnavn er lengre enn 99 tegn langt. 3. Overføring av filnavnet med n tegn, hvor n er lik det antall som ble kommunisert under punkt to. 4. Overføring av 12 tegn som angir tidspunktet for når prislisten skal tas i bruk. 5. Overføring av tre tegn som angir lengden på den første linjen i prislisten. 6. Så lenge det ikke er angitt at den neste linjen inneholder 0 tegn (a) Overføring av en linje i prislisten med det antall tegn som tidligere har blitt kommunisert. (b) Overføring av tre tegn som angir lengden på den neste linjen i prislisten. 7. Nedkobling. Dette lar seg også løse ved å først angi antall tegn på hele filen (inkludert linjeskift), hvorpå det hele overføres samlet altså uten while-løkke. Funksjonene safereadbyte, saferead, safewrite og TCPlyttesocket i deloppgave b er forelest, og det holder derfor at de henviser til forelesningsnotatene for disse. Hoveddelen av denne oppgaven består derfor i programmeringen av main. 1 #include <netinet/in.h> 2 #include <sys/socket.h> 3 #include <netdb.h> 4 #include <stdio.h> 5 #include <string.h> 6 7 8 /* Leser nøyaktig ett byte fra en socketforbindelse */ 9 char safereadbyte(int so) 10 { 11 int bytes; 12 char buf[1]; 13 14 bytes = read(so, buf, 1); 15 if (bytes<0) { 3
16 perror("error in saferead"); 17 if (close(so)) perror("close"); 18 exit(1); 19 } 20 if (bytes<=0) { 21 printf("server: end of file on %d\n",so); 22 if (close(so)) perror("close"); 23 exit(1); 24 } 25 return buf[0]; 26 } 27 28 /* Leser nøyaktig l bytes fra en socketforbindelse */ 29 int saferead(int so, char buf[], int l) 30 { 31 int i; 32 for (i=0; i<l; i++){ 33 buf[i]=safereadbyte(so); 34 } 35 return l; 36 } 37 38 /* Skriver til socket på samme måte som "write", men gir i tillegg 39 en feilmelding dersom det ikke gikk bra */ 40 int safewrite(int so, char buf[], int l) 41 { 42 int i; 43 if (i=write(so, buf, l)==0) 44 { 45 printf("kunne ikke skrive til socket"); 46 exit(1); 47 } 48 return i; 49 } 50 51 /* Oppretter en TCP lyttesocket. Klienter er forventet å opprette 52 forbindelse på det portnummerer som blir sendt med som parameter */ 53 54 55 int TCPlyttesocket(int portnummer) 56 { 57 struct sockaddr_in serveraddr, clientaddr; 58 int clientaddrlen; 59 int request_sock; 60 int i; 61 62 /* Opprett request-socket */ 63 if ((request_sock = socket(af_inet, SOCK_STREAM, IPPROTO_TCP)) < 0) { 64 printf("feil under oppretting av socket\n"); 65 exit(1); 66 } 67 68 /* Opprett adressestruct */ 69 bzero((void *) &serveraddr, sizeof(serveraddr)); 70 serveraddr.sin_family = AF_INET; 71 serveraddr.sin_addr.s_addr = INADDR_ANY; 72 serveraddr.sin_port = htons(portnummer); 73 74 /* Tillat socketen å gjenbruke et portnummer som forrige inkarnasjon 75 av tjeneren fremdeles kan tenkes å legge beslag på */ 76 i = 1; 77 setsockopt(request_sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); 4
78 79 /* bind adressen til socketen */ 80 if (bind(request_sock, (struct sockaddr *)&serveraddr, sizeof serveraddr) < 0) { 81 printf("feil under bind\n"); 82 exit(1); 83 } 84 85 /* aktiver lytting på socketen */ 86 if (listen(request_sock, SOMAXCONN) < 0) { 87 printf("feil under listen\n"); 88 exit(1); 89 } 90 91 return request_sock; 92 } 93 94 95 main(int argc, char *argv[]) 96 { 97 int lyttesocket, datasocket; 98 struct sockaddr_in klientaddr; 99 int klientaddrlen, intlengde; 100 char tidspunkt[12], filnavnlengde[2],filnavn[50],lengde[3], buffer[121]; 101 102 FILE *fil; 103 104 /* Start lytting på et portnummer */ 105 lyttesocket = TCPlyttesocket(2100); 106 107 /* Ta imot oppkobling fra klienten */ 108 klientaddrlen = sizeof(klientaddr); 109 datasocket = accept(lyttesocket,(struct sockaddr *)&klientaddr, &klientaddrlen); 110 if (datasocket < 0) printf("feil under tilkobling av klienten \n"); 111 112 113 /* ta imot filnavnet */ 114 saferead(datasocket, filnavnlengde, 2); 115 saferead(datasocket, filnavn, atoi(filnavnlengde)); 116 117 /* Åpne filen */ 118 filnavn[atoi(filnavnlengde)] = \0 ; 119 fil = fopen(filnavn, "w"); 120 121 /* ta imot tidspunktet filen skal tas i bruk */ 122 saferead(datasocket, tidspunkt, 12); 123 124 /* Så lenge du ikke har mottatt en tom linje - fortsett lesingen */ 125 126 /* les inn lengden på første posten */ 127 saferead(datasocket, lengde, 3); 128 intlengde = atoi(lengde); 129 130 /* Så lenge du ikke har mottatt en tom linje - fortsett lesingen */ 131 while (intlengde!= 0) { 132 saferead(datasocket, buffer, intlengde); 133 buffer[intlengde] = \0 ; 134 fprintf(fil, "%s \n", buffer); 135 136 /* les inn lengden på neste posten */ 137 saferead(datasocket, lengde, 3); 138 intlengde = atoi(lengde); 139 } 5
140 141 /*lukk filen*/ 142 fclose(fil); 143 144 /* lukk lsocketene */ 145 close(lyttesocket); 146 close(datasocket); 147 148 /* Informer systemet om den nye prislisten */ 149 newpricelist(filnavn, tidspunkt); 150 } 4 Flervalgsoppgave Rikige svaralternativ er vist i tabell 2. a b c d e f g h 3 1 4 3 4 4 1 2 Tabell 2: Riktig svar på oppgave 4 Kommentar til 4b Om man ikke har kalkulator, kan man enten regne 2 20 =(2 10 ) 2 = 1024 1024 eller man kan ressonere: 2 10 er omtrent 1000; da er 2 20 omtrent 1 000 000. Siden svaret må være et partall, må det være alternativ 1. 5 Programmet time Det viktigste i denne oppgaven er ikke tidtagingen (hvor koden kan kopieres rett fra oppgaveteksten) men prosesshåndteringen. Siden programmet som skal utføres er oppgitt med fullt filnavn, blir koden ganske enkel: 1 #include <limits.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <time.h> 5 #include <sys/times.h> 6 #include <sys/types.h> 7 #include <unistd.h> 8 #include <sys/wait.h> 9 10 #include "safefork.c" 11 12 extern char **environ; 13 14 int main (int argc, char *argv[]) 15 { 16 int status; 17 time_t start = time(null); 18 struct tms ts; 19 20 if (argc < 1) { 21 printf("usage: time command [param...]\n"); exit(1); 6
22 } 23 24 if (safefork() == 0) { 25 /* Barnet: */ 26 execve(argv[1], &argv[1], environ); 27 perror("time"); exit(0); 28 } 29 30 wait(&status); 31 times(&ts); 32 printf(" real %4ds\n", time(null)-start); 33 printf(" user %4ds\n", (ts.tms_utime+ts.tms_cutime)/clk_tck); 34 printf(" sys %4ds\n", (ts.tms_stime+ts.tms_cstime)/clk_tck); 35 return 0; 36 } 6 Operativsystemer På denne oppgaven er det svært mange svar som vil være fornuftige. Det viktige her er derfor ikke om de har «gjettet riktig», men at den begrunnelsen de gir for sine valg demonstrerer at de har forstått problemområdet. 6a Her finnes det prosesser med harde sanntidskrav. De må slippe til CPU-en med en viss fast frekvens. Det gjør at vi må ha en preemptiv skeduleringsmeknisme. Når vi ser bort fra nedlasting til disk og brukergrensesnittet, er de kravene som stilles av de andre prosesstypene ganske like. Vi foreslår derfor en enkelt round-robin skedulering mellom disse prosessene. For å tilfredsstille de kravene prosessene har om prosentvis andel av CPU-ens tid, må antallet prosesser begrenses. Dette overlates til høynivåskeduleringen. For brukergrensesnittets del foreslår vi her at den inngår i round robinskeduleringen på lik linje med de andre. På den måten blir det enklere å sikre at brukergrensesnittet ikke konsumerer så mye CPU-tid at den ødelegger for en filmavspilling for eksempel. De prosesene som kun driver med nedlasting av data kan (men må ikke) skeduleres for seg med lavest prioritet. Man må i tilfelle sørge for at de ikke opplever «sulting». Det kan for eksempel sikres ved at høynivåskeduleringen ikke slipper til så mange sanntidsprosesser at det ikke blir noe CPU-tid igjen. 6b Ofte er det bare tilgjengelighet av tilstrekkelig internminne som vurderres under høynivåskedulering. I dette tilfellet er det en del sanntidsprosesser som krever tilgang til en viss prosentandel av CPU-ens tid. Derfor er det naturlig her å vurdere CPU-tid ved høynivåskeduleringen, slik at det ikke slippes til flere prosesser enn det faktisk er regnekraft til. 6c Filene som skal håndteres her skal skrives og leses kun sekvensielt. Siden de også er av fast størrelse (en film og et musikkstykke endrer ikke størrelse) peker sekvensiell allokering seg ut. Det gir imidlertid to problemer: 7
Fragmentering. Det kan lett oppstå en situasjon hvor den ledige plassen på disk er delt opp i så mange små stykker at det ikke kan finnes en sekvensiell enhet som er stor nok Under opptak og innlesing av en film/et stykke kan man ofte på forhånd ikke avgjøre hvor stor den er. Følgelig er det vanskelig å vite hvor stort sekvensielt lagringsområde en trenger. Disse problemene kan lettest løses ved periodisk defragmentering og å tillate at en fil kan bestå av flere sammenhengende sekvenser (for eksempel ved pekere, eller indekser). 6d Ettersom vi over har valgt sekvensiell allokering, blir det viktig å finne en metode for håndtering av ledig diskplass som muliggjør enkel gjenfinning av lange sekvenser av ledige blokker. Et «bitmap» med en bit per blokk som angir om den er ledig eller ikke er derfor å foretrekke fremfor Kø/pekerkjede. Ulempen ved dette valget er at vi ikke lenger så lett kan kontrollere at den sist slettede filen ikke er den første som blir overskrevet. Det er likevel rimelig å ofre «angrefrist» på sletting av filer for å effektivisere disklesingen her. 8