Navigationskarta Insitutitionen för Datavetenskap Umeå Universitet

Lösningsförslag till tentamen 98-10-27

Gunnar Gunnarsson, 98-10-27

Uppgift 1

shellskript
En fil som innehåller kommandon avsedda att tolkas av ett shell. Shellen erbjuder också programmeringsmöjligheter, exempelvis val och slingor.
systemanropsfunktion
En C-funktion som gör ett systemanrop.
demonprocess
En process som ligger i bakgrunden (dvs den saknar kontrollerande terminal) och utför systemuppgifter, exempelvis att överföra e-post eller webbsidor.
zombie
En barnprocess som har dött, men som föräldern ännu inte väntat på med wait() eller waitpid().
set-user-ID-program
En körbar fil med den egenskapen att när den startas kommer processen att köras som den användare som äger programfilen. (Normalt körs processen som den användare som startar den.)
kernel
Kärnan, den centrala delen av Unix. Hanterar processer, minne och besvarar systemanrop.
race condition
När resultatet av en operation beror på vilken av ett antal asynkrona händelser som råkar inträffa först.
core dump
En kopia av minnet hos en process. Uppstår som regel på grund av ett allvarligt fel i programmet, exempelvis att en process skriver till minne som den inte äger. Kan inom parentes sagt skapas utan att något fel har uppstått, se gcore (1).

Uppgift 2

a) int *A[5];

b) int (*P)[5];

c) En array med fem element av typen pekare till pekare till int.

d) En pekare till en funktion som returnerar en pekare till int. Funktionens argument är en pekare till int och en pekare till pekare till char.

Uppgift 3

a) execvp() öppnar interpreter-filen och upptäcker att den inte går att köra som en binärfil, eftersom den är en interpreter-fil i textform. Därför startas istället interpreter-programmet av execvp(). Kommandoraden (argv[]) för interpreter-programmet är i princip:

Interpreter-programmet öppnar sedan interpreter-filen och behandlar de data som finns i den. På något sätt måste den första raden hanteras (den som börjar med #!). Detta löser vanligen sig självt, genom att man definierar alla rader som börjar med # som kommentarer. Man kan också tänka sig att helt enkelt kasta bort den första raden i infilen om den börjar med #!, men det är inte lika elegant, och under vissa omständigheter kan man råka kasta bort en datarad.

b) Vitsen är helt enkelt att slippa skriva både namnet på datafilen och på det program som ska tolka den. Istället för "perl perl-skript" kan man helt enkelt skriva "perl-skript", tack vare denna finess. Det går att lösa utan interpreter-filer också, men blir inte lika effektivt. Det finns dessutom en historisk anledning: när C shell introducerades behövde det kunna köra både sina egna skriptfiler och gamla Bourne shell-skript.

Uppgift 4

a) För det första har man glömt bort att resultatsträngen ska innehålla en avslutande nolla. Detta kan korrigeras med en tilldelningssats efter for-slingan (ns[i]='\0'). Man måste förstås också allokera utrymme för nollan genom att lägga till ett till malloc()-anropets argument (ns = malloc(strlen(s)+1)).

För det andra tilldelas ns värdet NULL i if-satsen, när man egentligen vill testa för likhet. Man använde alltså = istället för ==.

b) Sista meningen i uppgiften är viktig ("glöm inte att motivera dina teorier"). Båda felen kommer sannolikt att resultera i minnesfel (SIGSEGV+coredump).

Det första felet kommer att leda till att nästa funktion som läser strängen kommer att fortsätta förbi slutet på strängen (eftersom det inte finns någon nolla). Om man har tur slumpar det sig så att det ligger en nolla där ändå, men förr eller senare kommer detta att ge minnesfel.

Det andra felet kommer att leda till minnesfel omedelbart. Pekaren ns sätts till NULL, och resultatet av if-testet blir falskt. Därmed fortsätter exekveringen till for-slingan, som skriver till ns[0], dvs till adressen NULL.

c) För att det ska bli lättare att förstå vad felet beror på om något går fel. Om man inte testar och det blir fel kommer felet att visa sig någon annanstans i koden, och det blir mycket svårare att lista ut vad som egentligen är problemet (gäller särskilt malloc()).

d) För att man ska kunna skilja på felmeddelanden och data, särskilt om programmet körs i en pipa. Ofta vill man skicka felmeddelanden till ett ställe och data till ett annat ställe (typfallet är felmeddelanden till terminalen och data till en fil).

Uppgift 5

a) Kod som körs i kernel mode har större rättigheter (exempelvis att skriva till minne som ägs av andra processer).

b) För att man inte vill att alla processer ska kunna skriva till minne som ägs av andra processer, bland annat. En bugg i ett subsystem som inte körs i kernel mode kommer därmed inte att kunna sänka hela maskinen.

Uppgift 6

a) Här finns det mycket att välja på. Några exempel är att processen skriver till en pipa utan läsare (SIGPIPE), skriver till förbjudet minne (SIGSEGV) och att processägaren använder kill-kommandot i shellet (vilken signal som helst kan skickas på det sättet).

b) Processen kan ignorera signalen, installera en hanterare som fångar upp den eller (fult) blockera den hela tiden programmet körs.

Uppgift 7

Den här uppgiften är inte så svår som det kanske verkar. Flera personer frågade om man måste hantera # för kommentarer. Det kan man göra, men det är inte helt nödvändigt. Det går nämligen också att kasta bort den första raden, även om den förstnämnda lösningen är att föredra.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define MAXLINELEN 1024

int main(int argc, char **argv)
{
	int rownum=0;
	char line[MAXLINELEN];
	pid_t pid;
	FILE *fp;

	if (argc != 2) {
		fprintf(stderr, "Syntax error!\n");
		exit(1);
	}

	if ((fp = fopen(argv[1], "r")) == NULL) {
		fprintf(stderr, "Could not open ");
		perror(argv[1]);
		exit(2);
	}

	while (!feof(fp) && (fgets(line, MAXLINELEN, fp) != NULL)) {
		rownum++;
		if (line[0] == '#') {
			continue;
		}
		if (! strncmp("print ", line, 6)) {
			printf("%s", line+6);
		} else if (! strncmp("run ", line, 4)) {
			if ((pid = fork()) == (pid_t)-1) {
				perror("Could not fork()");
				exit(3);
			} else if (pid == 0) {
				if (line[strlen(line)-1] == '\n') {
					line[strlen(line)-1] = '\0';
				}
				execlp(line+4, line+4, NULL);
				fprintf(stderr, "Could not execute ");
				perror(line+4);
				exit(4);
			} else {
				if (waitpid(pid, 0, 0) == -1) {
					perror("wait() error");
				}
			}
		} else {
			fprintf(stderr, "Syntax error on line %d. Exiting.\n", \
					rownum);
			exit(5);
		}

	}
	fclose(fp);
	return 0;
}

Uppgift 8

Funktionen behöver två pipor: en från sig själv till ispell och en i motsatt riktning. Den måste dessutom duplicera ispell:s pipändar på stdout och stdin. Glöm för all del inte att stänga pipändar som inte behövs, åtminstone skrivändar. När väl piporna är korrekt uppsatta skickar föräldraprocessen data till ispell och räknar antalet rader (radslutstecken) i resultatet.

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

void exit_perr(const char *s)
{
    perror(s);
    exit(1);
}

int cnt_err(const char *s)
{
    int p1[2], p2[2], pid, n, cnt, i;
    char buf[512];

    if ( (pipe(p1) == -1) || (pipe(p2) == -1) ) 
	exit_perr("pipe");
    if ( (pid = fork()) == -1) 
	exit_perr("fork");

    if (pid == 0) { /* Child */
	close(p1[0]); 
	close(p2[1]);
	if (dup2(p1[1], STDOUT_FILENO) == -1) 
	    exit_perr("dup2"); /* stdout */
	if (dup2(p2[0], STDIN_FILENO) == -1) 
	    exit_perr("dup2"); /* stdin */
        close(p1[1]);
        close(p2[0]);
	execlp("ispell", "ispell", "-l", (char *) 0);
	perror("execlp");
	exit(1);
    }
    else { /* Parent */
	close(p1[1]); 
	close(p2[0]);
	write(p2[1], s, strlen(s));
	close(p2[1]);

	cnt = 0;
	while ( (n = read(p1[0], buf, 512)) > 0)
	    for (i = 0; i < n; i++)
		if (buf[i] == '\n') 
		    cnt++;
	if (n != 0) 
	    exit_perr("read");
	close(p1[0]);
    }
    return cnt;
}

Uppgift 9

a) Uttrycket *(p+3) är detsamma som p[3], vilket i sin tur är detsamma som a[3] eftersom a==p. Alltså ändras a[3] till 'E', och det nya innehållet är ABCE\0.

b) Arrayen b[] kommer inte att innehålla någon avslutande nolla.

c)
Efter strncpy(d+1, &a[1][1], 7):
Tlirp%slp

Efter *(d+1) = *p[3]:
TGirp%slp

Efter strcpy(&d[7], s):
TGirp%sf

Efter s = strcat(d, p[2]):
TGirp%sfatei!!!!

Efter *(d+5) = d[8]:
TGirpasfatei!!!!

Efter d[8] = a[2][2]:
TGirpasfetei!!!!

Efter d[7] = d[10] = p[0][6]:
TGirpastetti!!!!

Efter d[13] = *(s + 6):
TGirpastetti!s!!

Slutlig utskrift:
Grattis!

d) Utskriften ska bli "Grattis!". (Om du räknade fel i for-slingan och fick "Tipset!!" hoppas vi att du läste tipset en gång till.)

[an error occurred while processing this directive]