Examen final de USO (20-6-2007) Nom i Cognoms:............................................................................................................................... Qüestions de laboratori (2.5 punts) Contesteu justificadament les següents preguntes a l’espai proporcionat (a no ser que s’indiqui el contrari, les preguntes es refereixen al treball sobre el sistema operatiu Linux). 1.- De quina forma utilitza el shell el contingut de la variable PATH? S'utilitza per determinar la ubicació dels fitxers executables corresponents a les comandes. Cada cop que l'usuari escriu un nom de comanda sense especificar el directori on es troba l'executable, el shell recorre la llista de directoris que conté la variable PATH buscant un fitxer executable amb aquest nom. Si en troba un, l'executarà i suspendrà la búsqueda; altrament, mostrarà l'error Command not found. 2.- Sigui prova un programa que escriu per la sortida estàndar el valor del paràmetre argc de la rutina main. Suposant que el directori actual conté els fitxers a, a.c, b i b.c, indiqueu el valor que s'escriurà per la sortida estàndar si executeu prova *.c > out & El shell interpreta els metacaràcters abans de començar l'execució del procés. És a dir, el shell fa que s'executi en background (&), amb la stdout redireccionada a un fitxer (> out) i unicament amb els paràmetres a.c i b.c (els fitxers coincidents amb *.c). Com argc també comptabilitaza el nom de l'executable, el valor d'argc serà 3; aquest valor serà escrit a stdout (fitxer out). 3.- El directori dir té proteccions r-xr-xr-x. El directori conté els fitxers w, r i rw amb proteccions -w--w--w-, r--r--r-- i rw-rw-rw- respectivament. a) Quins fitxers del directori es podran esborrar amb la comanda rm? b) Quins fitxers del directori es podran editar amb vim o emacs? c) Quins fitxers del directori es podran sobreescriure utilitzant el metacaràcter > ? a) Cap perquè no podem escriure sobre el directori. b) r i rw (tot i que únicament podrem guardar les modificacions que fem a rw) perquè els editors només necessiten llegir i escriure sobre el fitxer. c) w i rw perquè per modificar un fitxer no cal el bit w al directori, només cal al fitxer.. 4.- Seria possible utilitzar un fitxer Makefile per crear dos fitxers executables en una única invocació a make sense especificar cap paràmetre? En cas afirmatiu, poseu un exemple de fitxer Makefile amb aquest comportament. Sí. Només cal que la primera regla del Makefile (és a dir, la regla que s'executa quan no especifiquem cap paràmetre) crei dos executables. Per exemple: all: file1 file2 # Primera regla file1: file1.c # æAnàleg per a file2 gcc -o file file1.c 5.- Cal ser l'usuari root per poder modificar les proteccions d'un fitxer? No. L'usuari propietari d'un fitxer sempre pot modificar les proteccions dels seus fitxers. 6.- A un programa escrit en llenguatge C es declara el vector char v[100] con a variable global i es realitza la inicialització for (i=0; i<1000; i++) v[i]=3;. En executar-lo, es produeix un Segmentation fault. A l'analitzar el fitxer core amb el debugger s'observa que l'error s'ha produit a l'assignació v[i]=3 quan i val 555. Com és que la i no té el valor 100? L'explicació és que les posicions de memòria immediatament posteriors a la posició on finalitza el vector també pertanyen a l'espai logic del procés (per estar assignades a altres variables o per fragmentació interna). En canvi, la direcció de l'hipotètic element 555 ja no pertany a l'espai logic del procés. 7.- Quines són les diferències més notables entre les crides al sistema de Unix i de Win32 relatives a la sincronització d'un procés amb la mort d'un procés fill? A Unix, la crida wait sincronitza amb la mort d'un fill i obté el seu codi d'acabament. En canvi, a Win32 hi ha una crida per a cada tasca (WaitForSingleObject i GetExitCode). A més, a Win32 és possible sincronitzar-se simultaniàment amb la mort de varis fills (crida WaitForMultipleObjects). Examen final de USO (20-6-2007) Nom i Cognoms:............................................................................................................................... Qüestions de teoria (2.5 punts) Contesteu justificadament les següents preguntes a l'espai proporcionat: 1.- Indique qué significa que una biblioteca pueda ser compartida. ¿Es posible tener bibliotecas estáticas compartidas? Una librería es compartida si puede ser utilizada al mismo tiempo por múltiples programas. Para el caso de librerías estáticas, las librerías serán "linkadas" o montadas en tiempo de compilación. Por lo que si bien resulta que es utilizada por múltiples programas, cada ejecutable tiene su propia copia del código. 2.- ¿Cómo se genera un proceso zombie? ¿Es posible hacer que un proceso deje de ser zombie? En caso afirmativo, describa cómo. Un proceso zombie es aquél que ha finalizado su ejecución pero todavía tiene una entrada en la tabla de procesos porque su proceso padre no ha leído aún su status. El proceso zombie dejará de existir cuando su padre invoque a la llamada sl sistema wait. 3.- Comente brevemente tres diferencias entre los mecanismos de comunicación entre procesos: pipes, named pipes y sockets. Dos procesos que se comunican mediante pipes tiene que estar relacionados jerárquicamente, mientras que con named pipes y sockets no es necesario. Pipes y named pipes son mecanismos para comunicar procesos en forma local unicamente, mientras que mediante sockets la comunicación puede ser remota. Pipes y named pipes son orientados a conexión, mientras que sockets puede ser no orientado a conexión también. 4.- ¿Qué tipos de respuestas se obtiene de un proceso al enviarle una señal (signal)? ¿Es posible modificar la respuesta? ¿Siempre? Acciones por defecto que se toman al recibir un signal: terminar la ejecución de un proceso, terminarla y generar un fichero imagen (core dump), suspender un proceso, continuar la ejecución en caso de que estuviera suspendido. Es posible reprogramar la acción (incluso inhibirla) de cualquier signal excepto de: SIGKILL y SIGSTOP 5.- ¿Qué tipo de RAID utilizaría para una aplicación que necesita velocidad en el acceso a los datos pero no es crítica en caso de fallo? ¿Y para otra que es crítica en caso de fallo pero la velocidad no es tan importante? En el primer caso es más eficiente paralelizar el acceso y evitar el cuello de botella en los accesos a la paridad: RAID-0, datos distribuídos y sin cálculo de paridad. En el segundo caso, según los tipos de RAID vistos en clase, el RAID-6 (doble paridad y stripping)sería el más adecuado. 6.- Indique en qué casos se ejecuta la sentencia posterior a una invocación a la llamada execl?. La sentencia a continuación de execl se ejecutará unicamente en el caso de que produzca un fallo en dicha llamada al sistema (por ejemplo, no existe el fichero o las protecciones no nos permiten accder). 7.- ¿Qué mostrará la ejecución del siguiente código? Si repetimos la ejecución varias veces, ¿habrá alguna variación en el valor numérico mostrado? int d; int *p; p = &d; printf("%d\n", p); Mostrará la dirección lógica de la variable d. Para diferentes ejecuciones el valor será el mismo ya que la dirección lógica es independiente de las direcciones físicas donde se cargue el programa. Examen Final de USO (20-6-2007) Analitzar shellscript (1 punt) Indiqueu justificadament quina és la funció del següent shellscript, indicant quin és el significat més lògic dels paràmetres. #!/bin/bash while read nom do if test -f $nom then if test ‘wc -c < $nom‘ -gt $1 then compress $nom fi fi done < $2 $1 ha de ser un enter perquè es fa servir en el contexte d'una comparació numèrica. $2 ha de ser un nom de fitxer perquè l'entrada estàndar del bucle while està direccionada a $2. El shellscript llegeix una a una les línies del fitxer $2. Si el contingut de la línia representa un nom de fitxer existent, es comprova si la seva mida (en bytes) és més gran que $1. En cas afirmatiu, s'executa la comanda compress passant-li com a paràmetre el nom del fitxer. El shellscript finalitza quant totes les línies del fitxer $2 hagin estat llegides. Per tant, el contingut del fitxer $2 hauria de ser noms de fitxers (un per línia). Escriure shellscript (1.5 punts) Escriviu un shellscript tal que, cada N segons, comprovi quants processos hi ha en execucio a la màquina. Si el nombre de processos supera el valor M, caldrà afegir al fitxer F la data actual i el nombre de processos en execució. Si, a més a més, el nombre de processos supera el valor P (on P > M), caldrà executar la comanda comm en modalitat background. N, M, P i F seran paràmetres del shellscript (i en aquest ordre); per exemple, 60 70 100 /tmp/log Observacions: Si no recordeu el nom d’algun paràmetre d’una comanda, podeu inventar-vos-el; en aquest cas, haureu d’indicar el significat que assigneu a aquest paràmetre inventat per tal que pugui entendre la vostra solució i pugui avaluar la seva correctesa. #!/bin/bash if (test $# -ne 4) then echo Nombre de parametres incorrecte exit fi N=$1 M=$2 P=$3 F=$4 while (sleep $N) do procs=`ps -aux|wc -l` if (test $procs -gt $M) then echo ‘date‘ $procs >> $F if (test $procs -gt $P) then comm & fi fi done Examen Final de USO (20-6-2007) Analitzar codi (1 punt) Indiqueu justificadament quin serà el resultat d’executar el següent programa (nombre de processos creats, relació pare/fill entre els processos creats, informació escrita,...). Podeu assumir que les crides mai retornaran error. main() { int p1, i; for (i=0; i<100; i++) { p1 = fork(); if (p1 == 0) fork(); fork(); printf("%d\n", i); } } Cada execució del codi intern del bucle crea 5 nous processos (2 fills, 2 nets i 1 besnet). Per tant, quan l'executi el procés inicial, en acabar la primera iteració (i=0) tindrem 6 (1 + 5) processos que escriuran el valor "0". Tots 6 processos entraran a la segona iteració (i=1). Cadascun d'ells crearà 5 nous processos amb el que tindrem un total de 6 + 5*6 = 6^2 processos que escriuran el valor "1". Tots 6^2 processos entraran a la tercera iteració (i=2). Cadascun d'ells crearà 5 nous processos amb el que tindrem un total de 6^2 + 5*6^2 = 6^3 processos que escriuran el valor "2". ... Finalment, tots 6^99 processos entraran a la darrera iteració (i=99). Cadascun d'ells crearà 5 nous processos amb el que tindrem un total de 6^99 + 5*6^99 = 6^100 processos que escriuran el valor "99". No podem garantir en quin ordre s'escriuran tots aquests valors perquè no es fa servir cap crida al sistema que restringeixi l'execució dels processos. Escriure codi (1.5 punts) Escribid el código C que calcule el producto de dos matrices. Los coeficientes de la matriz resultado han de ser calculados en paralelo, para ello se deberán crear tantos procesos hijo como elementos tenga la matriz resultado. Cada hijo, una vez hecho su cálculo, transferirá el resultado a un proceso servidor mediante un socket TCP. El resultado irá acompañado de las coordenadas del elemento de la matriz, todo ello en una estructura del tipo struct tupla_resultado. struct tupla_resultado { int i, j, value; }; El servidor se ejecuta en la dirección IP 147.83.12.90, escucha peticiones de conexión en el port 1234 y espera que todas las respuestas relativas a un mismo cálculo le llegen por la misma conexión. Observaciones: Es preciso especificar todos los argumentos de las llamadas al sistema. No es preciso hacer el tractamiento de errores en las llamadas ni especificar los includes. No es preciso implementar el programa en código C sintácticamente correcto, podéis utilizar un pseudo-código “razonable”. No es preciso que implementéis el código del servidor. Podéis asumir que las matrices de entrada están almacenadas en el área de datos del proceso padre y que están ya inicializadas. El algoritmo secuencial para el cálculo del producto de matrices es: int mat1[M][N], mat2[N][P], res[M][P], i, j, k; for (i=0; i<M; i++) { for (j=0; j<P; j++) { res [i][j] = 0; for (k=0; k<N; k++) { res [i][j] = res [i][j] + mat1[i][k] * mat2[k][j]; } } } Examen Final de USO (20-6-2007) int mat1[M][N], mat2[N][P]; int i, j, k, res=0, sock, status; struct tupla_resultado { int i, j, value; } t; /* inicializacion_cliente: Rutina similar a la utilizada en laboratorio. Crea el socket e invoca a la llamada connect para establecer la conexion con el servidor */ sock = inicializacion_cliente("147.83.12.90", 1234); for (i=0; i<M; i++) { for (j=0; j<P; j++) { if (fork()==0) { for (k=0; k<N; k++) res = res + mat1[i][k] * mat2[k][j]; t.i= htonl(i); t.j= htonl(j); t.value= htonl(res); write (sock, &t, sizeof (t)); exit(0); } } } for (i=0; i<M*P; i++) wait (&status);