Los procesos en UNIX no comparten memoria, ni siquiera los padres con sus hijos. Por tanto, hay que establecer algún mecanismo en caso de que se quiera comunicar información entre procesos concurrentes. El sistema operativo UNIX define tres clases de herramientas de comunicación entre procesos (IPC): los semáforos, la memoria compartida y los mensajes.
El tipo de llamadas al sistema para estos IPCs es análogo al de los
semáforos: existen sendas funciones shmget
y msgget
para crear o enlazarse a un segmento de memoria
compartida o a una cola de mensajes, respectivamente. Para alterar propiedades
de estos IPCs, incluyendo su borrado, están las funciones
shmctl
y msgctl
.
Para enviar o recibir mensajes, se utilizan las funciones
msgsnd
y msgrcv
.
En este apartado se describirán brevemente algunas llamadas al sistema disponibles para el uso de las IPCs dentro de la programación en C.
¿Qué es un semáforo para el UNIX? Formalmente es muy similar a la definición clásica de Dijkstra, en el sentido de que es una variable entera con operaciones atómicas de inicialización, incremento y decremento con bloqueo.
El UNIX define tres operaciones fundamentales sobre semáforos:
* semget
Crea o toma el control de un semáforo
* semctl
Operaciones de lectura y escritura del estado del
semáforo. Destrucción del semáforo
* semop
Operaciones de incremento o decremento con
bloqueo
Como el lenguaje C no tiene un tipo "semáforo" predefinido, si queremos
usar semáforos tenemos que crearlos mediante una llamada al sistema
(semget
). Esta llamada permite crear un conjunto de
semáforos, en lugar de uno solo. Las operaciones se realizan
atómicamente sobre todo el conjunto; esto evita interbloqueos y oscuras
programaciones en muchos casos, pero para esta práctica es más
bien un engorro.
Al crear un semáforo se nos devuelve un número identificador,
que va a funcionar casi igual que los identificadores de fichero de las
llamadas open
, creat
, etc. La
función semget
nos permite además "abrir" un
semáforo que ya esté creado. Así, por ejemplo, si un
proceso crea un semáforo, otros procesos pueden sincronizarse con
aquél (con ciertas restricciones) disponiendo del semáforo
con semget
.
Para darle un valor inicial a un semáforo, se utiliza la
función semctl
.
El UNIX no ofrece las funciones clásicas P y V o equivalentes, sino que
dispone de una función general llamada semop
que
permite realizar una gama de operaciones que incluyen las P y V.
semctl
también se emplea para destruir un
semáforo.
Esta es una descripción resumida de las tres llamadas al sistema
para operar con semáforos (semget
,
semctl
y semop
). Para una información
más completa y fidedigna, diríjanse al manual de llamadas al
sistema (sección 2).
Para el correcto uso de todas estas funciones, han de incluir el fichero
cabecera <sys/sem.h>
Las tres funciones devuelven -1 si algo ha ido mal y en tal caso la
variable errno
informa del tipo de error.
Sintaxis:
int semget ( key_t
key, int
nsems, int
semflg )
;
semget
devuelve el identificador del semáforo
correspondiente a la clave key. Puede ser un semáforo ya existente, o
bien semget
crea uno nuevo si se da alguno de estos
casos:
a) key vale IPC_PRIVATE
. Este valor especial obliga
a semget
a crear un nuevo y único identificador,
nunca devuelto por ulteriores llamadas a semget
hasta que
sea liberado con semctl
.
b) key no está asociada a ningún semáforo existente, y se
cumple que (semflg & IPC_CREAT
) es cierto.
A un semáforo puede accederse siempre que se tengan los permisos adecuados.
Si se crea un nuevo semáforo, el parámetro nsems indica cuántos semáforos contiene el conjunto creado; los 9 bits inferiores de semflg contienen los permisos estilo UNIX de acceso al semáforo (usuario, grupo, otros).
semflg es una máscara que puede contener
IPC_CREAT
, que ya hemos visto, o IPC_EXCL
, que
hace crear el semáforo, pero fracasando si ya existía.
Ejemplo:
int semid = semget ( IPC_PRIVATE, 1, IPC_CREAT | 0744 );
Sintaxis:
int semctl ( int
semid, int
semnum, int
cmd... );
Esta es una función compleja (y de interfaz poco elegante) para
realizar ciertas operaciones con semáforos. semid es un
identificador de semáforo (devuelto previamente por
semget
) y semnum, el semáforo del conjunto
sobre el que quieren trabajar. cmd es la operación
aplicada; a continuación puede aparecer un parámetro opcional
según la operación definida por cmd.
Las operaciones que les interesan a ustedes son
GETVAL semctl
retorna el valor actual del
semáforo
SETVAL
se modifica el valor del semáforo (un
cuarto parámetro entero da el nuevo valor)
IPC_RMID
destruye el semáforo
Ejemplos:
valor = semctl (semid,semnum,GETVAL);
semctl (semid,semnum,SETVAL,nuevo_valor);
Sintaxis:
int semop ( int
semid, struct sembuf*
sops, unsigned
nsops );
Esta función realiza atómicamente un conjunto de operaciones sobre semáforos, pudiendo bloquear al proceso llamador. semid es el identificador del semáforo y sops es un apuntador a un vector de operaciones. nsops indica el número de operaciones solicitadas.
La estructura sembuf
tiene estos campos:
struct sembuf { unsigned short sem_num; // número del semáforo dentro del conjunto short sem_op; // clase de operación // según sea >0, <0 o ==0 short sem_flg; // modificadores de operación };
Cada elemento de sops es una operación sobre algún semáforo del conjunto de semid. El algoritmo simplificado de la operación realizada es éste (semval es el valor entero contenido en el semáforo donde se aplica la operación).
si semop<0 si semval >= |semop| semval -= |semop| si semval < |semop| si (semflag & IPC_NOWAIT)!=0 la función semop() retorna si no bloquearse hasta que semval >= |semop| semval -= |semop| si semop>0 semval += semop si semop==0 si semval = 0 SKIP si semval != 0 si (semflag & IPC_NOWAIT)!=0 la función semop() retorna si no bloquearse hasta que semval == 0
Resumiendo un poco, si el campo semop de una operación es positivo, se incrementa el valor del semáforo. Asimismo, si semop es negativo, se decrementa el valor del semáforo si el resultado no es negativo. En caso contrario el proceso espera a que se dé esa circunstancia. Es decir, semop==1 produce una operación V y semop==-1, una operación P.
Para ilustrar de forma concreta el empleo de semáforos bajo UNIX, les mostramos unos ejemplos de subrutinas en C que les pueden servir como modelos para elaborar sus rutinas de sincronización en las prácticas de la asignatura.
En concreto, son unas funciones que implementan las operaciones P y V de un semáforo clásico (inicialización, incremento y decremento con posible bloqueo del proceso llamador). Así definidas, o con pocas modificaciones, les pueden servir como la interfaz para uso de semáforos en sus aplicaciones.
#include <sys/types.h> /* para key_t */ /* Crea un semáforo con un valor inicial, dada una clave */ /* Devuelve el identificador (válido o no) del semáforo */ int crea_sem ( key_t clave, int valor_inicial ); /* Operaciones P y V sobre un semáforo */ void sem_P ( int semid ); void sem_V ( int semid ); /***********************************/ /******** IMPLEMENTACIÓN *******/ /***********************************/ #include <sys/ipc.h> #include <sys/sem.h> #define PERMISOS 0644 /* crea_sem: abre o crea un semáforo */ int crea_sem ( key_t clave, int valor_inicial ) { int semid = semget( /* Abre o crea un semáforo... */ clave, /* con una cierta clave */ 1, /* con un solo elemento */ IPC_CREAT|PERMISOS /* lo crea (IPC_CREAT) con unos PERMISOS */ ); if ( semid==-1 ) return -1; /* Da el valor inicial al semáforo */ semctl ( semid, 0, SETVAL, valor_inicial ); return semid; } /* abre_sem: Abrir un semáforo que otro proceso ya creó */ int abre_sem (key_t clave) { return semget(clave,1,0); } /* Operaciones P y V */ void sem_P ( int semid ) /* Operación P */ { struct sembuf op_P [] = { 0, -1, 0 /* Decrementa semval o bloquea si cero */ }; semop ( semid, op_P, 1 ); } void sem_V ( int semid ) /* Operación V */ { struct sembuf op_V [] = { 0, 1, 0 /* Incrementa en 1 el semáforo */ }; semop ( semid, op_V, 1 ); }