Área de Ingeniería Telemática Departamento de Ingeniería de Sistemas y Automática




Дата канвертавання26.04.2016
Памер45.01 Kb.






Área de Ingeniería Telemática

Departamento de Ingeniería de Sistemas y Automática

Escuela Superior de Ingenieros

Universidad de Sevilla





COMUNICACIÓN BÁSICA ENTRE PROCESOS
1. OBJETIVO
Se pretende mostrar al alumno mecanismos simples de comunicación entre procesos. Para ello se van a estudiar las señales y las tuberías (pipes). También se van a estudiar las llamadas al sistema dup y dup2.

2. DESCRIPCIÓN
Señales:
Las señales son una forma de notificar al proceso acerca de la ocurrencia de un evento o error. Un proceso se comporta frente a una señal como el procesador frente a una interrupción, por eso se dice que las señales son interrupciones software. El origen de una señal es el sistema operativo u otro proceso. El comportamiento frente a la señal es de la siguiente forma:


  • El proceso detiene su ejecución en la instrucción máquina que está ejecutando.

  • Pasa a ejecutar una rutina de tratamiento de la señal (manejador de la señal), cuyo código forma parte del propio proceso.

  • Una vez terminada la ejecución de esta rutina, sigue la ejecución por la instrucción donde fue interrumpido.

La lista de señales posibles se pueden encontrar en el fichero de cabecera signal.h. A continuación se ponen algunos ejemplos de señales:


SIGABRT: terminación anormal.

SIGALRM: señal de fin de temporización.

SIGFPE: operación aritmética errónea.

SIGINT: señal de interrupción.

SIGKILL: señal de terminación.

SIGSEG: señal de referencia a memoria inválida.


Las señales proporcionan el mecanismo más básico de comunicación entre procesos.
Frente a las señales, el proceso puede:


  • Ignorarlas: la señal se desecha y no se tiene en cuenta. Hay señales que no pueden ser ignoradas por el proceso, por ejemplo SIGKILL.

  • Bloquearlas: la señal no se tiene en cuenta, pero queda pendiente hasta que se desbloquea.

  • Capturarlas: cuando llega la señal el proceso ejecuta una rutina de manejo de señal. El proceso puede indicar el manejador de señal que será ejecutado cuando se recibe la señal, esto se conoce como armar una señal. Hay señales que no pueden ser armadas por el proceso, por ejemplo SIGKILL.

Existen servicios para tratar conjuntos de señales, para el envío de señales, para indicar el manejador de una cierta señal, para el bloqueo y desbloqueo de señales, para esperar por la recepción de una señal y para la gestión de temporizadores. De todas estas funciones, se van a estudiar las siguientes:


int sigemptyset(sigset_t *set);
Inicia un conjunto de señales de modo que no contenga ninguna señal.
int sigaddset(sigset_t *set, int signo);
Añade una señal (signo) al conjunto de señales previamente iniciado (set).
int kill(pid_t pid, int sig);
Envía la señal sig al proceso o grupo de procesos especificado por pid. Si pid es mayor que cero, la señal se enviará al proceso con identificador de proceso igual a pid. Si pid es cero, la señal se enviará a todos los procesos cuyo identificador de grupo sea igual al identificador de grupo del proceso que envía la señal. Si pid es negativo pero distinto de –1, la señal se enviará a todos los procesos cuyo identificador de grupo sea igual al valor absoluto de pid. Para pid igual a –1 no se especifica nada.
Sólo se puede enviar una señal a un proceso con el mismo identificador de usuario real o efectivo, a no ser que el proceso que envía tenga los privilegios adecuados (por ejemplo, es un proceso del superusuario).
int sigaction(int sig, struct sigaction *act, struct sigaction *oact);
Permite indicar la función que manejará la señal cuando sea recibida. Tiene tres parámetros. El primer parámetro (sig) indica el número de señal para la cual se establecerá el manejador. El segundo parámetro (act) es un puntero a una estructura del tipo sigaction para establecer el nuevo manejador. El último parámetro (oact) es un puntero a una estructura del mismo tipo donde se almacena la información sobre el manejador asociado a la señal anteriormente.
La estructura sigaction está definida de la siguiente forma:
struct sigaction {

void (*sa_handler) (); /* Manejador para la señal */

sigset_s sa_mask; /* Señales bloqueadas durante */

/* la ejecución del manejador */

int sa_flags; /* Opciones especiales */

};

El primer campo (sa_handler) indica la acción a realizar. Su valor puede ser:




  • SIG_DFL: acción por defecto, en la mayoría de los casos consiste en matar al proceso.

  • SIG_IGN: ingnora la señal.

  • Una función que devuelve void y que acepta como parámetro un número entero. Este parámetro es el número de la señal que se ha enviado. Esto permite utilizar un mismo manejador para distintas señales.

int pause(void);


Espera la recepción de una señal. Bloquea al proceso que la invoca hasta que llegue la señal.
unsigned int alarm(unsigned int seconds);
Activa un temporizador. Envía al proceso la señal SIGALRM después de pasados el número de segundos especificados en el parámetro seconds.
int sleep(unsigned int seconds);
Suspende un proceso durante un número determinado de segundos.
Ejemplo: El siguiente programa hace que el proceso ignore la señal SIGINT que se genera cuando se pulsa CTLR-C. Luego ejecuta un bucle infinito. Para terminarlo, se puede parar y luego enviar una señal SIGKILL con la orden kill (kill –SIGKILL pid).



#include

#include
void main(void)

{

struct sigaction act;


act.sa_handler= SIG_IGN; /* Ignora la señal */

act.sa_flags= 0; /* Ninguna accion especial */

sigemptyset(&act.sa_mask); /* Ninguna señal bloqueada */
sigaction(SIGINT, &act, NULL);
while(1); /* Bucle infinito */

}



p2_prueba1.c


Ejemplo de alarma:



#include

#include

#include


void tratar_alarma(void)

{

printf(“Activada \n”);



}
void main(void)

{

struct sigaction act;



act.sa_handler = tratar_alarma; /* función a ejecutar */

act.sa_flags = 0; /* ninguna acción especifica*/

/* Se bloquea la señal SIGINT cuando se ejecute la función

tratar_alarma */

sigemptyset(&act.sa_mask);

sigaddset(&act.sa_mask, SIGINT);


sigaction(SIGALRM, &act, NULL);
for(;;)

{

alarm(3); /* se arma el temporizador */



pause(); /* se suspende el proceso hasta que se reciba una señal */

}

}





p2_prueba2.c


Ejemplo de kill:



#include

#include

#include

#include

#include


pid_t pid;
void matar_proceso(void)

{

kill(pid, SIGKILL); /* se envía la señal al hijo */



}
void main(int argc, char **argv)

{

int status;



struct sigaction act;

/* Se crea el proceso hijo */

pid = fork();

switch(pid)

{

case -1: /* error del fork() */



perror("fork");

break;


case 0: /* proceso hijo */

if(argc>1)

/* El proceso hijo ejecuta el mandato recibido */

execvp(argv[1], &argv[1]);

perror("exec");

break;


default: /* padre */

/* establece el manejador */

act.sa_handler = matar_proceso; /*función a ejecutar*/

act.sa_flags = 0; /* ninguna acción especifica */

sigemptyset(&act.sa_mask);

sigaction(SIGALRM, &act, NULL);

alarm(5);

/* Espera al proceso hijo */

wait(&status);

}
}




p2_prueba3.c

 Realizar un ejemplo básico de productor/consumidor que se comuniquen y sincronicen mediante señales. Para ello:


  • Crear un proceso hijo con fork. El padre será el productor y el hijo el consumidor.

  • El productor cada cierto tiempo (por ejemplo 5 segundos) enviará una señal al consumidor empezando por la señal 1 y terminando con la 5.

  • El consumidor debe quedarse indefinidamente a la espera, capturar estas señales e imprimir su valor por pantalla.

  • Para terminar, el productor transcurridos 5 segundos desde la última señal enviada manda una señal SIGKILL al consumidor.


Tuberías:
Una tubería es un canal de comunicación entre procesos emparentados (padre e hijo). El acceso se realiza como en una cola (FIFO)
El servicio que crea una tubería es:

Formato:
#include


int pipe (int descriptores[2]);

Parámetros:


Tabla que recibirá los descriptores de entrada (0, para lectura) y de salida (1, para escritura) de la tubería

Devuelve:


0 si se ha completado correctamente; -1 si error
A continuación se da un ejemplo de tubería entre un padre y un hijo:




#include

#define LECTURA 0

#define ESCRITURA 1

int main()

{
int descr[2], bytesleidos;

char mensaje[101];

char *frase="Veremos si la transferencia es buena";

pipe(descr);

if(fork()==0) /* Proceso hijo */

{

close(descr[LECTURA]);



write(descr[ESCRITURA], frase, strlen(frase));

close(descr[ESCRITURA]);

} else /* Proceso padre */

{

close(descr[ESCRITURA]);



bytesleidos=read (descr[LECTURA], mensaje, 100);

while(bytesleidos)

{

mensaje[bytesleidos]='\0';



printf("Bytes leidos: %d \n",bytesleidos);

printf("Mensaje: %s \n",mensaje);

bytesleidos=read (descr[LECTURA], mensaje, 100);

}

close(descr[LECTURA]);



}

}
p2_prueba4.c


 Estudiar el ejemplo e indicar el resultado.

 Realizar un ejemplo de productor/consumidor que se comuniquen y sincronicen mediante una tubería. Para ello:



  • Crear un proceso hijo con fork. El padre será el productor y el hijo el consumidor.

  • El productor escribe en la tubería enteros. Se puede escribir por ejemplo en la tubería 100 enteros, desde el 0 al 99.

  • El consumidor lee de la tubería enteros.

Los ejemplos anteriores muestran la comunicación entre procesos a través de una tubería. A continuación se va a utilizar una tubería tal como se entiende bajo el intérprete de órdenes. En el intérprete de órdenes un pipe o tubería se utiliza para que la salida estándar de un proceso sea la entrada estándar de otro. De esta forma ls | sort (el carácter | indica el pipe) en el intérprete significa que la salida de ls no se muestra por la pantalla sino que se da como entrada a sort, sort entonces ordena la salida de ls.


Para hacer esto, además de utilizar pipe se utilizan los servicios dup y dup2 que se explican a continuación.
int dup(int fd);
Duplica el descriptor existente fd.
Parámetros:

fd: índice de la tabla de descriptores que se quiere duplicar.


Devuelve el nuevo descriptor. Es el descriptor con una numeración más baja, que no está actualmente en uso. En caso de error devuelve –1
int dup2(int fd1, int fd2);
Duplica el descriptor fd1 en fd2.
Parámetros:

fd1: índice de la tabla de descriptores que se quiere duplicar.

fd2: el valor deseado para el nuevo descriptor.

Si fd2 está en uso, primero se cierra con close.


Devuelve el descriptor o -1 en caso de error
El siguiente ejemplo muestra la utilización de dup2.


#include


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

{

int desc_fich;



if(argc < 3)

printf("Formato: %s fichero comando [opciones].\n", argv[0]);

else

{

printf("Ejemplo de redireccion. \n");



desc_fich= open(argv[1],O_CREAT|O_TRUNC|O_WRONLY, 0);

dup2(desc_fich,1);

close(desc_fich);

execvp(argv[2], &argv[2]);

}

}



p2_prueba5.c


 Estudiar el ejemplo e indicar que es lo que hace.

 ¿Qué ocurre en la siguiente ejecución?

$ a.out dup2.sal ls *.c
A continuación se presenta la forma de crear una tubería entre dos procesos utilizando la entrada/salida estándar.



#include

#define LEER 0

#define ESCRIBIR 1

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

{

int descr[2];



if(argc!=3)

printf("Formato: %s comando_ent comando_sal. \n", argv[0]);

else

{

pipe(descr);



if(fork()==0)

{

close(descr[LEER]);



dup2(descr[ESCRIBIR],1);

close(descr[ESCRIBIR]);

execlp(argv[1],argv[1],NULL);

perror(argv[0]);

}

else


{

close(descr[ESCRIBIR]);

dup2(descr[LEER],0);

close(descr[LEER]);

execlp(argv[2],argv[2],NULL);

perror(argv[0]);

}

}

}


p2_prueba6.c
 Estudiar el ejemplo.

 Ejecutarlo de forma conveniente para ver el resultado de la ejecución.






База данных защищена авторским правом ©shkola.of.by 2016
звярнуцца да адміністрацыі

    Галоўная старонка