Upline: Infos & Dokus
Programmierung
C/C++
C/C++ unter Linux - Prozess-ManagementFHTW Berlin, Fachrichtung IT/Vernetzte Systeme (B/M), Betriebssysteme SS2005 bei Prof. Metzler Laborthema Systemprogrammierung Laborkomplex L1: Prozess-Management und Parallele Prozesse unter UNIX Die Sourcen wurden mit vim geschrieben und per GNU enscript 1.6.3 bunt gemacht. Der Rest entstand im Notepad - wo auch sonst ;) Aufgabenstellung (Prof. Dr. J. Schmidek • Laboranleitung Systemprogrammierung)1 ZielZiel dieses Laborkomplexes ist es, sich mit dem Prozesskonzept von UNIX aus Programmierersicht vertraut zu machen. Hierzu soll die Funktion der Systemaufrufe zum Prozess-Management anhand selbständig erstellter Programme demonstriert werden. Dabei wird der Vorlesungsstoff konkretisiert und praktisch umgesetzt.2 Vorbereitung2.1 Informieren Sie sich anhand Ihrer Vorlesungsunterlagen sowie der Linux-Manual-Pages über die exec-Systemaufruffamilie.Beachten Sie dabei besonders die verschiedenen Aufrufvarianten.
2.2 Machen Sie sich vertraut mit den Aufrufen fork(), wait(), waitpid(), getpid(), getppid() und exit() sowie mit der Bibliotheksfunktion sleep().Benutzen Sie dabei auch die Manual-Pages von Linux.
3 Durchführung3.1 Entwerfen Sie zwei Programme (flip und flop), ...die sich über einfache Bildschirmausgaben selbst identifizieren (flip – links, flop – rechts im Bild) und sich anschließend im Sekundentakt wechselseitig mit dem Code des jeweils anderen überlagern. Die Anzahl der Überlagerungen soll beim Start als Argument übergeben werden. Implementieren Sie ebenfalls eine Überprüfung der Gültigkeit des Argumentes. Ausgegeben werden sollen zur Laufzeit der jeweilige Programmname, die Prozess-ID und die Nummer des jeweiligen Durchlaufes. Probieren Sie unterschiedliche exec-Varianten aus. Die Programme sollen in der Programmiersprache C unter Linux / UNIX / Irix implementiert werden.3.2 Entwerfen Sie ein zweites Programm, dem zwei Argumente (Blockadezeiten in Sek.) übergeben werden können.Nach dem Prozessstart soll die Gültigkeit der Argumente überprüft und anschließend ein Child-Prozess erzeugt werden. Parent und Child-Prozess sollen jeder für sich (!) die eigenen PID und PPID sowie die jeweils vorgegebene Blockadezeit in folgendem Format ausgeben:Parent: PID=... PPID=... Blocked for ... s Child: PID=... PPID=... Blocked for ... s Danach sollen Parent und Child entsprechend den in den Argumenten übergebenen Zeiten blockieren und terminieren. Der Parent soll mit wait auf die Terminierung des Child-Prozesses warten und den vom Child übergebenen Exit-Status anzeigen. Lassen Sie durch Variation der Blockadezeiten den Child-Prozess einmal vor und einmal nach dem wait-Aufruf des Parent-Prozesses terminieren. Kontrollieren Sie Parent- und Child-Prozess-Status zur Laufzeit, z.B. über das Shell-Kommando "ps". Was stellen Sie in beiden Fällen bezüglich des laufenden Child-Prozessstatus fest? 3.3 Ermitteln Sie mit Hilfe eines einfachen C-Programms die tatsächliche Anzahl der auf Ihrem System pro User bzw. pro User-Prozess maximal möglichen Child-Prozesse.Ist diese konstant? Begründung?Protokoll2 VorbereitungEinen Teil der deutschen Manpages findet man u.a. auf http://www.linuxinfor.com/german/ und http://www.planetpenguin.de/manpages.html, von wo ich so weit zur Fragestellung passend teilweise direkt kopiert habe. Eine weitere sehr schöne Quelle zum Thema ist http://www.netzmafia.de/skripten/server/syscalls.html bzw. http://wwwuser.gwdg.de/~kboehm/ebook/25_kap19_w6.html, eines von beidem ist aber wohl nur eine leicht geänderte Kopie des anderen...2.1 Die exec-FunktionenDie im Rahmen des Labors betrachteten exec-Systemaufrufe (Bibliotheksfunktionen (3)) dienen als Schnittstelle zur Systemfunktion execve(2). Es gibt die Aufruffamilie der execl-Funktionen (execl, execlp, execle) und der execv-Funktionen (execv, execvp), wobei erstere die Parameter (auch: Argumente) in einer normalen Liste übernehmen und die zweite Variante mit den für C/C++ typischen Pointern auf die Parameter herumhantiert. In beiden Fällen werden als 1. Parameter der Dateiname und als letzter ein 0-String übergeben. Weiterhin müssen alle Strings, wie in C/C++ üblich, 0-terminiert sein. Die Funktion execle erhält als zusätzliche Parameter die Umgebungsvariablen des aufrufenden Programms, wärend alle anderen Funktionen die Umgebungsvariablen aus environ übernehmen. Die Funktionen execlp und execvp verfügen über zusätzliche Funktionalität in der Pfadauswertung.2.2 fork(), wait(), waitpid(), getpid(), getppid(), exit() und sleep()
3 DurchführungDie Quelltexte sind auf Grund ihrer Kürze und Übersichtlichkeit unter der Voraussetzung, daß diese Dokumentation gelesen wurde, auch ohne Kommentare weitestgehend selbsterklärend, weshalb ich nur wenige Kommentare eingefügt habe.Hinweis: Um den an verschiedenen Stellen gewünschten Effekt der Ausgabe in nur einer Zeile zu erreichen, benötigt man für meine Programme ein Terminalfenster mit mehr als 80 Zeichen Breite. Auf der Konsole ist dazu ein entsprechender Textmodus nötig, unter X genügt es, das Terminalfenster zu verbreitern. 3.1 flip und flop// flip.c #include <stdio.h> #include <unistd.h> int main(int argc, char **argv){ char befehl[]="./flop"; int z; char b2[10]; if(argc!=2){ printf("Aufruf: flip x mit x=Anzahl der Aufrufe\n"); return 1; } z=atoi(argv[1]); printf("flip | argc=%i | argv[1]=%i | ",argc,z); printf("PID %i | PPID %i \r",getpid(),getppid()); fflush(stdout); sleep(1); if(z>0){ sprintf(b2,"%i",--z); execl(befehl,befehl,b2,0); } else printf("\nausgeflipt\n"); return 0; } // flop.c #include <stdio.h> #include <unistd.h> int main(int argc, char **argv){ char befehl[]="./flip"; int z; char b2[10]; if(argc!=2){ printf("Aufruf: flip x mit x=Anzahl der Aufrufe\n"); return 1; } z=atoi(argv[1]); printf("\t\t\t\t\t\t\tflop | argc=%i | argv[1]=%i | ",argc,z); printf("PID %i | PPID %i \r",getpid(),getppid()); fflush(stdout); sleep(1); if(z>0){ sprintf(b2,"%i",--z); execl(befehl,befehl,b2,0); } else printf("\nausgeflopt\n"); return 0; }Es wird überprüft, ob die Anzahl der Parameter genau 1 ist und falls nicht, erfolgt eine Ausgabe des Programmaufrufes. Da unter Linux als 1. Parameter jedoch der Dateiname selbst mit übergeben wird, muß auf die Paramterlänge 2 getestet werden. Kann der Parameter nicht in einen Ordinaltypen umgewandelt werden, dann wird auch nicht gezählt, die Programme enden in dem Fall ohne Fehlermeldung. 3.2 Parent und Child - Prozeßstatus bei Terminierung// block.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main(int argc, char **argv){ int p; //Blockadezeit für Parent int c; //Blockadezeit für Child pid_t pid; int status; if(argc!=3){ printf("Aufruf: block x y mit x=Parent-Block-Zeit und y=Child-Block-Zeit\n"); return 1; } pid=fork(); if(pid<0){ printf("Fehler: fork()-Rueckgabe %d.\n", pid); exit(2); } if(pid==0){ //Child c=atoi(argv[2]); printf("\t\t\t\t\t\tChild: PID=%i PPID=%i Blocked for %is",getpid(),getppid(),c); fflush(stdout); sleep(c); exit(3); }else{ // Parent, pid=PID des Childs p=atoi(argv[1]); printf("Parent: PID=%i PPID=%i Blocked for %is\r",getpid(),getppid(),p); fflush(stdout); sleep(p); pid=wait(&status); printf("\nExit-Status des Child: "); if(WIFEXITED(status)!=0)printf("%d\n",WEXITSTATUS(status)); else printf("Fehler\n"); } return 0; }Zum Testen verwendete ich die Parameterreihenfolge 5 15, womit der Parent bereits vor dem Ende des Child per wait wartete und ein ps huax demzufolge nichts ungewöhnliches anzeigte. Nach dem Vertauschen der Parameter zu 15 5 konnte man für 10s einen Zombie im System finden. Hierbei wird in der Statusspalte von ps ein "Z" angezeigt und als Prozess ein "[block <defunct>]". 3.3 Child-Flood// childflood.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(void){ int z; pid_t pid; int status; printf("Bisher erzeugte Childs: \n"); for(z=0;z<10000;z++){ pid=fork(); if(pid<0){ printf("Fehler: fork()-Rueckgabe %d.\n", pid); exit(2); } if(pid==0){ //Child sleep(c); exit(3); }else{ // Parent, pid=PID des Childs printf("%i\n",z); fflush(stdout); } } return 0; }Getestet habe ich dieses Programm als root auf einem Debian 3.0 Woody testing mit Kernelversion 2.4, wobei als Hardware ein Dual-PII-266 mit 192MB RAM Verwendung fand. Etwa ab dem 1500. Child begann das System bei der Erzeugung weiterer Childs, dezent zu stottern. Bei Child #2484 konnte das Programm keine weiteren forks mehr durchführen und entsprechend erhielt ich die im Programm eingebaute Fehlermeldung mit dem Rückgabewert -1. Weitere Programme konnten bis zur Beendigung des Programms childflood auch nicht mehr gestartet werden, womit bei mir nicht nur die Obergrenze der Childs pro Userprozess sondern auch die Obergrenze für den gesamten User erreicht waren. Auch Programme des einfachen Users zeigten Merkwürdigkeiten. So wurde z.B. ein xpenguins gestartet, welches zwar normal lief, jedoch nach Beendigung die Pinguine nicht vom Bildschirm entfernte. Da ein einfaches ps huax bei den vielen Childs etwas unübersichtlich ist, mußte eine andere Möglichkeit gefunden werden, um den Parent wieder zu beenden. Hierfür verwendete ich folgende Zeile: kill -9 $(ps huax|grep childflood|cut --fields=7 --delimiter=" ")Dummer Weise funktionierte diese allerdings auch nicht immer auf Anhieb - abhängig davon, ob evtl. doch noch ein weiterer fork mgl. war oder nicht, was man mit dem Beenden eines (anderen) Prozesses erreichen kann. Hierbei kann es zu der Fehlermeldung "su: fork: Die Ressource ist zur Zeit nicht verfügbar" kommen. Sollte dies passieren, dann kann man die Named-Pipes nicht verwenden und muß erstmal ein paar Childs einzeln per Hand abschießen. Ist der Befehl dann ausführbar, entsteht eine (unwichtige) Fehlermeldung durch kill. Diese kann bei Bedarf durch ein tac vermieden werden, welches die letzte grep-Zeile abschneidet. Alternativ hätte man beim Erreichen der programmseitigen Fehlermeldung die Parent-PID ausgeben können, damit hätte dann ein einfaches kill des Parents gereicht ;) |