4 SEMÁFOROS.
Aparecen por vez primera en el tratado de Dijkstra de 1965 ya visto.
Visión a alto nivel:
cenicero con pajitas.
Visión a nivel medio:
semáforo_t s1;
[...]
wait(&s1);
signal(&s1);
Visión a bajo nivel:
A la operación
wait a veces se la llama operación P (del holandés proberen, comprobar) y a la operación signal, operación V (del holandés verhogen, incrementar).Significado de la variable asociada a un semáforo:
Cada S.O. puede proporcionar llamadas al sistema diferentes sobre las que construir wait y signal.
Ejemplo de realización de exclusión mutua con semáforos:
Varios procesos podrían ejecutar a la vez este mismo código sin interferir en la impresión de los demás.
ftok
que nos genera una de estas claves. Su
prototipo es:
key_t ftok(const char *path, int id);La clave será una variable de un tipo especial:
key_t
. La clave se genera a partir de una ruta
de acceso a un fichero que tiene que ser accesible en tiempo
de ejecución por el proceso y
un código de identificación,
que suele ser un carácter. Por ejemplo,
si un semáforo lo
van a usar hijos de un mismo proceso, podemos generar una
clave así:
key_t clave; [...] clave=ftok(argv[0],'G');Sabemos que tendremos acceso al propio programa ejecutable pues lo estamos ejecutando ;) ...
ipcs -s
:
<Tejo>/home/so$ ipcs -s IPC status from /dev/kmem as of Thu Mar 16 13:07:04 2000 T ID KEY MODE OWNER GROUP Semaphores: s 0 0x2f141f40 --ra-ra-ra- root sys s 1 0x41145e23 --ra-ra-ra- root root s 2 0x4e140002 --ra-ra-ra- root root s 3 0x41155197 --ra-ra-ra- root root s 4 0x01090522 --ra-r--r-- root root s 5 0x850b32f6 --ra------- ingres sysEn la columna ID vemos el identificador de semáforo. De él hablamos en el siguiente apartado. La columna KEY nos da en hexadecimal la clave asociada a ese semáforo. MODE indica los permisos de acceso del semáforo. OWNER y GROUP son el propietario el grupo al que pertenece el semáforo.
ipcrm
podemos eliminar un semáforo. Por
ejemplo:
<Tejo>/home/so$ ipcrm -s 5 ipcrm: semid(5): permission deniedEvidentemente, hay que tener permisos para hacerlo :) .
int semget(key_t clave, int nsems, int semflg);Como primer parámetro se le va a pasar una clave como las que hemos visto en el apartado anterior. El segundo es el número de semáforos que queremos que tenga el conjunto que queremos usar y el último parámetro es un campo de bits que nos permite especificar opciones. De estas opciones, interesa
IPC_CREAT
. Si varios procesos hacen un
semget
para acceder a un semáforo, uno de ellos
ha de especificar la macro IPC_CREAT
con un
OR (|
) de los permisos con los que quiere
crear el semáforo. El resto de procesos especificará
0 como último parámetro.
semget
la macro
IPC_PRIVATE
. Todos los hijos usarán entonces el
mismo valor devuelto por semget
para acceder al
semáforo. Por ejemplo:
int semAforo; [...] semaforo=semget(IPC_PRIVATE,3,IPC_CREAT | 0600); [...] switch (fork()) {case -1: /* Errores */ [...] case 0: /* Hijo */ /* Puede usar el semáforo pues conoce la variable semAforo. */ [...] default: /* Padre */ /* También puede usar el semáforo pues también conoce semAforo. */ }
semctl
. El prototipo
es:
int semctl(int semid, int semnum, int cmd, ... /* arg */);El primer parámetro es el identificador del conjunto de semáforos sobre el que queremos trabajar, que lo habremos obtenido con
semget
.
El segundo parámetro es el índice del
semáforo sobre el que queremos trabajar
(al primer semáforo le
corresponde un cero). El tercer parámetro es
la operación que
queremos realizar. Puede ser una de estas:
IPC_RMID
: elimina el array de semáforos.
Es necesario eliminar el array de semáforos si ya no
se va a usar, pues el número de arrays que existe
en el sistema es limitado. Normalmente lo eliminará
el proceso que lo haya creado. Ejemplo:
semctl(semAforo,0,IPC_RMID); /* Aunque pone 0, elimina todo el array. */
GETVAL
: devuelve el valor actual del
semáforo. No se suele usar
más que para depurar.
SETVAL
: da un valor al semáforo.
Ejemplo:
semctl(semAforo,3,SETVAL,2);Asigna el valor 2 al cuarto semáforo del array
semAforo
.
IPC_STAT
e IPC_SET
:
nos permite conocer o establecer ciertas propiedades
del array de semáforos.
Las propiedades se especifican
o se leen de una estructura de tipo
struct semid_ds
pasada por referencia.
Ejemplo:
struct semid_ds ssd; [...] semctl(semAforo,0,IPC_STAT,&ssd);
semctl
ha de ser del tipo union semun
.
Además, la definición de esta unión
tiene que incluirse
explícitamente en nuestro programa. La definición es:
union semun {int val; struct semid_ds *buf; ushort_t *array; };Es un caso extraño, porque lo normal sería que la definición viniera en algún fichero de cabeceras. Es más, si no se hace así y se hace como en HPUX, se puede obtener un fallo de acceso a memoria en tiempo de ejecución.
wait
y signal
. Esto es así
porque, si no, no permitiríamos que el proceso se bloquee.
Además,
las operaciones han de ser atómicas para no
dar lugar a inconsistencias.
wait
y
signal
, disponemos de la llamada al sistema
semop
. El prototipo es:
int semop(int semid, struct sembuf *sops, unsigned int nsops);
semop
se vuelve algo complicada porque permite
realizar más de una operación wait
y
signal
a la vez. Veámoslo con un ejemplo:
tenemos 7 semáforos cuyo identificador es
semAforo
y queremos
hacer un wait
sobre el primero, un
signal
sobre el tercero y 4 wait
s
a la vez sobre el séptimo. Se haría así:
struct sembuf sops[3]; [...] sops[0].sem_num=0; /* Sobre el primero, ... */ sops[0].sem_op=-1; /* ... un wait (resto 1) */ sops[0].sem_flg=0; sops[1].sem_num=2; /* Sobre el tercero, ... */ sops[1].sem_op= 1; /* ... un signal (sumo 1) */ sops[1].sem_flg=0; sops[2].sem_num=6; /* Sobre el séptimo, ... */ sops[2].sem_op=-4; /* ... 4 waits (resto 4) */ sops[2].sem_flg=0; semop(semAforo,sops,3);Observad cómo
sops
no lleva ampersand
(&
), pues el nombre de un array sin nada
detrás ya es un puntero al primer elemento del array.
También observad cómo la llamada al sistema
necesita que le
especifiquemos el número de operaciones que incluye el array
porque en C un puntero en tiempo de ejecución
no es más que
una dirección de memoria y no tiene información
adicional acerca
del objeto al que hace referencia. Esto es, la llamada
semop
no puede saber cuántos elementos tiene el
array que le pasamos.
La llamada semop
se queda bloqueada hasta
que se puedan
realizar todas las operaciones que se especifiquen en el
array sops
. Esto no suele ser equivalente en la
mayor parte de casos a realizar las operaciones una detrás
de otra.
sops
se puede
especificar en el campo sem_flg
la opción
IPC_NOWAIT
y, en ese caso,
la operación sería no
bloqueante. Esto es, si no se puede decrementar el semáforo
no nos quedaríamos bloqueados (aunque no se decremente).
Utilizar este flag es una práctica nefasta por cuanto
la esencia
de un semáforo es precisamente quedarse bloqueado en el caso
de no poder realizar la operación wait
.
sem_op
de struct sembuf
.
sleep
, aunque no
system
, para dormir.
Cuando se pulse CTRL+C, todos los procesos acabarán
y el semáforo será borrado del Sistema Operativo.
system("clear");Cuando un proceso entre en la sección crítica escribirá la última cifra de su PID en la línea de la pantalla cuyo número es igual a ella. Cuando vaya a salir de la sección crítica borrará (escribirá un espacio encima) en dicha línea. Así se podrá observar a simple vista que no hay más de dos procesos a la vez en la sección crítica. Para poner el cursor en la línea 7, columna 3, por ejemplo, se puede usar la función:
system("tput cup 7 3");Observación: si usáis
printf
para escribir en la
pantalla debéis saber que esta función tiene
un búfer intermedio
que hace que lo que se escriba no aparezca inmediatamente en
pantalla. El búfer se vacía en tres circunstancias:
\n
).
fflush(stdout);
.
/usuarios/profes/gyermo/SESION6/critica
el
ejecutable de esta práctica.
semctl
).
tput
ipcs
ipcrm
system
fflush
system("tput cup lugar 3");
lugar
es una variable entera donde
está la columna donde tiene que ir el cursor.
Hablé con el profesor y me dijo que poniendo
eso lo que hacía era que aparecieran los caracteres
'l', 'u', 'g', 'a' y 'r' en la cadena que se
pasa a system
, no el contenido
de la variable lugar
.
Así que, ni cort@ ni perezos@, lo cambié por:
system("tput cup %d 3",lugar);Y me sigue sin funcionar. ¿Me tiene manía el ordenador?
union semun
.
Esta unión tiene tres campos. ¿Cómo hay que rellenar los
tres campos?