		Tema 5.
		Comunicacion entre procesos I.

+ Comunicacin entre procesos.

Como se ha visto en los temas anteriores, UNIX proporciona la capacidad de
tener un sistema multitarea, donde los procesos pueden accesar a los
diferentes sistemas de cmputo utilizando el sistema de Entrada/Salida.

Sin embargo, en muchos casos, es necesario que varios procesos se
coordinen entre si para indicar que ya concluyeron una actividad o
comunicarse datos del resultado de procesamiento de uno de ellos.

Cuando se estudiaron los procesos, el unico esquema de coordinacion era
con la llamada wait, waitpid y exit, que permitia que el proceso padre
esperar a su hijo y en un momento, con ayuda de la llamada exit, se
podian comunicar padre e hijo un valor.

En este tema se va a estudiar esquemas de comunicacin entre procesos que
permiten comunicar datos entre procesos locales (es decir ejecutandose
en la misma computadora) y eventos entre procesos. Aun se supone que
no hay concurrencia entre procesos, o si se llega a presentar, por el
momento se ignora.


+ Tipos de procesamiento.

Cuando varios procesos estan trabajando en conjunto, puede ser que
algunos de ellos invoquen a otro proceso.

Cuando un proceso A invoca a otro proceso B, y dicho proceso A espera
 que B entrego todos los datos, se esta realizando un procesamiento sncrono.

Un procesamiento por entubamiento consiste en que varios procesos se
encadenen y cada uno realiza una parte del procesamiento, como las lineas
de ensamblado de carros. UNIX implanta este tipo de procesamiento utilizando
pipes y FIFOS.

Cuando un proceso A invoca a otro proceso B, pero no espera a que concluya
el proceso B y, recibe datos de ste cuando le notifica un evento, se
conoce como procesamiento asncrono.

En UNIX, los eventos entre procesos se conocen como seales, y son eventos
generados por uno o mas procesos y que son atendidos por procesos que
se interesaron en estas seales. UNIX utiliza el concepto de seales o signals
para definir "interrupciones de software".

+ Entubamientos o Pipes.

A partir de este punto, se manejara el concepto de entubamiento como pipe.

Un pipe para UNIX es un esquema que permite comunicar a dos procesos.

Como se estudio en el tema 4, los comandos UNIX estn diseados para que
en el caso de que no se proporcione un argumento de entrada, entonces la
entrada la toma de la entrada estndar. Y los resultados, cuando son correctos,
los envia a la salida estndar.

Es tpica esta forma de trabajar.

Redireccionar la entrada estndar de un archivo y enviar el resultado a la
salida estndar.

cat < /etc/passwd > archivo

Luego, aplicar una busqueda de un patrn sobre el archivo.

grep root archivo
root:x:0:1:Super-User:/:/sbin/sh

Como se observa, este procesamiento implico dos pasos, y la generacin de
un archivo temporal. 

Se puede optimizar este procesamiento utilizando un pipe.

cat < /etc/passwd | grep root 

Al utilizar el metacaracter |, se indica al shell de UNIX que la
salida estndar se redireccione no a pantalla y tampoco a un archivo,
sino que la direccione a la entrada estndar de otro proceso. Dicho
proceso es el comando grep, que tendr definida su entrada estndar
a partir de la salida estndar del proceso cat.

En este caso, el proceso cat y grep estan ejecutandose en paralelo.
solo que el comando grep se bloquea hasta que obtenga algo de la entrada
estndar, es decir, de la salida del comando cat.

El nmero de procesos que se pueden encadenar en un pipe no tiene
lmite, se podria realizar lo siguiente:

cat < /etc/passwd | grep root  | cut -d: -f3
0

este comando proporciona como salida el userid de root.


En resument, un pipe es un mecanismo de comunicacin entre procesos que
permite que varios procesos trabajen en conjunto y cada uno de ellos
se comunican datos utilizando como medio de comunicacion la entrada y
salida estndar.


Ejercicios.
1. Ilustrar graficamente el funcionamiento de pipes, incluyendo en
la ilustracin, las tablas de descriptores de archivos de los procesos.
2. Explicar el funcionamiento del comando

zcat < tema5.taz  | tar xvf -

que diferencia hay con:

mv tema5.taz tema5.tar.Z
uncompress tema5.tar.Z
tar xvf tema5

?
(nota, el comando zcat sirve para desplegar el contenido de un archivo que
se comprimi, uncompress aplica descompresion de un archivo y el comando
tar xvf extrae de un archivo un conjunto de archivos ).

3. Que hace el comando

tar cvf - apuntes src | compress > tema5.taz 
?

Proporcionar el conjunto de comandos que harian una tarea similar.


+ Llamadas al sistema para uso de pipes. pipe(2)

Para poder crear una tuberia o pipe entre procesos se debe utilizar
la llamada al sistema pipe.

#include <unistd.h>
int pipe(int fildes[2])

El argumento es un arreglo de descriptores de archivos, de tamao 2. 
En la celda 0 del arreglo, se retorna el descriptor de archivos del
cual se van a leer los datos y en la celda 1 del arreglo, se retorna el
descriptor de archivos al cual se escriben los datos. Si la llamada tiene
exito, retorna 0, de lo contrario 1.

El siguiente ejemplo crea un pipe entre la entrada y salida estndar de
un mismo proceso.


/*Ejemplo1, Crear un pipe con la entrada y salida estandar del proceso
	actual.
*/ 

#include <unistd.h>

main(){

	int fildes[2]; /*arreglo de descriptores de archivos*/
	int rtn;
	char mensaje[]="hola pipe\n";
	char buffer[128];
	int nbytes;

	/*Crear el pipe*/

	rtn=pipe(fildes);

	if (rtn < 0) {
		perror("No se puede crear el pipe");
		exit(1);
	}

	/*Checar los descriptores de archivos del pipe*/

	printf("Pipe read %d \n Pipe write %d\n",fildes[0],fildes[1]);

	/*Escribir algo en el pipe*/

	if ( write(fildes[1],mensaje,strlen(mensaje) ) != strlen(mensaje) ) {
		perror("Error al escribir al pipe");
		exit(2);
	}

	if ( 
		(nbytes=read(fildes[0],buffer,128)) <= 0
	) {
		perror("Error al leer el pipe\n");
		exit(3);
	}

	write(1,buffer,nbytes);

	exit(0);	
}


Para que dos procesos se comuniquen con ayuda de un pipe, dichos procesos
deben ser creados por un proceso padre que generar el pipe. 

El primer esquema de comunicacin es cuando un proceso padre crea a 
dos procesos hijos. Un proceso hijo (P1)
realiza la escritura sobre el descriptor
de archivos de escritura del pipe, y el otro proceso (P2) realiza la lectura
sobre el descriptor de archivos de lectura del pipe.

--	  ------       --
P1   --->| Pipe |----> P2
-- 	  ------       --

Esta forma de procesamiento es unidireccional, en donde el proceso servidor
no retorna la respuesta al cliente. Un anlogo es a un procedimiento.

En resumen, para hacer un "procedimiento cliente/servidor" con pipe, se
deben realizar los siguiente pasos:

1. El proceso padre crea un pipe.
2. El proceso padre crea dos hijos.
3. El proceso hijo cliente, debe cerrar el extremo de lectura del pipe.
4. El proceso padre servidor, debe cerrar el extremo de escritura del pipe.
5. El proceso hijo cliente, genera la informacin y la envia por medio
de un write al extremo de escritura del pipe.
6. El proceso padre servidor, lee el extremo de lectura del pipe (se queda
bloqueado hasta que alguien le escribe) y procesa la informacin que recibe.
7. El proceso cliente no espera respuesta del servidor.
8. El proceso servidor no genera respuesta al cliente.
9. Ambos procesos cierran los extremos restantes del pipe.

En ejercicios posteriores se desarrolla esta idea para escribir una 
funcin genrica para invocar procedimientos. 

El siguiente programa muestra este concepto.

#include <unistd.h>

main() {

	int pids[2];
	int pipefd[2];
	int rtn,i;

	
	/* 1. El proceso padre crea un pipe.
	Crear el pipe*/	
	rtn= pipe(pipefd);

	if (rtn< 0) {
		perror("No se puede generar el pipe");
		exit(1);
	}

	/*generar procesos hijos*/
	for (i=0;i<=1;i++) {

		/*2. El proceso padre crea dos hijos.*/

		printf("Proceso %d Bifurcando..\n",getpid());
		rtn=fork();

		if (rtn<0) {
			perror("no puedo generar un proceso hijo");
			exit(2);
		}
		if (rtn==0) {

			if (i==0) { /*proceso que genera informacion*/

				/*Este proceso se conoce como el proceso
				cliente */

				char buffer[]="hola mundo\n";
				char respuesta[128];
				int nbytes;

				printf("Proceso %d genera mensaje %s\n",
				getpid(),buffer);

				/*
				3. El proceso hijo cliente, 
				debe cerrar el extremo de lectura del pipe.
				*/
				close(pipefd[0]);
				/*
				5. El proceso hijo cliente, genera la
				 informacin y la envia por medio
				de un write al extremo de escritura del pipe.
				*/
				write(pipefd[1],buffer,strlen(buffer));
				/*9. Ambos procesos cierran los 
				extremos restantes del pipe.*/

				close(pipefd[1]);

				exit(0);
			} else { /*Proceso servidor */
				char mensaje[128];
				int nbytes;
				int j;

				printf("Proceso %d esperando mensaje\n",
				getpid());
			
				/*4. El proceso padre servidor, debe cerrar el
				 extremo de escritura del pipe.*/
				close(pipefd[1]);
				/*
				6. El proceso padre servidor, lee el extremo de
				 lectura del pipe (se queda
				bloqueado hasta que alguien le escribe) y
				 procesa la informacin que recibe.*/
				nbytes=
				read(pipefd[0],mensaje,128);

				/*Poner caracter nulo al final*/
				mensaje[nbytes]=0;
				printf("proceso %d procesando mensaje %s de %d bytes\n",
				getpid(),mensaje,nbytes);

				/*Poner el mensaje a mayusculas*/
				for(j=0;j<strlen(mensaje);j++) {
					mensaje[j]=toupper(mensaje[j]);
				}

				printf("proceso %d convierte mensaje %s\n",
				getpid(),mensaje);
				/*9. Ambos procesos cierran los 
				extremos restantes del pipe.*/

				close(pipefd[0]);
				exit(0);

			}
		} else {
			pids[i]=rtn;
		}
	}

	/*Esperar a los hijos*/
	for (i=0;i<=1;i++) {
		printf("Esperando a mis hijos...\n");
		wait((int *)0);
	}

	exit(0);	

}
	
La salida de este programa es la siguiente:
Ejemplo2
Proceso 361 Bifurcando..
Proceso 362 genera mensaje hola mundo

Proceso 361 Bifurcando..
Proceso 363 esperando mensaje
proceso 363 procesando mensaje hola mundo
 de 11 bytes
proceso 363 convierte mensaje HOLA MUNDO

Esperando a mis hijos...
Esperando a mis hijos...


Si se desea realizar un proceso servidor que responda al cliente, se
deben crear dos pipes.

El siguiente esquema muestra esta estructura:



--	  ------       --
P1   --->| Pipe1 |----> P2
 	  ------
	  -----
     <--| Pipe2 |<----  
-- 	  ------       --

 	        
Esta es una forma bidireccional, de tal manera que el cliente puede
enviar un mensaje a procesar y recibe respuests.

Para poder lograr esto con pipes, se deben realizar los siguientes pasos:


1. El proceso padre crea dos pipes.
2. El proceso padre crea dos hijos.
3. El proceso hijo cliente, debe cerrar el extremo de lectura del pipe1
y el extremo de escritura del pipe2.
4. El proceso padre servidor, debe cerrar el extremo de escritura del pipe1
y el extremo de lectura del pipe2.
5. El proceso hijo cliente, genera la informacin y la envia por medio
de un write al extremo de escritura del pipe1.
6. El proceso padre servidor, lee el extremo de lectura del pipe1 (se queda
bloqueado hasta que alguien le escribe) y procesa la informacin que recibe.
7. El proceso cliente espera respuesta del servidor leyendo del extremo
de lectura del pipe2.
8. El proceso servidor escribe la respuesta al cliente, escribiendo al
extremo de escritrua del pipe2.
9. Ambos procesos cierran los extremos restantes del pipe.

Este ejemplo muestra lo anterior:
 

#include <unistd.h>

main() {

	int pids[2];
	int pipefd1[2];
	int pipefd2[2];
	int rtn,i;

	/*1. El proceso padre crea dos pipes.
	 */	
	rtn= pipe(pipefd1);

	if (rtn< 0) {
		perror("No se puede generar el pipe 1");
		exit(1);
	}

	/*Crear el pipe*/	
	rtn= pipe(pipefd2);

	if (rtn< 0) {
		perror("No se puede generar el pipe 2");
		exit(1);
	}

	/*generar procesos hijos*/
	for (i=0;i<=1;i++) {
		/*2. El proceso padre crea dos hijos.*/
		printf("Proceso %d Bifurcando..\n",getpid());
		rtn=fork();

		if (rtn<0) {
			perror("no puedo generar un proceso hijo");
			exit(2);
		}
		if (rtn==0) {

			if (i==0) { /*proceso que genera informacion*/

				char buffer[]="hola mundo\n";
				char respuesta[128];
				int nbytes;

				printf("Proceso %d genera mensaje %s\n",
				getpid(),buffer);

				/*3. El proceso hijo cliente, debe cerrar el
				 extremo de lectura del pipe1
				y el extremo de escritura del pipe2.

 				*/
				close(pipefd1[0]);
				close(pipefd2[1]);

				/*
				5. El proceso hijo cliente, genera la informacin 
				y la envia por medio
				de un write al extremo de escritura del pipe1.
				*/

				write(pipefd1[1],buffer,strlen(buffer));
				printf("Proceso %d esperando respuesta\n",
				getpid()); 

				/*7. El proceso cliente espera respuesta del 
				servidor leyendo del extremo de lectura del pipe2.
				*/
				nbytes=read(pipefd2[0],respuesta,128);
				/*se pone el caracter nulo en la ultima posicion*/
				respuesta[nbytes]=0;
				printf("proceso %d leyendo respuesta %s de %d bytes\n",
				getpid(),respuesta,nbytes);
/*9. Ambos procesos cierran los extremos restantes del pipe.*/
				close(pipefd1[1]);
				close(pipefd2[0]);
				exit(0);
			} else { /*Proceso servidor, se arranca primero */
				char mensaje[128];
				int nbytes;
				int j;

				printf("Proceso %d esperando mensaje\n",
				getpid());
			
				/* 4. El proceso padre servidor, debe cerrar el
				 extremo de escritura del pipe1
				y el extremo de lectura del pipe2.  	*/					
				close(pipefd1[1]);
				close(pipefd2[0]);

				/*6. El proceso padre servidor, lee el 
				extremo de lectura del pipe1 (se queda
				bloqueado hasta que alguien le escribe) 
				y procesa la informacin que recibe.*/

				nbytes=
				read(pipefd1[0],mensaje,128);

				/*Poner caracter nulo al final*/
				mensaje[nbytes]=0;
				printf("proceso %d procesando mensaje %s de %d bytes\n",
				getpid(),mensaje,nbytes);

				/*Poner el mensaje a mayusculas*/
				for(j=0;j<strlen(mensaje);j++) {
					mensaje[j]=toupper(mensaje[j]);
				}

				printf("proceso %d enviando mensaje %s\n",
				getpid(),mensaje);

				/* 8. El proceso servidor escribe la respuesta al cliente, 
				escribiendo al extremo de escritrua del pipe2.
				*/
				write(pipefd2[1],mensaje,strlen(mensaje));
/*9. Ambos procesos cierran los extremos restantes del pipe.*/

				close(pipefd1[0]);
				close(pipefd2[1]);
				exit(0);
			}
		} else {
			pids[i]=rtn;
		}
	}

	/*Esperar a los hijos*/
	for (i=0;i<=1;i++) {
		printf("Esperando a mis hijos...\n");
		wait((int *)0);
	}

	exit(0);	

}

Ejemplo3
Proceso 445 Bifurcando..
Proceso 446 genera mensaje hola mundo

Proceso 446 esperando respuesta
Proceso 445 Bifurcando..
Proceso 447 esperando mensaje
proceso 447 procesando mensaje hola mundo
 de 11 bytes
proceso 447 enviando mensaje HOLA MUNDO

proceso 446 leyendo respuesta HOLA MUNDO
 de 11 bytes
Esperando a mis hijos...
Esperando a mis hijos...


Tambien es posible que un proceso cliente invoque a un proceso servidor, y al mismo tiempo, el
proceso servidor se vuelva cliente de otro proceso servidor.

El siguiente esquema muestra este tipo de procesamiento:



--	  ------     --      -------	
P1 --->	 | Pipe1|--> P2---->| Pipe2 |
-- 	  ------     --      -------

El siguiente programa muestra esta forma de procesamiento:


#include <unistd.h>

/*Procesamiento utilizando varios pipes encadenados*/

main() {

int pids[2];
int pipefd1[2];
int pipefd2[2];
int rtn,i;

	/*1. El proceso padre crea dos pipes.
	 */	
	rtn= pipe(pipefd1);

	if (rtn< 0) {
		perror("No se puede generar el pipe 1");
		exit(1);
	}

	/*Crear el pipe*/	
	rtn= pipe(pipefd2);

	if (rtn< 0) {
		perror("No se puede generar el pipe 2");
		exit(1);
	}

	for (i=0;i<3;i++) {

		rtn=fork();		
		if (rtn<0) {
			perror("no puedo generar un proceso hijo");
			exit(2);
		}
		if (rtn==0) {

			if (i==0) {

				/*este proceso cierra 
				extremo de lectura del pipe1*/
				close(pipefd1[0]);
				/*se cierran los extremos del otro pipe*/
				close(pipefd2[0]);
				close(pipefd2[1]);

				/*y escribe al extremo de escritura
				del pipe1*/

				rtn= write(pipefd1[1],&i,sizeof(i));
	
				if (rtn< 0) {
					perror("Error al escribir al pipe1");
					exit(1);
				}

				printf("Proceso %d escribe %d bytes con valor %d\n",
					getpid(),rtn,i);
				/*cierra extremo de escritura*/
				close(pipefd1[1]);
				exit(0);
			} else if ( i==1) {
				int j;
				/*este proceso cierra extremo de escritura
				del pipe1*/
				close(pipefd1[1]);
				/*cierra el extremo de lectura del pipe2*/
				close(pipefd2[0]);

				/*y lee el mensaje del cliente*/
				rtn=
				read(pipefd1[0], &j,sizeof(j));

				if (rtn< 0) {
					perror("Error al leer el pipe1");
					exit(1);
				}
				printf("Proceso %d lee %d bytes con valor %d\n",
                                        getpid(),rtn,j);

				/*se incrementa en 2 el valor que se recibe*/
				j=j+2;
			
				/*se escribe al pipe2 el valor de j*/
				rtn= write(pipefd2[1],&j,sizeof(j));
	
				if (rtn< 0) {
					perror("Error al escribir al pipe2");
					exit(1);
				}
				printf("Proceso %d escribe %d bytes con valor %d\n",
					getpid(),rtn,j);

				close(pipefd1[0]);
				close(pipefd2[1]);
				exit(0);
	
			} else if (i==2) {
				int k;

				/*este proceso cierra los extremos
				del pipe1*/

				close(pipefd1[0]);
				close(pipefd1[1]);
			
				/*y el extremo de escritura del pipe2*/
				close(pipefd2[1]);
				/*y lee el mensaje del cliente*/
				rtn=
				read(pipefd2[0], &k,sizeof(k));

				if (rtn< 0) {
					perror("Error al leer el pipe1");
					exit(1);
				}
				k+=2;
				printf("Proceso %d lee %d bytes con valor %d\n",
                                        getpid(),rtn,k);
			
				/*cierra los extremos*/
				close(pipefd2[1]);	
				exit(0);
			}
		} else {
			printf("Proceso %d genero hijo %d\n",getpid(),rtn);
		}
	}

	/*Esperar a los hijos*/
	for (i=0;i<=2;i++) {
		printf("Esperando a mis hijos...\n");
		wait((int *)0);
	}

	exit(0);	
}

Este programa, genera la siguiente salida:

Ejemplo4
Proceso 574 escribe 4 bytes con valor 0
Proceso 573 genero hijo 574
Proceso 575 lee 4 bytes con valor 0
Proceso 575 escribe 4 bytes con valor 2
Proceso 573 genero hijo 575
Proceso 576 lee 4 bytes con valor 4
Proceso 573 genero hijo 576
Esperando a mis hijos...
Esperando a mis hijos...
Esperando a mis hijos...

Es posible que se quieran encadenar comandos ya creados utilizando entubamientos.
Para esto, se debe combinar redireccionamiento con pipes.

El siguiente ejemplo crea un archivo y lo comprime, enviando la salida a un archivo taz.
(esto es equivalente a: tar cvf - apuntes src | compress > tema5.taz )
/*Ejemplo5 Pone en un solo archivo varios archivos y directorios
y los comprime*/

#include <unistd.h>

/*este es el comando tar cvf - */
char  comando1[]= "/bin/tar";
char  arcmd1[]="-cvf";
char  arcmd2[]="-";
char  comando2[]= "/bin/compress";
/*esto tambien se hubiera realizado con directivas define y
sin usar variables globales*/

void juntar(char ** lista,int numArchs) {
	char * args[1024];
	int limit=numArchs+2;
	int i,rtn;

	args[0]=comando1;
	args[1]=arcmd1;
	args[2]=arcmd2;

	for (i=0;i< numArchs; i++) {
		args[i+3]= lista[i];
		if ( (i+1 ) >= 1023 ) {
			limit=1022;
			break;
		}
	}

	args[limit+1]= (char *) 0;
	
	
	rtn=execv(comando1,args);

	if (rtn < 0) {
		perror("Error en execv tar");
		exit(5);
	}

}

void comprimir( char * archivo) {
	int rtn;
	int fd;

	fd= creat(archivo,0640);

	if (fd < 0) {
		perror("Error en creat");
		exit(5);
	}

	rtn= dup2(fd,1);

	if (rtn<0) {
		perror("Error en dup2");
		exit(5);
	}
	close(fd);

	rtn=
	execl(comando2,comando2, (char * )0);

	if (rtn < 0) {
		perror("Error en execl compress");
		exit(5);
	}
}

int main(int argc, char * argv[]) {

	int rtn,i;
	int pipefd[2];
	char * destino;
	char ** listaArchivos;
	if (argc <= 3) {
		printf("Ejemplo5 destino lista-archivos ...\n");
		exit(1);
	}

	destino=argv[1];
	listaArchivos= &argv[2];

	rtn= pipe(pipefd);		

	if (rtn < 0) {
		perror("Error al crear pipe");
		exit(2);
	}

	for (i=0;i<=1;i++) {

		rtn= fork();

		if (rtn < 0) {
			perror("Error en fork");
			exit(3);
		}

		if (rtn == 0) {
			if (i==0) {

				/*direccionar la salida estandar
				al pipe de escritura*/
				rtn= dup2(pipefd[1],1 ) ;

				if (rtn < 0) {
					perror("Error al redireccionar pipe");
					exit(4);
				} 
				/*cerrar pipe de escritura*/
				/*cerrar el extremo de lectura del pipe*/
				close(pipefd[0]);
				close(pipefd[1]);
				/*invocar comando tar*/
				juntar(listaArchivos,argc-2);
				exit(0);
			} else if (i==1) {

				/*direccionar la entrada estandar
				al extremo de lectura del pipe*/
				rtn= dup2(pipefd[0],0 ) ;
				if (rtn < 0) {
					perror("Error al redireccionar pipe");
					exit(4);
				}
				close(pipefd[0]);
				/*cerrar el extremo de escritura del pipe*/
				close(pipefd[1]);
				comprimir(destino);
				exit(0);
			}
		} 
	}

	/*el proceso padre debe liberar los extremos del pipe*/
	close(pipefd[0]);
	/*cerrar el extremo de escritura del pipe*/
	close(pipefd[1]);
	for (i=0;i<2;i++){
		wait((int *) 0);
	}
	exit(0);	
}




Ejercicios:

1. En base al ejemplo 5, construir un programa que efectue lo equivalente a la siguiente linea de comandos.

who | grep usuario | wc -l 

Es vlido utilizar llamadas exec para invocar al comando who, grep y wc. Proporcionar el nombre del usuario como
el primer argumento del programa.

2. Dado el ejemplo4, extenderlo para que el valor de la variable i que se escribe en el pipe1, sea
retornado con su valor de 4 al proceso hijo 0. Analizar bien este problema ya que implica
la creacin de 4 pipes.

3. Escribir, basado en el ejemplo5, un programa que descomprima un archivo en formato taz.

4. Investigar las funciones popen y pclose y dar un programa ejemplo de estas funciones.

5. Investigar para que sirve el comando tee.

6. Por qu es tan importante cerrar las entradas del pipe.


+ Entubamientos con nombre (named pipes) o FIFOs.

Con tuberas o pipes, es necesario que la tuberia que se comperte entre
procesos sea generada por un proceso padre y despus los hijos lo manipulen.
Pero es necesario que en algunos casos un proceso diferente, pueda enviar
datos a otro proceso, sin que sea necesario que ambos procesos sean del mismo
proceso padre.

Para esto UNIX proporciona un IPC denominado entubamiento con nombre 
(named pipe) que permite que un proceso introduzca datos a una tuberia que
fue creada por un proceso diferente.

Para lograr esto, se debe crear un named pipe o FIFO dentro de un directorio.
Cuando algun proceso necesite realizar escritura o lectura de datos, debe
abrir este  FIFO, proporcionando la ruta del FIFO.

La llamada del kernel que permite crear un FIFO es mknod(2). Esta llamada
tiene el siguiente formato:

#include <sys/stat.h>
int mknod(const char * path, mode_t mode, dev_t dev)

el primer argumento es una cadena de caracteres que representa la ruta del FIFO,
el segundo argumento debe tener un valor entero y normalmente se puede utilizar
S_IFIFO combinado con el esquema de permisos, y el  tercer argumento se pone
con un valor de 0.

El siguiente programa crea un FIFO, con permisos de lectura y escritura
para el propietario, y lectura para los demas usuarios.
#include <sys/stat.h>

/*Ejemplo6, crea un named PIPE */

main(int argc, char * argv[]) {

	int rtn;

	if (argc <= 1) {
		printf("Ejemplo6 rutaFIFO \n");
		exit(1);
	}

	/*Indica al kernel que debe crear un archivo de tipo PIPE
	  El segundo parametro es una mascar de bits.
	  Se efectua una operacion OR de bits para indicar que
	  es un FIFO y que los permisos son de lectura/escritura
	  propietario, lectura para grupo y lectura para otros
	*/
	rtn= mknod(argv[1],S_IFIFO | 0644, 0);

	if (rtn < 0) {

		perror("Error al crear el named pipe");
		exit(1);
	}	

	exit(0);
}

Este programa se ejecuta como

Ejemplo6 /tmp/namedpipe

Es necesario notar que el Named pipe no desaparece, a diferencia de un
PIPE, su referencia queda en el sistema de archivos. Checar al correr
el Ejemplo6 el contenido de /tmp, debe aperecer un archivo namedpipe con
la siguiente mascar de permisos:
prw-r-r-

la letra p indica que es un named pipe.

Un comando del sistema operativo que efectua el mismo procedimiento que
el Ejemplo6 es mknod(1M). Por ejemplo:

mknod /tmp/namedpipe1

 
Dado que el esquema de tuberias sirve para efectuar procesamiento de tipo
cliente/servidor, es necesario indicar como asociar un proceso servidor
y cliente a un named pipe.

Para que un proceso sea servidor, segn la filosofa adoptada en este captulo,
puede ser un servidor que lea un mensaje y no regrese respuesta o uno
que si regrese respuesta. En caso de que se un proceso servidor que regrese
respuesta se deben seguir los siguientes pasos:

1. El proceso servidor debe crear el named pipe con la llamada mknod(2).
2. El procesos servidor debe abrir de solo lectura al named pipe.
3. El proceso servidor se queda bloqueado en la llamada open hasta que
un proceso cliente abra al named pipe para escritura.
4. El proceso servidor, al desbloquearse, lee el mensaje del named pipe.
5. Procesa el mensaje.
6. Cierra el named pipe con una llamada close.
7. Destruye al named pipe con la llamada unlink.

El siguiente ejemplo muestra este tipo de logica.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

main(int argc, char * argv[]) {

	int rtn;
	int fd;
	int nbytes,j;
	char mensaje[128];

	if (argc <= 1) {
		printf("Ejemplo7 rutaFIFO \n");
		exit(1);
	}

	/*Indica al kernel que debe crear un archivo de tipo PIPE
	  El segundo parametro es una mascar de bits.
	  Se efectua una operacion OR de bits para indicar que
	  es un FIFO y que los permisos son de lectura/escritura
	  propietario, lectura para grupo y lectura para otros
	*/
	rtn= mknod(argv[1],S_IFIFO | 0644, 0);

	if (rtn < 0) {

		perror("Error al crear el named pipe");
		exit(1);
	}

	/*obtener el descriptor de archivo de lectura*/
	printf("PID %d Esperando el mensaje\n", getpid());
	fd= open(argv[1],O_RDONLY);	

	if (fd < 0) {
		perror("Error al abrir extremo de lectura del pipe");
	}

	printf("PID %d un proceso cliente tiene un mensaje, a leer\n", getpid());

	/*leer el named pipe*/
	nbytes= read(fd,mensaje,128);
	
	if (nbytes<=0) {
		perror("Error al leer el named pipe");
		exit(2);
	}

	mensaje[nbytes]=0;	
	printf("Leyendo %d bytes %s\n",nbytes,mensaje);

	for(j=0;j<strlen(mensaje);j++) {
        	mensaje[j]=toupper(mensaje[j]);
        }

	printf("proceso %d convierte mensaje %s\n", getpid(),mensaje);

	close(fd);

	/*borrar el named pipe*/	
	unlink(argv[1]);

	exit(0);
}

Para probar este ejemplo, correr:
$ Ejemplo7 /tmp/gus
PID 463 Esperando el mensaje

al momento que se corre este programa, se queda bloqueado. Para enviar
el mensaje, se puede aplicar el comando cat direccionado al named pipe

cat > /tmp/gus

y entonces, la llamada open se desbloquea, ya que un proceso abre para
escritura al named pipe, dando este mensaje.
PID 463 un proceso cliente tiene un mensaje, a leer

cuando en el comando cat se pone el mensaje
hola<enter>

el Ejemplo7 contesta

Leyendo 5 bytes hola

proceso 463 convierte mensaje HOLA

Para codificar el proceso cliente del Ejemplo7.
Este procesos debe abrir unicamente el named pipe de lectura, como
si fuera un archivo normal.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

main(int argc, char * argv[]) {

	int fd;
	int nbytes,j;
	char mensaje[]="hola\n";

	if (argc <= 1) {
		printf("Ejemplo7 rutaFIFO \n");
		exit(1);
	}


	/*Abrir de escritura al pipe*/

	fd=open(argv[1],O_WRONLY);

	if (fd< 0) {
		perror("Error al abrir el named pipe");
		exit(2);
	}

	nbytes=write(fd,mensaje,strlen(mensaje));

	if (nbytes < 0) {

	}

	printf("PID %d escribiendo %d bytes",getpid(), nbytes);
	close(fd);

	exit(0);
}

Para probar este ejemplo, es necesario ejecutar el programa Ejemplo7 primero
Ejemplo7 /tmp/gus
PID 502 Esperando el mensaje


y luego ejecutar el Ejemplo8

Ejemplo8 /tmp/gus
PID 507 escribiendo 5 bytes

lo cual lleva que el Ejemplo7 responda:

PID 502 un proceso cliente tiene un mensaje, a leer
Leyendo 5 bytes hola

proceso 502 convierte mensaje HOLA

Se puede ejectuar el Ejemplo8 sin necesidad del Ejemplo7 y utilizando
el comando cat y direccionamiento, para esto, ejecutar los siguientes pasos:


mknod /tmp/gus p
Ejemplo8 /tmp/gus &
cat < /tmp/gus

el primer comando crea un named pipe.
el segundo comando manda a segundo plano al Ejemplo8, este se queda
bloqueado en llamada open hasta que otro proceso abra el archivo para lectura.
luego entonces, el tercer comando que es un cat que lee su entradad del
named pipe, desbloquea al Ejemplo8.

Es vlido que mltiples procesos Ejemplo8 traten de escribir informacin,
por ejemplo, teclear 5 veces Ejemplo8 /tmp/gus &

y luego 
cat < /tmp/gus

esto lleva a que el comando cat mande a imprimir 5 veces hola.

De lo anterior se desprende que:
1. Si un proceso abre un named pipe de lectura, este se queda bloqueado en
la llamada open hasta que otro proceso abra al named pipe de escritura.
2. Si un proceso abre un named pipe de escritura, este se queda bloquedo en
la llamada open hasta que otro proceso efectua un open de lectura sobre
el named pipe.
3. Se puede anular el comportamiento 1 y 2 si la llamada open se invoca
con la bandera O_NONBLOCK.

El punto 3 se puede aplicar cuando un proceso no quiera esperar a que
otro proceso escriba o lea el named pipe. Bajo estas condiciones, el 
proceso, cuando quiera leer o escribir un dato, debe realizaro con escrutinio
o polling.


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>

/*Ejemplo9, realiza una lectura de un pipe, pero sin que se bloquee en
un open, sino que utiliza escrutinio o polling con la llamada select
*/

main(int argc, char * argv[]) {

	int rtn;
	int fd;
	int nbytes,j;
	char mensaje[128];
	fd_set readset; /*esta variable se utiliza para la llamada select*/

	if (argc <= 1) {
		printf("Ejemplo9 rutaFIFO \n");
		exit(1);
	}

	/*Indica al kernel que debe crear un archivo de tipo PIPE
	  El segundo parametro es una mascar de bits.
	  Se efectua una operacion OR de bits para indicar que
	  es un FIFO y que los permisos son de lectura/escritura
	  propietario, lectura para grupo y lectura para otros
	*/
	rtn= mknod(argv[1],S_IFIFO | 0644, 0);

	if (rtn < 0) {

		perror("Error al crear el named pipe");
		exit(1);
	}

	printf("PID %d Abriendo el named PIPE\n", getpid());
	/*obtener el descriptor de archivo de lectura y no bloquerase*/
	fd= open(argv[1],O_RDONLY|O_NONBLOCK);	

	if (fd < 0) {
		perror("Error al abrir extremo de lectura del pipe");
		exit(2);
	}

	/*Realizar escrutinio o polling*/
	printf("PID %d Esperando el mensaje con un select...\n", getpid());
	FD_ZERO(&readset);
	FD_SET(fd,&readset);
	rtn=select(fd+1,&readset,0,0,0);

	if (rtn< 0) {
		perror("Error en escrutinio");
		exit(2);
	}
	printf("PID %d un proceso cliente tiene un mensaje, a leer\n", getpid());

	/*leer el named pipe*/
	nbytes= read(fd,mensaje,128);
	
	if (nbytes<=0) {
		perror("Error al leer el named pipe");
		exit(2);
	}

	mensaje[nbytes]=0;	
	printf("Leyendo %d bytes %s\n",nbytes,mensaje);

	for(j=0;j<strlen(mensaje);j++) {
        	mensaje[j]=toupper(mensaje[j]);
        }

	printf("proceso %d convierte mensaje %s\n", getpid(),mensaje);

	close(fd);

	/*borrar el named pipe*/	
	unlink(argv[1]);

	exit(0);
}

Este programa, pera ejecutarlo, se efectua igual que el Ejemplo7, y
se puede utilizar el Ejemplo8 para probarlo.
El punto importante del Ejemplo9 es la utilizacion de la llamada select(3C)
Dicha llamada sirve para realizar polling. Las lineas:
 
	FD_ZERO(&readset);
	FD_SET(fd,&readset);
	rtn=select(fd+1,&readset,0,0,0);
implantan esta tecnica.

Una llamada select checa si se esta efectuando alguna operacin sobre
un descriptor de archivos dado. En este caso el descriptor de archivo es fd.
El primer parametro indica esto y dice que select debe estar realizando un
escrutinio de 0 a fd+1 descriptores de archivos. Para "inscribir" a aquellos
descriptores de archivos a los que se debe aplicar escrutinio, se debe
utilizar una estrctura de datos denominada fd_set. Las macros FD_ZERO y
fd_SET, permiten, limpiar la estructura fd_set e inscribir un descriptor
de archivos en la estructura fd_set.


Para que se pueda implantar un conjunto de procesos cliente/servidor en el
cual se envie un mensaje y se obtenga una respuesta, es necesario crear dos
named pipe en el sistema operativo.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

main(int argc, char * argv[]) {

	int rtn;
	int fd[2];
	int nbytes,j;
	char mensaje[128];

	if (argc <= 2) {
		printf("Ejemplo10 rutaFIFOIn rutaFIFOOut \n");
		exit(1);
	}

	/*Indica al kernel que debe crear un archivo de tipo PIPE
	  El segundo parametro es una mascara de bits.
	  Se efectua una operacion OR de bits para indicar que
	  es un FIFO y que los permisos son de lectura/escritura
	  propietario, lectura para grupo y lectura para otros
	*/
	rtn= mknod(argv[1],S_IFIFO | 0644, 0);

	if (rtn < 0) {
		perror("Error al crear el named pipe de lectura");
		exit(1);
	}

	/*Indica al kernel que debe crear un archivo de tipo PIPE
	  El segundo parametro es una mascara de bits.
	  Se efectua una operacion OR de bits para indicar que
	  es un FIFO y que los permisos son de lectura/escritura
	  propietario, lectura para grupo y lectura para otros
	*/
	rtn=mknod(argv[2],S_IFIFO | 0644,0);
	if (rtn < 0) {
		perror("Error al crear el named pipe de escritura");
		exit(1);
	}
	

	/*obtener el descriptor de archivo de lectura*/
	printf("PID %d Bloquado en open %s para lectura\n", getpid(),argv[1]);
	fd[0]= open(argv[1],O_RDONLY);	

	if (fd < 0) {
		perror("Error al abrir extremo de lectura del pipe");
	}

		


	printf("PID %d Bloquado en open %s para escritura\n", getpid(),argv[2]);
	fd[1]=open(argv[2],O_WRONLY);

	printf("PID %d un proceso cliente tiene un mensaje, a leer\n", getpid());

	/*leer el named pipe*/
	nbytes= read(fd[0],mensaje,128);
	
	if (nbytes<=0) {
		perror("Error al leer el named pipe");
		exit(2);
	}

	mensaje[nbytes]=0;	
	printf("Leyendo %d bytes %s\n",nbytes,mensaje);

	for(j=0;j<strlen(mensaje);j++) {
        	mensaje[j]=toupper(mensaje[j]);
        }

	printf("proceso %d convierte mensaje %s\n", getpid(),mensaje);

	write(fd[1],mensaje,strlen(mensaje));

	close(fd[0]);
	close(fd[1]);

	/*borrar el named pipe*/	
	unlink(argv[1]);
	unlink(argv[2]);

	exit(0);
}

El proceso cliente tiene que hacer lo contrario, abrir el named pipe de entrada
para escritura y el named pipe de salida para lectura.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

main(int argc, char * argv[]) {

	int fd[2];
	int nbytes,j;
	char mensaje[]="hola\n";

	if (argc <= 2) {
		printf("Ejemplo11 rutaFIFOin rutaFIFOout \n");
		exit(1);
	}


	/*Abrir de escritura al pipe*/

	fd[0]=open(argv[1],O_WRONLY);

	if (fd[0]< 0) {
		perror("Error al abrir el named pipe de escritura");
		exit(2);
	}

	fd[1]=open(argv[2],O_RDONLY);
	if (fd[1]< 0) {
		perror("Error al abrir el named pipe de lectura");
		exit(2);
	}

	nbytes=write(fd[0],mensaje,strlen(mensaje));

	if (nbytes < 0) {
		perror("Error al escribir el named pipe de escritura");
		exit(3);
	}

	printf("PID %d escribiendo %d bytes\n",getpid(), nbytes);

	nbytes=read(fd[1],mensaje,128);

	if (nbytes< 0) {
		perror("Error al leer el named pipe de lectura");
		exit(4);
	}
	mensaje[nbytes]=0;

	printf("PID %d leyendo %d bytes %s\n",getpid(), nbytes,mensaje);

	close(fd[0]);
	close(fd[1]);

	exit(0);
}

Es necesario notar que el orden con el que se abren los named pipes es importante,
ya que si no se sigue el orden necesario, puede llevar a una condicin de deadlock.



Ejercicios.

1. Entender el punto 3.9 del libro UNIX programacin prctica, especificamente
en la discusin de apertura del named pipe con la llamada O_NONBLOCK.
2. Investigar que hace la funcion mkfifo(3C).
3. Investigar el comando mkfifo(1M)

+ Procesamiento asncrono. Seales (Signals).
Procesamiento asncrono se refiere al hecho de que un proceso servidor
recibe una peticin de un cliente, pero el proceso servidor no tiene que
estar a la espera de un mensaje, implicando esto que el proceso servidor
puede realizar otro tipo de actividad.

La forma como un proceso asncrono se puede implantar es recurriendo a la idea
de un evento. Un evento indica la ocurrencia de un hecho.

Esto lleva a un modelo conocido como "publicar/suscribir" (publish & suscribe).
En este modelo, el proceso que se encarga de efectuar una accin ante un evento,
es el proceso servidor y se suscribe o interesa a la ocurrencia de un evento.
Por otra parte, el o los procesos clientes generan un evento que publican a los
interesados.

UNIX permite implantar esto, utilizando el concepto de seales o signals.
Una seal es una notificacin por software a un proceso de la ocurrencia de un
evento. Una seal es generada cuando ocurre el evento que causo la seal.
Las seales son conocidas como interrupciones de software.
En UNIX se puede dar una seal que es generada de un proceso a otro, o el kernel
puede generar una seal a un proceso (por ejemplo para indicar que cierto 
dispositivo tiene informacin o que un error ha ocurrido dentro del kernel).

+ Llamada sigaction.
 
En UNIX se puede implantar el modelo publish & suscribe.

Con este fin, un proceso se debe inscribir o interesarse en la ocurrencia de
una seal, indicando la accin que ejecuta ante una seal.

Para poder realizar esto, se debe utilizar la llamada al kernel sigaction(2),
que tiene la siguiente estructura:

     #include <signal.h>

     int sigaction(int sig, const struct sigaction *act,
          struct sigaction *oact);

Esta llamada tiene tres parmetros. El primer parmetro es el nmero de seal a
esperar y que significa lo siguiente:

   Name           Value   Default   Event
   ________________________________________________________________

   SIGHUP         1       Exit      Hangup (see termio(7I))
   SIGINT         2       Exit      Interrupt (see termio(7I))
   SIGQUIT        3       Core      Quit (see termio(7I))
   SIGILL         4       Core      Illegal Instruction
   SIGTRAP        5       Core      Trace or Breakpoint Trap
   SIGABRT        6       Core      Abort
   SIGEMT         7       Core      Emulation Trap
   SIGFPE         8       Core      Arithmetic Exception
   SIGKILL        9       Exit      Killed
   SIGBUS         10      Core      Bus Error
   SIGSEGV        11      Core      Segmentation Fault
   SIGSYS         12      Core      Bad System Call
   SIGPIPE        13      Exit      Broken Pipe
   SIGALRM        14      Exit      Alarm Clock
   SIGTERM        15      Exit      Terminated
   SIGUSR1        16      Exit      User Signal 1
   SIGUSR2        17      Exit      User Signal 2
   SIGCHLD        18      Ignore    Child Status Changed
   SIGPWR         19      Ignore    Power Fail or Restart
   SIGWINCH       20      Ignore    Window Size Change
   SIGURG         21      Ignore    Urgent Socket Condition
   SIGPOLL        22      Exit      Pollable       Event       (see
                                    streamio(7I))
   SIGSTOP        23      Stop      Stopped (signal)
   SIGTSTP        24      Stop      Stopped (user) (see termio(7I))
   SIGCONT        25      Ignore    Continued
   SIGTTIN        26      Stop      Stopped   (tty   input)    (see
                                    termio(7I))
   SIGTTOU        27      Stop      Stopped   (tty   output)   (see
                                    termio(7I))
   SIGVTALRM      28      Exit      Virtual Timer Expired
   SIGPROF        29      Exit      Profiling Timer Expired
   SIGXCPU        30      Core      CPU time  limit  exceeded  (see
                                    getrlimit(2))
   SIGXFSZ        31      Core      File size limit  exceeded  (see
                                    getrlimit(2))
   SIGWAITING     32      Ignore    Concurrency signal reserved  by
                                    threads library
   SIGLWP         33      Ignore    Inter-LWP  signal  reserved  by
                                    threads library
   SIGFREEZE      34      Ignore    Check point Freeze
   SIGTHAW        35      Ignore    Check point Thaw
   SIGCANCEL      36      Ignore    Cancellation signal reserved by
                                    threads library
   SIGRTMIN       *       Exit      First real time signal
   (SIGRTMIN+1)   *       Exit      Second real time signal
    ...
   (SIGRTMAX-1)   *       Exit      Second-to-last real time signal
   SIGRTMAX       *       Exit      Last real time signal

Cada seal tiene un significado diferente. Por ejemplo, SIGKILL significa que
el proceso que la recibe debe concluir su procesamiento y salir inmediatamente.

El segundo parmetro es es un apuntador a una estructura de datos denominada
struct sigaction. En esta estructura de datos se debe indicar el comportamiento
o accion a tomar ante la accin especificada por el primer parmetro.
Esta estructura de datos tiene los siguientes campos
void        (*sa_handler)(); --> Apuntador a Funcion que maneja la seal
sigset_t    sa_mask;	     --> Seales a bloquear durante el procesamiento
				del evento
int         sa_flags;	     ---> Banderas

El tercer parametro es una estructura de tipo sigaction y que se llena con
los valores del anterior struct sigacion utilizado.

Un ejemplo de como utilizar seales es un programa que al recibir un CTL-C
no interrumpa su procesamiento.
#include <signal.h>

void anulaCtlC(int signo) {
	/*esta funcion se llama cuando se recibe un CTRL-C*/
	printf("Recibo seal %d\n",signo);
}

main() {

	struct sigaction nuevaAccion;
	int rtn;

	/*Llenar la estructura sigaction*/
	nuevaAccion.sa_handler= anulaCtlC; /*la funcion manejadora*/
	sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/
	nuevaAccion.sa_flags=0; /*no hay banderas*/

	/*ante un CTRL-C no se muere el proceso*/
	rtn=sigaction(SIGINT,&nuevaAccion,(struct sigaction *)0);

	if (rtn < 0) {
		perror("Error al registar manejador de signal");
		exit(1);
	}
	/*se queda procesando para siempre*/
	printf("Procesando \n");
	while (1);

	
}

Probar
Ejemplo12
PID 1020 Procesando
^CRecibo seal 2
^CRecibo seal 2
^CRecibo seal 2
^CRecibo seal 2


Tambien se puede enviar la seal 2 utilizando el comando kill. Se debe
conocer el PID de Ejemplo12.

kill -2 1020

Las nicas seales que no pueden manipularse son SIGKILL y SIGSTOP.

El siguiente ejemplo lo muestra.

#include <signal.h>

void anulaKill(int signo) {
	/*esta funcion se llama cuando se recibe un CTRL-C*/
	printf("Recibo seal %d\n",signo);
}

main() {

	struct sigaction nuevaAccion;
	int rtn;

	/*Llenar la estructura sigaction*/
	nuevaAccion.sa_handler= anulaKill; /*la funcion manejadora*/
	sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/
	nuevaAccion.sa_flags=0; /*no hay banderas*/

	/*ante un kill -9 no se muere el proceso*/
	rtn=sigaction(SIGKILL,&nuevaAccion,(struct sigaction *)0);
	
	if (rtn < 0) {
		perror("Error al registar manejador de signal");
		exit(1);
	}
	/*se queda procesando para siempre*/
	printf("PID %d Procesando \n",getpid());
	while (1);

	
}

Este programa al ser ejecutado, arroja el siguiente error:

Ejemplo13
Error al registar manejador de signal: Invalid argument

ya que la funcion sigaction, si recibe un SIGKILL como primer parmetro,
no tiene utilidad, ya que no se puede manejar.

Una seal puede ser ignorada, implicando esto, que no se llama a un manejador
y por tanto no se procesa. Esto se logra asignado al campo sa_handler el valor
SIG_IGN, como lo muestra el siguiente ejemplo:

#include <signal.h>

void anulaCtrlC(int signo) {
	/*esta funcion se llama cuando se recibe un CTRL-C*/
	printf("Recibo seal %d\n",signo);
}

main() {

	struct sigaction nuevaAccion;
	int rtn;

	/*Llenar la estructura sigaction*/
	nuevaAccion.sa_handler= SIG_IGN; /*la funcion manejadora no existe,
					solo se ignora*/
	sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/
	nuevaAccion.sa_flags=0; /*no hay banderas*/

	/*ante un Ctrl-C no se muere el proceso*/
	rtn=sigaction(SIGINT,&nuevaAccion,(struct sigaction *)0);
	
	if (rtn < 0) {
		perror("Error al registar manejador de signal");
		exit(1);
	}
	/*se queda procesando para siempre*/
	printf("PID %d Procesando \n",getpid());
	while (1);

	
}

Si se desea hacer un programa que envie una seal a un proceso, se debe 
utilizar la llamada kill.

El siguiente ejemplo muestra como un programa realiza una divisin entre
cero, lo que provoca que el kernel genere una seal SIGFPE, pero el
programa cacha esta excepcin y sale con un estado mayor que 0.

#include <signal.h>

void division(int signo) {
	printf("Division incorrecta signo %d\n",signo);
	exit(1);
}
main() {

	struct sigaction nuevaAccion;
	int rtn,x;
	char * ptr=0;

	/*Llenar la estructura sigaction*/
	nuevaAccion.sa_handler= division; /*la funcion manejadora no existe,
					solo se ignora*/
	sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/
	nuevaAccion.sa_flags=0; /*no hay banderas*/

	/*ante un Ctrl-C no se muere el proceso*/
	rtn=sigaction(SIGFPE,&nuevaAccion,(struct sigaction *)0);
	
	if (rtn < 0) {
		perror("Error al registar manejador de signal");
		exit(1);
	}


	x=1/0;
}

El siguiente ejemplo muestra un abanico de procesos, y como el proceso
padre recibe seal de que un proceso hijo cambia de estado SIGCHLD.

#include <signal.h>

int contador=0;

void cambioHijo(int sig) {

	printf("Cambio un hijo signo %d contador %d\n",sig,contador);
	contador++;
}


main() {

	int i;

	struct sigaction nuevaAccion;
	int rtn;

	/*Llenar la estructura sigaction*/
	nuevaAccion.sa_handler=cambioHijo ; /*la funcion manejadora no existe,
					solo se ignora*/
	sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/
	nuevaAccion.sa_flags=0; /*no hay banderas*/

	rtn=sigaction(SIGCHLD,&nuevaAccion,(struct sigaction *)0);
	
	if (rtn < 0) {
		perror("Error al registar manejador de signal");
		exit(1);
	}

	for (i=0;i< 10; i++) {
		int pid;
		pid=fork();
			

		if (pid < 0) {
			perror("Error en fork");
			exit(1);
		} else if (pid == 0) {
			printf("PID %d valor de i=%d\n",getpid(),i);
			exit(0);
		} else {
			printf("Hijo con pid %d creado\n",pid);
		}
		
	}

	printf("Procesando...\n");
	while (1) {
		if (contador>=10)
			break;
	}

	printf("Acabaron todos mis hijos\n");
	exit(0);
}


Ejercicio.

1. Investigar la funcion signal.
2. Hacer un programa que cache la excepcion de accesar un apuntador nulo.

+ Publish & Suscribe.

El modelo publish & suscribe consiste en una forma de procesamiento en
la cual uno o mas procesos se muestran interesados en un evento o se
suscriben y uno o mas procesos generan estos eventos.

En este modelo, el proceso que genere un evento no sabe a quien va dirigido.
Esto es a diferencia del punto anterior, en el cual se tiene que indicar
un identificador de proceso.

Adems, este modelo plantea que el evento llega al proceso servidor, pero
con la llamada sigaction no se obtiene ningn argumento de entrada para
el proceso servidor.

Para poder implantar este modelo, se debe recurrir a una combinacin de
seales y tuberias con nombre.

Para esto, existir un proceso servidor, el cual estar a la espera
de que uno o ms procesos se suscriban a un evento. Cuando otro conjunto
de procesos desee enviar informacin a un grupo de procesos, debe referenciar
al proceso servidor e indicar el evento y la informacin del evento.
De ahi, el proceso servidor comunicar por medio de un pipe, la informacin
a los procesos cliente.
El Ejemplo17, Ejemplo18, Ejemplo19 y Ejemplo20 muestran este modelo.

/*Proceso mediador para manipuolar comunicacion publish & suscribe*/

#include  <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>

#define PIPESUSCRIBE "/tmp/psuscribe"
#define PIPEPUBLISH "/tmp/ppublish"
#define MAXLEN 1024
#define MAXSUSCRITOS 128

/*numero suscriptores*/
int nSuscriptores;

/*variable global para definir extremos de comunicacion entre
proceso suscriptor y publicador */

int internalPipe[2];

/*estructura de datos que define mensaje del suscriptor*/

struct mensajeSuscriptor {
	int pid; /*indica el PID del proceso suscriptor*/
};

struct mensajePublicador {
	char mensaje[MAXLEN]; /* se escribe un mensaje de cualquier tipo de
				datos*/
};

struct suscrito {
	int pid; /* identificador de proceso*/
	char pipe[128]; /*pipe donde toma el mensaje del publicador*/ 
};

struct suscrito lista[MAXSUSCRITOS]; /*lista suscritos*/
 
void finalizaPublicador(int signo) {
	int i;
	printf("Recibiendo signal %d\n",signo);

	unlink(PIPEPUBLISH);
	for (i=0;i<nSuscriptores ;i++) {
		unlink(lista[i].pipe);
	}
	/*se mata a si mismo*/
	printf("Fin de servidor de publicacion\n");
	exit(0);
}

void procesaSuscripcion() {
	
	int fd,rtn;
	int nbytes;
	int pid;
	struct mensajeSuscriptor mensaje; /*estructura para leer mensaje
					suscriptor*/
	int i=0;
	
	/* no utiliza extremo de lectura de pipe Interno*/
	close(internalPipe[0]);
	while (i<nSuscriptores) {
		/*pipe de suscripcion se abre de lectura*/
		printf("Esperando suscriptores...\n");
		fd=open(PIPESUSCRIBE,O_RDONLY);	

		if (fd < 0) {
			perror("Error al abrir pipe suscripcion");
			exit(4);
		}/*if*/
		/*esperar un mensaje sobre el pipe de suscripcion*/	

		nbytes= read(fd, &mensaje, sizeof(mensaje));

		if (nbytes < 0) {
			perror("error al leer mensaje suscriptor");
			continue; /*ignorar al suscriptor*/
		}

		pid= mensaje.pid;
	
		nbytes=	
		write(internalPipe[1],&pid,sizeof(int));
		i++; /*incrementar el numero de suscritos*/	

		/*se cierra el pipe*/
		close(fd);
		printf("Faltan %d suscriptores", nSuscriptores-i);
	} /*while*/
	close(internalPipe[1]);
	printf("Todos estan suscritos\n");
}

void procesaPublicacion(){
	int fd,rtn;
	int nbytes;
	int i=0;

	struct sigaction accion;

	/*manejar fin de servicio con SIGGUP*/
	accion.sa_handler=finalizaPublicador ;
	sigemptyset(&accion.sa_mask); /*no se bloque ninguna sennal*/
	accion.sa_flags = 0;

	sigaction(SIGHUP,&accion,(struct sigaction *) 0);
	/* no utiliza extremo de escritura de pipe Interno*/

	close(internalPipe[1]);
	while ( i <  nSuscriptores) {
		read(internalPipe[0],&(lista[i].pid),sizeof(lista[i].pid));
		i++;
	} /*while*/
	close(internalPipe[0]);
	
	for (i=0;i<nSuscriptores;i++) {
		char nombrePipe[128];

		sprintf(nombrePipe,"/tmp/p%d",lista[i].pid);

		strcpy(lista[i].pipe,nombrePipe);
		rtn=mknod(lista[i].pipe,S_IFIFO|0666,0);

		if (rtn< 0) {
			perror("Error al crear pipe de mensaje");
		}
		printf("Proceso inscrito %d\n",lista[i].pid);
	} /*for*/

	while (1) {
		struct mensajePublicador mensaje;
		printf("Esperando publicadores...\n");
		fd= open(PIPEPUBLISH,O_RDONLY);

		if (fd < 0) {
			printf("Error al abrir pipe de publicacion");
		} /*if*/

		nbytes=read(fd,&mensaje ,sizeof(mensaje));

		if (nbytes < 0) {
			perror("error al comunicarse con publicador");
			continue; /*ignorar*/
		} else {
			int fd2;

			/*enviar un signal a cada proceso*/

			for (i=0 ;i < nSuscriptores;i++) {
				/*enviando signal a cada proceso inscrito*/
				rtn=kill(lista[i].pid  ,SIGTERM);

				if (rtn < 0) {
					perror("error al enviar signal");
				} else {
					printf("Enviando signal a %d \n",lista[i].pid);
					printf("Enviando mensaje a %d %s\n",lista[i].pid,lista[i].pipe);
				 	fd2=open(lista[i].pipe,O_WRONLY);
					
					if (fd2< 0) {
						perror("Error al abir pipe de mensajes");
					} else {
						nbytes=
						write(fd2,mensaje.mensaje,strlen(mensaje.mensaje));
						if (nbytes < 0) {
							perror("error al escribir pipe de mensaje");
						} /*if*/
						close(fd2);
					} /*else*/
				} /*if*/
			} /*for*/
			close(fd);
		}/*else*/
	} /*while*/
}

void creaServidorSuscripcion(void){
	int pid;

	pid=fork();
	if (pid < 0) {
		exit(3);
	} else if (pid == 0) {
		procesaSuscripcion();
		printf("Fin servidor suscripcion");
		unlink(PIPESUSCRIBE );
		exit(0);
	} else {
		printf("Servidor suscripcion con PID %d \n",pid);
	}
}

void creaServidorPublicacion(){
	int pid;
	pid=fork();

	if (pid < 0) {
		exit(3);
	} else if (pid == 0) {
		procesaPublicacion();
		printf("Fin servidor publicacion");
		unlink(PIPEPUBLISH );
		exit(0);
	} else {
		printf("Servidor publicacion con PID %d \n",pid);
	}
}

main(int argc, char * argv []) {

	int rtn;

	if (argc<=1) {
		printf("Ejemplo17 numeroParticipantes");
		exit(1);
	}

	nSuscriptores=atoi(argv[1]);

	/*crear los dos pipes , utilizando mknod */

	rtn= mknod(PIPESUSCRIBE,S_IFIFO|0666,0);

	if (rtn < 0) {
		perror("Error al crear pipe de suscripcion");
		exit(1);
	}	    

	rtn= mknod(PIPEPUBLISH,S_IFIFO|0666,0);

	if (rtn < 0) {
		perror("Error al crear pipe de publicacion");
		exit(2);
	}


	/*Crea el pipe de comunicacion entre proceso suscripcion
	y publicador*/

	rtn= pipe(internalPipe);

	if (rtn < 0) {
		perror("Error al crear pipe de comunicacion interna");
	}
	/* Este proceso va a crear dos hijos (demonios), para
	   esperar la suscripcion y otro para publicacion de eventos*/
	creaServidorSuscripcion();
	creaServidorPublicacion();

	/*el proceso padre cierra el pipe, no lo necesita*/
	close(internalPipe[0]);
	close(internalPipe[1]);
	printf("Servicio Publish & Suscribe Inicializado\n");  	  
	exit(0);
}	
#include  <unistd.h>
#include  <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

#define PIPESUSCRIBE "/tmp/psuscribe"
struct mensajeSuscriptor {
        int pid; /*indica el PID del proceso suscriptor*/
};

void suscribe(char * argv[]) {
	int fd;
	int nbytes; 
	int rtn;

	struct mensajeSuscriptor mensaje; 

	fd=open(PIPESUSCRIBE,O_WRONLY);
	mensaje.pid=getpid();

	nbytes=
	write(fd,&mensaje,sizeof(mensaje));

	if (nbytes <0) {
		printf("error al suscribir el proceso\n");
	} else {
		printf("Inscrito proceso %d",getpid());
	}

	close(fd);

	printf(argv[0]);
	rtn=execvp(argv[0],&argv[0]);

	if (rtn< 0) {
		perror("error al ejecutar programa");
		exit(2);
	}
}

main(int argc, char * argv[]) {

	if ( argc <=1) {
		printf("Ejemplo18 programa");
		exit(1);
	}	
	suscribe(&argv[1]);
}
#include  <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>


int i=0;

void procesarEvento(int signo) {
	int fd,nbytes;
	char buffer[128];
	char nombrePipe[128];

	printf("Signal Evento %d\n",signo);
	printf("Valor de i=%d\n",i);

	sprintf(nombrePipe,"/tmp/p%d",getpid());
	fd= open(nombrePipe,O_RDONLY);

	if (fd < 0) {
		perror("No puedo abrir pipe de mensajes");
	} else {
		nbytes=read(fd,buffer,128); 

		if (nbytes < 0) {
			perror("No puedo abrir pipe de mensajes");
		} else {
			buffer[nbytes]=0;
			printf("%s\n",buffer);
		}
		close(fd);	
	}
}

main() {

	struct sigaction nuevaAccion;
	int rtn,x;
	char * ptr=0;

	/*Llenar la estructura sigaction*/
	nuevaAccion.sa_handler= procesarEvento; 
	sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/
	nuevaAccion.sa_flags=0; /*no hay banderas*/

	rtn=sigaction(SIGTERM,&nuevaAccion,(struct sigaction *)0);
	
	if (rtn < 0) {
		perror("Error al registar manejador de signal");
		exit(1);
	}


	while (1) {
		i++;
	}
}
#include  <unistd.h>
#include  <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

#define PIPEPUBLISH "/tmp/ppublish"

#define MAXLEN 1024
struct mensajePublicador {
        char mensaje[MAXLEN]; /* se escribe un mensaje de cualquier tipo de
                                datos*/
};


main(int argc, char *argv[]) {

	int fd;
	struct mensajePublicador mensaje;
	int nbytes;

	if (argc <=1) {
	        strcpy(mensaje.mensaje,"hola mundo\n");
	} else {
	        strcpy(mensaje.mensaje,argv[1]);

	}

	fd= open(PIPEPUBLISH,O_WRONLY);

	if (fd < 0) {
		perror("Error al abrir pipe publicacion");
		exit(2);
	}


	nbytes=write(fd,&mensaje,sizeof(struct mensajePublicador));

	if (nbytes< 0) {
		perror("no se puede enviar el mensaje");
	} else {
		printf("Mensaje enviado\n");
	}
	close(fd);

	exit(0);
}

Ejercicio:
1. Explicar el funcionamiento de los Ejemplos 17 a 20.
2. Estos ejemplos hacen uso de estructuras de datos comunes. Definir
en un archivo de encabezado (include) estas estructuras de datos.
3. Pensar en un juego de gato, entre dos jugadores. Plantear como se
tendria que redisear el Ejemplo17 al Ejemplo20.
* 4. Implantar el ejemplo3. 

+ Sockets.

Los modelos anteriores se pueden considerar de muy bajo nivel. 
UNIX proporciona un mecanismo para facilitar la comunicacion entre procesos
denominado sockets.
Un socket es un mecanismo de comunicacion entre procesos que permite implantar
procesos cliente y servidor.
UNIX permite comunicar procesos que estan ejecutandose en la misma computadora,
o entre computadoras remotas (esto se vera en el tema 7).
Cuando la comunicacion se establece entre procesos que residen en la misma computadora, se conocen como un socket UNIX.
Un socket es proporcionado por el kernel.
Para crear un socket se debe utilizar la llamada al kernel socket(2)
       #include <sys/types.h>
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);

En el caso de un socket local los parametros son los siguiente:
domain - AF_UNIX
type   - SOCK_STREAM , tratar el socket como un archivo
protocol - 0 , se deja al kernel configurar el tipo de protocolo
Esta llamada , si tiene exito, retorna un descriptor de archivo, de lo contrarioun numero negativo.

Tanto un proceso cliente y servidor deben invocar la llamada anterior.

En el caso de un proceso servidor se deben aplicar los siguientes pasos:
+ Aplicar la llamada bind sobre el socket.
       #include <sys/types.h>
       #include <sys/socket.h>
       int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

El primer parametro es el descriptor obtenido por la llamada socket.
El segundo parametro es un apuntador a una estructura generica para
representar sockets. En el caso de un socket UNIX se debe incluir 
	#include <sys/un.h>
        #define UNIX_PATH_MAX    108

        struct sockaddr_un {
           sa_family_t  sun_family;              /* AF_UNIX */
           char         sun_path[UNIX_PATH_MAX]; /* pathname */
        };


Esta estructura de datos se debe llenar de la siguiente manera:
  bzero( (char *) &serv_addr , sizeof(serv_addr) ); /*inicar a cero*/
  serv_addr.sun_family = AF_UNIX;
  strcpy(serv_addr.sun_path, argv[1] ); /*indicar donde esta el socket
	dentro del sistema de archivos*/

El tercer parametro permite indicar la longitud de la estructura anterior,
con valores indicados. Se debe aplicar de la siguiente forma
	addrlen = strlen(serv_addr.sun_path) + sizeof (serv_addr.sun_family);

+ Aplicar la llamada listen
       #include <sys/socket.h>
       int listen(int s, int backlog);

Permite indicar al servidor que debe escuchar peticiones de los clientes.
El primer parametro es el descriptor de archivo
El segundo parametro indica el numero maximo de clientes que puede
aceptar simultaneamente

+ Esperar peticiones de clientes, usando la llamada accept
       #include <sys/types.h>
       #include <sys/socket.h>

       int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
Esta llamada permite obtener un nuevo socket entre el servidor y el
proceso cliente. 
El primer parametro es el descriptor retornado por la llamada socket.
El segundo y tercer parametro se deben utilizar basandose en una
estructura de datos de tipo sockaddr_un, como en el caso de la llamada
bind. 
Si esta llamada tiene exito, se obtiene un descriptor de archivo
que es el socket entre servidor y cliente. Sobre este socket se pueden
aplicar llamadas relacionadas con archivos
+ Por cada cliente se crea un nuevo socket, aplicar operaciones sobre
dicho socket y cerrarlo.

+ Cerrar el socket principal cuando se concluya la vida del servidor.
Para cerrar un socket se debe utilizar la llamada close.

El siguiente ejemplo muestra el proceso descrito:


#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define MAXBUF 1024

main(int argc, char *argv[]) {
	int sockfd; /*descriptor de socket*/ 
	struct sockaddr_un serv_addr; /*estructura para configurar socket*/
	struct sockaddr_un cli_addr; /*estructura del cliente del socket*/
	int servlen; /*para calcuar la longitud de la estructura */
	int clilen;
	int newsockfd;
	char buffer[MAXBUF];
	int sz,i;

	if ( argc <= 1 ) {
		printf("Ejemplo21 rutasocket\n");
		exit(1);
	}
	sockfd = socket(AF_UNIX,SOCK_STREAM,0);
	
	if (sockfd < 0 ) {
		perror("servidor no puede abrir socket");
		exit(1);
	}
	printf("sockfd=%d\n",sockfd);	

	bzero( (char *) &serv_addr , sizeof(serv_addr) ); /*inicar a cero*/
	serv_addr.sun_family = AF_UNIX;
	strcpy(serv_addr.sun_path, argv[1] ); /*indicar donde esta el socket*/
	servlen = strlen(serv_addr.sun_path) + sizeof (serv_addr.sun_family);

	if ( bind(sockfd, (struct sockaddr *) &serv_addr, servlen) < 0 ) {
		perror("servidor no aplica bind");
		exit(2);
	}
	listen(sockfd,5); /*escuchar 5 peticiones*/

	while (1) {
		clilen = sizeof(cli_addr);
		newsockfd=accept(sockfd, (struct sockaddr *) &cli_addr , &clilen );
		if ( newsockfd < 0 ) {
			perror("servidor error al recibir peticion del cliente");
			continue;
		}
		sz =read(newsockfd,buffer,MAXBUF);
		if (sz < 0 ) {
			 perror("servidor error en lectura"); 
		   close(newsockfd);
			continue;
		}
		if ( sz > MAXBUF ) sz = MAXBUF - 1;
		buffer[sz] = (char) 0;
		printf("mensaje %s\n",buffer);
		for ( i=0; i<strlen(buffer) ; i++)
			 buffer[i] = toupper(buffer[i]);
		if ( write(newsockfd,buffer,strlen(buffer)) < 0 ) {
			perror("servidor error en lectura"); 
		  close(newsockfd);
			continue;
		}
		close(newsockfd);
	}
	exit(0);
}
En el caso de un proceso cliente.
+ Invocar la llamada socket, para obtener un descriptor de socket por
parte del kernel
+ Utilizar la llamada connect
       #include <sys/types.h>
       #include <sys/socket.h>

       int   connect(int  sockfd,  const  struct  sockaddr  *serv_addr,  socklen_t addrlen);

La estructura a utilizar para el segundo y tercer parametro debe ser de tipo
sockaddr_un, indicando la ruta donde se localiza el socket del proceso servidor.
+ Aplicar operaciones de archivos sobre el socket.
+ Cerrar el socket.

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define MAXBUF 1024

main(int argc, char *argv[]) {
	int sockfd; /*descriptor de socket*/
	struct sockaddr_un serv_addr; /*estructura para configurar socket*/
	int servlen;
	int sz;
	char msg[] = "hola mundo";
	char buffer[MAXBUF];	

	if ( argc <= 1 ) {
    	  printf("Ejemplo21 rutasocket\n");
    	  exit(1);
  	}

	sockfd = socket(AF_UNIX,SOCK_STREAM,0);
	if (sockfd < 0 ) {
		perror("cliente no puede abrir socket");
		exit(1);
	}
	printf("sockfd=%d\n",sockfd);

	bzero( (char *) &serv_addr , sizeof(serv_addr) ); /*inicar a cero*/
	serv_addr.sun_family = AF_UNIX;
	strcpy(serv_addr.sun_path, argv[1] ); /*indicar donde esta el socket*/
	servlen = strlen(serv_addr.sun_path) + sizeof (serv_addr.sun_family);
	
	if ( connect(sockfd,(struct sockaddr *) &serv_addr, servlen) < 0 ) {
		perror("cliente no puede abrir socket");
		exit(2);
	}
	if ( write(sockfd,msg,strlen(msg)) < 0 ) {
		perror("cliente error al escribir el mensaje");
		close(sockfd);
		exit(3);
	}
	sz = read(sockfd,buffer,MAXBUF);
	if ( sz < 0 ) {
		perror("cliente error al leer el mensaje");
		close(sockfd);
		exit(4);
	}
	if ( sz > MAXBUF ) sz = MAXBUF - 1;
	buffer[sz] = (char) 0;
	printf("%s\n",buffer);
	close(sockfd);	
	exit(0);
}


Ejercicios.
1. Implantar un proceso servidor utilizando llamadas fork.
2. Construir un servicio de "CHAT" local entre procesos UNIX.
3. Utilizando un servicio de base de datos disponible en el sistema
UNIX, construir un programa cliente/servidor que permita aplicar
consultas, modificaciones y borrado a la base de datos. El cliente
debe ser ligero, es decir, no habla directamente con la base de datos,
sino que utiliza el servidor que establece una conexion con la base de datos.
4. En cualquier sistema cliente/servidor se procura dar un conjunto de recursos preasignados. Por ejemplo, abrir un numero fijo de conexiones a bases de datos y administrar su uso. Esto se conoce como un pool de conexiones. Se podrian utilizar sockets con este fin.



Ejercicios.

+ Investigar como se implante en Windows NT el modelo de comunicacin
de procesos
