Σε αυτή την ενότητα θα μελετήσουμε τις σωληνώσεις.
Η σωλήνωση επιτρέπει την έξοδο μιας διεργασίας να γίνει είσοδος σε
μιά άλλη. Έχουμε δεί τέτοια παραδείγματα στη σψλήνωση εντολών φλοιού
στη γραμμή εντολών του UNIX/Linux (χρήση ). Τώρα θα πετύχουμε το ίδιο μέσα σε προγράμματα C.
Θα έχουμε δύο ή περισσότερες διεργασίες που έχουν δημιουργηθεί με fork() και επικοινωνούν μεταξύ τους.
Πρώτα πρέπει να ανοίξουμε μια σωλήνωση (pipe). Η σωλήνωση υλοποιείται ως μια ροή First In First Out, όπου η μια πλευρά εισάγει δεδομένα ενώ η άλλη εξάγει. Υπάρχουν δύο συναρτήσεις.
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;
}
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 tostdout
. */
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.