- ¿Se puede usar la biblioteca en un Linux de 64 bits?
En el momento de escritura de este LPE no se puede
mezclar código de 64 bits con código de 32 bits.
Por lo tanto, le tenéis que decir a
gcc
que os genere código de 32 bits para que vaya bien
con la biblioteca. Añadid -m32
en la
línea de compilación para lograrlo. Si os da
problemas al ejecutar el código es que necesitáis
instalar las bibliotecas de compatibilidad de 32 bits.
Dependiendo de la distribución se tratará de
un paquete u otro. Por ejemplo, para la familia
de Debian, se trata de un paquete llamado
ia32-libs
.
- ¿Se puede proporcionar la biblioteca para el
Sistema Operativo X, procesador Y? Por problemas
de eficiencia en la gestión y mantenimiento del
código no se
proporcionará la biblioteca más que para
Solaris-SPARC y Linux-Intel de 32 bits. A veces podéis
lograr encontrar una solución mediante el uso
de máquinas virtuales.
- ¿Dónde poner un semáforo? Dondequiera que
uséis la
frase, "el proceso puede llegar a esperar hasta que..."
es un buen candidato a que aparezca una operación
wait sobre un semáforo. Tenéis que plantearos
a continuación qué proceso hará signal
sobre ese presunto semáforo, dónde lo hará y
cuál será el valor inicial.
- Si ejecutáis la práctica en segundo plano
(con ampersand (&)) es normal que al pulsar
CTRL+C el programa no reaccione. El terminal sólo
manda
SIGINT
a los procesos que estén
en primer plano. Para probarlo, mandad el proceso
a primer plano con fg %
y pulsad
entonces CTRL+C.
- Un "truco" para que sea menos penoso el tratamiento
de errores consiste en dar valor inicial a los
identificadores de los recursos IPC igual a -1.
Por ejemplo,
int semAforo=-1
. En la
manejadora de SIGINT
, sólo si
semAforo
vale distinto de -1,
elimináis el recurso con semctl
.
Esto es lógico: si vale -1 es porque no se ha
creado todavía o porque al intentar crearlo
la llamada al sistema devolvió error. En ambos casos,
no hay que eliminar el recurso.
- Para evitar que todos los identificadores de
recursos tengan que ser variables globales
para que los vea la manejadora de
SIGINT
, podéis declarar una
estructura que los contenga a todos y así
sólo gastáis un identificador del espacio
de nombres globales.
- A muchos os da el error "Interrupted System Call".
Mirad la sesión quinta, apartado quinto. Allí
se explica lo que pasa con
wait
.
A vosotros os pasa con semop
, pero es
lo mismo. De las dos soluciones que propone el
apartado, debéis usar la segunda.
- A muchos, la práctica os funciona exasperantemente
lenta en encina. Debéis considerar que la máquina
cuando la probáis está cargada, por lo que debe
ir más lento que en casa o en el linux de clase.
- A aquellos que os dé "Bus error (Core dumped)" al
dar valor inicial al semáforo, considerad que hay
que usar la versión de
semctl
de
Solaris (con union semun
), como
se explica en la sesión de semáforos y no la de
HPUX.
- Al acabar la práctica, con CTRL+C, al ir a borrar
los recursos IPC, puede ser que os ponga
"
Invalid argument
", pero, sin embargo,
se borren bien. La razón de esto es que habéis
registrado la manejadora de SIGINT
para
todos los procesos. Al pulsar CTRL+C, la señal la
reciben todos, el padre y los otros procesos. El primero
que obtiene la CPU salta a su manejadora y borra los
recursos. Cuando saltan los demás, intentan borrarlos,
pero como ya están borrados, os da el error.
- El compilador de encina tiene un bug. El error
típicamente
os va a ocurrir cuando defináis una variable entera en
memoria compartida. Os va a dar
Bus Error. Core dumped
si no definís el
puntero a esa variable apuntando a una dirección que
sea múltiplo de cuatro. El puntero que os devuelve
shmat
, no obstante, siempre será una
dirección múltiplo de cuatro, por lo que solo os
tenéis que preocupar con que la dirección sea
múltiplo de
cuatro respecto al origen de la memoria compartida.
La razón se escapa un poco al nivel de este curso y
tiene que ver con el alineamiento de direcciones de
memoria en las instrucciones de acceso de palabras en
el procesador RISC de encina.
- Se os recuerda que, si ponéis señales para sincronizar
esta práctica, la nota bajará. Usad semáforos,
que son mejores para este cometido.
- Todos vosotros, tarde o temprano, os encontráis con
un error que no tiene explicación: un proceso que
desaparece, un semáforo que parece no funcionar, etc.
La actitud en este caso no es tratar de justificar la
imposibilidad del error. Así no lo encontraréis.
Tenéis que ser muy sistemáticos. Hay un árbol
entero
de posibilidades de error y no tenéis que descartar
ninguna de antemano, sino ir podando ese árbol.
Tenéis
que encontrar a los procesos responsables y tratar de
localizar la línea donde se produce el error. Si el
error es "
Segmentation fault. Core dumped
",
la línea os la dará si aplicáis
lo que aparece en la
sección Manejo del depurador.
En cualquier otro caso, no os quedará más remedio que
depurar mediante órdenes de impresión dentro del
código.
Para ello, insertad líneas del tipo:
fprintf(stderr,"...",...);
donde sospechéis que hay problemas. En esas líneas
identificad siempre al proceso que imprime el mensaje.
Comprobad todas las hipótesis, hasta las más
evidentes.
Cuando ejecutéis la práctica, redirigid el canal de
errores a un fichero con 2>salida
.
Si cada proceso pone un identificador de tipo "P1",
"P2", etc. en sus mensajes, podéis quedaros con las
líneas que contienen esos caracteres con:
grep "P1" salida > salida2
- Un peatón recién nacido puede ser arrollado
por otro peatón, si no tenéis cuidado.
Es necesario que el nuevo proceso lea el mensaje
correspondiente a su posición de nacimiento.
Para ello, debéis usar la nueva función
CRUCE_inicio_peatOn_ext
de la versión
2.0 de la biblioteca, en lugar de
CRUCE_inicio_peatOn
. Además, si al
nuevo proceso le quitan la CPU justo entre que nace y
es capaz de reservar la posición, puede haber
problemas. Para evitarlos, lo mejor es hacer una
sección crítica que impida avanzar al
resto de peatones hasta que se haya reservado la
posición de nacimiento.
- Los coches pasan el semáforo de uno en uno.
Una solución con Wait0 para ellos puede
volverse muy complicada. Es mucho más
fácil usar un único semáforo
para regular el paso. Cuando el semáforo
está a 1, se puede pasar. Si no, no.
El seudocódigo del
paso de verde a amarillo de uno de los semáforos
podía ser (usando un semáforo, valor
inicial=0):
Coche Gestor
===== ======
if (posSgte==justo en la raya) Poner en "VERDE"
{WAIT(S); SIGNAL(S);
posSgte=CRUCE_avanzar_coche(posSgte); esperar_tiempo_de_esa_fase
SIGNAL(S);} WAIT(S);
Poner en "AMARILLO"
- El paso de amarillo a rojo exige que el cruce esté
libre de coches. Un modo muy fácil de hacerlo
es iniciar un semáforo a un valor igual o superior
al número de coches que caben en la parte
problemática del cruce.
Cuando un coche quiere entrar a esa parte del cruce,
hace un WAIT. Después de salir, hace un
SIGNAL.
El valor en un momento dado del semáforo
distará del valor máximo que le dimos
en tantas unidades como coches bloqueen en ese momento
el cruce. Para pasar a rojo con seguridad basta con
que el gestor vaya "robando" una unidad a una unidad
valor al semáforo hasta lograr hacerlo cero.
Es decir, hará tantos WAITs como el valor
inicial que le dimos al semáforo.
Sabrá entonces que no hay nadie en el cruce.
- Una de las partes más complejas de la
práctica es gestionar bien el nacimiento de los
peatones. Los peatones nacen, al azar, en una
posición libre del lado izquierdo o inferior
de la acera inferior izquierda. Siempre se moverán
en esa acera hacia la derecha o hacia arriba.
Llamemos a la zona donde nacen los peatones "zona de
peligro". Debido, en parte, al modo en que está
construida la biblioteca, es complicado gestionar bien
los nacimientos debido al siguiente problema:
Imaginemos un peatón que nace en la fila de
abajo de la acera y que se empeña en ir
siempre a la derecha y no salir nunca de la zona
de peligro. En cualquier momento puede nacerle otro
peatón encima o echarse encima de un recién
nacido. Parece necesario usar un semáforo para
que esto no pueda ocurrir (valor inicial
de semáforo=1):
PEAToN NACIENDO PEAToN MOVIeNDOSE
=============== =================
WAIT(S); WAIT(S);
posSgte=CRUCE_inicio_peatOn(&posNac); posAnterior=posActual;
reservar(posNac); posActual=posSgte;
SIGNAL(S); reservar(posActual);
posSgte=CRUCE_avanzar_peatOn(posActual);
liberar(posAnterior);
SIGNAL(S);
De este modo, si un peatón está naciendo,
ningún otro peatón puede moverse hasta
que le haya dado tiempo a reservar y asegurar su
posición. Y, lo recíproco también
es cierto (recordad que la biblioteca nunca creará
un nuevo peatón en una posición ocupada).
El problema de este esquema es que un proceso se puede
bloquear en la función reservar y en el caso
de que esto ocurriera, habría interbloqueo
(él no puede avanzar por estar ocupada su
posición y el resto no pueden avanzar por
estar
él dentro de la sección crítica).
Hay que modificar el esquema para dar
con una solución aceptable. Algunas ideas:
- Solo los peatones en la "zona peligrosa"
hacen WAIT en el semáforo para
moverse. Esto alivia en parte el problema
de interbloqueo, pero no lo soluciona. Por lo
que no es aceptable.
- Espera semiocupada. Con la versión
IPC_NOWAIT
de msgrcv
,
si estoy en la zona peligrosa, compruebo si
hay mensaje esperándome. Si no es
así, salgo de la sección
crítica un rato y lo reintento más
tarde. Ya sabéis que la espera
semiocupada no es una buena solución y,
aunque vale, os baja la nota.
- Regla de la zona de peligro: mientras
haya un proceso en la zona de peligro, no
permitimos que nazca ninguno (mediante un
semáforo). Esta solución, aunque
presenta inconvenientes evidentes, se
aceptará como válida.
- La mejor solución. Algún
protocolo que ideéis que solucione
los problemas anteriores. Por ejemplo,
"los lugares de la zona peligrosa tendrán
asociados tres tipos de mensajes, el normal,
el bis y el tris. Cuando un peatón
nazca, trata de
de reservar su posición leyendo el
mensaje normal. Si lo hace,
estupendo. Si no es capaz, manda a un mensaje
bis al buzón, sale de la sección
crítica y se queda recibiendo un
mensaje tris. Los procesos que estén
avanzando en la zona peligrosa, después
de moverse, antes de liberar la posición
antigua, miran a ver si hay algún
mensaje bis correspondiente a esa
posición. Si no lo hay, liberan
normalmente. Si lo hay, liberan enviando
un mensaje tris, para que uno de los que
estén esperando lo lea y nazca.
- La biblioteca almacena en un
char
el número de procesos creados, por lo que
si ponéis más de 127 procesos
se produce desbordamiento y os dará
"Número de procesos negativo" sin razón
- En el caso de que no hagáis la parte de los peatones,
la forma correcta de hacerlo es llamar siempre a la
función
CRUCE_nuevo_proceso
y descartar
las veces que sale peató:n, es decir, no hacer nada
en esos casos y reintentar.
- Os puede dar un error que diga
Resource
temporarily unavailable
en el fork
del padre. Esto ocurre cuando no exorcizáis
adecuadamente a los procesos hijos zombies del padre.
Hay dos posibilidades para solucionarlo:
- La más sencilla es hacer que el padre
ignore la señal
SIGCLD
con un sigaction
y
SIG_IGN
. El S.O. tradicionalmente
interpreta esto como que no queréis que
los hijos se queden zombies, por lo que no
tenéis que hacer waits sobre ninguno de
ellos para que acaben de morir
- Interceptar SIGCLD con una manejadora en el padre
y, dentro de ella, hacer los waits que sean
necesarios para que los hijos mueran. Pero esto
trae un segundo problema algo sutil: al recibirse
la señal, todos los procesos bloqueados
en cualquier llamada al sistema bloqueante (en
particular, los WAITs de los semáforos)
van a fallar. Si no habéis puesto
comprobación de errores, los
semáforos os fallarán sin motivo
aparente. Si la habéis puesto, os
pondrá
Interrupted system call
en el perror. Como podéis suponer,
eso no es un error y debéis interceptarlo
para que no ponga el perror y reintente el WAIT.
La clave está en la variable
errno
que valdrá
EINTR
en esos casos.
- No se debe dormir (es decir, ejecutar
sleep
s
o pausa
s) dentro de una sección
crítica. El efecto que se nota es que, aunque
la práctica no falla, parece como si solamente
un proceso se moviera o apareciera en la pantalla a la
vez. Siendo más precisos, si dormís dentro
de la sección crítica, y soltáis
el semáforo para, acto seguido, volverlo a coger,
dais muy pocas posibilidades al resto de procesos de que
puedan acceder.
- La zona peligrosa del cruce que debe estar libre
antes de pasar de fase en el ciclo semáforico
puede variar. Por ejemplo, en el caso del paso de la
fase segunda a la tercera, no pueden quedar coches en
todo el cruce, pues se llevarían por delante
a los peatones de P1.
- La biblioteca no reintenta los WAITs de su
semáforo, por lo que, de recibirse una
señal, podría fallar. En principio,
esto no ha de ocurrir, puesto que la única
señal en juego durante la ejecución
es el SIGCLD que se envía al padre cuando
van muriendo los hijos y la única función
que el padre ha de llamar es
CRUCE_nuevo_proceso
y esta función
no hace uso del semáforo. En cualquier caso,
si os da problema, simplemente ignorad la señal
SIGCLD en el padre como se explica más arriba.