Chiamate di sistema
Pipe
Esempio di chiamata di sistema
#include <stdio.h>
int main(int argc, char ** argv)
{
FILE * f;
f = fopen("nuovo-file", "w");
fprintf(f, "Hello World!\n");
fclose(f);
Esempio chiamata di sistema
#include <stdio.h>
#include <sys/fcntl.h>
int main(int argc, char ** argv)
{
int fd; // file descriptor
fd = open("nuovo-file", O_WRONLY |
O_CREAT, 0666);
write(fd, "Hello World\n", 12);
close(fd);
}
0 1 2 ... stdin stdout stderr ... FilePipe
• Questa comunicazione tra processi appare al
programmatore simile alla scrittura-lettura dei file
• Una pipe è un file di dimensione limitata gestito come una
coda FIFO:
– un processo produttore deposita dati (e resta in attesa se la
pipe è piena)
– un processo consumatore legge dati (e resta in attesa se la
coda è vuota)
POSIX: creazione di pipe
• pipe senza nome, create e aperte da
pipe
int
pipe
(int
fd
[2]);
• crea una pipe, la apre in lettura e scrittura
• restituisce l'esito dell'operazione (0 o -1)
• assegna a
fd
[0] il file descriptor del lato aperto in lettura, e
a
fd
[1] quello del lato aperto in scrittura
• Si possono creare pipe con nome, devono essere create da
POSIX: creazione di pipe
$ mknod fifo1 p $ ls -l fifo*
prw-rw-rw- 1 lucal lucal 0 Apr 19 09:11 fifo1 (si noti la p nei permessi) $ mkfifo fifo2
$ ls -l fifo*
prw-rw-rw- 1 lucal lucal 0 Apr 19 09:11 fifo1 prw-rw-rw- 1 lucal lucal 0 Apr 19 09:11 fifo2 $ cat fifo1 & echo ciao > fifo1
[1] 8088 ciao
$ cat fifo2 ; echo ciao > fifo2
(cursore lampeggiante: il programma è bloccato)
Ovviamente fifo1 e fifo2 sono equivalenti (ma diversi) osboxes@osboxes: ~
File e pipe ordinari
• Creazione e uso di un file ordinario:
int fd;
...
if ((fd=open(filename,...)<0) err();
...
write(fd, …); …;
• Creazione e uso di una pipe (senza nome):
int fd[2];
...
if (pipe(fd) < 0) err();
...
write(fd[1], …); …; read(fd[0], …);
Note:
– la pipe è analoga a open, ma non specifica il nome
e produce
due
file descriptor
Pipe: uso tipico
• Generalmente una pipe viene usata per far comunicare un
processo padre con un suo figlio
• Il processo figlio eredita i file aperti, quindi anche le pipe
– La comunicazione è unidirezionale
pipe
write(fd[1]);
read(fd[0]);
write(fd[1]);
read(fd[0]);
pipe
oppure
pipe(fd);
fork();
Processo figlio
Processo padre
Pipe: uso tipico
#include <stdio.h>;
int main(int argc, char ** argv) {
int fd[2], pid, status; char i, j, i1, j1;
pipe(fd);
pid = fork();
if(pid!=0) { // padre
for(i=0; i<10; i++) write(fd[1], &i, 1); printf("scritto!\n");
waitpid(-1, &status, 0);
printf("pid=%d i1=%d j=%d\n", pid, i1, j); } else { // figlio
for(j=0; j<10; j++) {
read(fd[0], &j1, 1); printf("j1=%d\n",j1); }
printf("pid=%d i=%d\n", pid, i); }
Pipe: comunicazione bidirezionale
int main(int argc, char ** argv) { int fd[2], fd2[2], pid; char i, j; pipe(fd); pipe(fd2); pid = fork(); if(pid!=0) {
for(i=0; i<10; i++) write(fd[1], &i, 1); read(fd2[0], &j, 1);
printf("pid=%d i=%d j=%d\n", pid, i, j); } else {
for(j=0; j<10; j++) {
read(fd[0], &i, 1); printf("i=%d\n", i); }
write(fd2[1], &j, 1);
printf("pid=%d i=%d j=%d\n", pid, i, j); }
Pipe: uso tipico
% cat documento.txt | sort
fork();
fork();
exec(“cat”, …);
exec(“cat”, …);
close(1);
dup(fd[1]);
close(fd[0]);
close(fd[1]);
close(1);
dup(fd[1]);
close(fd[0]);
close(fd[1]);
pipe(fd);
fork();
pipe(fd);
fork();
exec(“sort”, …);
exec(“sort”, …);
close(0);
dup(fd[0]);
close(fd[0]);
close(fd[1]);
close(0);
dup(fd[0]);
close(fd[0]);
close(fd[1]);
shell
processo figlio
Produttore
Consumatore
Realizzazione
0 1 2 ... ... stdin stdout stderr ... ... Filedescriptor File table
pipe(fd); 0 1 2 3 4 stdin stdout stderr pipe-in pipe-out close(1); 0 1 2 3 4 stdin ... stderr pipe-in pipe-out dup(fd[1]); close(fd[0]); close(fd[1]); 0 1 2 3 stdin ... stderr ... 1) 2a) 3a) 4a) 0 1 2 3 4 stdin ... stderr pipe-in pipe-out close(0); 0 1 2 3 4 ... stdout stderr pipe-in pipe-out dup(fd[0]); close(fd[0]); close(fd[1]); 0 1 2 3 ... stdout stderr pipe-in 2b) 3b) 4b) 0 1 2 3 4 ... stdout stderr pipe-in pipe-out
Pipe: realizzazione
#include <stdio.h>
int main(int argc, char ** argv) { int fd[2], pid; char j, j1; pipe(fd); 1) pid = fork(); if(!pid) {
close(1); dup(fd[1]); 2a)-3a)
close(fd[0]); close(fd[1]); 4a)
execlp("cat", "cat", __FILE__, NULL); } else {
close(0); dup(fd[0]); 2b)-3b)
close(fd[0]); close(fd[1]); 4b)
for(j=0; j<10; j++) {
read(0, &j1, 1); printf("j1=%3d %c\n", j1,j1); }
}
fprintf(stderr, "pid=%d j1=%d j=%d\n", pid, j1, j); }
Risultato
j1= 35 #
j1=105 i
j1=110 n
j1= 99 c
j1=108 l
j1=117 u
j1=100 d
j1=101 e
j1= 32
j1= 60 <;
pid=1070 j1=60 j=10
Ridirezioni
% ls -l . pippo > ris 2>log
fork();
fork();
exec(“ls”, …);
exec(“ls”, …);
close(1);
open(“ris”, …);
close(2);
open(“log”, …);
close(1);
open(“ris”, …);
close(2);
open(“log”, …);
shell
processo figlio
Ridirezioni: realizzazione
#include <stdio.h> #include <unistd.h> #include <sys/fcntl.h>
int main(int argc, char ** argv) {
close(1);
open("ris", O_WRONLY | O_CREAT | O_TRUNC, 0666); close(2);
open("log", O_WRONLY | O_CREAT | O_TRUNC, 0666); execlp("ls", "LS", "-l", ".", "pippo", NULL); /* non dovrebbe mai essere eseguito */
fprintf(stderr, "Error: execlp\n"); return 1;
}
File “log”:
Semaforo tramite pipe
int up(int *pipe) {
char tmp = 0;
return write(pipe[1],&tmp,1)==1? 0 : -1; }
int down(int *pipe) { char tmp = 0; return read(pipe[0],&tmp,1)==1 ? 0 : -1; } int mk_mutex(int *p) {
if( pipe(p) < 0) return -1;
return write(p[1], p, 1) == 1 ? 0 : -1; }
Nota 1:
Il valore effettivamente letto o o scritto non ha importanza, conta solo il numero di byte letti o scritti
Nota 2:
Anche l’operazione di up può diventare bloccante (quando si riempie il buffer)
Semaforo tramite pipe
import java.io.*;
public class Semaforo { private PipedReader in; private PipedWriter out; public void down() { try {
in.read();
} catch(IOException e) { } }
public void up() { try { out.write(0); } catch(IOException e) { } } public Semaforo() { this(0); } public Semaforo(int v) { try { in = new PipedReader(); out = new PipedWriter(in); while(v-- > 0) up();
popen
#include <stdio.h> #include <stdlib.h>
int main(int argc, char **argv) {
FILE *pin; char line[10];
pin = popen("cat " __FILE__, "r"); fread(line, 1, 10, pin);
fwrite(line, 1, 10, stdout); pclose(pin);
return 0; }
popen
/* equivale a ls –l | sort */ #include <stdio.h>
#include <stdlib.h>
int main(int argc, char ** argv) {
FILE * pp, * pout; char line[100];
pp = popen("ls -l", "r"); pout = popen("sort", "w"); if(!pp || !pout) exit(1); while(fgets(line, 100, pp)) fprintf(pout, "%s", line); pclose(pp); pclose(pout); return 0; }
Duplicazione di processi
#include <stdio.h> #include <sys/fcntl.h>
int main(int argc, char ** argv) {
int i, j, pid; int fin, fout; char ch[8];
fin = open("dati", O_RDONLY); pid = fork();
if(!pid) {
fout = open("uno", O_WRONLY | O_CREAT, 0666); } else {
fout = open("due", O_WRONLY | O_CREAT, 0666); } for(j=0; j<400; j++) { read(fin, ch, 8); write(fout, ch, 8); } }
Duplicazione di processi
100001
100401
100402
100403
...
100792
100793
100794
100795
100796
100797
100798
100799
100000
100002
...
100391
100392
100393
100394
100395
100396
100397
100398
100399
100400
due
uno
100000
100001
100002
100003
100004
...
100993
100994
100995
100996
100997
100998
100999
dati
Duplicazione di processi
#include <stdio.h>
int main(int argc, char ** argv) {
int i, j, pid;
FILE * fin, * fout;
fin = fopen("dati", "r"); fscanf(fin, "%d\n", &i); pid = fork(); if(!pid) { fout = fopen("uno", "w"); } else { fout = fopen("due", "w"); } for(j=0; j<400; j++) { fscanf(fin, "%d\n", &i); fprintf(fout, "%d\n", i); } fclose(fout); }
Duplicazione di processi
100001
100002
100003
...
100124
100125
100126
100127
100512
100513
...
100782
100783
100784
100001
100002
100003
...
100124
100125
100126
100127
100128
100129
...
100398
100399
100400
due
uno
I/O bufferizzato
• Si utilizza un meccanismo di caching per ridurre il numero di
operazioni di I/O: solo quando il buffer è pieno viene chiesto
l’intervento del sistema operativo e (forse!) scritto su disco
fprintf(f, ...) f=fopen(“file”, ...)
buffer
Duplicazione di processi / 2
#include <stdio.h> #include <sys/fcntl.h>
int main(int argc, char ** argv) {
int i, j, pid; int fin, fout; char ch[8]; pid = fork();
fin = open("dati", O_RDONLY); if(!pid) {
fout = open("uno", O_WRONLY | O_CREAT, 0666); } else {
fout = open("due", O_WRONLY | O_CREAT, 0666); } for(j=0; j<400; j++) { read(fin, ch, 8); write(fout, ch, 8); } }
Prestazioni e chiamate di sistema
#include ...
#define BUFFSIZE 8192
int main(void)
{
int n;
char buf[BUFFSIZE];
while( (n=read(STDIN_FILENO,buf, BUFFSIZE))>0)
if (write(STDOUT_FILENO, buf, n) != n) perror("main");
if (n<0) perror("main");
return(0);
}
Note
:
– È corretto usare, come file descriptor:
STDIN_FILENO
e
STDOUT_FILENO
(definiti in
<unistd.h>) al posto di O e 1
Prestazioni e chiamate di sistema
#include ...
#define BUFFSIZE 8192
int main(void)
{
int n;
char buf[BUFFSIZE];
while( (n=fread(buf, BUFFSIZE, 1, stdin))>0 )
fwrite(buf, sizeof(char), n, stdout);
return(0);
}
Caching del SO
• Anche il SO usa una sua cache per ridurre il numero di
operazioni di I/O
• Il comando
sync
forza la scrittura su disco
disco 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 cache $ sync --help
Usage: sync [OPTION]
Force changed blocks to disk, update the super block. --help display this help and exit