Procesos - cs.buap.mxhilario_sm/slide/pcp2016/Procesos 2012.pdf · ejecución y también la unidad...

49

Transcript of Procesos - cs.buap.mxhilario_sm/slide/pcp2016/Procesos 2012.pdf · ejecución y también la unidad...

Procesos

Aspectos básicos

1. Atributos de un proceso

2. Tabla de procesos y área de usuario

3. Planificadores

4. Comandos

a. ipcs

b. ipcrm

5. Creación procesos

6. Llamadas al sistema.

7. Como manipular proceso

a. Comunicación usando archivos

b. Pipe

c. Memoria compartida

d. FIFO

e. Socket

8. Procesos y señales.(tinaco y temporizador)

Sincronización entre procesos

1. Semáforos(volumen)

Ejemplo procesos

1. FIFO (lotería)

2. Memoria compartida(mínimos)

3. Colas de mensajes(simulación cajas)

4. Socket(cajeros)

Procesos

Atributos de proceso

¿Qué es exactamente un proceso? Un proceso es una instancia de un programa en

ejecución y también la unidad básica de planificación de Linux.

Básicamente tres son los estados de un proceso Estados

1. En ejecución • Utiliza la CPU en el instante dado

2. Listo • Ejecutable, se detiene en forma temporal para que se ejecute otro

proceso 3. Bloqueado

• No se puede ejecutar debido a la ocurrencia de algún evento externo

La transición de los estados de un proceso se lleva de la siguiente manera:

1. El proceso se bloquea en espera de datos

2. El planificador elije otro proceso

3. El planificador elige este proceso

4. Los datos están disponibles

Planificadores

Comando ipcs

Cuando se trabaja con procesos y los mecanismos de comunicación entre ellos son

importantes y para verificar la correcta comunicación el comando ipcs es de gran ayuda, a

través de este comando se puede verificar los semáforos, memoria compartida y cola de

mensajes.

Para verificar el comando y sus acciones tecleamos

#ipcs -h

Si solo se teclea solo ipcs proporciona estado de los segmentos de memoria compartida,

semáforos y cola de mensajes.

Comando ipcrm

Al trabajar con mecanismo de tipo FIFO es importante conocer el comando ipcrm que

tiene como tareas borrar mensajes, semáforos o indicadores de memoria compartida

Su sintaxis se es la siguiente:

Donde:

Opción Acción -m shmid Borra el segmento de memoria compartida con identificador shmid -q msgid Borra la cola de mensaje don identificador msgid -s semid Borra el semáforo con identificador semid -M shmkey Borra la memoria compartida, que tiene como llave shmkey -Q msqkey Borra la cola de mensaje, creada con la llave msqkey -S semkey Borra el semáforo, creado con la llave semkey

Ejemplo:

Para ejemplificar más crearemos dos colas de mensajes

Verificamos su creación

Y a continuación las eliminaremos desde la terminal y verificamos el estado.

Creación de un proceso (fork)

Para la creación de un nuevo proceso se utiliza una llamada al sistema fork, la cual crea

un nuevo proceso. El nuevo proceso, o proceso hijo será una copia del proceso que llama,

o proceso padre.

Su sintaxis de fork es: \begin{verbatim} #include <unistd.h> pid_t fork( ); \end{verbatim}

la forma de invocarla es pid=fork(). La llamada a fork hace que el proceso actual se

duplique. A la ejecución de fork , los dos procesos tiene una copia idéntica del contexto

del nivel de usuario en otras palabra se podría decir que todo lo que se encuentra antes

de fork se hereda mas no implica que su variables sean compartidas entre procesos, cabe

mencionar que para realizar esta tarea existes otro mecanismos de comunicación como por

ejemplo memoria compartida, además sus identificadores de procesos(PID) son

diferentes, el proceso padre toma el valor del PID del proceso hijo y para el proceso hijo

toma el valor 0, El proceso 0, creado por el núcleo cuando arranca el sistema, es el único

que no se crea con una llamada a fork.

Figura1

Si la llamada a fork falla, devolverá el valor -1, (ver figura 1)

Cuando llamamos a fork , el núcleo realiza las siguientes operaciones:

Busca una entrada libre en la tabla de procesos y la reserva para el proceso hijo.

Asigna un identificador de proceso PID para el proceso hijo. Este número es único

e invariable durante toda la vida del proceso y es la clave para poder controlar desde

otro proceso.

Realiza una copia del contexto del nivel de usuario del proceso padre para el

proceso hijo. Las secciones que deben ser compartidas, como el código o las zonas

de memoria compartida, no se copia, sino que se incrementan los contadores que

indican cuántos procesos comparten esa zonas.

Las tablas de control de fichero locales al proceso -como pueden ser la tabla de

descriptores de archivos- también se copian del proceso padre al proceso hijo, ya

que forman parte del contexto del nivel usuario. En las tablas globales del núcleo -

tabla de ficheros y tabla de inodes- se incrementan los contadores que indican

cuántos procesos tienen abiertos esos ficheros.

Rertorna el proceso padre el PID del proceso hijo, y al proceso hijo le devuelve el

valor 0.

Cuando se hace programación concurrente con procesos o hilos dos aspectos son muy

importantes para un correcto funcionamiento como son: sincronización y comunicación.

Terminación de procesos (exit y wait)

Existen múltiples mecanismo de sincronización de procesos como son: semáforos,

monitores, variables de condición y mutex, ya que es uno de los problemas más difíciles

cuando se hace programación concurrente, los más sencillos son usando la llamada al

sistema sleep o exit y wait, no son los mecanismos más recomendados.

exit

Una situación muy típica en programación concurrente es que el proceso padre

espere a la terminación del proceso hijo antes de terminar su ejecución.

Un ejemplo de esta situación es la forma de operar de los intérpretes de órdenes. Cuando

escribimos una orden, el intérprete arranca un proceso para ejecutarla y no devuelve el

control hasta que no se ha ejecutado completamente. Naturalmente, esto no se aplica

cuando la orden se ejecuta en segundo plano.

Para sincronizar los procesos padre e hijo se emplean las llamadas exit" y

\verb"wait". La declaración de \verb"exit" es la siguiente:

\begin{verbatim}

#include <stdio.h>

void exit(int status);

\end{verbatim}

Esta llamada termina la ejecución de un proceso y le devuelve el valor de

\verb"status" al sistema.\\

La llamada exit tien además las siguientes consecuencias:

\begin{itemize}

\item Las funciones registradas por \verb"atexit" son invocadas en orden inverso a

como fueron resgistradas. \verb"atexit" permite indicarle al sistemas

las acciones que se deben ejecutar al producirse la terminación de un proceso.

\item El contexto del proceso es descargado de memoria, lo que implica que la

tabla de descriptores de fichero es cerrada y sus ficheros asociados

cerrados, si no quedan más procesos que los que tengan abiertos.

\item Si el proceso padre está ejecutando una llamada a \verb"wait", se le notifica

la terminación de su proceso hijo y se le envia 8 bits menos

significativos de status. Con esta información, el proceso padre puede saber

en qué condiciones ha terminado el proceso hijo.

\item Si el proceso padre no está ejecutando una llamada a \verb"wait", el proceso

hijo se transforma en un proceso \textcolor{rojo}{zombi}. Un proceso

Llamadas al sistema

Suponiendo que existe una archivo llamado "suma" o en caso contrario se crear usando

#vi suma o usando un editor grafico, y está organizado de la siguiente manera:

//proceso5.c

#include <stdio.h>

#include <wait.h>

#include <sys/types.h>

#include <unistd.h>

#include <errno.h>

#include <stdlib.h>

#define n 6

static char *cmd[]={"who","ls","date","ps","uname"};

int j,i,pid,status,proc;

int buf; FILE *fp;

int process_fork(int nproc) {

int i;

for(i=1; i<=nproc-1;i++) if(fork()==0) return(i);

return(0);

}

main() {

j=rand()%5; /* utilizado por el proceso uno*/

pid=process_fork(n);

switch(pid)

{

case 0: printf("estoy en proceso");

for(i=1;i<=n;i++) wait(&i);

fprintf(stdout,"\n\n padre con ID=%d \n\n",getpid());

exit(1);

case 1: fprintf(stdout,"\n proceso 1 con ID=%d \n\n",getpid());

execlp(cmd[j],cmd[j],0);

exit(0);

case 2: fprintf(stdout,"\n proceso 2 con ID=%d \n\n",getpid());

system("sort -n suma");

exit(0);

case 3: fprintf(stdout,"\n proceso 3 con ID=%d \n\n",getpid());

execlp("sort","sort","-n","suma",0);

exit(0);

case 4: fprintf(stdout,"\n proceso 4 con ID=%d \n\n",getpid());

if((fp=fopen("suma","r"))==NULL)

{printf("error al abri el archivo");

exit(0);

}

else {printf("buscando archivo \n");

while(!feof(fp)){

fscanf(fp,"%d",&buf);

printf("%d \n",buf);

}

fclose(fp);

exit(0);

}

case 5: fprintf(stdout,"\n proceso 5 con ID=%d \n\n",getpid());

if((fp=fopen("suma","a"))==NULL)

{printf("error al abrir el archivo/escritura");

exit(0);

}

else {printf("escribiendo archivo \n");

fprintf(fp,"%d\n",2000);

fclose(fp);

exit(0);

}

default: printf("no hay tal comando \n");

exit(0);

}

wait(&status);

}

COMUNICACIÓN ENTRE PROCESOS

En cuanto a la comunicación existen varios mecanismos de comunicación.

A continuación se plantea una forma simple de comunicación, la cual consiste en utilizar un

archivo para comunicar procesos

Ejemplo:

se tiene un conjunto de datos A={ai | aiR 0≤i≤99}, y se generan cuatro

procesos(P1,P2,P3,P4) y el proceso padre(P0), cada hijo con una rebanada de 25 datos

A1={ ai | 0≤i≤24} P1=min(A1)

A2={ ai | 25≤i≤49} P2=min(A2)

A3={ ai | 50≤i≤74} P3=min(A3)

A4={ ai | 75≤i≤99} P4=min(A4)

P0=min{ P1 ,P2, P3 ,P4}

#include <stdio.h>

#include <string.h>

#include <wait.h>

#include <sys/types.h>

#include <unistd.h>

#include <errno.h>

#include <stdlib.h>

int j,i,pid,status,proc,min=100,imin;

int buf; FILE *fp;

int cont=0,a[100];

char s2[20];

int process_fork(int nproc)

{

int i;

for(i=1;i<=nproc-1;i++)

{

if(fork()==0)

return(i);

}

return(0);

}

main()

{

strcpy(s2,"minimo.txt");

fp= fopen (s2,"w+");

printf("valores del vector a[100]\n");

for(i=0;i<100;i++)

{

a[i]=rand()%100;

printf("%d ",a[i]);

if(((cont+1)%25)==0)printf("\n");

cont++;

}

printf("\n\n");

pid=process_fork(5);

switch(pid)

{

case 0:

for(i=1;i<5;i++) wait(&i);

printf("\n");

fp= fopen (s2,"r+");

while(!feof(fp))

{

fscanf(fp,"%d",&j);

printf("** %d ** ",j);

if(min>j) min=j;

}

fclose(fp);

printf("\n padre ID=%d valor minimo total %d\n",getpid(),min);

remove(s2);

exit(0);

case 1:

printf("\n");

min = a[0];

imin = 0;

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

if (a[i]<min)

min = a[i];

printf("%d ",a[i]);

}

printf("\n minimo parcial %d proceso 1 ID %d \n", min,getpid());

fprintf(fp,"%d\n",min);

fclose ( fp );

exit(0);

case 2:

printf("\n");

min = a[25];

imin = 0;

for (i=25; i<=49; i++){

if (a[i]<min)

min = a[i];

printf("%d ",a[i]);

}

printf("\nminimo parcial %d proceso 2 ID %d \n", min,getpid());

fprintf(fp, "%d\n",min);

fclose ( fp );

exit(0);

case 3:

printf("\n");

min = a[50];

imin = 0;

for (i=50; i<=74; i++) {

if (a[i]<min)

min = a[i];

printf("%d ",a[i]);

}

printf("\nminimo parcial %d proceso 3 ID %d \n", min,getpid());

fprintf(fp, "%d\n",min);

fclose ( fp );

exit(0);

case 4:

printf("\n");

min = a[75];

imin = 0;

for (i=75; i<=99; i++) {

if (a[i]<min)

min = a[i];

printf("%d ",a[i]);

}

printf("\nminimo parcial %d proceso 4 ID %d \n", min,getpid());

fprintf(fp, "%d\n",min);

fclose ( fp );

exit(0);

default: printf("no hay tal comando \n");

exit(0);

}

exit(0);

}

Pipe

En cuanto al mecanismo de comunicación de tuberías hablaremos poco ya que solo el

sistema operativo lo utiliza para comunicación y que nos es más que la comunicación

utilizando un archivo como se planteo en la sección anterior, y son pocos utilizados

programadores, cabe comentar que a veces los comandos no se ejecutan en el orden en

que se proporcionan.

Ejemplo:

Tenemos un archivo llamado pba.txt

Y se le da al sistema operativo la orden siguiente

#cat pba.txt | sort –n

Proceso 1 Proceso 2

cat pba.txt sort -n Conducto sin

nombre

Archivo temporal

El resultado es el siguiente:

Existen dos tipo de tuberías una llamada tuberías sin nombres y una con nombre y pueden

ser direccionales o bidireccionales

Memoria compartida

La forma más rápida y una de las más usadas de comunicar dos procesos es hacer que compartan una zona de memoria. Para enviar datos de un proceso a otro, solo hay que escribir en memoria y automáticamente estos datos estarán disponibles para que los lea otro proceso.

M e m o r i a

. ……… ……………… Var

………………… ………

Recordemos que los procesos no comparten memoria, no basta con declarar la variable como global, si se quiere compartir memoria tiene que implementarse primero, además se tiene que usar semáforos como mecanismo de sincronización para proteger la sección critica.

Proceso 1

Var Apuntador

Proceso 2

Var Apuntador

44468

Llave acceso

Los paso son los siguiente

Como ejemplo resolveremos el mismo problema visto al inicio de la comunicación entre

procesos.

#include <stdlib.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/sem.h>

#include <stdio.h>

Proceso

Obtener un manejador A través de shmid= shmid(llave,sizeof(..),…);

Anclar el segmento a una estructura definida dentro del proceso

var=shmat(shmid,0,0);

Leer /escribir var

Quitar el ancla shmdt(var)

Borrar segmento de memoria

shmctl(shmid,IPC_RMID,0);

Fin del

Proceso

#include <errno.h>

void spin_lock_init(); //inicializa los semáforos

void spin_lock(); //P

void spin_unlock();//V

key_t shm_key;

int shmid, semid, pid;

char *shm;

int *A, *addr;

int i,j=10, *vol,*vol2;;

int minimos[5];

int j,i,pid,status,proc;

int cont,min,imin, a[100];

int process_fork(int nproc)

{

int i;

for(i=1;i<=nproc-1;i++)

{

if(fork()==0)

return(i);

}

return(0);

}

main()

{

spin_lock_init(&semid);

shm_key=0x5675;

//shmget devuelve devuelve un entero

shmid=shmget(shm_key,sizeof(minimos),(IPC_CREAT|0600));

vol=shmat(shmid,0,0); //shmat devuelve un puntero char

vol2=vol;

printf("valores del vector a[100]\n");

for(i=0;i<100;i++)

{

a[i]=rand()%100;

printf("%d ",a[i]);

if(((cont+1)%25)==0)printf("\n");

cont++;

}

printf("\n\n");

pid=process_fork(5);

switch(pid)

{

case 0:

for(i=1;i<5;i++) wait(&i);

printf("\n");

min=vol[0];

for(i=1;i<4;i++) if(vol[i]<min)min=vol[i];

printf("\n padre ID=%d valor minimo total %d\n",getpid(),min);

exit(0);

case 1:

printf("\n");

min = a[0];

imin = 0;

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

if (a[i]<min)

min = a[i];

printf("%d ",a[i]);

}

printf("\n padre ID=%d valor minimo total %d\n",getpid(),min);

spin_lock(&semid);

vol[pid-1]=min;

spin_unlock(&semid);

exit(0);

case 2:

printf("\n");

min = a[25];

imin = 0;

for (i=25; i<=49; i++){

if (a[i]<min)

min = a[i];

printf("%d ",a[i]);

}

printf("\n padre ID=%d valor minimo total %d\n",getpid(),min);

spin_lock(&semid);

vol[pid-1]=min;

spin_unlock(&semid);

exit(0);

case 3:

printf("\n");

min = a[50];

imin = 0;

for (i=50; i<=74; i++) {

if (a[i]<min)

min = a[i];

printf("%d ",a[i]);

}

printf("\n padre ID=%d valor minimo total %d\n",getpid(),min);

spin_lock(&semid);

vol[pid-1]=min;

spin_unlock(&semid);

exit(0);

case 4:

printf("\n");

min = a[75];

imin = 0;

for (i=75; i<=99; i++) {

if (a[i]<min)

min = a[i];

printf("%d ",a[i]);

}

printf("\n padre ID=%d valor minimo total %d\n",getpid(),min);

spin_lock(&semid);

vol[pid-1]=min;;

spin_unlock(&semid);

exit(0);

default: printf("no hay tal comando \n");

exit(0);

}

if(semctl(semid,0,IPC_RMID,1)==-1)

{

perror("semctl");

exit(1);

}

if(shmctl(shmid,IPC_RMID,0)==-1)

{

perror("shmctl");

exit(1);

}

exit(0);

}

//inicializa los semaforos

void spin_lock_init(int *lok) {

int init_sem_value=1;

*lok=semget(IPC_PRIVATE,1,(0600|IPC_CREAT));

if(*lok==-1)

{

perror("semget");

exit(1);

}

if(semctl(*lok,0,SETVAL,init_sem_value)<0)

{

perror("semctl");

exit(1);

}

}

/*end of spin_lock_init */

// Operación P

void spin_lock(int *lok) {

struct sembuf sembuffer, *sops;

sops=&sembuffer;

sops->sem_num=0;

sops->sem_op=-1;

sops->sem_flg=0;

if(semop(*lok,sops,1)<0){

perror("semop");

exit(1);

}

} /*end of spin_lock */

//operación V

void spin_unlock(int *lok){

struct sembuf sembuffer, *sops;

sops=&sembuffer;

sops->sem_num=0;

sops->sem_op=1;

sops->sem_flg=0;

if(semop(*lok,sops,1)<0)

{

error("semop");

exit(1);

}

}

/*end of spin_unlock */

Colas de mensajes

El mecanismo conocido como cola de mensajes es una lista enlazada y almacenada en el

núcleo la cual tienen un id, este último es similar al de memoria compartida y semáforos.

De forma básica diremos que los mensajes tienen una estructura de acuerdo a los datos a

ser almacenados, y opera de la siguiente manera

mensaje n Etiqueta n

……..

mensaje 3 Etiqueta 3

mensaje 2 Etiqueta 2

mensaje 1 Etiqueta 1

Como podemos observar opera como una estructura de datos conocida como cola, a

excepción de que si conoce la etiqueta (número) puede sacar el mensaje correspondiente

sin necesidad de sacar los mensajes anteriores a el, a través de la función msgrcv( ) .

Cada mensaje consiste en dos partes, las cuales están definidas en la estructura struct msgbuf, tal como aparece en sys/msg.h:

struct msgbuf { long mtype; char mtext[1];

} Donde: mtype Es usado para recibir mensajes, y puede ser cualquier número positivo. mtext Es el dato que se añadirá a la cola, este campo puede variar según las necesidades

del programador.

Esta estructura es la más básica, pero puede ampliarse a mas campos de datos, el único campo que es necesario es long mtype,

in

out

Además

type msgrcv( )

0 Recibe el próximo mensaje de la cola (el de arriba).

> Se devuelve el primer mensaje cuyo msg_type sea igual a type.

< se devuelve el primer mensaje cuyo msg_type sea el menor valor o igual que el valor absoluto de type

Previamente describimos comandos para trabajar y verificar el estado de los mecanismos

de comunicación, en este apartado solo hablaremos específicamente sobre el uso para

colas de mensajes.

Si queremos ver la colas activa del núcleo y su estado, tecleamos el #ipcs –q

Para eliminar una cola desde la línea de comandos tecleamos #ipcrm –q id_cola

El siguiente ejemplo genera una colas de mensaje , envía datos y los saca como una pila, esto se logra dando valores mayores que cero a type en la función msgrcv.

//cola1.c #include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#define BUFSZ 512

/* estructura del mensaje*/

struct msg{

long msg_type;

char msg_text[BUFSZ];

}pmsg;

/*puntero a la estructura de mensaje*/

main(int argc, char *argv[])

{

int qid; /*identificador de la cola */

key_t key; /*clave de la cola */

int cont=0,len; /*el tamaño de los datos enviados*/

int cont2;

long etiq;

//creando la llave de la cola de mensaje

key=ftok("hola",'h');

//crea la cola */

if((qid=msgget(key, IPC_CREAT |0666))<0){

perror("msgget:create");

exit(EXIT_FAILURE);

}

printf("creado ID de cola = %d \n ",qid);

printf("para terminar el envio de mensajes enter\n");

for(;;){

printf("mensaje a enviar: -> ");

fgets((&pmsg)->msg_text, BUFSZ, stdin);

len=strlen(pmsg.msg_text);

if(len==1)

{

puts("fin de envio");

break;

}

cont++;

/*asocia el mensaje con este proceso*/

pmsg.msg_type=cont;

msgsnd(qid, &pmsg,len,0);

printf("etiqueta -> %d mensaje -> %s enviado \n",cont,pmsg.msg_text);

}

cont2=cont;

for(;;){

if(cont2==0){

msgctl(qid,IPC_RMID,NULL);

printf("\n cola %d eliminada \n ",qid);

exit(EXIT_SUCCESS);

}

msgrcv(qid, &pmsg,BUFSZ,cont2,0);

printf("mensaje -> %s ",(&pmsg)->msg_text);

cont2--;

}

}

Otra modificación que se puede realizar es poner todas las etiquetas (type) con números

negativos y se volverá a comportar como una pila.

La cola creada y conociendo su ID se pueden conectar varios proceso sin tener ancestros

en común, o también generar varias colas y conectarse a ellas, el ejemplo siguiente crea

dos colas de mensajes y simula como dos cajas de un súper mercados, al final del día saca

el total de la venta del día.

//colas.c #include <stdio.h>

#include <stdlib.h>

#include <sys/msg.h>

#include <string.h>

#include <time.h>

int nfork (int np){

int i;

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

if (fork()==0){

return(i);

}

}

return(0);

}

struct men{

long menid;

char nom[10];

int dato;

}pmen;

main (void){

int cid1,cid2,pid=0,i,total1=0,total2=0,total=0;

int cont=1;

key_t key1,key2;

key1=1020;

key2=2400;

cid1=msgget(key1,IPC_CREAT|0666);

cid2=msgget(key2,IPC_CREAT|0666);

pid=nfork (2);

switch (pid){

case 0: //printf ("Padre \n");

for(i=0;i<2;i++)wait(&i);

printf("Productos caja uno \n");

for (i=0;i<5;i++)

{

msgrcv (cid1, &pmen, sizeof(pmen), 0,0);

printf("%s %d \n",pmen.nom,pmen.dato);

total1+=pmen.dato;

}

printf ("Total caja uno : %d \n",total1);

msgctl (cid1,IPC_RMID,NULL);

printf("\n");

printf("Productos caja dos \n");

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

{

msgrcv (cid2, &pmen, sizeof(pmen), 0,0);

printf("%s %d \n",pmen.nom,pmen.dato);

total2+=pmen.dato;

}

printf ("Total caja dos : %d \n",total2);

msgctl (cid2,IPC_RMID,NULL);

printf ("\nVenta del dia= %d \n",total1+total2);

exit (0);

case 1: //printf ("Hijo 1 \n");

srand(getpid());

printf ("producto registrado en caja 1 \n");

for (i=0;i<5;i++)

{

total=(1+rand()%50);

printf ("producto registrado en caja 1 \n");

pmen.menid=total;

pmen.dato=total;

sprintf(pmen.nom,"producto-%d",cont);

msgsnd (cid1,&pmen,sizeof(pmen), 0);

cont++;

}

exit (0);

case 2: //printf ("Hijo 2 \n");

srand(getpid());

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

{

total=(1+rand()%50);

printf ("producto registrado en caja 2\n");

pmen.menid=total;

pmen.dato=total;

sprintf(pmen.nom,"producto-%d",cont);

msgsnd (cid2,&pmen,sizeof(pmen), 0);

cont++;

}

exit (0);

default: printf ("Error al crear procesos \n");

exit (0);

}

}

Sockets

Es un mecanismo que comunica dos proceso, que pueden intercambiar diferente tipos de

datos ya sea que los procesos estén corriendo en un computadora o en un red de

computadoras, además guarda cierta similitud con las tuberías (pipe).

Para que la comunicación se establezca entre los procesos es necesario los siguientes

aspectos: Una dirección IP, un protocolo y un número de puerto.

Para conseguir esto aparece el concepto de conector o socket: dos procesos distintos

crean cada uno su conector por lo tanto.

1. Cada conector esta ligado a una dirección.

2. Un proceso puede enviar información, a través de un socket propio, al socket de otro

proceso, siempre y cuando conozca la dirección asociada (la del otro socket). La

comunicación se realiza entre una pareja de sockets.

Dominios y direcciones

Definimos un dominio de comunicación como una familia de protocolos que se pueden

emplear para conseguir el intercambio de datos entre sockets.

Un sistema UNIX particular puede ofertar varios dominios, aunque los más habituales son

estos dos:

1. Dominio UNIX (PF1_UNIX) Para comunicarse entre procesos dentro de la misma

máquina.

2. Dominio Internet(PF_INET). Para comunicarse entre procesos en dos

computadoras conectadas mediante los protocolos de Internet (TCP-UDP/IP).

Además un sockets del dominio PF_UNIX no puede dialogar con sockets del dominio

PF_INET.

Cada familia de protocolos define una serie de convenciones a la hora de especificar los

formatos de las direcciones. Tenemos, por lo tanto, estas dos familias de direcciones:

1. Formato UNIX(AF2_UNIX). Una dirección de socket es como nombre de

fichero(pathnmae).

1 PF significa Protocol Family

2 AF significa una Address Family

2. Formato Internet(AF_INET). Una dirección de sockets precisa de estos tres

campos:

a) Una dirección de red de la maquina (Dirección IP). b) Un protocolo (TCP o UDP) y c) Un puerto correspondiente a ese protocolo.

Es importante resaltar que un socket puede ser referenciado desde el exterior solo si su

dirección es conocida. Sin embargo, se puede utilizar un socket local aunque no se

conozca la dirección que tiene.

Estilos de comunicación

Orientados a conexión:- Mecanismo que sirven para conseguir un canal para el

intercambio de octenos. Un proceso pone, a su ritmo, bytes en el canal, que recibe de

forma fiable y ordenada en el otro extremo, donde hay un proceso que los recoge a su

conveniencia.

Ejemplo: pipes y los socketpairs

Comunicación sin conexión:- también denominada comunicación con datagramas. El

emisor envía mensajes envía un mensaje autónomo que el receptor debe recibir enteros.

Por el camino algún mensajes pueden perderse, retrasarse o llegar desordenados.

La comunicación entre dos sockets puede realizarse con cualquiera de estas dos formas

(aunque los dos sockets comunicantes deben de hacerse creados con el mismo estilo,

ademas de el mismo dominio). Para ello, al crear un socket se indica el estilo de

comunicación correspondiente:

• SOCK_STREAM la comunicación pasa por tres fases

1. Apertura de conexión

2. Intercambio de datos

3. Cierre de conexión

El intercambio de datos es fiable y orientado a octenos (como los pipes): no hay frontera de

mensajes

• SOCK_DGRAM. No hay conexiones. Cada mensaje (o datagrama) es

autocontenido. Los datagramas no se mezclas se mezclan unos con otros. No hay

garantia de entrega: se pueden perder, estropear o desordenar.

Protocolos Un protocolo es un conjunto de reglas, formato de datos y convenciones que es necesario

respetar para conseguir la comunicacion entre dos entidades, ejemplos: IP, TCP, UDP,

FTP, ETC.,

Cuando se crea un socket es necesario indicar cual es el protocolo de trasporte a emplear

para el intercambio de datos que se realizara a traves de el.

La mayoría de las aplicaciones con socket de tipo INTERNET trabjan bajo el paradigma

cliente servidor el proceso se da de la forma siguiente:

Operaciones del servidor operaciones cliente

Crea un socket

Anclar a un puerto

Escuchar a través del puerto y

definir cola de peticiones

Aceptar la conexión

Leer/Escribir en la conexión

Cerrar la conexión

Crea un socket

Conectarse al puerto del servidor

Leer/Escribir en la conexión

Cerrar la conexión

Llamadas de sistemas para la creación y uso de sockets

Creación de socket

Un proceso puede crear un socket utilizando la función socket(). Su prototipo es este:

#include <sys/types.h>

#include <sys/socket.h>

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

protocolo sirve para especificar el protocolo de comunicación a emplear, en general este

argumento es 0, indicando que se usa el protocolo por omisión.

Asociar el socket a un puerto: bind()

Una vez creado el socket necesitamos anclarlo a un puerto para que el proceso tenga que

escuchar atreves de ese puerto. Y se hace utilizando la llamada del sistema bind().

int bind(int nomsocket, const struct sockaddr *dirsocket, size_t dirsocket_longitud)

Donde:

nomsocket Socket creado a partir de la llamada bind() dirsocket Contiene todos los datos asociados a una dirección donde se

va a trasmitir a través del socket dirsocket_longitud Contiene el tamaño de la direccion

bind() regresa 0 si tuvo éxito, en caso contrario -1 que indica que existe un error

Escuchando en el puerto: listen()

Una vez creado, anclado a un puerto, es momento de ponerse a escuchar y s utiliza

llamada del sistemas listen().

Int listen(int nomsocket, int num_max_proc)

Donde:

nomsocket Socket creado a partir de la llamada bind() Num_ma_proc Número máximo de procesos en espera

listen() regresa 0 si tuvo éxito, en caso contrario -1 que indica que existe un error

Aceptando una llamada: accept()

En protocolos orientados a comunicación el servidor debe esperar una petición de conexión

para crear un canal de comunicación a través del cual se dará el envió y recepción de

datos entre procesos.

Se utiliza la llamada del sistema accept().

int accept(int nomsocket, struct sockaddr *ctladdr, int *addrlen)

Donde:

nomsocket Descriptor del socket ctladdr Dirección del proceso con el que se estableció el canal de

comunicación (cliente) addrlen Longitud de la dirección

accept() regresa 0 si tuvo éxito, en caso contrario -1 que indica que existe un error.

Conectándose al puerto: connect()

Esta llamada de sistema es usada por el cliente para solicitar una conexión al servidor.

int connect(int nomsocket, struct sockaddr *servaddr, int *addrlen)

Donde:

nomsocket Descriptor del socket ctladdr Dirección del servidor addrlen Longitud de la dirección

connect() regresa 0 si tuvo éxito, en caso contrario -1 que indica que existe un error.

Recepción de mensajes: read(), recv() y recvfrom()

Enviando mensajes: write(), send() y sendto()

Cerrando la comunicación: close()

Comunicación con socketpairs

Este mecanismo es muy similar a las tuberías solo que con la particularidad de que son

bidireccionales y solos son usados cuando los procesos tienen un ancestro en común.

Para creas un socketpairs se hace de la siguiente función:

#include <sys/types.h> #include <sys/socket.h> Int socketpair(int domain, int type, int protocolo, int vecsock[2]

Las tareas que se deben realizar se muestran en la siguiente tabla

PADRE HIJO

Crear un socketpair

Crear el hijo

Leer un mensaje Escribir un mensaje

Escribir un mensaje Leer un mensaje

Terminar Terminar

Ejemplo: //sockepair.c #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define men1 "papa esta ahi" #define men2 "aqui estoy hijo" main() { int n,sockets[2], child; char buf[1024]; if(socketpair(PF_UNIX,SOCK_STREAM,0,sockets)<0) { perror("abriendo el par de sockets\n"); exit(1); } if ((child=fork())==-1) perror("creando el proceso hijo\n"); else if(child) { //este es el padre close(sockets[0]); if(read(sockets[1],buf,1024)<0) perror("padre leyendo el mensaje \n"); printf("ID padre = %d mensaje de mi hijo --> %s \n",getpid(),buf); if(write(sockets[1],men2,strlen(men2)+1)<0) perror("padre escribiendo el mensaje"); printf("ID padre = %d respuesta --> %s \n",getpid(),men2); close(sockets[1]); exit(1); } else { /*este es el hijo */ close(sockets[1]);

printf("ID hijo = %d pregunta --> %s\n",getpid(),men1); if(write(sockets[0],men1,strlen(men1)+1)<0) perror("hijo escribiendo mensaje"); wait(&n); if(read(sockets[0],buf,1024)<0) perror("hijo leyedo mensaje\n"); printf("ID hijo = %d respuesa de papa--> %s\n",getpid(),buf); close(sockets[0]); } }

Datagrama en el dominio UNIX

Para ejemplificar utilizaremos dos programas uno que actuara como emisor y otro como

receptor, la tarea del emisor es enviar un mensaje al receptor y el receptor la recibe y

muestra el mensaje.

//receptor sock-dgram-receptor.c //Ejemplo sobre data gramas en el dominio PF_UNIX //extremo receptor //proceso para el intercambio de información //1.- ejecutamos el receptor //2.- Ejecutar el emisor #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define NAME "socketunix" main() { int sock, length, lon2; struct sockaddr_un name; char buf[1024]; sock=socket(PF_UNIX, SOCK_DGRAM,0);

if(sock<0){ perror("abriendo socket de dtagramas"); exit(1); } name.sun_family=AF_UNIX; strcpy(name.sun_path,NAME); if(bind(sock,(struct sockaddr *)&name,sizeof(name))<0){ perror("asociado con nombre al socket"); exit(1); } printf("Direccion del socket -> %s \n",NAME); if(recvfrom(sock,buf,1024,0,NULL,&lon2)<0) perror("recibiendo un datagrama"); printf("ID proceso %d --> %s\n",getpid(),buf); close(sock); unlink(NAME); exit(0); }

// Emisor sock-dgram-emisor.c //ejemplo sobre datagrama en el dominio PF_UNIX //Extremo Emisor #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define DATA "emisor esta ahiiiiiiiiiii" main(int argc, char *argv []) { int sock; struct sockaddr_un name; sock=socket(PF_UNIX, SOCK_DGRAM,0); if(sock<0){ perror("Abriendo scoket de datagrama"); exit(1); } name.sun_family=AF_UNIX; strcpy(name.sun_path,argv[1]); printf("ID proceso %d pregunta: --> %s\n",getpid(),DATA); if(sendto(sock,DATA,strlen(DATA)+1,0,(struct sockaddr *)&name,sizeof name)<0) perror("enviando un datagrama"); close(sock); exit(0);\ }

Socket tipo INTERNET en modo comunicación

Conexión simultaneas

Consiste en crear aplicaciones concurrentes bajo el esquema cliente-servidor, en este caso creamos un

servidor multiprocesos, el proceso principal crea procesos hijos los cuales se les asigna una la tarea de

atender la petición de algún servidor os sea cada servidor le corresponde un proceso hijoI(i)

/*Serveco3.c : Un servidor de eco orientado a conexión concurrente*/ /* multiproceso basado en sockets.*/ #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include<signal.h> #include <errno.h> /*----------------------------------------------------------------------------*/ void do_echo(int); /*----------------------------------------------------------------------------*/ extern int errno; /*----------------------------------------------------------------------------*/ void do_echo(int fd) { char buf[4096],resp[100]; int cc,org,faltan,cc2,opc; //read(int fd, void *buf, size_t nbyte); //fd descriptor del socket(ssocket) //buf almacena la lectura //sizeof(buf) tamaño de buf while (cc=read(fd,buf,sizeof(buf))) { if (cc<0) { perror("Read"); exit(6); } //printf("buf %s opc=%d \n",buf,opc); //printf("lect-> %s longitud %d \t",buf,strlen(buf)); switch(buf[0]) { case '0':strcpy(resp,"number zero\n"); break; case '1':strcpy(resp,"number one\n"); break; case '2':strcpy(resp,"number two\n"); break; default: strcpy(resp,"fuera de rango[0,2]\n"); break; } org=0;

faltan=strlen(resp); /*Los que hay que mandar*/ //write(int fd, void *buf, size_t nbyte); while (faltan) { /// if ((cc2=write(fd,&resp[org],faltan))<0) { perror("Fallo al escribir"); exit(7); } org+=cc2; faltan-=cc2; } } close(fd); } /*----------------------------------------------------------------------------*/ int main() { struct sockaddr_in sin,fsin; int s,ssock,alen;//read(int fd, void *buf, size_t nbyte); sin.sin_family=AF_INET; sin.sin_addr.s_addr =htonl(INADDR_ANY); sin.sin_port =htons(4000); //crea socket //s=/-1 entonces error if ((s=socket(PF_INET,SOCK_STREAM,0))<0) { perror("No se puede crear el socket"); exit(1); } //asocia el socket a un puerto los datos se dant //en los campos de la estructura sin if (bind (s,(struct sockaddr *)&sin,sizeof(sin))<0) { perror("No se puede asignar la dirección"); exit(2); } //escucha s id-sock, 5 el numnero max de proceso en espera if (listen(s,5)<0) { perror("No puedo poner el socket en modo escucha"); exit(3); } signal(SIGCHLD, SIG_IGN); while (1) { alen=sizeof(fsin); //acepta o se emite mensajes //*fsin direccion en donde el proceso establecio el canal de comunicacion el cliente //*alen longitud de la direccion if ((ssock =accept(s, (struct sockaddr *)&fsin,&alen))<0) { if (errno == EINTR) continue; perror("Fallo en funcion accept"); exit(4); } switch (fork())

{ case -1: perror("No puedo crear hijo"); exit(5); case 0 : close(s); /*Proceso hijo*/ do_echo(ssock); break; default: close(ssock); break; } } }

Bibliografia

TCP/IP en Unix “programación de aplicaciones distribuidas” Jose Miguel Alonso RA-MA