procesos - cs.buap.mxhilario_sm/slide/pcp2016/procesos 2012.pdf · ejecución y también la unidad...
Post on 02-Oct-2018
221 Views
Preview:
TRANSCRIPT
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
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);
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; } } }
top related