<-- Studium
Netzwerke und Protokolle
Hallo Welt mit write (anstatt printf):
int main ()
{
// Hallo und newline wird in das Character Array h geschrieben
char h[] = "Hallo\n";
// Eine Variable um den Rueckgabewert der write Funktion aufzunehmen wird deklariert
int retval;
// Aufruf der write Funktion:
// 1 ist file descriptor fuer die Konsole (0=input, 1=output, 2=error)
// h ist was ge-write-ed wird, unsere Variable in der Hallo steht
// 6 ist die Anzahl der Zeichen die geschrieben werden (5 fuer Hallo, 1 fuer newline)
retval = write(1, h, 6);
}
 
"Normales" printf Hallo Welt (mit korrekter Ueber und Rueckgabe):
#include < stdio.h >
int main (int argc, char *argv[])
{
printf("Hallo\n");
return(0);
}
Ein einfaches Kopierprogramm mit read und write:
/*
*
* Beispielprogramm fuer Dateideskriptoren
*
* kompilieren und linken mit cc -o copy copy.c
*
* ausfuehren ./copy Paramater1 Parameter2
*
* wobei Parameter1 und Parameter2 Dateiname oder
* Geraetename sein koennen.
*/
// Standardincludes
#include < sys/types.h >
#include < fcntl.h >
#include < unistd.h >
//Wird zum Verwenden der errno Variable benoetigt
#include < errno.h >
#define BUF_SIZE 4096
#define MODE 0644
// Uebergabe der Parameter ueber argc und argv.
// in argc steht die Anzahl der Uebergabeparameter plus 1
// denn in argv[0] steht der Programmname, in argv[1] der
// erste Uebergabeparameter, in argv[2] der zweite usw.
// Systemweite Fehlervariable, erhaelt vom BS den Fehlerwert
int errno;
main (int argc, char *argv[])
{
// die Dateidescriptoren sind einfache Interger-Variablen.
// Sie beschreiben im Endeffekt eine logische Kanalnummer
// (Kanalnummern 0,1,2 sind fest zugewiesen stdin, stdou,sterr)
int src, dst, in, out;
char buf[BUF_SIZE];
// Stimmt die Anzahl der Uebergabeparameter ?
if (argc !=3) exit(1);
// Oeffnen der ersten "Datei" Rueckgabewert ist das Filehandle == Kanalnummer
// Ubergeben wird der "Dateiname" (String) und der Modus (hier: nur lesend)
// siehe auch man open
src = open(argv[1], O_RDONLY);
// Im Gutfall bekommt man einen Wert groesser/gleich NULL zurueck
// Im Fehlerfall bekommt man als Rueckgabewert -1
if (src < 0){
perror(" ");
exit(2);
}
// Oeffnen der zweiten "Datei" Rueckgabewert ist das Filehandle == Kanalnummer
// Uebergeben wird der "Dateiname" (String) und der Modus (hier: falls diese noch
// nicht existiert wird sie angelegt )
dst = creat(argv[2], MODE);
// Im Gutfall bekommt man einen Wert groesser/gleich NULL zurueck
// Im Fehlerfall bekommt man als Rueckgabewert -1
//printf("\nsrc: %d, dst: %d\n",src,dst);
if (dst < 0) {
// Fehler ausgeben
perror(" ");
exit(3);
}
while (1){
// Lesen von der ersten Kanalnummer. Der read-Aufruf blockiert so lange
// bis in dem Kanal Zeichen kommen und liest dann bis max. BUF_SIZE
// Zeichen aus dem Kanal und kopiert diese in das Feld buf.
// Als Rueckgabewert steht in der Variablen in dann die tatsaechlich
// gelesene Anzahl von Bytes
// siehe auch man 2 read
in = read(src, buf, BUF_SIZE);
// Ist der Rueckgabewert -1 dann ist ein Fehler aufgetreten
if (in <= 0) break;
// Schreiben in den zweiten Kanal.
// Als Rueckgabewert steht in der Variablen in dann die tatsaechlich
// geschriebene Anzahl von Bytes
// siehe auch man 2 write
out = write (dst, buf, in);
// Ist der Rueckgabewert -1 dann ist ein Fehler aufgetreten
if (out <= 0) break;
}
// Schliesen der Kanaele. Beim exit() schliesst das Betriebssystem diese automatisch
close(src);
close(dst);
exit(0);
}
 
Beispielprogramm zur Fehlerbehandlung:
#include < stdio.h >
#include < errno.h >
// Systemweite Fehlervariable. Wenn ein Fehler bei einem
// Funktionsaufruf auftritt, so setzt das Bestriebssystem
// diese Variable auf einen Wert (errno.h) und mit dem
// Aufruf perror() kann der zugehoerige Fehlertext auf den
// Bildschirm ausgegeben werden.
int errno;
main(argc, argv)
int argc;
char *argv[];
{
int i;
for (i=0; i < 128; i++)
{
fprintf(stderr, "%3d",i);
// Hier wird die Fehlervariable "kuenstlich" gesetzt
errno = i;
// Ausgabe der Fehlertextes (vgl. errno.h)
perror (" ");
}
exit(0);
}
 
Prozesse, Signalverarbeitung:
pid = process id
ppid = parent process id
fork = "gabel" ezeugt prozesskopie
kill -INT pid = Signal SIGINT, Signalnummer 2
 
Neuer Prozess starten heisst erstmal alten Prozess kopieren und dann aendern
Interprozesskomunikation = Signal schicken
 
/*
* Arbeiten mit Signalen.
*
*/
#include < stdio.h >
#include < signal.h >
//neu: 3 folgende includes werden für open() gebraucht
#include < sys/types.h >
#include < sys/stat.h >
#include < fcntl.h >
static int sigrecv = 0; // Zaehler fuer die empfangenen Signale
void sigint(); // Signalbehandlungsroutiune
void sighup(); // Signalbehandlungsroutiune
//neu: "globale Variable buf in der dann nach dem Aufruf von einlesen() der Text aus der Datei steht
char buf[4096];
//neu: Funktion einlesen().
//neu: Datei abc zum lesen öffnen. Variablen src, in werden zum einlesen benötigt. Filedescriptor ist src
void einlesen(){
int src, in;
src = open("abc", O_RDONLY);
in = read(src, buf, 4096);
}
main (argc, argv)
int argc;
char argv[];
{
int rc = 0;
//neu: Funktion beim starten des Programms aufrufen um die Textdatei einzulesen
einlesen();
// Signalhandler initialisieren - Beim Empfang der spezifizierten
// Signale wird in die entsprechende Routine gesprungen.
// Die Werte der Signale stehen in signal.h
// Signal SIGKILL kann nicht abgefangen werden
signal(SIGINT, sigint);
signal(SIGHUP, sighup);
printf("My PID: %d My PPID: %d\n",getpid(), getppid());
// Warten auf die Signale
for (;;)
{
//neu: Statt dieser "Waiting for signal..." wird jetzt
//der Inhalt der Datei abc ausgegeben den unsere
//Funktion in buf geschrieben hat
printf("Waiting for Signal ..\n");
printf(buf);
sleep(5);
}
exit(rc);
}
// Wenn das Signal SIGINT (Nr. 2) an diesen Prozess geschickt wird,
// dann wird aus der obigen Endlosschleife zu dem mit dem signal()-
// Aufruf definierten Unterprogramm gesprungen. Das Betriebssystem
// sichert den Zustand so dass mit dem return wieder an der Stelle
// weitergemacht wird an der Prozess war als das Signal empfangen
// wurde.
void sigint(signo)
int signo;
{
sigrecv++;
printf("Signal Nr. %d received by sigint, %d signal\n", signo,sigrecv);
//neu: Wenn dieses Signal ermpfancen wird, z.B. durch [Strg] + [c]
//drücken wird die Textdatei erneut eingelesen
einlesen();
fflush (stdout);
}
// Wenn das Signal SIGHUP (Nr. 1) an diesen Prozess geschickt wird,
// dann wird aus der obigen Endlosschleife zu dem mit dem signal()-
// Aufruf definierten Unterprogramm gesprungen. Das Betriebssystem
// sichert den Zustand so dass mit dem return wieder an der Stelle
// weitergemacht wird an der Prozess war als das Signal empfangen
// wurde.
void sighup(signo)
int signo;
{
sigrecv++;
printf("Signal Nr. %d received by sighup, %d signal\n", signo,sigrecv);
fflush (stdout);
exit(1);
}
Die Datei abc ist eine ascii Datei und kann mit beliebigen (soso ;-) Inhalt angelegt werden.
 
Unglaublicher Komfort mit Praeprozessor power:
/*
* Signal.h
*
*/
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPOLL SIGIO
/*
#define SIGLOST 29
*/
#define SIGPWR 30
#define SIGSYS 31
#define SIGUNUSED 31
 
[Strg] + [c] abfangen
#include < stdio.h >
#include < signal.h >
static int sigrecv = 0; // Zaehler fuer die empfangenen Signale
void sigint(); // Signalbehandlungsroutiune
void sighup(); // Signalbehandlungsroutiune
int strgc = 0;
void irhandler()
{
char eing;
fflush (stdin);
printf("Wollen sie wirklich beenden?\n");
scanf("%c", &eing);
if (eing == 'y'){
printf("Beenden..\n");
sleep(1);
exit(1);
}
if (eing == 'n'){
printf("Weiter..\n");
sleep(1);
fflush (stdin);
eing = 'u';
main();
}
strgc = 0;
}
main (argc, argv)
int argc;
char argv[];
{
int rc = 0;
// Signalhandler initialisieren - Beim Empfang der spezifizierten
// Signale wird in die entsprechende Routine gesprungen.
// Die Werte der Signale stehen in signal.h
// Signal SIGKILL kann nicht abgefangen werden
signal(SIGINT, sigint);
signal(SIGHUP, sighup);
printf("My PID: %d My PPID: %d\n",getpid(), getppid());
// Warten auf die Signale
for (;;)
{
if (strgc==0){
printf("Waiting for Signal ..\n");
sleep(5);}
else{
irhandler();}
}
exit(rc);
}
// Wenn das Signal SIGINT (Nr. 2) an diesen Prozess geschickt wird,
// dann wird aus der obigen Endlosschleife zu dem mit dem signal()-
// Aufruf definierten Unterprogramm gesprungen. Das Betriebssystem
// sichert den Zustand so dass mit dem return wieder an der Stelle
// weitergemacht wird an der Prozess war als das Signal empfangen
// wurde.
void sigint(signo)
int signo;
{
//sigrecv++;
strgc = 1;
fflush (stdin);
fflush (stdout);
}
// Wenn das Signal SIGHUP (Nr. 1) an diesen Prozess geschickt wird,
// dann wird aus der obigen Endlosschleife zu dem mit dem signal()-
// Aufruf definierten Unterprogramm gesprungen. Das Betriebssystem
// sichert den Zustand so dass mit dem return wieder an der Stelle
// weitergemacht wird an der Prozess war als das Signal empfangen
// wurde.
void sighup(signo)
int signo;
{
sigrecv++;
printf("Signal Nr. %d received by sighup, %d signal\n", signo,sigrecv);
fflush (stdout);
exit(1);
}
 
Fork-Aufrug erstellt Kopie eines Prozesses
Ziel: Erzeugen eines neuen Prozesses
Fork kehrt 2x zurück :-)
+-------------+ +-------------+
| | | |
| P1 | | P2 |
| | fork() | |
+-------+-----+ +-------------+
| | Benutzerebene
----------------+--------------------+-----------------------
| | Systemebene
#=============# #=============#
" " " "
" Heap "---->" Heap "
" Daten " " Daten "
#=============# #=============#
Alter und neuer Prozess haben die gleichen Rechte
Rückgabewert = 0 neuer Prozess
Rückgabewert = 1 alter Prozess
 
#include < stdio.h >
#include < errno.h >
main (argc, argv)
int argc;
char *argv[];
{
int retval = 0;
if ( (retval = fork()) < 0)
{
printf("\nFehler beim Erzeugen des Prozess: Fehlernummer: %d\n", errno);
fflush (stdout);
perror("");
}
else if (retval != 0)
{
printf("I am the parent. My PID: %d, My parent is %d\n",getpid(), getppid());
sleep(15);
}
else
{
printf("I am the child. My PID: %d, My parent is %d\n",getpid(), getppid());
sleep(15);
}
exit(retval);
}
 
Ziel dieser Uebung ist das Erstellen eines etwas umfangreicheren Programms unter Anwendung des bisher gelernten:

 
Startprogramm:
#include < stdio.h >
#include < errno.h >
#include < sys/stat.h > /* Braucht man fuer mknod */
main (argc, argv)
int argc;
char *argv[];
{
int retval = 0;
//Da kommen die pids der Kinderprozesse rein
int pidSender, pidEmpfaenger;
//Named Pipe mit dem Namen myfifo und rw-r--r-- Berechtigungen erstellen
mknod("myfifo", S_IFIFO | 0644 , 0);
// Erster fork fuer das Sendeprogramm
retval = fork();
if ( retval < 0)
{
printf("\nFehler beim Erzeugen des Prozess: Fehlernummer: %d\n", errno);
fflush (stdout);
perror("");
}
else if (retval != 0)
{
//Vaterprozess
}
else
{
execlp( "xterm", "xterm", "-geometry", "+550+10", "-e", "./sendeprogramm", NULL );
pidSender = getpid();
}
// Zweiter fork fuer das Empfangsprogramm
retval = fork();
if ( retval < 0)
{
printf("\nFehler beim Erzeugen des Prozess: Fehlernummer: %d\n", errno);
fflush (stdout);
perror("");
}
else if (retval != 0)
{
//Vaterprozess
}
else
{
execlp( "xterm", "xterm", "-geometry", "+550+380", "-e", "./empfangsprogramm", NULL );
pidEmpfaenger = getpid();
}
//Billigloesung, wartet aber garantiert auf beide Soehne
wait();
wait();
//Named pipe loeschen
execlp( "rm", "rm", "myfifo", NULL );
exit(retval);
}
 
Sendeprogramm:
#include < stdio.h >
#include < errno.h >
main (argc, argv)
int argc;
char *argv[];
{
printf("Ich bin das Sendeprogramm\n");
//Datei Zeiger definieren
FILE *rohr;
//fifo Datei zum Schreiben oeffnen
rohr=fopen("myfifo", "r+");
char c;
while((c=getchar()) != '.'){
fputc(c, rohr);
//Bei Eingabetaste uebertragen
if(c = '\n'){
fflush(rohr);
}
}
//Sonst kriegt das Empfangsprogramm ja nie sein '.'
fputc('.', rohr);
fflush(rohr);
printf("\nEnde\n");
fclose(rohr);
exit(0);
}
 
Empfangsprogramm:
#include < stdio.h >
#include < errno.h >
main (argc, argv)
int argc;
char *argv[];
{
printf("Ich bin das Empfangsprogramm\n");
//Datei Zeiger definieren
FILE *rohr;
//fifo Datei zum Schreiben oeffnen
rohr=fopen("myfifo", "r+");
char c;
while((c=fgetc(rohr)) != '.'){
putchar(c);
}
printf("\nEnde\n");
fclose(rohr);
exit(0);
}
 
Die Musterloesung, auch ganz nett, aber lange nicht so low-level-hack-maessig :-) wie meine.