Beispielprogramme:
Inhalt:
Die hier aufgeführten Programm-Beispiele gehören zur Vorlesung "Rechnerorganisation", die ich von 2000 bis 2007 an der Fachrichtung Nachrichtentechnik der Berufsakademie Stuttgart (jetzt: Duale Hochschule Baden-Württemberg, DHBW) gehalten habe. Sie zeigen das Verhalten und die Anwendung einiger Systemfunktionen.
Die Beispiele wurden unter Linux erstellt, sollten aber auf allen POSIX-konformen Systemen übersetzt und ausgeführt werden können. Für das Programm gui_bsp sind außerdem Header- und Bibliotheks-Dateien für X11 und OSF/Motif notwendig.
Die Quelldateien aller Beispiele und das Makefile sind zusammen als zip-Datei (ca. 50 KB) verfügbar.
Übersetzen aller Programme: make
Alignment: Ausrichtung von Variablen im Speicher
Variante 1: Wirkung von fork()
Datei: | align_bsp.c Makefile (gemeinsam für alle Beispiele) |
Übersetzen: | make align_bsp |
Aufruf: | ./align_bsp |
Das Programm align_bsp zeigt, wie Variablen im Arbeitsspeicher ausgerichtet sind:
Das Programm verwendet dazu eine Datenstruktur mit Elementen unterschiedlicher Datentypen. In mehreren Schritten zeigt das Programm zunächst die Größe der einzelnen Elemente der Datenstruktur, die Gesamtgröße der Datenstruktur und die Anfangs-Adressen der Elemente an.
Die Ausrichtung der Elemente der Datenstruktur - und damit die Ausgabe des Programms - kann je nach CPU-Familie und / oder Betriebssystem unterschiedlich sein.
zum Seitenanfang ↑
Kind-Prozess starten
Variante 1: Wirkung von fork()
Systemfunktion: | fork(), wait()
|
Dateien: | fork_simple.c Makefile (gemeinsam für alle Beispiele) |
Übersetzen: | make fork_simple |
Aufruf: | ./fork_simple |
Das Programm fork_simple zeigt das Erzeugen eines
Kind-Prozesses mit fork()
:
Durch den Aufruf der Funktion fork()
wird ein neuer
Prozess erzeugt. Dieser Kind-Prozess ist immer eine 1:1-Kopie des
Eltern-Prozesses zum Zeitpunkt des Aufrufs der Funktion
fork()
. Nach dem Rücksprung aus der Funktion
fork()
laufen Eltern- und Kind-Prozess voneinander unabhängig
weiter.
In diesem Beispiel führen Eltern- und Kind-Prozess nach der
Funktion fork()
den gleichen Programmcode aus, d. h. alle
Befehle nach fork()
werden je einmal vom Eltern- und vom
Kind-Prozess ausgeführt. Die Ausgabe "... Ergebnis von
fork() ..." erscheint deshalb zweimal, jedoch mit
unterschiedlichen Werten für die Prozess-ID und für das Ergebnis von
fork()
.
In der Praxis sollen Eltern- und Kind-Prozess jedoch unterschiedliche Aufgaben ausführen, wie z. B. in der folgenden Variante 2.
zum Seitenanfang ↑
Variante 2: Ausführen unterschiedlicher Programmteile in Eltern- und Kind-Prozess
Systemfunktionen: | fork(), wait()
|
Dateien: | childproc.c Makefile (gemeinsam für alle Beispiele) |
Übersetzen: | make childproc |
Aufruf: | ./childproc |
Das Programm childproc zeigt ebenfalls das Erzeugen eines
Kind-Prozesses mit fork()
. In diesem Beispiel führen Eltern-
und Kind-Prozess jedoch zum Teil unterschiedliche Befehle aus. Außerdem
wird in diesem Beispiel der Inhalt von Variablen beobachtet um zu zeigen,
dass beim Aufruf von fork()
alle Variablen und deren Inhalte
vom Eltern-Prozess in den Kind-Prozess kopiert werden, die Variablen der
beiden Prozesse aber danach voneinander unabhängig sind.
zum Seitenanfang ↑
Programm in separatem Prozess starten
Systemfunktionen: | fork(), execl()
|
Dateien: | startprog.c progbsp.c printargs.h printargs.c Makefile (gemeinsam für alle Beispiele) |
Übersetzen: | make startprog progbsp |
Aufruf: | ./startprog mit beliebigen Argumenten |
Dieses Beispiel zeigt das Ausführen eines anderen Programms in einem separaten Prozess:
Das Programm startprog spaltet zunächst mit fork()
einen Kind-Prozess ab. Danach wird in dem Kind-Prozess durch Aufrufen von
execl()
das Programm progbsp ausgeführt.
Die Argumente, mit denen startprog aufgerufen wird, werden vom Programm zu Demonstrationszwecken angezeigt, haben aber keinen Einfluss auf den Programmablauf.
zum Seitenanfang ↑
Exit-Status vom Kind- zum Eltern-Prozess übergeben
Systemfunktionen: | fork(), exit(), wait(), WIFEXITED(), WEXITSTATUS()
|
Dateien: | exitstatus.c Makefile (gemeinsam für alle Beispiele) |
Übersetzen: | make exitstatus |
Aufruf: | ./exitstatus mit mehreren numerischen
Argumenten,
0 <= Argument <= 255
Beispiel: ./exitstatus 5 0 2 1 4 |
Dieses Beispiel zeigt die Übergabe des Exit-Status vom Kind- an den Eltern-Prozess und dessen Auswertung im Eltern-Prozess:
Der Eltern-Prozess startet mit fork()
für jedes in der
Befehlszeile übergebene numerische Argument einen Kind-Prozess, der sich
nach einer zufälligen individuellen Wartezeit mit exit()
beendet. Dabei wird der als Argument übergebenen Exit-Status übergeben.
Nachdem der Eltern-Prozess alle Kind-Prozesse erzeugt hat, wartet er auf
das Ende der Kind-Prozesse, fragt mit wait()
deren Exit-Status
ab und zeigt ihn mit der Prozess-ID des betreffenden Kind-Prozess-ID an.
zum Seitenanfang ↑
Funktion in separatem Thread ausführen
Systemfunktionen: | pthread_create(), pthread_join()
|
Dateien: | pthread.c Makefile (gemeinsam für alle Beispiele) |
Übersetzen: | make pthread |
Aufruf: | ./pthread |
Das Programm pthread startet mit pthread_create()
eine Funktion in einem separaten Thread. Jeder Thread schreibt Adresse und
Inhalt einiger Variablen auf den Bildschirm. Diese Informationen zeigen,
dass
- die als Thread gestartete Funktion und die von dort aus aufgerufenen Funktionen einen separaten Register-Satz und einen eigenen Stack verwenden, aber
- beide Threads gemeinsam auf die globalen Variablen und den Stack bis zu der Funktion, die den Thread gestartet hat, zugreifen.
zum Seitenanfang ↑
Graphische Bedienoberfläche mit X Window und OSF/Motif
Dateien: | gui_bsp.c Makefile (gemeinsam für alle Beispiele) |
Übersetzen: | make gui_bsp Voraussetzung: System mit X11 und OSF/Motif-Bibliotheken. Bei Linux-Distributionen finden Sie die OSF/Motif-Header und -Bibliotheken in Paketen wie z. B. openmotif-devel Die Pfade der Header- und Bibliotheks-Dateien müssen im Makefile nach Bedarf angepasst werden. |
Aufruf: | ./gui_bsp |
Der Quellcode gui_bsp.c des Programms zeigt die typische Programmstruktur eines Programms mit graphischer Bedienoberfläche mit der daraus resultierenden Ereignis-orientierten Steuerung des Programmablaufs: Das Betätigen von Buttons veranlasst die Ereignis-Behandlung, die Callback-Funktion aufzurufen, die für den betreffenden Button registriert ist.
Das Programm erzeugt ein Fenster mit einer Text-Anzeige und Buttons. Über die Buttons können wahlweise weitere Fenster erzeugt, oder das Programm beendet werden.
zum Seitenanfang ↑
Signal-Behandlung entsprechend POSIX.1 = IEEE 1003.1-1990
Systemfunktionen: | sigaction(), sigemptyset()
|
Dateien: | sig_posix.c Makefile (gemeinsam für alle Beispiele) |
Übersetzen: | make sig_posix |
Aufruf: | ./sig_posix |
Das Programm sig_posix zeigt die Signal-Behandlung des Signals SIGTERM durch eine eigene Signalbehandlungs-Funktion:
Nach dem Aktivieren der neuen Signal-Behandlung für SIGTERM schreibt das Programm seine eigene Prozess-ID auf den Bildschirm. Wenn der Prozess das Signal SIGTERM erhält, wird der Prozess nicht beendet, sondern die Anzahl der empfangenen Signale hochgezählt. Das Programm zeigt den Zählerstand periodisch auf dem Bildschirm an und endet, wenn ein im Programm vorgegebener Zählerstand erreicht ist.
Unter UNIX, Windows-NT, Windows-XP usw. kann der Befehl
kill Prozess-ID
verwendet werden, um ein SIGTERM an den Prozess mit der betreffenden Prozess-ID zu senden.
zum Seitenanfang ↑
Datentransfer über eine Pipe vom Kind- zum Eltern-Prozess
Systemfunktionen: | pipe(), fork()
|
Dateien: | pipe.c Makefile (gemeinsam für alle Beispiele) |
Übersetzen: | make pipe |
Aufruf: | ./pipe |
Das Programm pipe zeigt ein Beispiel für den Datentransfer vom Kind- zum Eltern-Prozess über eine Pipe.
Dazu wird zunächst mit pipe()
eine Pipe angelegt.
Anschließend wird mit fork()
ein Kind-Prozess erzeugt, der
damit auch die Lese- und Schreib-Deskriptoren der Pipe erbt. Nachdem
Eltern- und Kind-Prozess jeweils den von ihnen nicht benötigten Deskriptor
geschlossen haben, sendet der Kind-Prozess Daten an den Eltern-Prozess. Der
Eltern-Prozess zeigt die empfangenen Daten auf dem Bildschirm an.
zum Seitenanfang ↑
Interprozess-Kommunikation über ein Semaphor
Systemfunktionen: | semget(), semctl(), semop()
|
Dateien: | sem_demo.c Makefile (gemeinsam für alle Beispiele) |
Übersetzen: | make sem_demo |
Aufruf: | ./sem_demo Semaphor-Key |
Das Programm sem_demo zeigt die Kommunikation zwischen Prozessen über ein Semaphor: Nach dem Start wird ein Semaphor zu dem als Programm-Argument angegebenen Semaphor-Key angelegt und initialisiert, sofern das Semaphor noch nicht existiert. Danach wartet das Programm auf Befehle, mit denen der Semaphor-Zustand verändert oder abgefragt werden kann.
Damit das Verhalten der Semaphor-Funktionen des Betriebssystems direkt gezeigt wird, führt das Programm die eingegebenen Befehle ohne Plausibilitätskontrolle aus!
Um die Kommunikation zwischen verschiedenen Prozessen auszuprobieren, muss das Programm gleichzeitig in mehreren Prozessen laufen. Starten und betreiben Sie das Programm dazu parallel in mehreren Kommandozeilen-Fenstern und verwenden Sie dabei jeweils die selbe positive ganze Zahl als Argument für den Semaphor-Key, z. B. 23 (willkürlich gewählt). Der verwendete Zahlenwert ist dabei innerhalb erlaubten des Wertebereichs beliebig.
Sie können natürlich auch parallel dazu weitere Prozesse mit anderen Semaphoren betreiben, z. B.
- 4 (oder n) Prozesse mit Semaphor-Key 23 und
- 3 (oder m) Prozesse mit Semaphor-Key 29
Die genannte Anzahl der Prozesse und die Werte für die Semaphor-Keys wurden ebenfalls willkürlich gewählt.
zum Seitenanfang ↑
Datentransfer über Shared Memory
Systemfunktionen: | shmget(), shmat(), shmdt(), shmctl()
|
Dateien: | shm_send.c shm_recv.c shm_common.h shm_common.c Makefile (gemeinsam für alle Beispiele) |
Übersetzen: | make shm_demo |
Aufrufe: | ./shm_send SHM-Key
./shm_recv SHM-Key |
Die Programme shm_send und shm_recv zeigen ein Beispiel für den Datentransfer zwischen Prozessen mit Hilfe eines Shared Memory:
shm_send legt für den übergebenen Shared-Memory-Key zunächst ein Shared Memory an, sofern es noch nicht existiert. Danach verbindet sich der Prozess mit dem Shared Memory und schreibt seine eigene Prozess-ID sowie einen Text mit Datum und Uhrzeit in das Shared Memory.
Bei jeder Betätigung der Enter-Taste wird eine neue Meldung mit der eigenen Prozess-ID und aktuellem Datum und Uhrzeit in das Shared Memory geschrieben. Die geschriebene Meldung wird jeweils auf dem Bildschirm angezeigt.
shm_recv verbindet sich ebenfalls mit dem Shared Memory, das zum angegebenen Shared-Memory-Key gehört. Danach liest das Programm den aktuellen Inhalt des Shared Memory gelesen und angezeigt.
Bei jeder Betätigung der Enter-Taste wird der aktuelle Inhalt des Shared Memory erneut gelesen und angezeigt.
shm_send und shm_recv sollen in unterschiedlichen Kommandozeilen-Fenstern gestartet werden. Die Programme können auch mehrfach parallel in verschiedenen Fenstern laufen.
Alle Programme, die den selben Shared-Memory-Key verwenden, arbeiten mit dem selben Shared Memory. Der Shared Memory Key ist - analog zum Semaphor-Key - eine positive ganze Zahl, die innerhalb des erlaubten Wertebereichs frei wählbar ist.
zum Seitenanfang ↑