Prácticas de Sistemas operativos David Arroyo Guardeño Escuela Politécnica Superior de la Universidad Autónoma de Madrid Tercera Semana: Comunicación entre procesos con Tuberı́as 1 Entregas 2 Introducción 3 Tuberı́as Ejemplo 1 Ejemplo 2 Ejemplo 3 Ejemplo 4 Entregas ☠ Ejercicio 9 ☠☠ Entrega antes de la sesión del próximo jueves 19 de febrero ☠☠☠ Examen de la primera práctica : jueves 19 de febrero Comunicación entre procesos Ø Formas elementales 7 Envı́o de señales 7 Uso de ficheros ordinarios ptrace 7 padre −−−−→ hijo Ø Tuberı́as Ø Facilidades IPC del Unix System V 1 2 3 Semáforos Memoria compartida Colas de mensajes Tuberı́as Canal de comunicación entre dos procesos: semi-dúplex 1 Tuberı́as con nombre: FIFOS 2 Tuberı́as sin nombre Tuberı́as sin nombre I #include <unistd.h> int pipe(int fildes [2]); fd[1] Proceso padre fd1[1] Proceso hijo Núcleo (kernel) fd[0] fd1[0] 7 Sólo el proceso que hace la llamada y sus descendientes pueden utilizarla 7 fildes: descriptores de fichero 3 Leemos (read) de fildes[0] (fichero de sólo lectura) 3 Escribimos (write) en fildes[1] (fichero de sólo escritura) 7 Tras fork/exec los hijos heredan los descriptores de ficheros 3 Abrimos la tuberı́a en el padre 3 Padre e hijo comparten la tuberı́a 7 La tuberı́a es gestionada por el núcleo 3 La dota de una disciplina de acceso en hilera 3 Llamadas a read sobre la tuberı́a no devolverán el control hasta que no haya datos escritos por otro proceso mediante write # include <s t d i o . h> # include < s t d l i b . h> # include <e r r n o . h> # include <u n i s t d . h> # define MAX 256 main ( ) { i n t tube [ 2 ] ; char message [MAX ] ; i f ( p i p e ( tube ) == −1){ p e r r o r ( ” pipe ” ) ; exit (1) ; } p r i n t f ( ” W r i t i n g TO t h e f i l e w i t h d e s c r i p t o r #%d\n ” , tube [ 1 ] ) ; w r i t e ( tube [ 1 ] , ” TEST ” , 5 ) ; p r i n t f ( ” Reading FROM t h e f i l e w i t h d e s c r i p t o r #%d\n ” , tube [ 0 ] ) ; read ( tube [ 0 ] , message , 5 ) ; p r i n t f ( ”READ DATA : \”%s \ ” \ n ” , message ) ; c l o s e ( tube [ 0 ] ) ; c l o s e ( tube [ 1 ] ) ; exit (0) ; } Ejemplo 2 fd[1] Proceso padre Proceso hijo Núcleo (kernel) fd[0] # include <s t d i o . h> # include < s t d l i b . h> # include <s t r i n g . h> # define MAX 256 main ( ) { i n t tube [ 2 ] ; int pid ; char message [MAX ] ; i f ( p i p e ( tube ) == −1){ p e r r o r ( ” pipe ” ) ; e x i t ( −1) ; } i f ( ( p i d = f o r k ( ) ) == −1) { perror ( ” fork ” ) ; e x i t ( −1) ; } else i f ( p i d == 0 ) { / ∗ C i e r r a l a t u b e r i a de e s c r i t u r a porque no l a va a usar ∗ / c l o s e ( tube [ 1 ] ) ; while ( read ( tube [ 0 ] , message ,MAX)> 0 && strcmp ( message , ”END\n ” ) ! = 0) p r i n t f ( ” \ n r e c e i v e r process . Message : %s\n ” , message ) ; exit (0) ; } else { / ∗ C i e r r a l a t u b e r i a de l e c t u r a porque no l a va a usar ∗ / c l o s e ( tube [ 0 ] ) ; while ( p r i n t f ( ” sender process . message : ” ) !=0 && f g e t s ( message , s i z e o f ( message ) , s t d i n ) ! = NULL && w r i t e ( tube [ 1 ] , message , s t r l e n ( message ) + 1 ) > 0 && strcmp ( message , ”END\n ” ) ! = 0 ) ; exit (0) ; } } Ejemplo 3 tubetx [1] Proceso padre tuberx [1] Proceso hijo Núcleo (kernel) tuberx [0] tubetx [0] Comunicación bidireccional I main ( ) { int tube tx [2] , tube rx [ 2 ] ; int pid ; char message [MAX ] ; i f ( p i p e ( t u b e t x ) == −1 | | p i p e ( t u b e r x ) == −1){ p e r r o r ( ” pipe ” ) ; e x i t ( −1) ; } i f ( ( p i d = f o r k ( ) ) == −1) { perror ( ” fork ” ) ; e x i t ( −1) ; } else i f ( p i d == 0 ) { close ( tube tx [ 1 ] ) ; close ( tube rx [ 0 ] ) ; while ( read ( t u b e t x [ 0 ] , message ,MAX)> 0 && strcmp ( message , ”END\n ” ) ! = 0 ) { p r i n t f ( ” \ n r e c e i v e r process . Message : %s\n ” , message ) ; s t r c p y ( message , ”READY” ) ; w r i t e ( t u b e r x [ 1 ] , message , s t r l e n ( message ) + 1 ) ; Comunicación bidireccional II } exit (0) ; } else { close ( tube rx [ 1 ] ) ; close ( tube tx [ 0 ] ) ; while ( p r i n t f ( ” sender process . message : ” ) !=0 && f g e t s ( message , s i z e o f ( message ) , s t d i n ) ! = NULL && w r i t e ( t u b e t x [ 1 ] , message , s t r l e n ( message ) + 1 ) > 0 && strcmp ( message , ”END\n ” ) ! = 0 ) { do{ read ( t u b e r x [ 0 ] , message , MAX) ; } while ( strcmp ( message , ”READY” ) ! = 0 ) ; } exit (0) ; } } Duplicación de descriptores de ficheros 3 Si en la shell hacemos 2>&1 7 Por ejemplo, ¿qué ocurre si hacemos lo siguiente? $ls -la . fichero_inventado > results.log 2>&1 Duplicación de descriptores de ficheros 3 Si en la shell hacemos 2>&1 7 Por ejemplo, ¿qué ocurre si hacemos lo siguiente? $ls -la . fichero_inventado > results.log 2>&1 7 La salida estándar de error (descriptor de fichero= 2) es redirigida al mismo sitio al que se envı́a la salida estándar (descriptor de fichero=1 Duplicación de descriptores de ficheros 3 Si en la shell hacemos 2>&1 7 Por ejemplo, ¿qué ocurre si hacemos lo siguiente? $ls -la . fichero_inventado > results.log 2>&1 7 La salida estándar de error (descriptor de fichero= 2) es redirigida al mismo sitio al que se envı́a la salida estándar (descriptor de fichero=1 7 La shell efectúa el redireccionamiento de la salida estándar de error 1 2 Duplica el descriptor de fichero 2 Dicho descriptor ahora se refiere al mismo 3 fcntl → man fcntl 3 dup → man dup 3 dup2 → man dup2 Si la shell sólo ha abierto los ficheros con descriptores 0,1, y el descriptor 2 se refiere al programa en ejecución y no existen otros descriptores 7 ¿Qué hace la siguiente instrucción? newfd = dup(1); 7 ¿Cómo puedo asociar el descriptor 2 a nuestro duplicado? Si la shell sólo ha abierto los ficheros con descriptores 0,1, y el descriptor 2 se refiere al programa en ejecución y no existen otros descriptores 7 ¿Qué hace la siguiente instrucción? newfd = dup(1); 3 Crea el duplicado del descriptor 1 usando el fichero con descriptor 3 7 ¿Cómo puedo asociar el descriptor 2 a nuestro duplicado? Si la shell sólo ha abierto los ficheros con descriptores 0,1, y el descriptor 2 se refiere al programa en ejecución y no existen otros descriptores 7 ¿Cómo puedo asociar el descriptor 2 a nuestro duplicado? 3 Primero cierro el fichero con descriptor 2 y luego llamo a dup close(2); newfd = dup(1); ⇒ En el ejemplo de antes bastarı́a hacer dup2(1, 2); # include < s t d l i b . h> # include <s t d i o . h> char ∗cmd1 [ ] = { ” / b i n / l s ” , ”−a l ” , ” / ” , 0 } ; char ∗cmd2 [ ] = { ” / u s r / b i n / t r ” , ” a−z ” , ” A−Z ” , 0 } ; void run1 ( i n t tube [ ] ) ; void run2 ( i n t tube [ ] ) ; i n t main ( i n t argc , char ∗∗ argv ) { i n t pid , s t a t u s ; i n t tube [ 2 ] ; i f ( p i p e ( tube ) ==−1){ f p r i n t f ( s t d e r r , ” E r r o r en l a l i n e a %d d e l f i c h e r o %s\n ” , L I N E , FILE ) ; e x i t ( EXIT FAILURE ) ; } run1 ( tube ) ; run2 ( tube ) ; c l o s e ( tube [ 0 ] ) ; c l o s e ( tube [ 1 ] ) ; / ∗ I m p o r t a n t e : hay que c e r r a r l o s dos d e s c r i p t o r e s de la tuberia ∗/ while ( ( p i d = w a i t (& s t a t u s ) ) ! = −1) / ∗ Esperar a que hayan terminado todos l o s h i j o s ∗ / f p r i n t f ( s t d e r r , ” E l proceso %d ha terminado y su estado de f i n a l i z a c i o n es %d\n ” , pid , WEXITSTATUS( s t a t u s ) ) ; e x i t ( EXIT SUCCESS ) ; } void run1 ( i n t tube [ ] ) / ∗ E j e c u t a r l a p r i m e r a p a r t e de l a t u b e r i a ∗ / { int pid ; switch ( p i d = f o r k ( ) ) { case 0 : / ∗ h i j o ∗ / dup2 ( tube [ 1 ] , 1 ) ; / ∗ Cerramos e l d e s c r i p t o r 1 , l a s a l i d a estandar , que pasa a s e r l a s a l i d a de l a t u b e r i a ∗ / c l o s e ( tube [ 0 ] ) ; / ∗ Este proceso no n e c e s i t a e l o t r o extremo de l a tuberia ∗/ execvp ( cmd1 [ 0 ] , cmd1 ) ; / ∗ E j e c u t a r e l p r i m e r comando , cmd1 ∗ / p e r r o r ( cmd1 [ 0 ] ) ; / ∗ Estamos a q u i s o l o s i ha habido algun f a l l o ∗ / d e f a u l t : / ∗ E l padre no hace nada ∗ / break ; case −1: perror ( ” fork ” ) ; e x i t ( EXIT FAILURE ) ; } } void run2 ( i n t t u b e r i a [ ] ) / ∗ Se e j e c u t a l a segunda p a r t e de l a t u b e r i a ∗ / { int pid ; switch ( p i d = f o r k ( ) ) { case 0 : / ∗ h i j o ∗ / dup2 ( t u b e r i a [ 0 ] , 0 ) ; / ∗ Este extremo de l a t u b e r i a pasa a s e r l a entrada estandar ∗/ c l o s e ( t u b e r i a [ 1 ] ) ; / ∗ Extremo de t u b e r i a que no n e c e s i t a e s t e proceso ∗/ execvp ( cmd2 [ 0 ] , cmd2 ) ; / ∗ Se e j e c u t a e s t e comando ∗ / p e r r o r ( cmd2 [ 0 ] ) ; / ∗ Estoy a q u i s o l o s i ha p r o d u c i d o algun e r r o r ∗ / d e f a u l t : / ∗ E l padre no hace nada ∗ / break ; case −1: perror ( ” fork ” ) ; exit (1) ; } } Referencias ⇒ Francisco M. Márquez. Unix, Programación Avanzada. Editorial: Ra-Ma. 3a Edición. ISBN: 84-7897-603-5