Está en la página 1de 21

Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux

1
COMUNICACIN Y SINCRONIZACIN ENTRE PROCESOS EN LINUX
Los medios de comunicacin entre procesos (Inter-Process Communication o IPC) de Linux proporcionan un
mtodo para que mltiples procesos se comuniquen unos con otros. Hay varios mtodos de IPC disponibles en
Linux:
o Pipes UNIX Half-duplex
o FIFOs (pipes con nombre)
o Colas de mensajes estilo SYSTEM V
o Semforos estilo SYSTEM V
o Segmentos de memoria compartida estilo SYSTEM V
Pipes UNIX Semi-duplex
La forma ms simple de IPC en Linux son los pipes o tuberas, han estado presentes desde los primeros orgenes
del sistema operativo UNIX y proporcionan un mtodo de comunicaciones en un sentido (unidirecional, semi-
duplex) entre procesos.
Una tubera (pipe) es simplemente un mtodo de conexin que une la salida estndar de un proceso a la entrada
estndar de otro. Para esto se utilizan descriptores de archivos reservados, los cuales en forma general son:
! 0: entrada estndar (stdin).
! 1: salida estndar (stdout).
! 2: salida de error (stderr).
Este mecanismo es ampliamente usado, incluso en la lnea de comandos UNIX (en la shell):
l s | sor t | l p
Lo anterior es un ejemplo de pipeline, donde se toma la salida de un comando l s como entrada de un
comando sor t , quien a su vez entrega su salida a la entrada de l p. Los datos corren por la tubera semi-duplex,
viajando (virtualmente) de izquierda a derecha por la tubera.
Cuando un proceso crea una tubera, el kernel instala dos descriptores de archivos para que los use la tubera. Un
descriptor se usa para permitir un camino de entrada a la tubera (write), mientras que la otra se usa para obtener
los datos de la tubera (read). A estas alturas, la tubera tiene un pequeo uso practico, ya que la creacin del
proceso solo usa la tubera para comunicarse consigo mismo. Se podra considerar esta representacin de un
proceso y del kernel despus de que se haya creado una tubera:
Del diagrama anterior, es fcil ver como se conectan los descriptores. Si el proceso enva datos por la tubera
(fd0), tiene la habilidad obtener (leer) esa informacin de fd1. Sin embargo, hay un objetivo ms amplio sobre el
esquema anterior. Mientras una tubera conecta inicialmente un proceso a si mismo, los datos que viajan por la
tubera se mueven por el kernel.
in
Proceso
out
Kernel
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
2
A estas alturas, la tubera es bastante intil. Para que crear una tubera si solo estamos hablando con nosotros
mismos? Ahora, el proceso de creacin bifurca un proceso hijo. Como un proceso hijo hereda cualquier
descriptor de archivo abierto del padre, ahora tenemos la base para la comunicacin multiprocesos (entre padre e
hijo). La versin actualizada del esquema simple quedara como:
Arriba, se ve que ambos procesos ahora tienen acceso al descriptor del archivo que constituye la tubera. En sta
fase se debe tomar una decisin critica. En que direccin se quiere que viajen los datos? El proceso hijo enva
informacin al padre, o viceversa? Se debe proceder a "cerrar" el extremo de la tubera que no interesa.
Suponiendo que el hijo ejecuta su cdigo, y devuelve informacin por la tubera al padre. El esquema ya
revisado aparecera como:
Ahora la construccin de la tubera esta completa. Lo nico que queda por hacer es usar la tubera. Para acceder
a una tubera directamente, se puede usar la misma llamada al sistema que se usa para un archivo E/S de bajo
nivel.
Para enviarle datos a la tubera, se usa la llamada al sistema write(), y para recuperar datos de la tubera, se usa la
llamada al sistema read(). Se debe tener presente que ciertas llamadas al sistema, como por ejemplo lseek(), no
trabaja con descriptores a tuberas.
Creacin de tuberas en C
Para crear una tubera simple con C, se usa la llamada al sistema pipe(). Toma un argumento solo, que es un
arreglo de dos enteros, y si tiene xito, la tabla contendr dos nuevos descriptores de archivos para ser usados
por la tubera.
Este primer ejemplo (bastante intil), crea, escribe y lee desde un pipe:
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <er r no. h>
#i ncl ude <uni st d. h>
i nt mai n( )
{
in
Proceso
Padre
out
Kernel

in
Proceso
Hijo
out
in
Proceso
Padre
out
Kernel
in
Proceso
Hijo
out
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
3
i nt f d[ 2] ;
char buf f er [ 30] ;
i f ( pi pe( f d) == - 1) / * pi pe( ) devuel ve 0 en caso de xi t o
{ o 1 en caso de er r or */
per r or ( " pi pe" ) ;
exi t ( 1) ;
}
pr i nt f ( " Escr i bi endo en el descr i pt or de ar chi vo #%d\ n" , f d[ 1] ) ;
wr i t e( f d[ 1] , " pr ueba" , 5) ;
pr i nt f ( " Leyendo desde el descr i pt or de ar chi vo #%d\ n" , f d[ 0] ) ;
r ead( f d[ 0] , buf f er , 5) ;
pr i nt f ( " Le do \ " %s\ " \ n" , buf f er ) ;
}
Este ejemplo no tiene mucho sentido, ya que el proceso se est comunicando consigo mismo. Veamos que pasa
si se llama a fork() y se hace que el proceso hijo mande la palabra "hola" al padre.
Primero, el padre debe llamar a pipe(). Segundo, se llama a fork(). El hijo recibe una copia de todos los
descriptores de archivos del padre, incluyendo una copia de los descriptores de archivos del pipe. Esto permite
que el hijo mande datos al extremo de escritura del pipe, y el padre los reciba del extremo de lectura.:
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <sys/ t ypes. h>
#i ncl ude <uni st d. h>
i nt mai n( )
{
i nt f d[ 2] ;
char buf f er [ 30] ;
pi pe( f d) ;
i f ( ! f or k( ) ) {
pr i nt f ( " HI J O: Escr i bi endo en el pi pe\ n" ) ;
wr i t e( f d[ 1] , " hol a" , 5) ;
pr i nt f ( " HI J O: Chao. . . \ n" ) ;
exi t ( 0) ;
} el se {
pr i nt f ( " PADRE: Leyendo desde el pi pe\ n" ) ;
r ead( f d[ 0] , buf f er , 5) ;
pr i nt f ( " PADRE: He l e do \ " %s\ " \ n" , buf f er ) ;
wai t ( NULL) ;
}
}
La salida en pantalla debera ser la siguiente:
PADRE: Leyendo desde el pi pe
HI J O: Escr i bi endo en el pi pe
HI J O: Chao. . .
PADRE: He l e do " hol a"
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
4
En este caso, el padre intent leer desde el pipe antes que el hijo escribiera. Cuando esto ocurre, se dice que el
padre se bloquea, o duerme, hasta que llegan datos que leer. Al parecer el padre intent leer, se durmi, el hijo
escribi y termin, y el padre despert y ley el dato.
Veamos ahora un ejemplo ms completo y con ms verificacin de errores.
El primer elemento del arreglo fd (elemento 0) esta fijado y abierto para lectura, mientras el segundo entero
(elemento 1) esta fijado y abierto para escritura. Visualmente hablando, la salida de fd1 se vuelve la entrada para
fd0.
Todo los datos que se mueven por la tubera lo hacen por el kernel. Se debe recordar que un nombre de arreglo
en C es un puntero a su primer miembro. Es decir, fd es equivalente a &fd0. Una vez establecida la tubera, se
puede crear (mediante fork) un nuevo proceso hijo.
Si el padre quiere recibir datos del hijo, debe cerrar fd1, y el hijo debe cerrar fd0. Si el padre quiere enviarle
datos al hijo, debe cerrar fd0, y el hijo debe cerrar fd1. Como los descriptores se comparten entre el padre y hijo,
siempre se debe cerrar el extremo de la tubera que no interesa, nunca se devolver EOF si los extremos
innecesarios de la tubera no son explcitamente cerrados. Como se menciono previamente, una vez se ha
establecido la tubera, los descriptores de archivo se tratan como descriptores a archivos normales.
/ *****************************************************************************
Par t e de l a " Gu a Li nux de Pr ogr amaci n - Capi t ul o 6"
( C) opyr i ght 1994- 1995, Scot t Bur ket t
*****************************************************************************
MODULO: pi pe. c
*****************************************************************************/
#i ncl ude <st di o. h>
#i ncl ude <uni st d. h>
#i ncl ude <sys/ t ypes. h>
i nt mai n( voi d)
{
i nt f d[ 2] , nbyt es;
pi d_t chi l dpi d;
char st r i ng[ ] = " Hol a a t odos! \ n" ;
char r eadbuf f er [ 80] ;
pi pe( f d) ;
i f ( ( chi l dpi d = f or k( ) ) == - 1)
{
per r or ( " f or k" ) ;
exi t ( 1) ;
}
i f ( chi l dpi d == 0)
{
/ * Ci er r e del descr i pt or de ent r ada en el hi j o */
cl ose( f d[ 0] ) ;
/ * Envi ar el sal udo v a descr i pt or de sal i da */
wr i t e( f d[ 1] , st r i ng, st r l en( st r i ng) ) ;
exi t ( 0) ;
}
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
5
el se
{
/ * Ci er r e del descr i pt or de sal i da en el padr e */
cl ose( f d[ 1] ) ;
/ * Leer al go de l a t uber a. . . el sal udo! */
nbyt es = r ead( f d[ 0] , r eadbuf f er , si zeof ( r eadbuf f er ) ) ;
pr i nt f ( " He r eci bi do el st r i ng: %s" , r eadbuf f er ) ;
}
r et ur n( 0) ;
}
Otro uso de los pipes, es la implementacin de por ejemplo los comandos ls | wc -l" en C. Esto requiere del uso
de dos funciones: exec() y dup(). Las funciones exec() reemplazan el proceso actualmente en ejecucin con
cualquier proceso que se le pasa a exec(). Esta funcin se utilizar para ejecutar ls y wc -l. La funcin dup()
recibe un descriptor de archivos abierto y lo duplica. As es como se puede conectar la salida estndar de ls a la
entrada estndar de wc.
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <uni st d. h>
i nt mai n( )
{
i nt f d[ 2] ;
pi pe( f d) ;
i f ( ! f or k( ) ) {
cl ose( 1) ; / * cer r ar el st dout nor mal */
dup( f d[ 1] ) ; / * hacer que st dout sea i gual a f d[ 1] */
cl ose( f d[ 0] ) ; / * f d[ 0] no se necesi t a */
execl p( " l s" , " l s" , NULL) ;
} el se {
cl ose( 0) ; / * cer r ar el st di n nor mal */
dup( f d[ 0] ) ; / * hacer que st di n sea i gual a f d[ 0] */
cl ose( f d[ 1] ) ; / * f d[ 1] no se necesi t a */
execl p( " wc" , " wc" , " - l " , NULL) ;
}
}
close(1) libera el descriptor de archivo 1 (salida estndar). dup(fd[1]) crea una copia del extremo de escritura del
pipe en el primer descriptor de archivo que est disponible, que es "1", ya que recin fue cerrado. De esta
manera, cualquier cosa que ls escribe a la salida estndar (descriptor de archivo 1) ir a fd[1] (el extremo de
escritura del pipe). La seccin de cdigo de wc funciona de la misma manera, excepto al revs.
Hay otra llamada al sistema, dup2 (), que se puede usar tambin. Esta llamada particular tiene su origen con la
Versin 7 de UNIX, se realizo por una versin de BSD y ahora es requerida por el estndar POSIX.
Con esta particular llamada, se tiene la operacin de cerrado, y la duplicacin del descriptor actual, relacionado
con una llamada al sistema. Adems, se garantiza el ser atmica, que esencialmente significa que nunca se
interrumpir por la llegada de una seal (interrupcin). Toda la operacin transcurrir antes de devolverle el
control al kernel para despachar la seal.
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
6
Con la llamada al sistema dup() original, se tenia que ejecutar un close() antes de llamarla. Esto resultaba en dos
llamadas del sistema, con un pequeo grado de vulnerabilidad en el breve tiempo que transcurre entre ellas. Si
llega una seal durante ese tiempo, la duplicacin del descriptor fallara. dup2 () resuelve este problema.
.
.
chi l dpi d = f or k( ) ;
i f ( chi l dpi d == 0)
{
/ * Cer r ar ent r ada est ndar , dupl i cando a est a l a
sal i da de dat os de l a t uber a */
dup2( 0, f d[ 0] ) ;
execl p( " sor t " , " sor t " , NULL) ;
.
.
}
Operaciones atmicas con tuberas
Para que una operacin se considere "atmica", no se debe interrumpir de ninguna manera. Todo su
funcionamiento ocurre de una vez. La norma POSIX indica en /usr/include/posix1_lim.h que el tamao mximo
del buffer para una operacin atmica en una tubera es:
#def i ne _POSI X_PI PE_BUF 512
Hasta 512 bytes se pueden escribir o recuperar de una tubera atmicamente. Cualquier cosa que sobrepase este
limite se partir. Bajo Linux sin embargo, se define el limite atmico operacional en "linux/limits.h" como:
#def i ne PI PE_BUF 4096
Como se puede ver, Linux adapta el numero mnimo de bytes requerido por POSIX, y se le pueden agregar
bastantes. La atomicidad del funcionamiento de la tubera se vuelve importante cuando implica mas de un
proceso (FIFOS). Por ejemplo, si el numero de bytes escritos en una tubera excede el limite atmico para una
simple operacin, y procesos mltiples estn escribiendo en la tubera, los datos sern "intercalados" o
"chunked". En otras palabras, un proceso insertara datos en la tubera entre la escritura de otro.
Notas acerca de las tuberas semi-duplex:
! Se pueden crear tuberas de dos direcciones abriendo dos tuberas, y reasignando los descriptores de archivo
al proceso hijo.
! La llamada a pipe() debe hacerse ANTES de la llamada a fork(), o los hijos no heredaran los descriptores.
! Con tuberas semi-duplex, cualquier proceso conectado debe compartir el ancestro indicado. Como la tubera
reside en el kernel, cualquier proceso que no sea ancestro del creador de la tubera no tiene forma de
direccionarlo. Este no es el caso de las tuberas con nombre (FIFOS).
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
7
Tuberas con Nombre (FIFO - First In First Out)
Un FIFO tambin se conoce como una tubera con nombre. El nombre es de un archivo que mltiples procesos
pueden abrir, leer y escribir. Una tubera con nombre funciona como una tubera normal, pero tiene algunas
diferencias notables:
! Las tuberas con nombre existen en el sistema de archivos como un archivo de dispositivo especial.
! Los procesos de diferentes padres pueden compartir datos mediante una tubera con nombre.
! Una vez finalizadas todas las operaciones de E/S, la tubera con nombre permanece en el sistema de archivos
para un uso posterior.
Creacin de una FIFO
Hay varias formas de crear una tubera con nombre. Las dos primeras se pueden hacer directamente desde el
shell:
mknod MI FI FO p
mkf i f o a=r w MI FI FO
Los dos comandos anteriores realizan operaciones idnticas, con una excepcin. El comando mkfifo proporciona
una posibilidad de alterar los permisos del archivo FIFO directamente tras la creacin. Con mknod ser necesaria
una llamada al comando chmod.
Los archivos FIFO se pueden identificar rpidamente en un archivo fsico por el indicador "p" que aparece en la
lista del directorio.
$ l s - l MI FI FO
pr w- r - - r - - 1 r oot r oot 0 Dec 14 22: 15 MI FI FO|
Tambin hay que observar que la barra vertical ("smbolo pipe") esta situada inmediatamente detraes del nombre
de archivo.
Para crear un FIFO en C, se puede hacer uso de la llamada del sistema mknod() con sus respectivos argumentos:
mknod( " / t mp/ MI FI FO" , S_I FI FO| 0644 , 0) ;
En este caso el archivo "/tmp/MIFIFO" se crea como archivo FIFO. El segundo argumento es el modo de
creacin, que sirve para indicarle a mknod( ) que crea un FIFO (con S_I FI FO) y pone los permisos de acceso
a ese archivo (644 octal, rw-r--r--) al igual como se puede hacer con el comando chmod. Finalmente, el tercer
argumento de mknod() se ignora (al crear un FIFO) salvo que se este creando un archivo de dispositivo. En ese
caso, se debera especificar los nmeros mayor y menor del archivo de dispositivo.
Los permisos se ven afectados por la configuracin de umask de la siguiente forma:
umask_def i ni t i va = per mi sos_sol i ci t ados & ~umask_i ni ci al
Un truco comn es usar la llamada del sisterma umask() para borrar temporalmente el valor de umask:
umask( 0) ;
mknod( " / t mp/ MI FI FO" , S_I FI FO| 0666, 0) ;
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
8
Operaciones con FIFOs
Las operaciones E/S sobre un FIFO son esencialmente las mismas que para las tuberas normales, con una gran
excepcin. Se debe usar una llamada del sistema open( ) o una funcin de librera para abrir fsicamente un
canal para la tubera. Con las tuberas semi-duplex, esto es innecesario, ya que la tubera reside en el kernel y no
en un sistema de archivos fsico. En nuestro ejemplo trataremos la tubera como un stream, abiendolo con
fopen(), y cerrndolo con fclose().
Consideramos un proceso servidor simple:
/ *****************************************************************************
Par t e de l a " Gu a Li nux de Pr ogr amaci n - Capi t ul o 6"
( C) opyr i ght 1994- 1995, Scot t Bur ket t
*****************************************************************************
MODULO: f i f oser ver . c
*****************************************************************************/
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <sys/ st at . h>
#i ncl ude <uni st d. h>
#i ncl ude <l i nux/ st at . h>
#def i ne FI FO_FI LE " MI FI FO"
i nt mai n( voi d)
{
FI LE *f p;
char r eadbuf [ 80] ;
/ * Cr ea el FI FO si no exi st e */
umask( 0) ;
mknod( FI FO_FI LE, S_I FI FO| 0666, 0) ;
whi l e( 1)
{
f p = f open( FI FO_FI LE, " r " ) ;
f get s( r eadbuf , 80, f p) ;
pr i nt f ( " Cadena r eci bi da: %s\ n" , r eadbuf ) ;
f cl ose( f p) ;
}
r et ur n( 0) ;
}
Como un FIFO bloquea por defecto, se debe ejecutar el servidor en segundo plano tras compilarlo:
$ f i f oser ver &
Se discutir la accin de bloqueo de un FIFO ms adelante. Primero consideremos el siguiente cliente:
/ *****************************************************************************
Par t e de l a " Gu a Li nux de Pr ogr amaci n - Capi t ul o 6"
( C) opyr i ght 1994- 1995, Scot t Bur ket t
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
9
*****************************************************************************
MODULO: f i f ocl i ent . c
*****************************************************************************/
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#def i ne FI FO_FI LE " MI FI FO"
i nt mai n( i nt ar gc, char *ar gv[ ] )
{
FI LE *f p;
i f ( ar gc ! = 2 ) {
pr i nt f ( " USO: f i f ocl i ent [ cadena] \ n" ) ;
exi t ( 1) ;
}
i f ( ( f p = f open( FI FO_FI LE, " w" ) ) == NULL) {
per r or ( " f open" ) ;
exi t ( 1) ;
}
f put s( ar gv[ 1] , f p) ;
f cl ose( f p) ;
r et ur n( 0) ;
}
Acciones Bloqueantes en una FIFO
Normalmente, el bloqueo ocurre en un FIFO. En otras palabras, si se abre el FIFO para lectura, el proceso estar
"bloqueado" hasta que cualquier otro proceso lo abra para escritura. Esta accin funciona al revs tambin. Si
este comportamiento no nos interesa, se puede usar la bandera O_NONBLOCK en la llamada a open() para
desactivar la accin de bloqueo por defecto.
En el caso de nuestro servidor simple, lo hemos puesto en segundo plano, y permitido hacer su bloqueo all.
Al tener el nombre del pipe que se encuentra en el disco hace que todo sea mucho ms fcil. Procesos no
relacionados entre ellos pueden ahora comunicarse mediante los pipes.
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
10
Colas de Mensajes
Una cola de mensajes funciona como una FIFO, pero con algunas diferencias. Generalmente, los mensajes son
sacados de la cola en el orden en que se pusieron. Sin embargo, hay maneras de sacar cierto mensaje de la cola
antes de que alcance llegar al inicio de la cola.
Un proceso puede crear una nueva cola de mensajes, o se puede conectar a una ya existente. De esta forma, dos
procesos pueden compartir informacin mediante la misma cola de mensajes.
Una vez que se crea una cola de mensajes, esta no desaparece hasta que se destruya. Todos los procesos que
alguna vez la usaron pueden finalizar, pero la cola todava existir. Una buena costumbre sera usar el comando
i pcs para verificar si existe alguna cola de mensajes que ya no est en uso y destruirla con el comando i pcr m.
Primero que nada queremos conectarnos a una cola, o crearla si no existe. Para hacer esto se utiliza la llamada al
sistema msgget ( ) :
i nt msgget ( key_t key, i nt msgflg) ;
msgget ( ) devuelve el ID de la cola de mensajes en caso de xito, o - 1 en caso de error.
El primer argumento, key, es un identificador nico que describe la cola a la cual uno se quiere conectar (o
crear). Cualquier otro proceso que quiera conectarse a esta cola deber usar el mismo key.
El segundo argumento, msgflg, le dice a msgget ( ) qu hacer con la cola. Para crear una cola, este campo
debe ser igual a I PC_CREAT junto a los permisos para la cola. (Los permisos de la cola son los mismos que los
permisos estndar de archivos las colas reciben los user-id y group-id del programa que las cre.)
Cmo crear el identificador key? Ya que el tipo key_t es un l ong, se puede usar cualquier nmero que se
quiera. Pero que pasara si otro programa utiliza el mismo nmero pero quiere usar otra cola? La solucin es usar
la funcin f t ok( ) la cual genera un identificador a partir de dos argumentos:
key_t f t ok( const char *path, i nt id) ;
Bsicamente, path debe ser un archivo que el proceso pueda leer. El otro argumento, id es normalmente un
char escogido arbitrariamente, como por ejemplo 'A'. La funcin f t ok( ) usa informacin sobre el archivo y el
id para generar un identificador, probablemente nico, para ser usado en msgget ( ) . Los programas que
quieran usar la misma cola deben generar el mismo identificador key, as que deben pasarle algunos parmetros
a f t ok( ) .
#i ncl ude <sys/ msg. h>
key = f t ok( " / home/ r munoz/ cual qui er _ar chi vo" , ' b' ) ;
msqi d = msgget ( key, 0666 | I PC_CREAT) ;
En este ejemplo, se le ponen los permisos 666 (o r w- r w- r w- ) a la cola. Ahora tenemos msqi d que se puede
utilizar para mandar y recibir mensajes desde la cola.
Enviar a una cola
Una vez que uno se conecta a la cola de mensajes usando msgget ( ) , se puede mandar y recibir mensajes.
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
11
Para mandar mensajes:
Cada mensaje consiste de dos partes, las cuales estn definidas en la estructura st r uct msgbuf , tal como
aparece en sys/ msg. h:
st r uct msgbuf {
l ong mt ype;
char mt ext [ 1] ;
};
El campo mt ype es usado para recibir mensajes, y puede ser cualquier nmero positivo. mt ext es el dato que
se aadir a la cola.
Se puede usar cualquier estructura para mandar mensajes a la cola, siempre y cuando el primer elemento es un
long. Por ejemplo, se podra usar la siguiente estructura para almacenar todo tipo de informacin:
st r uct pi r at a_msgbuf {
l ong mt ype; / * debe ser posi t i vo */
char nombr e[ 30] ;
char t i po_de_bar co;
i nt cr uel dad;
};
Una vez definida la estructura, solo resta enviar la informacin a la cola usando msgsnd( ) :
i nt msgsnd( i nt msqid, const voi d *msgp, si ze_t msgsz, i nt msgflg) ;
msqid es el identificador de cola de mensajes devuelta por msgget ( ) . El puntero msgp es un puntero a los
datos que se quiere poner en la cola.. msgsz es el tamao de los datos que se quieren enviar. Finalmente,
msgflg permite poner parmetros adicionales, que por el momento se ignorarn ponindolos en 0.
Este es el cdigo que muestra como nuestros datos son aadidos a la cola de mensajes:
#i ncl ude
key_t key;
i nt msqi d;
st r uct pi r at a_msgbuf pmb = {2, " LeChuck" , ' S' , 80};
key = f t ok( " / home/ r munoz/ cual qui er _ar chi vo" , ' b' ) ;
msqi d = msgget ( key, 0666 | I PC_CREAT) ;
msgsnd( msqi d, &pmb, si zeof ( pmb) , 0) ; / * aadi r l o a l a col a */
Slo faltara verificar los errores. El valor de mt ype se dej arbitrariamente en 2. Esto ser importante en la
prxima seccin.
Recibir desde la cola
La contraparte de msgsnd( ) es msgr cv( ) . Esta sera la forma de recibir usando la llamada msgr cv( ) :
#i ncl ude
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
12
key_t key;
i nt msqi d;
st r uct pi r at a_msgbuf pmb; / * dnde se guar dar a LeChuck */
key = f t ok( " / home/ r munoz/ cual qui er _ar chi vo" , ' b' ) ;
msqi d = msgget ( key, 0666 | I PC_CREAT) ;
msgr cv( msqi d, &pmb, si zeof ( pmb) , 2, 0) ; / * sacar l o de l a col a! */
Hay algo nuevo que notar en la llamada msgr cv( ) : el 2! Qu significa esto? Aqu esta la sintaxis de la
llamada:
i nt msgr cv( i nt msqid, voi d *msgp, si ze_t msgsz, l ong msgtyp, i nt msgflg) ;
El 2 que especificamos en la llamada, es el msgtyp (tipo de mensaje) requerido. Recuerden que dejamos
arbitrariamente mt ype en 2, en la seccin de msgsnd( ) , as que este ser el tipo de mensaje que se recibir.
De hecho, el comportamiento de msgr cv( ) puede ser modificado drsticamente seleccionando un msgtyp
que sea positivo, negativo, o cero:
msgtyp Efecto en msgrcv()
Cero Recibir el prximo mensaje de la cola, independientemente de su mt ype.
Positivo Obtener el prximo mensaje con un mt ype igual al especificado.
Negativo
Recibir el primer mensaje de la cola cuyo mt ype es menor o igual al valor absoluto del
argumento msgtyp.
Muchas veces se quiere simplemente recibir el prximo mensaje de la cola, sin importar su mt ype. En ese caso,
se dejara el parmetro msgtyp en 0.
Eliminar una cola de mensajes
Llega un momento cuando se quiere eliminar una cola de mensajes. Como ya se mencion, las colas de mensajes
quedan a menos que se eliminen explcitamente; es importante hacer esto para no gastar recursos del sistema.
Existen dos formas de hacerlo:
1. Usar el comando de UNIX i pcs para obtener una lista de colas de mensajes definidas, luego usar el
comando i pcr mpara eliminar la cola.
2. Escribir el cdigo necesario.
Generalmente, es ms recomendada la segunda opcin, ya que se puede desear que el programa limpie la cola en
cierto momento. Para hacer esto se utiliza otra funcin: msgct l ( ) .
La sintaxis de msgct l ( ) es:
i nt msgct l ( i nt msqid, i nt cmd, st r uct msqi d_ds *buf) ;
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
13
msqid es por supuesto el identificador de la cola obtenido de msgget ( ) . El argumento importante es cmd que
le indica a msgct l ( ) qu hacer. Pueden ser varias cosas, pero slo veremos I PC_RMI D, que se usa para
eliminar colas de mensajes. El argumento buf se puede dejar en NULL ya que estamos utilizando I PC_RMI D.
Para destruir la cola que creamos anteriormente:
#i ncl ude
.
.
msgct l ( msqi d, I PC_RMI D, NULL) ;
Y la cola desaparecer.
Algunos ejemplos
Estos dos programas se comunican entre ellos usando colas de mensajes. El primero, ki r k. c aade mensajes
en la cola, y spock. c los saca.
Este es el cdigo de kirk.c:
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <er r no. h>
#i ncl ude <sys/ t ypes. h>
#i ncl ude <sys/ i pc. h>
#i ncl ude <sys/ msg. h>
st r uct my_msgbuf {
l ong mt ype;
char mt ext [ 200] ;
};
i nt mai n( voi d)
{
st r uct my_msgbuf buf ;
i nt msqi d;
key_t key;
i f ( ( key = f t ok( " ki r k. c" , ' B' ) ) == - 1) {
per r or ( " f t ok" ) ;
exi t ( 1) ;
}
i f ( ( msqi d = msgget ( key, 0644 | I PC_CREAT) ) == - 1) {
per r or ( " msgget " ) ;
exi t ( 1) ; }
pr i nt f ( " I ngr ese l neas de t ext o, ^D par a sal i r : \ n" ) ;
buf . mt ype = 1; / * no i mpor t a en est e caso */
whi l e( get s( buf . mt ext ) , ! f eof ( st di n) ) {
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
14
i f ( msgsnd( msqi d, ( st r uct msgbuf *) &buf , si zeof ( buf ) , 0) == - 1)
per r or ( " msgsnd" ) ;
}
i f ( msgct l ( msqi d, I PC_RMI D, NULL) == - 1) {
per r or ( " msgct l " ) ;
exi t ( 1) ;
}
r et ur n 0;
}
El programa ki r k permite que se ingresen lneas de texto. Cada lneas es insertada en un mensaje y aadida a la
cola de mensajes. Luego la cola de mensajes es leda por spock.
Este es el cdigo de spock.c:
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <er r no. h>
#i ncl ude <sys/ t ypes. h>
#i ncl ude <sys/ i pc. h>
#i ncl ude <sys/ msg. h>
st r uct my_msgbuf {
l ong mt ype;
char mt ext [ 200] ;
};
i nt mai n( voi d)
{
st r uct my_msgbuf buf ;
i nt msqi d;
key_t key;
i f ( ( key = f t ok( " ki r k. c" , ' B' ) ) == - 1) { / *mi smo key que ki r k. c*/
per r or ( " f t ok" ) ;
exi t ( 1) ;
}
i f ( ( msqi d = msgget ( key, 0644) ) == - 1) { / * conect ar se a l a col a */
per r or ( " msgget " ) ;
exi t ( 1) ;
}
pr i nt f ( " spock: l i st o par a r eci bi r mensaj es, capi t an. \ n" ) ;
f or ( ; ; ) { / * Spock nunca t er mi na */
i f ( msgr cv( msqi d, ( st r uct msgbuf *) &buf , si zeof ( buf ) , 0, 0) == - 1) {
per r or ( " msgr cv" ) ;
exi t ( 1) ;
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
15
}
pr i nt f ( " spock: \ " %s\ " \ n" , buf . mt ext ) ;
}
r et ur n 0;
}
Notar que spock, en la llamada a msgget ( ) , no incluye la opcin I PC_CREAT. Dejamos que ki r k creara la
cola de mensajes, y spock devolver un error si esto no ha ocurrido.
Notar tambin que pasa si se ejecutan los dos y se termina el uno o el otro. Tambin prueben ejecutar dos copias
de ki r k o dos copias de spock para ver que pasa al tener dos lectores o dos escritores. Otra demostracin
interesante es ejecutar a ki r k, ingresar un montn de mensajes, luego ejecutar spock y ver como los recupera
todos de un golpe.
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
16
SEMAFOROS
Los semforos se pueden utilizar para controlar el acceso a los archivos, memoria compartida, y en general
cualquier otro recurso compartido.
Obtener semforos
Con IPC de System V, se obtienen conjuntos de semforos. Obviamente, se puede obtener un conjunto de
semforos que slo contiene un semforo, pero la idea es que se puede tener una gran cantidad de semforos al
crear un solo conjunto de semforos.
Cmo se hace para crear un conjunto de semforos? Se hace con la llamada semget ( ) , que devuelve el id
(identificador) del semforo (de aqu en adelante ser referido como semid):
#i ncl ude <sys/ sem. h>
i nt semget ( key_t key, i nt nsems, i nt semflg) ;
Que lo que es el key? Es un identificador nico que es usado por procesos diferentes para identificar este
conjunto de semforos. (El key ser generado usando f t ok( ) , descrito en los apuntes sobre las colas de
mensajes).
El siguiente argumento, nsems, es la cantidad de semforos en este conjunto de semforos.
Finalmente, el argumento semflg le indica a semget ( ) entre otras cosas; qu permisos tendr el nuevo
conjunto de semforos, si se est creando un nuevo conjunto o si slo se quiere conectar a un conjunto ya
existente. Para crear un nuevo conjunto, se puede combinar los permisos con el argumento I PC_CREAT.
Aqu hay un ejemplo donde se crea el key con f t ok( ) y luego se crea un conjunto de 10 semforos, con los
permisos 666 (r w- r w- r w- ):
#i ncl ude <sys/ i pc. h>
#i ncl ude <sys/ sem. h>
key_t key;
i nt semi d;
key = f t ok( " . " , ' E' ) ;
semi d = semget ( key, 10, 0666 | I PC_CREAT) ;
Una vez ejecutado este ejemplo, se puede verificar la existencia del conjunto de semforos con el comando
i pcs. (No se olviden eliminar el semforo con el comando i pcr m!)
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
17
semop(): poder atmico!
Todas las operaciones sobre semforos utilizan la llamada al sistema semop( ) . Esta llamada al sistema es de
propsito general, y su funcionalidad es dictada por una estructura que se le pasa, st r uct sembuf :
st r uct sembuf {
ushor t sem_num;
shor t sem_op;
shor t sem_f l g;
};
sem_numes el nmero del semforo en el conjunto que se quiere manipular. sem_op es lo que se quiere hacer
con ese semforo. Esto tiene varios significados, dependiendo si sem_op es positivo, negativo, o cero, tal como
se muestra en la siguiente tabla:
sem_op
Lo que pasa
Positivo
El valor de sem_op es sumado al valor del semforo. As es como un programa usa un
semforo para marcar un recurso como ocupado.
Negativo
Si el valor absoluto de sem_op es mayor que el valor del semforo, el proceso que lo llama
se bloquear hasta que el valor del semforo alcanza el valor absoluto de sem_op.
Finalmente, el valor absoluto de sem_op ser restado del valor del semforo. As es como un
proceso entrega un recursos vigilado por el semforo.
Cero Este proceso esperar hasta que el semforo en cuestin alcance el 0.
As que, bsicamente, lo que se hace es cargar un st r uct sembuf con los valores que se quieran, y luego
llamar a semop( ) , de esta manera:
i nt semop( i nt semid , st r uct sembuf *sops, unsi gned i nt nsops) ;
El argumento semid es el nmero obtenido de la llamada a semget ( ) . sops es un puntero a una estructura
st r uct sembuf que ya se haya rellenado con los comandos de los semforos. Incluso se puede crear un
arreglo de st r uct sembuf s, para realizar muchas operaciones al mismo tiempo.
El argumento sem_f l g permite al programa especificar modificadores que modifican ms an los efectos de
una llamada a semop( ) .
Uno de estos modificadores es I PC_NOWAI T, que causa que la llamada semop( ) devuelva el error EAGAI N si
se encuentra con una situacin donde normalmente se bloquear. Esto sirve para cuando se quiere sondear con
el fin de verificar si se puede ocupar algn.
Otro modificador muy til es SEM_UNDO. Este causa que semop( ) grabe, de cierta forma, el cambio realizado
en el semforo. Cuando el programa termina su ejecucin, el kernel automticamente deshace todos los cambios
que fueron marcados con el modificador SEM_UNDO.
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
18
Destruir un semforo
Hay dos maneras de deshacerse de un conjunto semforos: una es usando el comando i pcr mde UNIX. La otra
manera es a travs de la llamada semct l ( ) con los argumentos adecuados.
Esta es la unin uni on semun, junto con la llamada a semct l ( ) para destruir el semforo:
uni on semun {
i nt val ; / * usado sl o par a SETVAL */
st r uct semi d_ds *buf ; / * par a I PC_STAT y I PC_SET */
ushor t *ar r ay; / * usado par a GETALL y SETALL */
};
i nt semct l ( i nt semid, i nt semnum, i nt cmd, uni on semun arg) ;
Bsicamente, se pone en semid el ID del semforo que se quiere destruir. El cmd debe estr en I PC_RMI D, lo
que le indica a semct l ( ) que debe sacar este conjunto de semforos. Los parmetros semnum y arg no
tienen ningn significado en el contexto de I PC_RMI Dy se puede dejar en cualquier cosa.
Esto es un ejemplo de cmo eliminar un semforo:
uni on semun dummy;
i nt semi d;
.
.
semi d = semget ( . . . ) ;
.
.
semct l ( semi d, 0, I PC_RMI D, dummy) ;
Cuando recin se crearon los semforos, estos quedan inicializados en cero. Esto es grave, ya que significa que
todos estn marcados como ocupados; y se necesita otra llamada (ya sea a semop( ) o a semct l ( ) para
marcarlos como libres). Qu significa todo esto? Esto significa que la creacin de semforos no es atmica. Si
dos procesos estn tratando de crear, inicializar y usan un semforo al mismo tiempo, se puede producir una
condicin de carrera.
Este problema se puede resolver teniendo un solo proceso que crea e inicializa el semforo. El proceso principal
slo lo accesa, pero nunca lo crea o lo destruye.
Ejemplos
Hay tres ejemplos. El primero, semi ni t . c, crea e inicializa el semforo. El segundo, semdemo. c, controla el
acceso a un supuesto archivo. Finalmente, semr m. c es usado para destruir el semforo (esto podra realizarse
con el comando i pcr m)
La idea es ejecutar semi ni t . c para crear el semforo. Se puede usar el comando i pcs desde la lnea de
comandos para verificar que el semforo existe. Luego ejecutar semdemo. c en un par de ventanas y ver como
ellos interactan. Finalmente, se debe usar semr m. c para eliminar el semforo.
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
19
Este es el cdigo de semi ni t . c (debe ejecutarse de los primeros!):
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <er r no. h>
#i ncl ude <sys/ t ypes. h>
#i ncl ude <sys/ i pc. h>
#i ncl ude <sys/ sem. h>
i nt mai n( voi d)
{
key_t key;
i nt semi d;
uni on semun ar g;
i f ( ( key = f t ok( " . " , ' J ' ) ) == - 1) {
per r or ( " f t ok" ) ;
exi t ( 1) ;
}
/ * cr ear un conj unt o de semf or os con 1 semf or o: */
i f ( ( semi d = semget ( key, 1, 0666 | I PC_CREAT) ) == - 1) {
per r or ( " semget " ) ;
exi t ( 1) ;
}
/ * i ni ci al i zar el semf or o #0 en 1: */
ar g. val = 1;
i f ( semct l ( semi d, 0, SETVAL, ar g) == - 1) {
per r or ( " semct l " ) ;
exi t ( 1) ;
}
r et ur n 0;
}
Este es el cdigo de semdemo. c:
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <er r no. h>
#i ncl ude <sys/ t ypes. h>
#i ncl ude <sys/ i pc. h>
#i ncl ude <sys/ sem. h>
i nt mai n( voi d)
{
key_t key;
i nt semi d;
st r uct sembuf sb = {0, - 1, 0}; / * par a r eser var r ecur sos */
i f ( ( key = f t ok( " . " , ' J ' ) ) == - 1) {
per r or ( " f t ok" ) ;
exi t ( 1) ;
}
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
20
/ * conect ar se al conj unt o de semf or os cr eado por semi ni t . c: */
i f ( ( semi d = semget ( key, 1, 0) ) == - 1) {
per r or ( " semget " ) ;
exi t ( 1) ;
}
pr i nt f ( " Pr esi one ENTER par a bl oquear : " ) ;
get char ( ) ;
pr i nt f ( " Tr at ndo de bl oquear . . . \ n" ) ;
i f ( semop( semi d, &sb, 1) == - 1) {
per r or ( " semop" ) ;
exi t ( 1) ;
}
pr i nt f ( " Bl oqueado. \ n" ) ;
pr i nt f ( " Pr esi one ENTER par a desbl oquear : " ) ;
get char ( ) ;
sb. sem_op = 1; / * l i ber ar r ecur so */
i f ( semop( semi d, &sb, 1) == - 1) {
per r or ( " semop" ) ;
exi t ( 1) ;
}
pr i nt f ( " Desbl oqueado\ n" ) ;
r et ur n 0;
}
Este es el cdigo de semr m. c:
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <er r no. h>
#i ncl ude <sys/ t ypes. h>
#i ncl ude <sys/ i pc. h>
#i ncl ude <sys/ sem. h>
i nt mai n( voi d)
{
key_t key;
i nt semi d;
uni on semun ar g;
i f ( ( key = f t ok( " . " , ' J ' ) ) == - 1) {
per r or ( " f t ok" ) ;
exi t ( 1) ;
}
/ * conect ar se al semf or o cr eado por semi ni t . c: */
i f ( ( semi d = semget ( key, 1, 0) ) == - 1) {
per r or ( " semget " ) ;
exi t ( 1) ;
}
Sistema Operativo Comunicacin y Sincronizacin entre procesos en Linux
21
/ * el i mi nar l o */
i f ( semct l ( semi d, 0, I PC_RMI D, ar g) == - 1) {
per r or ( " semct l " ) ;
exi t ( 1) ;
}
r et ur n 0;
}
Los semforos son muy tiles en una situacin de concurrencia. Sirven para controlar el acceso a un archivo,
pero tambin a otro recurso como por ejemplo la memoria compartida.
Siempre cuando se tienen mltiples procesos ejecutndose dentro de una seccin crtica de cdigo, se necesitan
los semforos.

También podría gustarte