BUGH Wuppertal Fachbereich 7 - Mathematik Axel Rogat
Betriebssysteme und betriebssystemnahes Programmieren
Übungsblatt 6

Aufgabe 13
Bauen Sie die letzte Version der in der Vorlesung entwickelten Shell um folgende Details aus (das Original finden Sie unten):

  1. Implementieren Sie ein eingebautes Kommando bgp (ohne Argumente), das die PIDs der von der Shell aus gestarteten Hintergrundprozesse ausgibt. Die Nummern können Sie einfach aus dem Vektor children lesen (orientieren Sie sich dabei an der Schleife am Schluß der vorgegebenen Shell).

  2. Implementieren Sie cd als eingebautes Kommando. (Als externes Kommando würde es keinen Sinn machen, warum?)

    Ein Argument (der Directory-Name) ist notwendig, überzählige sollen ignoriert werden. Geben Sie nach dem Wechseln den absoluten Pfad des neuen Verzeichnisses aus.

  3. Ermöglichen Sie das Zusammenfassen mehrerer physischer Zeilen zu einer logischen durch einen Backslash am Ende der ersten Zeile(n), nach dem Schema vom Skript:
    sh_a13: echo gib das aus \ > und das \ > und das auch noch!
    Als Prompt für die Fortsetzungszeilen zeigen Sie ein "> " an!

    Beachten Sie, daß Sie die Zeilen (vermutlich) immer wieder in denselben Puffer lesen. strtok-Aufrufe überschreiben daher die Argumente aus der vorhergehenden Zeile! Erzeugen Sie mit strdup Kopien von jedem Argument. Vergessen Sie nicht, nach dem Starten des Kommandos diese Strings mit free wieder freizugeben.

  4. Ermöglichen Sie das Expandieren der alleinstehenden (!) Wildcard `*'. Wenn also "*" als Wort (!) in der Aufrufzeile vorkommt, erzeugen Sie für jedes Objekt im aktuellen Verzeichnis (dessen Name nicht mit `.' beginnt) ein Aufrufargument (dabei nicht das strdup vergessen)!

    Damit sollten also Aufrufe wie "echo *" oder "grep include * > out" möglich sein!

  5. Lassen Sie die Shell nicht mehr erst nach jeder Eingabe auf inzwischen verstorbene Kinder warten. Implementieren Sie stattdessen einen Handler, der auf das Signal SIGCHLD reagiert, und der daher Kinder sofort verabschieden kann.

    Beachten Sie, daß ein SIGCHLD nicht nur beim Tod eines Kinds ausgelöst wird, sondern auch, wenn es nur durch SIGSTOP angehalten wird.

    Wenn ein Hintergrundprozeß beendet ist, geben Sie in diesem Handler seine PID und seinen Rückgabewert aus:

    background process 557 just died (exit code 0)
    Falls irgendein Kind (auch ein Vordergrundprozeß) durch ein Signal stirbt (etwa durch ein CTRL-C in der Shell), geben Sie die Nummer des Signals aus, z.B.:
    sh_a13: xv <CTRL-C> (killed by signal 2) sh_a13: xv & sh_a13: bgp 559 sh_a13: kill 559 background process 559 just died (killed by signal 15) sh_a13:
    Sie müssen im Handler außerdem die PID eines gestorbenen Hintergrundprozesses aus dem Vektor children löschen (mit children.erase(*it), wenn der Iterator it auf die PID des Kinds zeigt).

Aufgabe 14
Simulieren Sie in C (C++) einen einfachen Dispatcher und Round-Robin-Scheduler, indem Sie die Signal-Mechanismen von UNIX zugrundelegen.

Ihr Programm soll beliebig viele Prozesse starten können, von denen immer nur einer zur selben Zeit aktiv sein können soll. Die simulierte CPU-Kontrolle soll in Round-Robin-Manier reihum wechseln. Arbeiten Sie mit einer großen Zeitscheibe von beispielsweise einer Sekunde, damit man die Umschalt-Effekte gut nachverfolgen kann.

Starten Sie gleich zu Beginn drei neue Prozesse. Danach zeigen Sie (ähnlich wie bei Aufgabe 10) ein Menü an. Die Eingabe von `+' soll einen neuen Prozeß hinzunehmen, `-' den gerade laufenden Prozeß entfernen, `q' den Scheduler abbrechen.

Hinweise:

  • Als Programme für die einzelnen Prozesse eignen sich gut Fensterprogramme, die kontinuierlich eine wechselnde Grafik anzeigen. Sie können z.B. xcirc verwenden, das weiter unten als Quelltext zur Verfügung gestellt wird. Geben Sie das für jeden Prozeß zu startende Programm fest in Ihrem Code vor oder übergeben Sie es durch die Kommandozeile.

  • Die "CPU-Kontrolle" handhaben Sie einfach durch die Signale SIGSTOP (Anhalten eines Prozesses) und SIGCONT (Weiterlaufen). Die Zeitscheiben-Steuerung erfolgt (analog zu Aufgabe 11) mit den Intervall-Timern und einem SIGALRM-Handler.

  • Sie müssen einen erzeugten Kind-Prozeß direkt nach dem fork mit einem SIGSTOP anhalten. Um sicherzustellen, daß das Kind nicht vor dem Elter schon weiterläuft, lassen Sie es zunächst auf ein SIGUSR1 warten. Schicken Sie im Elter-Prozeß aber zunächst das SIGSTOP, dann das SIGUSR1!

  • Merken Sie sich die PIDs der Kinder (wie bei der Shell) in einem Vektor. Vorsicht: Da dynamisch neue Prozesse erzeugt und vernichtet werden, können Sie nicht davon ausgehen, daß von einem Context-Switch zum nächsten die PID des laufenden Prozesses noch an derselben Stelle im Vektor steht. Merken Sie sich also nicht den Index des laufenden Prozesses, sondern seine PID!

  • Sie müssen außerdem (wie in Aufgabe 13e) einen SIGCHLD-Handler implementieren, der gestorbene Kinder aus dem Task-Switching herausnimmt.

    Die "CPU" soll nie "idle" sein, d.h. wenn der gerade aktive Prozeß gekillt werden sollte, soll der Rest der Zeitscheibe vom nächsten Prozeß gefüllt werden.

email: axel@math.uni-wuppertal.de Zurück Abgabe: 11.6.1999
 

Anhang: Quellcode der Shell aus der Vorlesung sh_vorl.cpp
#include <iostream.h> #include <vector.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> #include <sys/wait.h>

vector<pid_t> children; bool signaled; struct sigaction sa,oldsa;

void sigusr1_handler(int) { signaled=true; }

int main() { sa.sa_flags=SA_RESTART; sa.sa_handler=SIG_IGN; sigaction(SIGINT,&sa,&oldsa); sa.sa_handler=sigusr1_handler; sigaction(SIGUSR1,&sa,&oldsa);

for (;;) { char buffer[256]; cout << "sh9: "; cin.getline(buffer,256);

if (cin && *buffer!=0) { char *command=strtok(buffer," "); if (strcmp(command,"exit")==0) break;

bool bg=false; vector<char *> arg_vec; char *s, *redir[2]; redir[0]=redir[1]=0; while ((s=strtok(0," "))!=0) { if (s[0]=='&') { bg=true; break; } if (s[0]=='<' || s[0]=='>') { int redir_num=(int)(s[0]=='>'); if (s[1]==0) { if ((s=strtok(0," "))==0) { cerr<<"illegal redir\n"; break; } } else ++s; if (redir[redir_num]!=0) cerr << "multiple redir\n"; else redir[redir_num]=s; } else arg_vec.push_back(s); }

int argc=1+arg_vec.size(); char **argv=new(char *)[argc+1]; argv[0]=command; copy(arg_vec.begin(),arg_vec.end(),argv+1); argv[argc]=0;

signaled=false; pid_t pid=fork(); if (pid==0) { static int flags[2]={ O_RDONLY, O_CREAT|O_TRUNC|O_WRONLY }; if (bg && redir[0]==0) redir[0]="/dev/null";

for (int i=0;i<=1;++i) if (redir[i]!=0) { int fd=open(redir[i],flags[i],0644); if ( fd<0 || dup2(fd,i)<0 ) cerr << "redir error\n"; close(fd); }

if (bg) { setpgid(0,0); kill(getppid(),SIGUSR1); } else signal(SIGINT,SIG_DFL);

execvp(command,argv); cerr << "command not found!\n"; _exit(0); } else if (!bg) waitpid(pid,0,0); else { if (!signaled) pause(); children.push_back(pid); } while (waitpid(-1,0,WNOHANG)>0); delete[] argv; } }

vector<pid_t>::iterator it; for ( it=children.begin() ; it!=children.end() ; ++it ) kill(*it,SIGTERM); }

 
Anhang 2: Quellcode zu xcirc xcirc.cpp swindow.cpp swindow.h
 

© 1999 Axel Rogat