Introducció als Sistemes Operatius

Anuncio
Introducció als Sistemes Operatius
Split
Facultat d'Informàtica de Barcelona - UPC
25 de juny de 1999
durada: 3 hores
Les notes es publicaran el dia 5 de juliol a les 12:00 al WEB de la facultat. Per a demanar
revisió es podrà veure l'examen el dia 5 de juliol de 17:00 a 19:00 i el dia 6 de juliol de 10:00
a 12:00. Les notes definitives es publicaran el dia 7 de juliol a les 12:00.
Preguntes curtes
(2 punts)
Contesta les següents preguntes tot justificant les teves respostes (un parell de línies
de justificació per pregunta aproximadament).
1.- És necessari que l'entrada estàndar d'un procés sempre estigui associada al teclat ?
2.- Quina utilitat té el paràmetre de la crida al sistema exit?
3.- Classifica les Named Pipes de UNIX com a mecanisme de pas de missatges
(directe/indirecte, simètric/asimètric, capacitat, mida missatges).
4.- A quina(es) fase(s) de la generació d'un fitxer executable (corresponent a un fitxer
font escrit en llenguatge C) es poden detectar els següents errors:
a) disc plé
b) no has indicat una llibreria de les que s'ha de muntar
c) una funció cridada amb un nom incorrecte (per exemple rid en comptes de
read)
d) uns parèntesi mal balancejats
e) no has implementat una rutina utilitzada al teu programa
5.- Cal que una crida al sistema que no accedeix a cap dispositiu físic s'executi en
mode d'execució privilegiat (per exemple la crida al sistema kill) ?
6.- Quina d'aquestes afirmacions és correcta:
Els semàfors que hem vist a l'assignatura ...
a) ... permeten sincronitzar fluxes que comparteixen memòria.
b) ... permeten sincronitzar fluxes que no comparteixen memòria.
c) ... són necessaris per a garantir una comunicació correcta entre fluxes que no
comparteixen memòria i que fan servir algun mecanisme de pas de missatges.
d) totes les anteriors.
(2,5 punts)
Queremos implementar el comando split tal que a partir de un fichero de entrada,
distribuya sus datos (valores enteros) entre diversos ficheros de salida. Para ello
utilizaremos un fichero de control que nos dirá a qué fichero de salida ha de ir cada dato de
entrada.
El formato de cada fichero es el siguiente:
data0.dat
• Fichero de entrada: enteros en formato interno de nuestra
IN
máquina.
• Fichero de control: parejas de carácteres F y Q, donde F nos
data1.dat
indica el fichero de salida, y Q la operación a realizar con el
dato de entrada (la pareja i-ésima del fichero de control hace
data2.dat
referencia al entero i-ésimo del fichero de entrada). Q puede
tomar los siguientes valores:
CTRL
-‘A’: añadir al final del fichero de salida correspondiente
-‘r’: reemplazar el último entero añadido al fichero
-‘R’: resetear el fichero (es decir, eliminar el contenido del
fichero de salida y añadir al principio)
• Ficheros de salida: se llamarán datax.dat, donde la x identifica que fichero es y que
corresponderá a la F en el fichero de control (por ejemplo, carácter ‘0’, carácter ‘1’, etc.).
En él se almacenarán los datos que correspondan en formato interno de nuestra
máquina.
Un ejemplo de fichero de control y de cómo se invoca el comando split sería el siguiente:
alabi_% more f.ctrl
0A0A2A0r3A2R2A0R0A
alabi_% split f.ctrl 3 < f.data
Donde:
• f.ctrl es un parámetro que representa el nombre del fichero de control
• 3 es un parámetro que representa cuantos ficheros de salida son necesarios (como
máximo habrá 5).
• f.data es el fichero con los datos de entrada
Se pide que implementes el comando split utilitzando las llamadas al sistema vistas
en clase.
NOTA: suponed que el formato y contenido de los ficheros de entrada y control es correcto.
No es preciso especificar los includes ni implementar la rutina de tratamiento de errores.
Para servirle
(3,5 punts)
Disposem d'un programa que llegeix una petició per la seva entrada estàndar, la
tracta i escriu un caràcter per la seva sortida estàndar quan ha acabat el tractament;
aquestes accions les va repetint mentre hi hagi peticions pendents. D'aquest programa en
direm servidor, i ja disposem del fitxer executable corresponent. El bucle principal del
servidor és:
main()
{ char c='*';
while (read(0, &peticio, sizeof(peticio)) > 0) {
tractar_peticio(&peticio); /* No llegeix de l'entrada estàndar */
write(1, &c, sizeof(c));
}
}
Resulta que cal processar un gran volum de peticions. Com les peticions són
independents, ens demanen escriure un programa (en direm manager) que s'encarregui
d'executar varis servidors de forma concurrent.
manager rebrà com a únic paràmetre el nom del fitxer ordinari que conté les peticions.
manager s'haurà d'encarregar de que 3 servidors s'executin concurrentment i de que tractin
totes les peticions del fitxer. A més a més, manager ha d'anar comptant el nombre total de
peticions que han estat tractades (pots aprofitar el caràcter que els servidors escriuen per la
seva sortida estàndar). Quan els 3 servidors finalitzin, manager haurà de terminar.
Es demana:
a) Dibuixa un esquema on apareguin els processos involucrats al sistema i com estan
comunicats. Es valorarà que utilitzis el mínim de recursos. Hauries d'intentar que els
servidors sempre estiguin ocupats.
b) Implementa el programa manager.
c) Volem oferir la possibilitat d'augmentar dinàmicament el nombre de servidors que
s'estan executant concurrentment. Cada cop que manager rebi un signal SIGUSR1, haurà de
crear un nou servidor. Indica les modificacions que caldria fer a la solució anterior i
implementa-les.
Observacions:
No pots modificar el codi del servidor.
Pots suposar que les peticions tenen el format correcte.
No cal especificar els includes ni implementar la rutina de tractament d'errors.
Només es poden utilitzar crides al sistema vistes a classe.
Mi hermosa lavandería
(2 punts)
Un nuevo servicio de lavandería funciona de la siguiente forma: cuando un cliente
entra en la lavandería, inserta monedas en una de las muchas estaciones de reserva y
selecciona la cantidad de lavadoras que va a necesitar. La estación de reserva le asigna
lavadoras libres devolviéndole unas fichas que identifican cada lavadora. A continuación el
cliente inserta las fichas en las lavadoras indicadas, y lava la ropa. Una vez acabado el ciclo
de lavado, las lavadoras estarán libres otra vez.
Tanto las estaciones de reserva como las lavadoras están conectadas a un ordenador
central. El ordenador central mantiene dos variables para el control del sistema:
• Un vector de booleanos declarado como disponible[NLAVADORA] que indica si una
lavadora está libre o no (NLAVADORA es el número total de lavadoras que hay en la
lavandería). Inicialmente está a todo CIERTO.
• Un semáforo llamado nlibres inicializado a NLAVADORA que indica cuantas lavadoras
libres hay.
A continuació se muestra el código de las rutinas actualmente implementadas en el
ordenador central para obtener las lavadoras en la estación de reserva y para liberarlas:
void obtener_lavadoras (int nlavs)
{
int i, j;
for (i=0; i<nlavs; i++) {
sem_wait(nlibres);
j = 0;
while ((disponible[j] == FALSE)
&& (j<NLAVADORA))
j++;
if (j==NLAVADORA)
error(“FATAL ERROR”);
else
disponible[j] = FALSE;
}
void liberar_lavadora (int lav)
{
sem_signal(nlibres);
disponible[lav] = CIERTO;
}
}
Después de unas semanas de funcionamiento, han aparecido tres tipos de problemas:
(1) A veces ocurre que una estación de reserva nos da FATAL ERROR y no hay más
remedio que reinicializar todo el sistema.
(2) Parece ser que, de vez en cuando, a dos clientes distintos se les asigna la misma
lavadora simultáneamente.
(3) Ocasionalmente, algún cliente se tiene que quedar esperando indefinidamente en la
estación de servicio a que le sean asignadas las fichas.
Tú has sido llamado para resolver estos problemas. Se te pide lo siguiente:
(a) ¿Por qué ocurre el primer problema? Modifica el código para resolverlo.
(b) ¿Por qué ocurre el segundo problema? Modifica el código para resolverlo.
(c) ¿Por qué ocurre el tercer problema? Justifícalo poniendo un ejemplo y modifica el
código (si hace falta) para resolverlo.
NOTA: suponed que se ejecuta un flujo por cada estación de reserva y por cada lavadora.
SOLUCIONS EXÀMEN ISO (25-6-99)
}
while ((n=read(0, &data, sizeof(int))) > 0) {
if (read(f_crtl, &F, sizeof(char)) < 0)
error(“Read”, SISTEMA);
f = F - ‘0’;
if (read(f_crtl, &Q, sizeof(char)) < 0)
error(“Read”, SISTEMA);
Preguntes curtes
1.- No és necessari. Inicialment, l'entrada estàndar acostuma a estar associada al teclat
però la podem redireccionar a qualsevol dispositiu d'entrada de la màquina.
2.- Aquest valor es posa a disposició del procés pare quan el procés fill mor. La
interpretació d'aquest valor és un conveni entre el procés pare i el procés fill; el sistema
operatiu no fa res amb ell.
3.- Les Named Pipes són un mecanisme indirecte (els missatges no són enviats
directament a un altre procés), de capacitat (en bytes) igual a la mida de la named pipe, i la
mida dels missatges és variable. Podem considerar que és simètric ja que les crides send
(write) i receive (read) tenen la mateixa interfície.
4.a) a qualsevol fase que hagi d'escriure dades a disc (edició, compilació, muntatge),
b) muntatge ja que el muntador no podrà resoldre les referències a símbols de la
llibreria
c) muntatge ja que el compilador es pensarà que rid és un símbol extern i el
muntador no podrà resoldre'l
d) compilació ja que es tracta d'un error sintàctic del codi font
e) muntatge (com al cas c).
5.- Tot i que la crida al sistema kill no accedeix a cap dispositiu físic, està provocant
una acció que només la pot realitzar un usuari autoritzat; per tant, cal que s'executi en
mode d'execució privilegiat. De totes formes, algun sistema podria implementar certes
crides al sistema en mode usuari (per exemple, la crida getpid()).
6.- Considerant la llibreria de semàfors vista a l'assignatura, la solució és l'opció a).
a) És certa. El semàfor és una variable compartida entre els dos fluxes. Si l'inicialitzem
a zero, un sem_wait sobre el semàfor serà bloquejant mentre no es faci un sem_signal. Per
tant, permetrà la sincronització dels dos fluxes.
b) No és certa ja que els semàfors que hem vist són variables que han de ser
compartides entre els fluxes que els utilitzen. Si els fluxes no comparteixen memòria, no
poden fer servir un semàfor per sincronitzar-se.
c) El pas de missatges i els semàfors són mecanismes independents. Per tant, és fals.
Split
void main (int argc, char *argv[])
{
int fd_ctrl, nfiles, fd_out[5];
int i, n, f, data;
char name[10], F, Q;
if (argc!=3)
error(“Parametros incorrectos”, PROPIO);
if ((fd_ctrl=open(argv[1], O_RDONLY)) < 0)
error(“Open”, SISTEMA);
nfiles = atoi(argv[2]);
for (i=0; i<nfiles; i++) {
sprintf(name, “data%d.dat”, i);
if ((fd_out[i]=open(name, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0)
error(“Open”, SISTEMA);
switch (Q) {
case ‘r’:
if (lseek(fd_out[f], -sizeof(int), SEEK_CUR) < 0)
error(“Lseek”, SISTEMA);
case ‘A’:
if (write(fd_out[f], &data, sizeof(int)) < 0)
error(“Write”, SISTEMA);
break;
case ‘R’:
close(fd_out[f]);
sprintf(name, “data%d.dat”, f);
if ((fd_out[f]=open(name, O_WRONLY|O_TRUNC)) < 0)
error(“Open”, SISTEMA);
if (write(fd_out[f], &data, sizeof(int)) < 0)
error(“Write”, SISTEMA);
break;
}
}
if (n < 0)
error(“Read”, SISTEMA);
}
Para servirle
a)
0 serv1 1
1
fitxer
entrada
0 serv2 1
manager
0 serv3 1
Hem de garantir que es processin totes les peticions del fitxer un únic cop. El més
senzill seria que els 3 servidors llegeixin les peticions del fitxer (redireccionant l'entrada
estàndar convenientment) i fent que tots 3 comparteixin el punter de lectura sobre el fitxer.
Per a que aquesta solució sigui factible cal garantir que no hi hagi problemes si varis
servidors intenten llegir simultàniament una petició del fitxer; en aquest cas, no hi ha
problema ja que el codi del servidor ens mostra que cada petició es llegeix fent una única
invocació a la crida al sistema read. A més a més, aquesta solució garanteix que els
servidors sempre estaràn ocupats ja que llegeixen directament del fitxer.
Els servidors acabaran la seva execució quan detectin el final del fitxer d'entrada.
Quan tots tres servidors acabin, manager ha d'acabar. Per a fer-ho farem que tots tres
servidors tinguin direccionada la seva sortida estàndar cap a una única pipe. Quan els tres
morin, la pipe no tindrà cap escriptor, i la crida read sobre la pipe buida que faci el mànager
retornarà zero caràcters llegits. A més a més, aprofitarem els caràcters escrits en aquesta
pipe per a comptar el nombre de peticions que han estat tractates.
b)
if (errno!=EEXIST) error("creant named pipe");
for (i=0; i<3; i++)
crear_servidor();
if ((lect = open("NP", O_RDONLY)) < 0) error("open NP lect");
while ((n=read(lect, &c, 1)) != 0) {
if (n==1) peticions_acabades++;
if ((n==-1) && (errno != EINTR)) error("read");
if (unmes) {
crear_servidor();
unmes = FALSE;
}
}
main(int argc, char *argv[])
{ int p[2], peticions_acabades = 0, i, n; char c;
if (argc != 2) error("arguments");
close(0);
if (open(argv[1], O_RDONLY) < 0) error("open");
if (pipe(p) < 0) error("pipe");
for (i=0; i<3; i++)
crear_servidor();
close(p[1]);
while ((n=read(p[0], &c, 1)) > 0)
peticions_acabades++;
if (n<0) error("read");
}
void crear_servidor()
{
switch (fork()) {
case -1 : error("fork");
case 0 : close(1); dup(p[1]); close(p[1]); close(p[0]);
execlp("servidor", "servidor", (char *)0);
error("exec");
}
}
c)
A l'apartat b) estem aprofitant que la crida read sobre la pipe retorna 0 caràcters llegits
per a detectar la finalització de tots els servidors. Ho podem fer així perque el manager
tanca el canal d'escriptura sobre la pipe tan aviat com ha creat tots els servidors.
A l'apartat c) no ho podem fer d'aquesta manera ja que manager hauria de mantenir
aquest canal obert per a que els servidors addicionals el puguin heredar.
Solucionarem aquest problema fent servir named pipes ja que hi podem accedir-hi
sense necessitat d'heredar el canal.
A més a més, com el signal SIGUSR1 ens pot arribar mentre estem bloquejats llegint
de la Named Pipe, caldrà tractar l'error EINTR.
}
void crear_servidor()
{
switch (fork()) {
case -1 : error("fork");
case 0 : close(1);
if (open("NP", O_WRONLY) < 0) error("open NP escr");
execlp("servidor", "servidor", (char *)0);
error("exec");
}
}
Mi hermosa lavandería
a) Ocurre porque en liberar_lavadora hacemos el sem_signal(nlibres) antes de decir
que cierta lavadora está libre. Si hubiera un cambio de contexto entre estas dos
instrucciones, es posible que en obtener_lavadoras pasemos el “filtro” del
sem_wait(nlibres), pero como la variable disponible está todavía a FALSO para esa
lavadora en particular, el bucle while termine porque j ha llevado a NLAVADORA.
void obtener_lavadoras (int nlavs)
{
int i, j;
for (i=0; i<nlavs; i++) {
sem_wait(nlibres);
j = 0;
while ((disponible[j] == FALSE)
&& (j<NLAVADORA))
j++;
if (j==NLAVADORA)
error(“FATAL ERROR”);
else
disponible[j] = FALSE;
}
int unmes = FALSE;
void rutusr1(int signo)
{
unmes = TRUE;
}
main(int argc, char *argv[])
{ int lect, peticions_acabades = 0, i, n; char c;
if (argc != 2) error("arguments");
close(0);
if (open(argv[1], O_RDONLY) < 0) error("open");
signal (SIGUSR1, rutusr1);
if (mknod("NP", S_IFIFO|0600) < 0)
void liberar_lavadora (int lav)
{
disponible[lav] = CIERTO;
sem_signal(nlibres);
}
}
(b) El problema ocurre porque no hay protección en la consulta y modificación de la
variable global disponible. Así pues, si hay un cambio de contexto, es posible que dos
estaciones de reservas vean la condición disponible[j] == FALSE como falsa, y por lo
void liberar_lavadora (int lav)
{
disponible[lav] = CIERTO;
sem_signal(nlibres);
}
void liberar_lavadora (int lav)
{
disponible[lav] = CIERTO;
sem_signal(nlibres);
}
}
sem_wait(mutex);
for (i=0; i<nlavs; i++) {
sem_wait(nlibres);
j = 0;
while ((disponible[j] == FALSE)
&& (j<NLAVADORA))
j++;
if (j==NLAVADORA)
error(“FATAL ERROR”);
else
disponible[j] = FALSE;
}
sem_signal(mutex);
void obtener_lavadoras (int nlavs)
{ int i, j;
void liberar_lavadora (int lav)
{
disponible[lav] = CIERTO;
sem_signal(nlibres);
}
Otra posible solución (la cual también nos resolvería el problema (2)) sería:
}
sem_wait(mutex);
for (i=0; i<nlavs; i++)
sem_wait(nlibres);
j = 0;
while ((disponible[j] == FALSE)
&& (j<nlavs))
j++;
disponible[j] = FALSE;
sem_signal(mutex);
void obtener_lavadoras (int nlavs)
{ int i, j;
Una posible solución sería:
(c) El problema está en que a varios clientes se les puede haber asignado unas cuantas
lavadoras libres y están esperando a que se les asigne el resto. Por ejemplo, si hay 4
lavadoras y los clientes A y B necesitaban 3 cada uno, podemos llegar a la situación de
que a cada uno se le hayan asignado 2 y estén esperando por la tercera, pero el
problema es que no hay ninguna libre (llegamos a una situación de abrazo mortal).
}
for (i=0; i<nlavs; i++) {
sem_wait(nlibres);
sem_wait(mutex);
j = 0;
while ((disponible[j] == FALSE)
&& (j<NLAVADORA))
j++;
if (j==NLAVADORA)
error(“FATAL ERROR”);
else
disponible[j] = FALSE;
sem_signal(mutex);
}
void obtener_lavadoras (int nlavs)
{
int i, j;
tanto asignen la misma lavadora a los dos clientes.
Descargar