Mecanismos de IPC's Para la comunicación entre procesos se disponen de varios mecanismos: Pipes Named Pipes (FIFOS) File Locking System V IPC's Message queues Semaphores Shared Memory Mem. Mapped Files Sockets De estos mecanismos se desarrollan en este documento todos con excepción de File locking, Mapped Files y Sockets; los dos primeros no se dictan en esta cátedra y el tercero se trata en otra clase. Pipes Básicamente la función pipe() retorna un par de descriptores de archivos. Uno de esos descriptores es para escribir y el otro es para leer el pipe. Lo que se escribe por el descriptor de escritura se lee en el otro. #include <unistd.h> int pipe(int descf[2]); Ejemplo #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> int main(void) int pfds[2]; char buf[30]; if (pipe(pfds) == -1) perror("pipe"); printf("escribiendo al descriptor #%d\n", pfds[1]); write(pfds[1], "test\0", 5); printf("leyendo desde el descriptor #%d\n", pfds[0]);
read(pfds[0], buf, 5); printf("leí \"%s\"\n", buf); Este mecanismo tiene una dificultad importante, ambos descriptores son conocidos por el proceso que crea el pipe, no hay manera que otro proceso conozca estos valores a menos que sea un proceso creado por este utilizando fork() por ejemplo. Named Pipes (FIFOS) Son similares a los pipes con excepción que estos tienen nombre, con lo cual distintos procesos pueden accederlos para lectura o escritura como si fueran archivos. Hay dos formas de crear un FIFO. Mediante línea de comando mknod: mdoallo@guri:~$ mknod pepe p mdoallo@guri:~$ ls -l pepe prw-r--r-- 1 mdoallo mdoallo 0 sep 14 23:15 pepe O bien crearlo directamente en el código de un programa mediante la función mknod(). #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int mknod(const char *pathname, mode_t mode, dev_t dev); mknod intenta crear en el sistema de ficheros un nodo-i de tipo tubería nombrada (o FIFO) llamado pathname, especificado por mode y dev. Un nodo-i (o i-node) referencia a un archivo en Linux, estos pueden ser archivos normales (archivo propiamente dicho, carpetas o enlaces), un archivo especial de caracteres (Device Char Driver) que permite comunicar al sistema operativo con dispositivos de tipo carácter, un archivo especial de bloques; o bien, una tubería nombrada. mode especifica tanto los permisos de uso del nodo-i a crear como su tipo. Debe ser una combinación (mediante un O-lógico) de uno de los tipos de ficheros enumerados a continuación y de los permisos para el nuevo nodo-i. El tipo de fichero debe ser uno de los siguientes: S_IFREG, para especificar un fichero normal (que será creado vacío); S_IFCHR, un fichero especial de caracteres; S_IFBLK, un fichero especial de bloques; S_IFIFO, FIFO (tubería con nombre), o cero, lo que creará un fichero normal. En este caso se usa S_IFIFO. Si el tipo de fichero es S_IFCHR o S_IFBLK entonces dev especifica los números mayor y menor del fichero especial de dispositivo creado; en caso contrario, es ignorado. Entonces ponemos 0.
Ejemplo de programa productor de información para un FIFO: #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <sys/stat.h> #include <unistd.h> #define FIFO_NAME "canio_canion" int main(void) char s[300]; int num, fd; mknod(fifo_name, S_IFIFO 0666, 0); printf("esperando por lectores...\n"); fd = open(fifo_name, O_WRONLY); printf("conseguí un lector tipea algo \n"); while (gets(s),!feof(stdin)) if ((num = write(fd, s, strlen(s))) == -1) perror("write"); else printf("se enviaron %d bytes.\n", num); Ahora para probar este programa hace falta alguien que lo lea, pues es suficiente con hacer: $ cat canio_canion Otro ejemplo de programa consumidor de información de un FIFO: #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <sys/stat.h> #include <unistd.h> #define FIFO_NAME "canio_canion" int main(void) char s[300]; int num, fd;
mknod(fifo_name, S_IFIFO 0666, 0); printf("esperando escritores...\n"); fd = open(fifo_name, O_RDONLY); printf("conseguí un escritor\n"); do if ((num = read(fd, s, 300)) == -1) perror("read"); else s[num] = '\0'; printf("se recibieron %d bytes: \"%s\"\n", num, s); while (num > 0); Nótese que en ambos la función open() en bloqueante, a menos que se utilice el flag O_NDELAY. Message queues Pipes y FIFO's permiten comunicación entre dos procesos, un emisor y otro receptor; no hay forma de alterar el orden en que la información se recibe (concepto FIFO), ni que intervengan múltiples procesos. Message queues es un sistema de comunicación entre procesos superior a lo visto hasta el momento. Permite crear estructuras de información, asignarles etiquetas, disponer de múltiples emisores y múltiples receptores de información; además, cada proceso receptor puede leer sólo las etiquetas que son de su interés. La forma de operar es la siguiente: Primero se crea la cola de mensajes (message queue), luego los procesos se conectan a la cola para escribir o leer mensajes de la misma; finalmente se borra la cola. El comando de consola ipcs permite ver el estado de una cola. El comando ipcrm permite borrar colas. Para crear la cola primero hay que general una clave por medio de la función ftok(). # include <sys/types.h> # include <sys/ipc.h> key_t ftok ( char *camino, int proy ) La función ftok utiliza la identidad del fichero que indica camino (que debe referirse a un fichero existente y accesible) y los 8 bits menos significativos de proy (que debe ser distinto de cero) para generar una clave IPC de System V de tipo key_t, adecuada para el uso con msgget(), semget(), o shmget(). El valor resultante es el mismo para todos los nombres de ruta que hacen referencia al mismo fichero, cuando se utiliza el mismo valor de proy. El valor devuelto debería ser diferente cuando los ficheros (que existen simultáneamente) o los identificadores de proyecto son distintos.
En caso de éxito se devuelve la clave key_t generada. En caso de fallo se devuelve -1, y la variable errno indica el error al igual que con la llamada al sistema stat(2). Luego de creada la clave se puede crear la cola con la función msgget() #include <sys/msg.h> int msgget(key_t key, int msgflg); La función devuelve el identificador de la cola de mensajes asociada a key. Se crea una nueva cola de mensajes si msgflg tiene el valor IPC_CREAT. En la creación, los 9 bits más bajos del argumento msgflg definen los permisos de acceso a la cola de mensajes. Estos bits para los permisos tienen la misma forma y semántica que los de los permisos de acceso en las llamadas al sistema open(2) o creat(2) (Los permisos de ejecución no son usados.) Qué se envía o recibe en una cola? Para enviar se utiliza la función msgsnd(). #include <sys/msg.h> int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg); ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg); Para enviar o recibir un mensaje, el proceso invocador reserva una estructura de la siguiente manera genérica: struct msgbuf long mtype; /* tipo de msj., debe ser > 0 */ char mtext[1]; /* datos del mensaje */ ; El campo msgbuf es una estructura cuyo tamaño viene determinado por msgsz, un valor entero no negativo. Los mensajes de longitud cero (p.e., sin campo mtext) están permitidos. El campo mtype debe tener un valor entero estrictamente positivo que puede ser empleado por el proceso receptor para la selección de mensaje (vea la sección sobre msgrcv). El argumento msgsz especifica el tamaño máximo en bytes para el miembro mtext de la estructura apuntada por el argumento msgp. Si el texto del mensaje tiene una longitud mayor de msgsz, entonces si el argumento msgflg contiene MSG_NOERROR, el texto del mensaje se truncará (y la parte truncada se perderá), y si no, el mensaje no se borra de la cola y la llamada al sistema falla regresando y poniendo el valor E2BIG en errno.
Ejemplos de estructuras struct msgbuf long mtype; char mtext[1]; ; Otro: struct pirate_msgbuf long mtype; /* must be positive */ struct pirate_info char name[30]; char ship_type; int notoriety; int cruelty; int booty_value; info; ; Otro: struct cheese_msgbuf ; long mtype; char name[20]; int type; float yumminess; /* calculate the size of the data to send: */ int size = sizeof(struct cheese_msgbuf) - offsetof(struct cheese_msgbuf, name); Ejemplo de envío de mensajes #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/msg.h> struct my_msgbuf long mtype; char mtext[200]; ; int main(void) struct my_msgbuf buf; int msqid;
key_t key; if ((key = ftok("msg_tx.c", 'B')) == -1) perror("ftok"); if ((msqid = msgget(key, 0644 IPC_CREAT)) == -1) perror("msgget"); printf("enter lines of text, ^D to quit:\n"); buf.mtype = 1; /* we don't really care in this case */ while(fgets(buf.mtext, sizeof buf.mtext, stdin)!= NULL) int len = strlen(buf.mtext); /* ditch newline at end, if it exists */ if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0'; if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */ perror("msgsnd"); if (msgctl(msqid, IPC_RMID, NULL) == -1) perror("msgctl"); Ejemplo de recepción de mensajes #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/msg.h> struct my_msgbuf long mtype; char mtext[200]; ; int main(void) struct my_msgbuf buf; int msqid; key_t key; if ((key = ftok("msg_tx.c", 'B')) == -1) /* same key as kirk.c */
perror("ftok"); if ((msqid = msgget(key, 0644)) == -1) /* connect to the queue */ perror("msgget"); printf("listo para recibir mensajes.\n"); for(;;) /* Nunca sale! */ if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) perror("msgrcv"); printf("recibí: \"%s\"\n", buf.mtext); Semaphores Los semáforos permiten que procesos sincronicen su ejecución, no intercambian información, si no que permiten que un proceso espere hasta que otro proceso termine una determinada tarea. La forma de utilizarlos es similar a las colas de mensajes, por cuanto hay que generar una clave con ftok() y luego se crea el semáforo (o se vincula a un semáforo existente) por medio de la función semget(). Luego el semáforo es un contador que se opera con la función semctl(). #include <sys/sem.h> int semget(key_t key, int nsems, int semflg); Esta función devuelve el identificador del conjunto de semáforos asociado con el argumento key. Un nuevo conjunto de nsems semáforos se crea si msgflg tiene el valor IPC_CREAT. Con el ID de semáforo ahora se lo controla con semctl(). #include <sys/sem.h> int semctl(int semid, int semnum, int cmd,...); La función semctl realiza la operación de control especificada por cmd sobre el conjunto de semáforos identificado por semid, o en el semáforo semnum-th de dicho conjunto. (Los semáforos
son numerados comenzando por el 0.) Esta función tiene tres o cuatro argumentos. Cuando hay cuatro, la llamada es semctl(semid,semnum,cmd,arg); donde el cuarto argumento arg tiene como tipo union semun definido como sigue: union semun int val; /* valor para SETVAL */ struct semid_ds *buf; /* buffer para IPC_STAT, IPC_SET */ unsigned short int *array; /* array para GETALL, SETALL */ struct seminfo * buf; /* buffer para IPC_INFO */ ; Los comandos se pueden leer de manpages. Ejemplo de uso de semáforos #include #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <errno.h> <sys/types.h> #include <sys/sem.h> #define MAX_RETRIES 10 union semun int val; ; struct semid_ds *buf; ushort *array; /* ** initsem() -- more-than-inspired by W. Richard Stevens' UNIX Network ** Programming 2nd edition, volume 2, lockvsem.c, page 295. */ int initsem(key_t key, int nsems) /* key from ftok() */ int i; union semun arg; struct semid_ds buf; struct sembuf sb; int semid; semid = semget(key, nsems, IPC_CREAT IPC_EXCL 0666); if (semid >= 0) /* we got it first */ sb.sem_op = 1; sb.sem_flg = 0; arg.val = 1; printf("press return\n"); getchar(); for(sb.sem_num = 0; sb.sem_num < nsems; sb.sem_num++) /* do a semop() to "free" the semaphores. */
/* this sets the sem_otime field, as needed below. */ if (semop(semid, &sb, 1) == -1) int e = errno; semctl(semid, 0, IPC_RMID); /* clean up */ errno = e; return -1; /* error, check errno */ else if (errno == EEXIST) /* someone else got it first */ int ready = 0; semid = semget(key, nsems, 0); /* get the id */ if (semid < 0) return semid; /* error, check errno */ /* wait for other process to initialize the semaphore: */ arg.buf = &buf; for(i = 0; i < MAX_RETRIES &&!ready; i++) semctl(semid, nsems-1, IPC_STAT, arg); if (arg.buf->sem_otime!= 0) ready = 1; else sleep(1); if (!ready) errno = ETIME; return -1; else return semid; /* error, check errno */ return semid; int main(void) key_t key; int semid; struct sembuf sb; sb.sem_num = 0; sb.sem_op = -1; /* set to allocate resource */ sb.sem_flg = SEM_UNDO; if ((key = ftok("sem.c", 'J')) == -1) perror("ftok"); /* grab the semaphore set created by seminit.c: */
if ((semid = initsem(key, 1)) == -1) perror("initsem"); printf("press return to lock: "); getchar(); printf("trying to lock...\n"); if (semop(semid, &sb, 1) == -1) perror("semop"); printf("locked.\n"); printf("press return to unlock: "); getchar(); sb.sem_op = 1; /* free resource */ if (semop(semid, &sb, 1) == -1) perror("semop"); printf("unlocked\n"); Shared Memory Este mecanismo permite compartir información entre procesos. Trabaja con clave igual que los semáforos y las colas de mensajes, uso de la función ftok(). Luego la memoria compartida se crea y se vincula a cada proceso interesado. Para crear se utiliza la función shmget(). #include <sys/shm.h> int shmget(key_t key, int size, int shmflg); shmget() devuelve el identificador del segmento de memoria compartida asociado con el valor del argumento key. Se crea un nuevo segmento de memoria compartida, de tamaño igual al valor de size, si shmflg tiene el valor IPC_PRIVATE crea la memoria compartida. Luego para conectarnos a la memoria se utiliza shmat(). #include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shmaddr); La función shmat pega el segmento de memoria compartida identificada por shmid al espacio de direcciones del proceso que llama a la función. La dirección del pegado se especifica en shmaddr según uno de los criterios siguientes: Si shmaddr es NULL, el sistema escoge una dirección adecuada (sin utilizar) en donde pegar el segmento. El resto ver en manpages. La función shmdt despega el segmento de memoria compartida localizado en la dirección especificada por shmaddr del espacio de direcciones del proceso invocador. En caso de fallo ambas funciones devuelven -1 con errno indicando el error. En caso de éxito shmat devuelve la dirección del segmento de memoria compartido pegado, y shmdt devuelve 0. Ejemplo de uso de memoria compartida #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/shm.h> #define SHM_SIZE 1024 /* make it a 1K shared memory segment */ int main(int argc, char *argv[]) key_t key; int shmid; char *data; int mode; if (argc > 2) fprintf(stderr, "usage: shmdemo [data_to_write]\n"); /* make the key: */ if ((key = ftok("shmdemo.c", 'R')) == -1) perror("ftok"); /* connect to (and possibly create) the segment: */ if ((shmid = shmget(key, SHM_SIZE, 0644 IPC_CREAT)) == -1) perror("shmget");
/* attach to the segment to get a pointer to it: */ data = shmat(shmid, (void *)0, 0); if (data == (char *)(-1)) perror("shmat"); /* read or modify the segment, based on the command line: */ if (argc == 2) printf("escribiendo al segmento: \"%s\"\n", argv[1]); strncpy(data, argv[1], SHM_SIZE); else printf("el segmento contiene: \"%s\"\n", data); /* detach from the segment: */ if (shmdt(data) == -1) perror("shmdt"); Bibliografía Este apunte está baso en: http://beej.us/guide/bgipc manpages Apuntes de la cátedra