Σωληνώσεις



Είδαμε πως μπορούμε να δημιουργήσουμε και να ελέγξουμε διεργασίες μέσα από ένα πρόγραμμα C. Σε πολλές εφαρμογές οι διεργασίες αυτές πρέπει να επικοινωνούν μεταξύ τους για ανταλλαγή δεδομένων ή πληροφοριών ελέγχου. Στις επόμενες ενότητες θα συζητήσουμε για:

Σε αυτή την ενότητα θα μελετήσουμε τις σωληνώσεις.

Σωλήνωση σε προγράμματα C <stdio.h>

Η σωλήνωση επιτρέπει την έξοδο μιας διεργασίας να γίνει είσοδος σε μιά άλλη. Έχουμε δεί τέτοια παραδείγματα στη σψλήνωση εντολών φλοιού στη γραμμή εντολών του UNIX/Linux (χρήση $\mid$ ). Τώρα θα πετύχουμε το ίδιο μέσα σε προγράμματα C.  Θα έχουμε δύο ή περισσότερες διεργασίες που έχουν δημιουργηθεί με fork() και επικοινωνούν μεταξύ τους.

Πρώτα πρέπει να ανοίξουμε μια σωλήνωση (pipe). Η σωλήνωση υλοποιείται ως μια ροή First In First Out, όπου η μια πλευρά εισάγει δεδομένα ενώ η άλλη εξάγει. Υπάρχουν δύο συναρτήσεις.

popen() -- Μορφοποιημένη Σωλήνωση

FILE *popen(char *command, char *type) 

Ανοίγει μια σωλήνωση Ε/Ε όπου η συμβολοσειρά command είναι η διεργασία που θα εκτελεστεί μέσω της συνάρτησης system() και θα συνδεθεί με τη καλούσα διεργασία, έτσι ώστε δημιουργείται μια σωλήνωση. Ο συμβολοσειρά type είναι έιτε "r'' - για Είσοδο (ανάγνωση), ή "w'' για Έξοδο (εγγραφή).

Η συνάρτηση popen() επιστρέφει ένα δείκτη σε ροή (αρχείο) ή δείκτη NULL για σφάλμα.

Μια σωλήνωση που ανοίγει με popen() πρέπει πάντα να κλείνει με τη συνάρτηση

pclose(FILE *stream)

Η εγγραφή και ανάγνωση γίνεται συνήθως με τη χρήση των συναρτήσεων fprintf() και fscanf(), δηλαδή η σωλήνωση αντιμετωπίζεται απλά ως ροή (αρχείο) κειμένου.

Παράδειγμα:

#include <stdio.h>
#include <stdlib.h>

void write_data (FILE * stream)
{
int i;
for (i = 0; i < 100; i++)
fprintf (stream, "%d\n", i);
if (ferror (stream))
{
fprintf (stderr, "Output to stream failed.\n");
exit (EXIT_FAILURE);
}
}

int main (void)
{
FILE *output;

output = popen ("more", "w");
if (!output)
{
fprintf (stderr,"incorrect parameters or too many files.\n");
return EXIT_FAILURE;
}
write_data (output);
if (pclose (output) != 0)
{
fprintf (stderr,"Could not run more or other error.\n");
}
return EXIT_SUCCESS;
}

pipe() -- Σωλήνωση Χαμηλού Επιπέδου

int pipe(int fd[2])

Ανοίγει μια σωλήνωση και επιστρέφει δύο περιγραφείς αρχείου, fd[0], fd[1].  Ο περιγραφέας fd[0] χρησιμοποιείται για ανάγνωση ενώ ο fd[1] για εγγραφή.

Η συνάρτηση pipe() επιστρέφει 0 στην επιτυχία και -1 στην αποτυχία, θέτοντας το errno στη κατάλληλη τιμή.

Η ανάγνωση και εγγραφή γίνεται συνήθως με τη χρήση των συναρτήσεων read() and write(), αλλά μπορούν να χρησιμοποιηθούν και συναρτήσεις μορφοποιημένης Ε/Ε (τόσο δυαδικής όσο και κειμένου) εάν συνδέσουμε τους περιγραφείς αρχείου με ροές (δείκτη σε αρχείο) μέσω της συνάρτησης fdopen().

Μια σωλήνωση που ανοίγει με pipe() πρέπει πάντα να κλείνει με διπλή χρήση της συνάρτησης

close(int fd).

Πρώτο παράδειγμα: Η γονική διεργασία στέλνει δεδομένα στη θυγατρική διεργασία

int pdes[2];
 
pipe(pdes);

if ( fork() == 0 )
{
/*this is the child process */
close(pdes[1]); /* it is not required, it will read */
read( pdes[0]); /* read from parent */
.....
}
else
{ /* this is the parent process */
close(pdes[0]); /* it is not required, it will write */
write( pdes[1]); /* write to child */
.....
}
Δεύτερο παράδειγμα: Ανάγνωση χαρακτήρων από σωλήνωση και εμφάνιση στη πρότυπη έξοδο.

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

/* Read characters from the pipe and echo them to stdout. */

void read_from_pipe (int file)
{
FILE *stream;
int c;
stream = fdopen (file, "r");
while ((c = fgetc (stream)) != EOF)
putchar (c);
fclose (stream);
}

/* Write some random text to the pipe. */

void write_to_pipe (int file)
{
FILE *stream;
stream = fdopen (file, "w");
fprintf (stream, "hello, world!\n");
fprintf (stream, "goodbye, world!\n");
fclose (stream);
}

int main (void)
{
pid_t pid;
int mypipe[2];

/* Create the pipe. */
if (pipe (mypipe))
{
fprintf (stderr, "Pipe failed.\n");
return EXIT_FAILURE;
}

/* Create the child process. */
pid = fork ();
if (pid == (pid_t) 0)
{
/* This is the child process. Close other end first. */
close (mypipe[1]);
read_from_pipe (mypipe[0]);
return EXIT_SUCCESS;
}
else if (pid < (pid_t) 0)
{
/* The fork failed. */
fprintf (stderr, "Fork failed.\n");
return EXIT_FAILURE;
}
else
{
/* This is the parent process. Close other end first. */
close (mypipe[0]);
write_to_pipe (mypipe[1]);
return EXIT_SUCCESS;
}
}

Ένα μεγαλύτερο παράδειγμα σωλήνωσης είναι το πρόγραμμα plot.c που λειτουργεί ως εξής:

Ο κώδικας του plot.c είναι:

/* plot.c - example of unix pipe. Calls gnuplot graph drawing package to draw
graphs from within a C program. Info is piped to gnuplot */
/* Creates 2 pipes one will draw graphs of y=0.5 and y = random 0-1.0 */
/* the other graphs of y = sin (1/x) and y = sin x */

/* Also uses a plotter.c module */
/* compile: cc -o plot -lm plot.c plotter.c */

#include "externals.h"
#include <signal.h>

#define DEG_TO_RAD(x) (x*180/M_PI)

double drand48();
void quit();

FILE *fp1, *fp2, *fp3, *fp4, *fopen();

main()
{ float i;
float y1,y2,y3,y4;

/* open files which will store plot data */
if ( ((fp1 = fopen("plot11.dat","w")) == NULL) ||
((fp2 = fopen("plot12.dat","w")) == NULL) ||
((fp3 = fopen("plot21.dat","w")) == NULL) ||
((fp4 = fopen("plot22.dat","w")) == NULL) )
{ printf("Error can't open one or more data files\n");
exit(1);
}

signal(SIGINT,quit); /* trap ctrl-c call quit fn */
StartPlot();
y1 = 0.5;
srand48(1); /* set seed */
for (i=0;;i+=0.01) /* increment i forever use ctrl-c to quit prog */
{ y2 = (float) drand48();
if (i == 0.0)
y3 = 0.0;
else
y3 = sin(DEG_TO_RAD(1.0/i));
y4 = sin(DEG_TO_RAD(i));

/* load files */
fprintf(fp1,"%f %f\n",i,y1);
fprintf(fp2,"%f %f\n",i,y2);
fprintf(fp3,"%f %f\n",i,y3);
fprintf(fp4,"%f %f\n",i,y4);

/* make sure buffers flushed so that gnuplot */
/* reads up to data file */
fflush(fp1);
fflush(fp2);
fflush(fp3);
fflush(fp4);

/* plot graph */
PlotOne();
usleep(250); /* sleep for short time */
}
}

void quit()
{ printf("\nctrl-c caught:\n Shutting down pipes\n");
StopPlot();

printf("closing data files\n");
fclose(fp1);
fclose(fp2);
fclose(fp3);
fclose(fp4);

printf("deleting data files\n");
RemoveDat();
}

Το τμήμα plotter.c έχει ως εξής:

/* plotter.c module */
/* contains routines to plot a data file produced by another program */
/* 2d data plotted in this version */
/**********************************************************************/

#include "externals.h"

static FILE *plot1,
*plot2,
*ashell;

static char *startplot1 = "plot [] [0:1.1]'plot11.dat' with lines, 'plot12.dat' with lines\n";

static char *startplot2 = "plot 'plot21.dat' with lines, 'plot22.dat' with lines\n";

static char *replot = "replot\n";
static char *command1= "/usr/local/bin/gnuplot> dump1";
static char *command2= "/usr/local/bin/gnuplot> dump2";
static char *deletefiles = "rm plot11.dat plot12.dat plot21.dat plot22.dat";
static char *set_term = "set terminal x11\n";

void
StartPlot(void)
{ plot1 = popen(command1, "w");
fprintf(plot1, "%s", set_term);
fflush(plot1);
if (plot1 == NULL)
exit(2);
plot2 = popen(command2, "w");
fprintf(plot2, "%s", set_term);
fflush(plot2);
if (plot2 == NULL)
exit(2);
}

void
RemoveDat(void)
{ ashell = popen(deletefiles, "w");
exit(0);
}

void
StopPlot(void)
{ pclose(plot1);
pclose(plot2);
}

void
PlotOne(void)
{ fprintf(plot1, "%s", startplot1);
fflush(plot1);

fprintf(plot2, "%s", startplot2);
fflush(plot2);
}

void
RePlot(void)
{ fprintf(plot1, "%s", replot);
fflush(plot1);
}

Το αρχείο κεφαλής externals.h περιέχει τα παρακάτω:

/* externals.h */
#ifndef EXTERNALS
#define EXTERNALS

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/* prototypes */
void StartPlot(void);
void RemoveDat(void);
void StopPlot(void);
void PlotOne(void);
void RePlot(void);
#endif

Ασκήσεις

Άσκηση 12732

Προγραμματίστε μια σωλήνωση δύο κατευθύνσεων μεταξύ γονικής και θυγατρικής διεργασίας, δηλαδή και οι δύο να λαμβάνουν και να αποστέλουν δεδομένα.

Άσκηση 12733

Γράψτε ένα πρόγραμμα παραγωγού-καταναλωτή με απομονωτή περιορισμένου μεγέθους και σήματα ελέγχου σε περίπτωση που γεμίσει ο απομονωτής του καταναλωτή.

Άσκηση 12734

Τροποποιείστε το πρόγραμμα plot.c ώστε να τερματίζεται με διαλογική επιλογή χρήστη και όχι με σήμα ctrl-c.


Dave Marshall
1/5/1999
μετάφραση και προσαρμογή στα Ελληνικά Κ.Γ. Μαργαρίτης
15/3/2008