charlie.c
. La
correcta compilación de dicho programa, producirá un
fichero ejecutable, cuyo nombre será obligatoriamente
charlie
. Respetad las
mayúsculas/minúsculas de los nombres, si las hubiere.
pids.bin
. El fichero tendrá cabida para 20
números enteros almacenados en formato binario (cuatro bytes
por cada entero). Al principio todos los enteros estarán a
cero. Cuando se reencarna el malo, escribe su nuevo PID en una posición
libre del fichero al azar. Cuando un ángel quiere disparar,
elegirá una posición al azar del fichero. Si vale cero,
significa que la pistola se ha encasquillado y no manda señal.
Si vale distinto de cero, manda la señal al PID que ha leído.
Si coincide que dicho PID es el de la reencarnación actual del
malo, lo mata y acaba. Si no, continúa disparando.
CHARLIE: "El pAjaro volO. Ahora se pone tibio a daiquiris en el Caribe"
CHARLIE: "Bien hecho, Sabrina, siempre fuiste mi favorita"
CHARLIE: "Bravo por Jill"
CHARLIE: "Kelly, donde pones el ojo, pones la bala"
CHARLIE: "Kelly, mala suerte, tus compaNeras acertaron y tU, no"
CHARLIE: "Jill, otra vez serA, te apuntarE a una academia de tiro"
CHARLIE: "Sabrina, no te preocupes, seguro que la pistola estA mal"
CHARLIE: "Pobre malo. Le habEis dejado como un colador... Sois unos Angeles letales"
sleep
. Así,
los ángeles emplean, al azar, entre 6 y 12 segundos
antes de poder disparar. El malo vive entre 1 y 3 segundos,
al azar, en cada reencarnación.
charlie [velocidad]El argumento opcional podrá valer
normal
o veloz
. Si no se especifica
este argumento, se entiende que su valor es normal
.
La diferencia estriba en que, a velocidad veloz, no se
debe ejecutar ninguna pausa por parte de los procesos,
aunque se indiquen en el enunciado. Esto es, a esa velocidad,
no se invoca a sleep
nunca.
CHARLIE: "Bosley, hijo de mis entretelas, tu PID es x. Espero a que me avises..."
CHARLIE: "Veo que los Angeles ya han nacido. Creo al malo..."
CHARLIE: "El malo ha nacido y su PID es x. Aviso a Bosley"
BOSLEY: "Hola, papA, dOnde estA mamA? Mi PID es x y voy a crear a los Angeles..."
BOSLEY: "Los tres Angeles han acabado su misiOn. Informo del resultado a Charlie y muero"
SABRINA: "Hola, he nacido y mi PID es x"
(lo mismo para las otras dos)SABRINA: "Pardiez! La pistola se ha encasquillado"
(lo mismo para las otras)SABRINA: "Voy a disparar al PID x"
(lo mismo para las otras)SABRINA: "BINGO! He hecho diana! Un malo menos"
(lo mismo para las otras)SABRINA: "He fallado. Vuelvo a intentarlo"
(lo mismo para las otras)SABRINA: "He fallado ya tres veces y no me quedan mAs balas. Muero"
(lo mismo para las otras)MALO: "JA, JA, JA, me acabo de reencarnar y mi nuevo PID es: x. QuE malo que soy..."
MALO: "AY, me han dado... pulvis sumus, collige, virgo, rosas"
MALO: "He sobrevivido a mi vigEsima reencarnaciOn. Hago mutis por el foro"
ps
desde la línea de órdenes,
deben mostrar su nombre, es decir: charlie, bosley, sabrina, kelly, jill y malo.
Para lograr esto, haced que los procesos recién nacidos que lo necesiten
hagan un exec
al mismo ejecutable, pero variando argv[0]
de modo acorde. Si necesitárais pasar información extra, usad más
argumentos. Nada más iniciar la función main
una
comprobación del valor de argv[0]
os puede servir para
guiar al proceso a ejecutar el código que le corresponde.
printf
no interfiera con la salida de los procesos, es importante
usar write
para la salida por pantalla en su lugar.
Los ordenadores son las máquinas más previsibles que os podéis encontrar. Para ellas, es muy difícil hacer algo al azar. Por eso, cuando quieren generar un número al azar, lo que hacen es usar una fórmula matemática que, partiendo de un número (la semilla) genera otro tratando de que el nuevo se parezca poco al anterior. El número nuevo pasa a ser la nueva semilla para generar el siguiente.
Para obtener una ristra aleatoria se debe, pues, hacer dos pasos: primero, establecer la semilla inicial (esto solamente se hace una vez) y segundo, ir sacando números.
Para establecer la semilla inicial, la biblioteca de C usa la
función void srand(unsigned int semilla)
.
Pero, ¿qué semilla ponemos? Si ponemos un número
que se nos ocurra, los números al azar que obtendremos
serán buenos, pero siempre los mismos cada vez que
ejecutemos el programa. Para obtener números diferentes,
se le suele pasar a srand
algo que dependa del
momento en que se lanza el programa. Algo muy típico es:
srand(time(NULL));
Cuando tenemos varios procesos, la cosa se complica. Primero, los
procesos van a heredar la misma semilla del padre, por lo que todos
los hijos sacarán los mismos números de su chistera.
Si el proceso, hace un exec
es aún peor, pues
todo ocurre como si nunca se hubiera llamado a srand
.
Hay, por consiguiente, que hacer un srand
en cada nuevo
proceso una sola vez y después de haber hecho sus exec
s,
si es que los hace. Ahí no acaban nuestros problemas, pues si ponemos
la fórmula de arriba, que tiene una resolución de
segundos, dará la misma semilla a todos los procesos, pues
se crean muy rápidos. Lo mejor es que la modifiquéis
a vuestro gusto para que también incluya el PID del proceso
que hace la llamada.
Una vez hemos iniciado el generador de números aleatorios,
hay que obtener los propios números. La función que
hace esto es int rand(void)
. La dificultad estriba
en que esta función nos devuelve un número al azar
entre 0 y una macro llamada RAND_MAX
, lo que es muy poco últil.
Lo más normal es que queráis obtener un número
entre a y b, ambos incluidos (por ejemplo, entre 1 y 6, para la
tirada de un dado). Esta expresión logra vuestro objetivo:
a+(int)(rand()/(1.0+RAND_MAX)*(b-a+1))
system
, salvo indicación explícita en
el enunciado de la práctica.
fork
,
cada proceso imprime quién es
y los dejamos en pause
.
Al hacer un ps
desde otro
terminal, debemos observar a los dos
procesos, uno hijo del otro.
charlie
en el punto anterior. Debemos lograr que
el malo cambie su nombre a malo
.
Para ello, usaremos un truco. Haremos una
llamada a una función de la familia
exec
, por ejemplo, execl
,
que ejecute el ejecutable charlie
,
pero con argv[0]
igual a malo
.
Daos cuenta que, al hacer el exec
, el
proceso olvida todo y recomienza su ejecución.
Lo tenemos que redirigir a una función para
que haga las tareas que le son propias. Pero para
eso, nos podemos valer del contenido de argv[0]
.
Moved el mensaje donde el malo dice que es el malo
a dicha función y comprobad que todo va
como debe ir.
od
,
que sirve para ver el contenido binario de un
fichero. El fichero debe medir 80 bytes justos.
Si no lo mide, muy probablemente lo habéis
creado y escrito en modo de texto, no en binario.
sleep()
o
similares para sincronizar los procesos. Hay que
usar otros mecanismos.
pause()
.
Salvo en bucles infinitos de pause
s,
su uso puede estar mal. Mirad la solución a la
práctica
propuesta en la sesión quinta acerca de él o el
siguiente LPE.
lseek
el resto lo
ve movido. Este efecto secundario hace que el
siguiente código sea erróneo para
tratar de bloquear el fichero:
lseek(fd,0,SEEK_SET); lockf(fd,F_LOCK,0);La razón es que entre las dos instrucciones se puede perder la CPU y otro proceso nos puede mover el puntero que nosotros pensamos que está al principio. La solución pasa por no usar los descriptores heredados sino que cada hijo, al nacer haga algo similar a:
case 0: /* COdigo del hijo */ close(fd); // Cerramos el descriptor heredado fd=open(... // Lo volvemos a abrir
open
que hace el hijo no puede llevar O_TRUNC
.
Si lo lleva, cuandoquiera que nazca un hijo, borrará
el fichero pudiendo pillar justo antes de una lectura,
que no leería nada. Lo debe abrir solamente con
el flag O_RDWR