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.