Clase 9: Linux: Señales Atrapar una señal (ctrl-C) #include <signal.h> #include <stdio.h> #include <stdlib.h> int brk = 0; /* variable externa que cuenta los ctrl-C */ /* handler de la interrupcion */ void ctrl_c() { brk++; fprintf(stderr, "Ay numero: %d!\n", brk); // exit(0); /* si quieren morir a la primera: descomentar */ } main() { char c; signal(SIGINT, ctrl_c); while((c=getchar()) != EOF) putchar(c); } Dormir un rato (sleep) Programa que atrapa interrupción de reloj para implementar una función sleep: #include <signal.h> #include <stdio.h> #include <stdlib.h> void clock() { printf("ring!\n"); } void msleep(int n) { signal(SIGALRM, clock); /* una buena implementacion guarda el handler original */ alarm(n); pause(); } main() { char c; msleep(2); printf("ok2\n"); msleep(1); printf("ok1\n"); } Timeouts (longjmp/setjmp) Ahora usamos interrupciones de reloj para implementar timeouts. La función call_with_timeout llama una función y espera su retorno un máximo de segundos. Si todo estuvo bien, retorna lo mismo que la función. Si hubo un timeout, retorna -1. Un programa de prueba es: #include <stdio.h> #include <signal.h> /* * Prueba de la implementacion de call_with_timeout * tambien se asegura que el handler de reloj vuelva al antiguo */ int ls() { /* return system("ls -R /usr | less"); */ /* esto si quieren ver un comando largo ejecutando */ sleep(5); /* esta funcion demora 5 segundos, asi que un timeout de 1 debiera bastar */ return(0); } main() { printf("llamando a ls con timeout (3 veces)\n"); printf("debiera ser 0, retorna: %d\n", call_with_timeout(ls, 10)); printf("debiera ser -1, retorna: %d\n", call_with_timeout(ls, 1)); printf("debiera ser -1, retorna: %d\n", call_with_timeout(ls, 1)); /* Esto es para probar que el hadlr de reloj volvió al default */ printf("debiera morir con alarm clock\n"); kill(getpid(), SIGALRM); sleep(1); } Una primera implementación, usando setjmp/longjmp: #include <signal.h> #include <setjmp.h> /* * Ejemplo de uso de signal y longjmp para implementar un timeout * Esta implementación usualmente solo funciona para la primera invocación */ static jmp_buf ring; void clock(int sig) { longjmp(ring, 1); } int call_with_timeout(int (*f)(), int timeout) { int res; void (*hdlr)(); hdlr = signal(SIGALRM, clock); if(setjmp(ring) != 0) { signal(SIGALRM, hdlr); /* si llegamos aquí es que ocurrió el timeout */ return(-1); } alarm(timeout); /* programamos el timer */ res = f(); alarm(0); /* apagamos el timer */ signal(SIGALRM, hdlr); return(res); } Esa implementación antes funcionaba bien, pero hoy en la mayoría de los linux modernos la función signal se implementa con una semántica diferente: el handler de interrupciones se ejecuta con la interrupción que atrapa deshabilitada (es para evitar que el handler sea interrumpido por la misma interrupción). Al retornar del handler, la interrupción se habilita de nuevo. El problema es que, al hacer longjmp, nuestro handler nunca retorna. Linux permite manejar las máscaras de interrupciones bastante al detalle (ver man sigaction), pero usaremos solo un flag muy simple que evita el comportamiento de bloquear la interrupción atrapada. Para ello, no usaremos la función signal, sino su reemplazo más moderno (pero más complejo): sigaction. #include <signal.h> #include <setjmp.h> #include <stdio.h> /* * Ejemplo de uso de signal y longjmp para implementar un timeout * Uso sigaction para borrar el bloqueo de la señal */ static jmp_buf ring; void clock(int sig) { longjmp(ring, 1); } int call_with_timeout(int (*f)(), int timeout) { int res; struct sigaction sig, osig; sig.sa_handler = clock; sig.sa_flags = SA_NODEFER; sigaction(SIGALRM, &sig, &osig); if(setjmp(ring) != 0) { sigaction(SIGALRM, &osig, NULL); return(-1); } alarm(timeout); res = f(); alarm(0); sigaction(SIGALRM, &osig, NULL); return(res); } Clase 10: Linux: Procesos Crear un proceso (fork) La única forma de crear un proceso en Linux es fork(). Este programa simplemente crea una copia de si mismo. Para distinguir el original del clon, el truco es mirar lo que retorna la función fork: el original recibe el pid del clon (hijo), en cambio el clon recibe 0 (cero): #include <stdio.h> #include <stdlib.h> /* * Ejemplo trivial de un fork */ main() { int pid; printf("voy al fork\n"); pid = fork(); if(pid < 0) { fprintf(stderr, "falla fork!\n"); exit(1); } printf("ahora somos 2 clones\n"); if(pid == 0) { /* soy el hijo */ // sleep(1); printf("yo soy el clon!\n"); exit(1); } printf("yo soy el original!\n"); } Pueden ejecutar varias veces este programa y ver que la salida es no determinística ahora. Si des-comentan el sleep en el hijo, pueden verlo escribir sus mensajes después que su padre ya murió y el shell ya les dió el prompt (%). Ejecutar un archivo ejecutable (exec) Modificamos un poco el ejemplo anterior para que ahora ejecute el comando ls, que está en el archivo /bin/ls : #include <stdio.h> #include <unistd.h> #include <stdlib.h> /* * Ejemplo trivial de un fork/exec */ main() { int pid; printf("voy al fork\n"); pid = fork(); if(pid < 0) { fprintf(stderr, "falla fork!\n"); exit(1); } printf("ahora son 2 clones\n"); if(pid == 0) { /* soy el hijo */ printf("yo soy el clon!\n"); execl("/bin/ls", "ls", "-l", 0); fprintf(stderr, "nunca debio ocurrir!\n"); exit(1); } printf("yo soy el original!\n"); } Esperar que un hijo muera (wait) Obviamente sería mejor que el padre esperara al hijo para evitar que salgan lineas despues que el comando ya terminó. Para eso usamos wait: #include <stdio.h> #include <unistd.h> #include <stdlib.h> /* * Ejemplo trivial de un fork/exec/wait */ main() { int pid; int status; printf("voy al ls\n"); printf("---------------\n"); fflush(stdout); /* Para asegurarme que lo anterior ya salga por la salida * prueben comentando el fflush y redirijan la salida a un archivo. */ pid = fork(); if(pid < 0) { fprintf(stderr, "falla fork!\n"); exit(1); } if(pid == 0) { /* soy el hijo */ execl("/bin/ls", "ls", "-l", 0); fprintf(stderr, "nunca debio ocurrir!\n"); exit(1); } waitpid(pid, &status, 0); /* espera la muerte del proceso pid */ printf("---------------\n"); } Manejar propiedades de un hijo Entre el fork y el exec, se pueden manipular las característica que el hijo va a heredar: directorio actual, señales ignoradas, entrada y salida estándar, etc. Este ejemplo hace que el hijo ignore el ctrl-C. Ustedes pueden ejecutarlo, matarlo con ctrl-C y verán que el padre muere, pero el hijo sigue ejecutando. Linux maneja las señales que van desde el teclado y le envía ctrl-C a todos los procesos que están asociados con esa ventana. Los procesos heredan de su padre la ventana a la que se asocian. Si quieren ser independientes, deben separarse del grupo (ver la función setpgroup). #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h> /* * Ejemplo trivial de un fork/exec/wait * cambiamos al hijo para protegerlo de ctrl-C */ main() { int pid; int status; printf("voy al ls\n"); printf("---------------\n"); fflush(stdout); pid = fork(); if(pid < 0) { fprintf(stderr, "falla fork!\n"); exit(1); } if(pid == 0) { /* soy el hijo */ printf("yo soy el clon!\n"); signal(SIGINT, SIG_IGN); chdir("/"); /* vamos a la raiz del sistema */ execl("/bin/ls", "ls", "-l", 0); fprintf(stderr, "nunca debio ocurrir!\n"); exit(1); } waitpid(pid, &status, 0); printf("---------------\n"); } Deberes de un padre En Linux, un padre es completamente independiente de sus hijos, pero debe cumplir con una responsabilidad fundamental: enterrarlos cuando mueren. Para esto, basta con que invoque la función wait en cualquier momento, pero es indispensable que sea informado que su hijo murió para que Linux pueda deshacerse por completo de él. Un hijo muerto pero no enterrado (cuyo padre nunca ha sido informado de su muerte vía wait) es un Zombie: un proceso que ya no existe pero no puede reciclarse por completo. Siempre deben evitar generar Zombies en el sistema responsabilidad hacer wait de todos sus hijos muertos. y es su En este ejemplo, el programa crea 10 procesos de corta duración y se demora en esperarlos. Mientras el padre duerme, ustedes pueden ejecutar el comando “ps” que lista los procesos del sistema y verán los Zombies aparecer. #include <stdio.h> #include <stdlib.h> int vars[10]; int create_proc(int i) { int pid; pid = fork(); if(pid == 0) { /* proceso hijo de corta duracion */ vars[i] = 1; printf("hijo %d, pid=%d,var_i=%d\n", i, getpid(), vars[i]); retorna mi pid */ exit(0); } return pid; } main() { int pids[10]; int i; /* que pasa si no pongo este exit? (prueben!) */ /* getpid() for(i=0; i < 10; i++) vars[i] = 0; for(i=0; i < 10; i++) pids[i] = create_proc(i); sleep(20); /* pueden mirar los zombies durante un rato for(i=0; i < 10; i++) */ /* ahora los entierro para que descansen en paz */ waitpid(pids[i], NULL, 0); for(i=0; i < 10; i++) /* Esto muestra que mis hijos heredan mis variables pero no me las pueden modificar a mi */ printf("%d, ", vars[i]); printf("\n"); sleep(20); } Clase 11: Linux: E/S Nos queda aprender a modificar la E/S estándar de un proceso. Para ello, se definen los descriptores de archivos (file descriptors) que son enteros pequeños, asignados en orden. El descriptor 0 es la entrada estándar, el 1 es la salida estándar y el 2 es la salida de errores. Las funciones oficiales de Linux son read y write para leer y escribir datos. Un ejemplo es una nueva versión del comando copy que hicimos al comienzo del curso, pero en vez de usar las funciones de biblioteca, usamos las primitivas de Linux directamente: #include <stdlib.h> #define BUF_SIZ 1024 main() { char buf[BUF_SIZ]; int cnt; while((cnt=read(0, buf, BUF_SIZ)) > 0) EOF */ if(write(1, buf, cnt) != cnt) /* read retorna los bytes leidos o 0 si es exit(1); /* algo falló */ exit(0); } La forma más habitual de obtener un descriptor nuevo es con la función open, que permite abrir un archivo para leerlo o escribirlo. Pipes Un caso particular es el uso de pipes. Veamos una función que trata de hacer lo mismo que: % ls | more Debemos hacer dos fork/exec y conectarlos con un pipe. La lógica del código es: 1. Crear el pipe (arreglo de dos descriptores) 2. fork hijo 1 que hace: I. II. asignar su salida al pipe exec de ls 3. fork hijo 2 que hace: I. II. asignar su entrada al pipe exec de more 4. padre cierra ambos extremos del pipe 5. espera la muerte de ambos (y se preocupa de cómo murió ls) #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> /* * Este programa ejecuta el comando ls|more en el directorio / (raiz) * Y luego se despide y termina */ main() { int lspid, morepid, pid; int status = 0; int fds[2]; if(pipe(fds) < 0) { /* Antes del fork: crear el pipe comun */ fprintf(stderr, "fallo el pipe\n"); exit(1); } printf("ls de / es:\n======================\n"); lspid = fork(); if(lspid < 0) { fprintf(stderr, "fallo el fork\n"); exit(1); } if(lspid == 0) { /* ls */ /* Cerrar el extremo read del pipe que no voy a usar */ close(fds[0]); /* Asigno: 1 (stdout) = fds[1] (lado de escritura del pipe) */ close(1); dup(fds[1]); /* Cerrar la copia que me queda sobre el pipe o no tendre' EOF */ close(fds[1]); chdir("/"); execl("/bin/ls", "ls", "-l", 0); /* Ponerle -R para probar sigpipe */ fprintf(stderr, "Fallo el exec\n"); exit(-1); } morepid = fork(); if(morepid < 0) { fprintf(stderr, "fallo el fork\n"); exit(1); } if(morepid == 0) { /* more */ /* Cerrar el extremo write del pipe que no voy a usar */ close(fds[1]); /* Asigno: 0 (stdin) = fds[0] (lado de lectura del pipe) */ close(0); dup(fds[0]); /* Cerrar la copia que me queda sobre el pipe o no tendre' EOF */ close(fds[0]); execl("/bin/more", "more", 0); fprintf(stderr, "Fallo el exec\n"); exit(-1); } /* Como padre comun, cierro el pipe, ambos extremos (yo no lo uso) */ close(fds[0]); close(fds[1]); /* como buen padre, espero la muerte de todos mis hijos */ /* while((pid = wait(&status)) != -1); */ /* O los puedo esperar explicitamente (si tuviera otros) */ if(waitpid(morepid, &status, 0) != morepid) { fprintf(stderr, "fallo el wait2\n"); perror("wait2"); exit(-1); } if(waitpid(lspid, &status, 0) != lspid) { perror("wait1"); exit(-1); } if( !WIFEXITED(status) ) { printf("ls anormal\n"); if( WIFSTOPPED(status) ) { printf("Esta detenido\n"); if(WSTOPSIG(status) == SIGSTOP) printf("Con SIGSTOP\n"); } else if( WIFSIGNALED(status) ) { printf("Murio con signal...\n"); if( WTERMSIG(status) == SIGPIPE ) printf("Murio con sigpipe\n"); } } else { /* Normal */ printf("ls muere normalmente con exit code = %d\n", WEXITSTATUS(status)); } printf("========================\n"); } Si ejecutan este programa mostrando todos los archivos, ls muere normalmente y more también al recibir EOF por el pipe. Si terminan a more antes de tiempo (con 'q' desde el teclado) ls muere con sigpipe. Clase 12: Linux: Ejemplos con pipes Se trata de hacer una versión biblioteca system (ver man system): simple #include <stdio.h> #include <sys/wait.h> int system(char *cmd) { int pid; int status; if( (pid=fork()) < 0 ) return(-1); if( pid == 0 ) /* Es el hijo */ { execl("/bin/sh", "sh", "-c", cmd, NULL); exit(127); } /* Es el padre: debemos esperar la muerte del hijo */ waitpid( pid, &status, 0 ); return(WEXITSTATUS(status)); } /* ejemplo de uso */ main() de la función de { int ret; ret = system("ls | more"); printf("done: %d\n", ret); } Otro ejemplo: la función popen de la biblioteca, pero a nivel de descriptores de bajo nivel: #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #define READ 0 #define WRITE 1 int popen_id; int fdpopen(char *cmd, int mode) { int fds[2]; if(pipe(fds) < 0) return -1; if((popen_id=fork()) == 0) /* soy el hijo */ { /* soy cmd */ if(mode == READ) { /* yo voy a escribir (ej: ls) */ close(fds[READ]); close(1); dup(fds[WRITE]); close(fds[WRITE]); } else { /* yo voy a leer (ej: wc) */ close(fds[WRITE]); close(0); dup(fds[READ]); close(fds[READ]); } execl("/bin/sh", "sh", "-c", cmd, NULL); exit(127); } if(popen_id < 0) return -1; /* Este es el padre */ /* Cerrar los fds que no vamos a usar */ close((mode==READ) ? fds[WRITE] : fds[READ]); return(fds[mode]); } int fdpclose(int fd) { int status; close(fd); waitpid(popen_id, &status, 0); return(status); } #define BUF_SIZ 1024 main() { int fd; char buf[BUF_SIZ]; int cnt; /* abrimos ls en lectura */ fd = fdpopen("ls", READ); if(fd < 0) { fprintf(stderr, "No pude abrir ls!\n"); perror("open"); exit(1); } /* mostramos la salida de ls */ while((cnt=read(fd, buf, BUF_SIZ)) > 0) if(write(1, buf, cnt) != cnt) exit(1); fdpclose(fd); printf("========================\n ingrese texto...\n"); fd=fdpopen("wc", WRITE); /* copiamos nuestra entrada a wc */ while((cnt=read(0, buf, BUF_SIZ)) > 0) if(write(fd, buf, cnt) != cnt) exit(1); fdpclose(fd); printf("Fin del Mundo-------\n"); } Clase 13: Linux: Sistema de archivos Versión recursiva de una función find que recorre un árbol de archivos (muestra uso de readdir y sus amigos): #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <dirent.h> #include <stdio.h> /* * Find: * Es mejor recorrer alargando el pathname que haciendo chdir * porque chdir("..") no siempre me lleva a donde yo creo por culpa de * los links */ void find(char *dir) { DIR *fdir; struct dirent *ent; struct stat st; char *fn; /* Pregunto por el directorio o archivo */ if( stat( dir, &st ) < 0 ) /* o lstat si no quiero seguir los links a dir */ return; printf("%s\n", dir); /* Si no es un directorio, no hago nada mas */ if( !S_ISDIR(st.st_mode) ) { return; } /* Si es directorio lo recorro */ if( (fdir = opendir(dir)) == NULL) return; /* ent es el par nombre/i-node recorriendo el directorio */ for(ent = readdir(fdir); ent != NULL; ent = readdir(fdir)) { /* Saltamos . y .. que siempre estan */ if( strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue; /* llamo a find con todos los elementos del directorio */ /* fn = dir + "/" + ent->d_name; */ fn = (char *) malloc(strlen(dir)+strlen(ent->d_name)+2); strcpy(fn, dir); strcat(fn, "/"); strcat(fn, ent->d_name); find( fn ); free(fn); } closedir(fdir); } main() { /* esto recorre el arbol a partir del dir actual */ find( "." ); } Clase 17: Threads: Monitores y Condiciones Tbox con monitores Cuando las condiciones para la espera son más complejas que simplemente contar buffers, usamos condiciones. En pthreads los monitores como tales no existen, pero se implementan con un mutex común y condiciones que permiten quedarse bloqueados. Las condiciones solo pueden usarse dentro de un mutex común que ya está tomado. Aunque es un mal ejemplo, podemos implementar el administrador de buffers con múltiples productores/consumidores para mostrar como funcional. Como ahora no tenemos los semáforos protegiéndonos, debemos preguntar explícitamente por las condiciones de todos los buffers vacíos o todos los buffers llenos. Para eso, agregamos dos variables (llenos y vacios) que siempre suman N: tbox-cond.h: #include <pthread.h> #include <stdio.h> #include <stdlib.h> #define NBUFS 1000 typedef struct { char buf[NBUFS]; pthread_cond_t vacios, llenos; /* condiciones de espera */ pthread_mutex_t mutex; /* mutex compartido */ int in, out; int nvacios, nllenos; } BOX; BOX *createbox(); void putbox(BOX *b, char c); char getbox(BOX *b); tbox-cond.c: #include <stdio.h> #include "tbox-cond.h" BOX *createbox() { BOX *b; b = (BOX *)malloc(sizeof(BOX)); pthread_mutex_init(&b->mutex, NULL); pthread_cond_init(&b->llenos, NULL); pthread_cond_init(&b->vacios, NULL); b->in = b->out = 0; b->nllenos = 0; b->nvacios = NBUFS; return(b); } /* Implementacion para un Productor y un Consumidor */ void putbox(BOX *b, char c) { pthread_mutex_lock(&b->mutex); while(b->nvacios == 0) pthread_cond_wait(&b->vacios, &b->mutex); b->buf[b->in] = c; b->in = (b->in+1)%NBUFS; b->nllenos++; b->nvacios--; pthread_cond_signal(&b->llenos); pthread_mutex_unlock(&b->mutex); } char getbox(BOX *b) { char c; pthread_mutex_lock(&b->mutex); while(b->nllenos == 0) pthread_cond_wait(&b->llenos, &b->mutex); c = b->buf[b->out]; b->out = (b->out+1)%NBUFS; b->nllenos--; b->nvacios++; pthread_cond_signal(&b->vacios); pthread_mutex_unlock(&b->mutex); return(c); } Si se fijan, siempre partimos por tomar el mutex compartido. Luego vemos si se cumple la condición que necesitamos para poder ejecutar. Si no se cumple, nos bloqueamos en un wait de una condición. Esa llamada siempre es bloqueante. Al contrario de los semáforos, las condiciones no tienen un estado. Las operaciones no las modifican, simplemente sirven para darle un nombre al punto de sincronización. Al esperar la condición, debo especificar el mutex que la protege, de modo que la llamada me bloquea y libera el mutex. Si se fijan, no hacemos: if(b->nllenos == 0) pthread_cond_wait(&b->llenos, &b->mutex); sino que usamos un while. Esto es obligatorio, porque siempre debo volver a verificar la condición antes de seguir ejecutando mi código. Para despertar de un wait, otro thread debe invocar un signal sobre esa misma condición. En este caso, lo hace la función putbox es la que me despierta al hacer signal de la condición llenos. Esa función despierta a un thread que esté esperando. Si no hay ninguno, no hace nada. Como usamos la estructura clásica de monitores (con un mutex compartido) no estamos siendo óptimos y no permitimos paralelismo entre productores y consumidores. Pthreads lo permite y podríamos tener dos mutexes (pruébenlo como tarea). En algunos casos más avanzados, podemos querer despertar a todos los threads que están esperando en una condición (no es el caso del ejemplo). Para eso, existe pthread_cond_broadcast(). Semáforo con monitores Como otro ejemplo, podemos implementar un semáforo usando condiciones. Tendrá las funciones P() (wait) y V() (post) como siempre: jsem.h: #include <pthread.h> typedef struct { int val; pthread_cond_t paso; /* Condicion para esperar entrar al semáforo */ pthread_mutex_t mutex; } JSEM; JSEM *jsem_init(int); void P(JSEM *s); void V(JSEM *s); jsem.c: #include "jsem.h" JSEM *jsem_init(int val) { JSEM *s; s = (JSEM *)malloc(sizeof(JSEM)); if(s == NULL) return NULL; s->val = val; pthread_cond_init(&s->paso, NULL); pthread_mutex_init(&s->mutex, NULL); return s; } void P(JSEM *s) { pthread_mutex_lock(&s->mutex); while(s->val == 0) /* Nunca un if! */ pthread_cond_wait(&s->paso, &s->mutex); s->val--; pthread_mutex_unlock(&s->mutex); } void V(JSEM *s) { pthread_mutex_lock(&s->mutex); s->val++; pthread_cond_signal(&s->paso); /* siempre libero a uno solo */ pthread_mutex_unlock(&s->mutex); } Pueden probar los productores/consumidores con esta implementación de semáforos. Todos los códigos de threads vistos hasta aquí se los dejo en tbox.tgz Clase 19: Threads: capturadora de video Un ejemplo que muestra bien la utilidad de las condiciones es un administrador de buffers que almacena frames de video capturados de una cámara (productor) y que se comparten entre múltiples clientes que los despliegan (consumidores). Es un esquema de un productor y múltiples consumidores y un número fijo de buffers en memoria. La cámara genera un nuevo frame y busca un buffer en memoria donde almacenarlo. Esto lo pide con una función: void PutFrame(TIME tstamp, char *buf); Los threads de los clientes buscan un frame entre los buffers y lo marcan para usarlo un rato. Después de terminar con él, lo liberan y piden otro. Esto lo hacen con dos funciones: /* Busca un buffer */ int GetFrame(TIME tstamp); /* Libera un frame */ void FreeFrame(int i) Sin embargo, debemos respetar que la cámara no puede usar un buffer que esté siendo ocupado por un cliente. Por otro lado, el cliente, cuando pide un frame, sólo le sirve uno más nuevo que el último que recibió. Para estas condiciones usamos, para cada frame, un contador de referencias (ref_cnt) que cuenta el número de clientes que tiene “marcado” ese frame y un timestamp que marca la hora absoluta en que fue generado (suponemos que es un número monótonamente creciente). La implementación con monitores y condiciones es: /* Busca un buffer con ref_cnt == 0 */ void PutFrame(TIME tstamp, char *buf) { int i; pthread_mutex_lock( &(master_lock)); for(;;) { for(i=0; i<NBUFS; i++) if(Buffers[i].ref_cnt == 0) break; if(i==NBUFS) pthread_cond_wait(&free_frame, &master_lock); else break; } Buffers[i].tstamp = tstamp; Buffers[i].buf = buf; pthread_cond_broadcast(&new_frame); pthread_mutex_unlock( &(master_lock)); } /* Busca un buffer con timestamp > tstamp */ int GetFrame(TIME tstamp) { int i; pthread_mutex_lock( &(master_lock)); for(;;) { for(i=0; i<NBUFS; i++) if(Buffers[i].tstamp > tstamp) break; if(i==NBUFS) pthread_cond_wait(&new_frame, &master_lock); else break; } Buffers[i].ref_cnt++; pthread_mutex_unlock( &(master_lock)); return i; } /* Libera un frame */ void FreeFrame(int i) { pthread_mutex_lock( &(master_lock)); Buffers[i].ref_cnt--; if(Buffers[i].ref_cnt == 0) pthread_cond_signal(&free_frame); pthread_mutex_unlock( &(master_lock)); } Estudien el código. Vean porqué hacemos cond_signal en un lugar y cond_broadcast en otro. Clase 20: Threads: Filósofos muertos de hambre Un problema clásico de sincronización es el de los filósofos hambrientos. La idea es un grupo de N filósofos y N tenedores sentados alrededor de una mesa. El problema es que se necesitan 2 tenedores para poder comer. Cuando alguno de los tenedores está ocupado por nuestro vecino, debemos esperar que lo desocupe. Esta es la solución inocente que podríamos proponer (vean que piensan poco y comen harto… ¡no son buenos filósofos!): #include <stdio.h> #include <pthread.h> #define N_FILO 10 pthread_mutex_t forks[N_FILO]; /* Si pensar es mucho mas rápido que comer, aumentamos las * probabilidades de deadlock */ void pensar(int f) { printf("%d: pensando...\n", f); } void comer(int f) { printf("%d: comiendo..\n", f); sleep(1); } void *filo(void *f2) { int i, f=(int)f2; for(i=0; i < 10; i++) { pensar(f); pthread_mutex_lock(&forks[f]); printf("%d: locked %d\n", f ,f); pthread_mutex_lock(&forks[(f+1)%N_FILO]); printf("%d: locked %d\n", f, (f+1)%N_FILO); comer(f); pthread_mutex_unlock(&forks[(f+1)%N_FILO]); printf("unlocked %d\n", f); pthread_mutex_unlock(&forks[f]); printf("unlocked %d\n", (f+1)%N_FILO); } printf("%d: finito!\n", f); return NULL; } main() { int i; pthread_t pid[N_FILO]; for(i=0; i < N_FILO; i++) pthread_mutex_init(&forks[i], NULL); for(i=0; i < N_FILO; i++) pthread_create(&pid[i], NULL, filo, (void *)i); for(i=0; i < N_FILO; i++) pthread_join(pid[i], NULL); } Si ejecutan este código, después de un rato aleatorio, termina bloqueándose para siempre en undeadlock. El problema es que podemos caer en el caso en que cada filósofo tiene un tenedor y está esperando que su vecine suelte el otro, formando un ciclo de espera infinito. Para evitar este deadlockdebemos esperar una condición (que ambos tenedores estén desocupados) sin tomar ninguno. Con condiciones queda así: #include <stdio.h> #include <pthread.h> /* * Solucion con condiciones: sin deadlocks pero, ¿es justa? */ #define N_FILO 4 pthread_mutex_t table; pthread_cond_t forks2[N_FILO]; int forks[N_FILO]; void pensar(int f) { printf("%d: pensando...\n", f); } void comer(int f) { printf("%d: comiendo..\n", f); sleep(1); } void* filo(void *f2) { int i, f = (int)f2; for(i=0; i < 10; i++) { pensar(f); pthread_mutex_lock(&table); while(forks[f] != 1 || forks[(f+1)%N_FILO] != 1) pthread_cond_wait(&forks2[f], &table); forks[f]=forks[(f+1)%N_FILO]=0; pthread_mutex_unlock(&table); comer(f); pthread_mutex_lock(&table); forks[f] = forks[(f+1)%N_FILO] = 1; pthread_cond_signal(&forks2[(f-1+N_FILO)%N_FILO]); pthread_cond_signal(&forks2[(f+1)%N_FILO]); pthread_mutex_unlock(&table); } printf("%d: finito!\n", f); return NULL; } main() { int i; pthread_t pid[N_FILO]; pthread_mutex_init(&table, NULL); for(i=0; i < N_FILO; i++) { pthread_cond_init(&forks2[i], NULL); forks[i] = 1; } for(i=0; i < N_FILO; i++) pthread_create(&pid[i], NULL, filo, (void *)i); for(i=0; i < N_FILO; i++) pthread_join(pid[i], NULL); } Un nuevo problema que aparece al resolver este deadlock es la justicia: ¿podemos garantizar que todos los filósofos comerán algún día? ¿Todos reciben comida al mismo ritmo? Pues no. Si ejecutan este programa, verán que se genera un orden de a pares donde 2 filósofos comen mucho más que los otros 2. Hacer una solución justa es complejo y requiere usualmente manejar las colas de espera a mano. El conflicto habitual es al señalar una condición y despertar un thread y luego liberar el mutex. ¿Quién parte ejecutando? Puede entrar un nuevo thread que esperaba en el mutex, o puede partir el thread que se despertó de su condición. Usualmente, no sé quién es. El fenómeno de la injusticia en sincronización puede llegar al extremo que un filósofo no coma nunca y muera de hambre. Por eso, se conoce como el problema de starvation o inanición. AUX 5 Pregunta 1 Se quiere implementar un sistema de autos que se adelantan en una calle con tres vías: una sólo para el sur, otra sólo para el norte y una al medio que sólo se usa para adelantamientos en ambas direcciones. Para evitar accidentes, implementamos acceso exclusivo a la pista de adelantamiento: pthread_mutex_t pista; adelantar(int dir) { pthread_mutex_lock(&pista); } volver(int dir) { pthread_mutex_unlock(&pista); } auto(int dir) { /* Un thread por auto */ for(;;) { avanzar_hasta_prox_auto(dir); adelantar(dir); avanzar_pista_aux(dir); volver(dir); }} Parte I El problema de esta solución es que no permite compartir la vía de adelantamiento entre autos que van en la misma dirección. Modifique el código para que esto sea posible. 1 Parte II Modifique su solución para asegurar que la pista de adelantamiento no pueda ser monopolizada en una sola dirección. Para ello, utilice un contador de autos que van entrando en la misma dirección. Llegado a MAX autos, si hay autos esperando adelantar en la dirección contraria, deben dejar de aceptar nuevos autos, esperar que se desocupe completamente la pista, y aceptar los autos en dirección contraria. Pregunta 2 Se propone la siguiente solución para implementar un semáforo usando mutexes: typedef struct { int n; pthread_mutex key; } SEM; SEM *init_sem(int n) { SEM *s; s = (SEM *)malloc(sizeof(SEM)); s->n = n; pthread_mutex_init(&s->key, NULL); pthread_mutex_lock(&s->key); return s; } void P(SEM *s) { s->n--; if(s->n < 0) pthread_mutex_lock(&s->key); } void V(SEM *s) { s->n++; if(s->n <= 0) pthread_mutex_unlock(&s->key); } Comente si está correcta, si permite paralelismo entre P (wait) y V (signal) y si no genera deadlocks o incorrectitudes. Corrija lo necesario. 2 Pregunta 3 Se quiere implementar una primitiva de sincronización que permite esperar un número de threads totales que lleguen al mismo punto antes de seguir ejecutando. La idea es que se inicializa la estructura con un entero que indica la cantidad de threads que deben llegar: SYNC *create_sync(int n); void sync(SYNC *s); Por ejemplo, esta creación SYNC *s; s = create_sync(10); Hace que 9 threads se quedarán bloqueadas llamando a sync(s) hasta que llegue la décima que la invoque, y entonces las 10 retornarán de sync(), y seguirán su ejecución en paralelo. Cada vez que esto ocurre, la variable vuelve a funcionar igual, y comienza a dejar bloqueados a los threads siguientes hasta completar otros 10. Implemente la estructura con su función de inicialización y la primitiva sync(). P1 a) int dir = SUR; int count = 0; pthread_cond_t cnt0; cur_dir = null; adelantar(int dir) { pthread_mutex_lock(&pista); while(dir != cur_dir && count > 0) pthread_cond_wait(&cnt0, &pista); count++; cur_dir = dir; pthread_mutex_unlock(&pista); } volver(int dir) { pthread_mutex_lock(&pista); count--; if(count == 0) pthread_cond_broadcast(&cnt0); pthread_mutex_unlock(&pista); } b) int cur_dir = SUR; int count = 0; int tot_lado = 0; pthread_cond_t cntsur0, cntnorte0; int wait_norte = 0; int wait_sur = 0; adelantar(int dir) { pthread_mutex_lock(&pista); while((dir != cur_dir && count > 0) || tot_lado >= MAX){ if(dir == SUR) { wait_sur++; pthread_cond_wait(&cntsur0, &pista); wait_sur--; } else {  wait_norte++; pthread_cond_wait(&cntnorte0, &pista); wait_norte--; } } count++; tot_lado++; cur_dir = dir; pthread_mutex_unlock(&pista); } volver(int dir) { pthread_mutex_lock(&pista); count--; if(count == 0) { if(dir == SUR) { if(wait_norte > 0) pthread_cond_broadcast(&cntnorte0); else pthread_cond_broadcast(&cntsur0); } if(dir == NORTE) { if(wait_sur > 0) pthread_cond_broadcast(&cntsur0); else pthread_cond_broadcast(&cntnorte0); } tot_lado = 0; } pthread_mutex_unlock(&pista); } P2 /* * * * * * * lo importante es que analicen los casos correctamente: 1) si un P() y un V() se ejecutan concurrentemente: podemos hacer un unlock de mas dejando el sistema indefinido. Necesitamos rodear el codigo de un mutex. 2) probar paralelismo entre varios P() y varios V(): varios V() pueden evitar que se haga un unlock(), siendo grave. Solucion propuesta: */ void P(SEM *s) { pthread_mutex_lock(&s->mutex); s->n--; if(s->n < 0) { pthread_mutex_unlock(&s->mutex); pthread_mutex_lock(&s->key); pthread_mutex_lock(&s->mutex); } pthread_mutex_unlock(&s->mutex); } void V(SEM *s) { pthread_mutex_lock(&s->mutex); s->n++; if(s->n <= 0) pthread_mutex_unlock(&s->key); pthread_mutex_unlock(&s->mutex); } /* * * * * Analizar la nueva solucion: punto de fragilidad que debieran mostrar es el instante en que uno suelta el mutex en P() antes de hacer el lock de key. Si la llave acepta bien hacer mas unlocks que locks, la solucion esta correcta. El caso contrario no se puede solucionar :( */ P3 typedef struct { int nmax; int waiting; pthread_mutex_t mutex; pthread_cond_t arrived; } SYNC; SYNC *create_sync(int n){ SYNC *s; s = (SYNC *)malloc(sizeof(SYNC)); s->waiting = 0; s->nmax = n; pthread_mutex_init(&s->mutex, NULL); pthread_cond_init(&s->arrived, NULL); return s; } void sync(SYNC *s) { pthread_mutex_lock(&s->mutex); s->waiting++; if(s->waiting == s->nmax) { // es un error poner un while aqui... s->waiting = 0; pthread_cond_broadcast(&s->arrived);// Justo despertaran nmax } else pthread_cond_wait(&s->arrived, &s->mutex); pthread_mutex_unlock(&s->mutex); } AUX 6 1 Se desea escribir un programa que escriba en pantalla una secuencia de enteros, y que cada vez que se presione CTRL+C, se reinicie el contador de secuencias. El programa debe esperar 1 segundo entre secuencias. También se debe poder especificar un tiempo máximo de ejecución del programa. A continuación se encuentran los parámetros a recibir y una salida de ejemplo: ./sseq ini step fin [maxtime] sseq 5 2 20 25 1: 5 7 9 11 13 15 17 19 <CTRL+C> --------------1: 5 7 9 11 13 15 17 19 2: 5 7 9 11 13 15 17 19 3: 5 7 9 11 13 15 17 19 <CTRL+C> --------------1: 5 7 9 11 13 15 17 19 2: 5 7 9 11 13 15 17 19 3: 5 7 9 11 13 15 17 19 4: 5 7 9 11 13 15 17 19 5: 5 7 9 11 13 15 17 19 --------------Se acabo el tiempo Terminando el programa 1 HINT: Las variables no se mantienen a través de un jmp a menos que sean definidas a través del keyword volatile. Pregunta 2 Para demostrar el concepto de proceso zombie se le pide que haga un programa modificable a través de la macro ZOMBIE, que deja procesos zombies si está en un 1, y los erradica si es que está en 0. El flujo del programa consiste en que el proceso padre trate de matar reiteradas veces al proceso hijo a través de la función kill, acción que es impedida sólo 3 veces por un handler. Después de esto, si la macro ZOMBIE está activada, debe imprimirse permanentemente un mensaje, avisando que existen procesos zombies. Pregunta 3 Se quiere implementar una marca en el código a la que se retorna cuando se han recibido 5 CTRL+C y que envía una advertencia al cuarto CTRL+C. Además, al sexto CTRL+C el programa debe cerrarse. El uso sería: if(setjmp(ctrlc) != 0) printf("5 CTRL+C! \n"); Para esto, programe un handler de manejar la señal enviada por CTRL+C. P1 #include #include #include #include #include <stdio.h> <stdlib.h> <setjmp.h> <signal.h> <unistd.h> /*para alarm*/ volatile int count=1; volatile int execute=1; jmp_buf jump; /*cuando llega una señal se ejecuta handler * y el numero de la señal se pasa como argumento */ void handler(int sig){ if(sig==SIGINT){ /*reseteamos el handler*/ signal(SIGINT,handler); count=1; /*ejecutamos longjmp para reiniciar el proceso*/ longjmp(jump,1); }else if(sig==SIGALRM){ printf("---------------\nSe acabo el tiempo\nTerminando el programa\n"); execute=0; v} /*impime forma de uso*/ void usage(){ printf("Uso:\n\tsseq ini step fin [maxtime]\n"); printf("Se recomienda el uso de maxtime para que el programa muera\n"); } /*escribe secuencias de numeros * recomendable que se use parametro maxtime para qeu el programa muera*/ int main(int argc, char *argv[]){ int ini,fin,step,time,i; if(argc==4||argc==5){ ini=atoi(argv[1]); step=atoi(argv[2]); fin=atoi(argv[3]); if(argc==5) time=atoi(argv[4]); }else{ usage(); return EXIT_FAILURE; } signal(SIGINT,handler); signal(SIGALRM,handler); if(argc==5) alarm(time); while(execute){ if(!setjmp(jump)){ printf("%d: ",count); for(i=ini;i<=fin;i+=step) printf("%d ",i); count++; }else{ /*count lo maneja handler */ printf("\n---------------"); } printf("\n"); sleep(1); } return EXIT_SUCCESS; } P2 #include #include #include #include #include #include <stdio.h> <stdlib.h> <setjmp.h> <signal.h> <unistd.h> /*para alarm*/ <sys/wait.h> #define ZOMBIE 1 /* 1 TRUE... 0 FALSE, cambiar entre estos valores para ver que pasa cuando un proceso es un zombie */ int i; void zombie() { if(i < 3){ printf("\nEl padre est‡ tratando de matar a su hijo :(\n\n"); } else{ printf("\nSuficiente: el hijo ha de morir!\n\n"); exit(0); } ++i; } void child() { int status; while((status=waitpid(-1, NULL, WNOHANG)) > 0) fprintf(stderr, "Padre: por fin acabe contigo, hijo numero %d\n", status); exit(0); } int main() { pid_t child_pid; i = 0; int j=0; signal(SIGINT, zombie); #if ZOMBIE == 0 signal(SIGCHLD, child); #endif child_pid = fork(); if(child_pid > 0){ printf("Padre: Proceso %d, yo soy tu padre\n", child_pid); while(i++ < 5){ sleep(3); kill(child_pid, SIGINT); } #if ZOMBIE == 1 for(;;){ printf("Mi hijo esta muerto, pero como no hice wait es un zombie!!\n"); printf("El proceso %d deber’a ser un zombie!\n", child_pid); sleep(4); } #endif }else{ printf("Hijo: Ya llegue\n"); for(;;){ printf("Hijo: Estoy esperando senales ahora!\n"); pause(); /*esperara por se–ales*/ printf("Hijo: Me acaba de llegar una senal!\n"); } } } P3 #include #include #include #include #include <stdio.h> <stdlib.h> <setjmp.h> <signal.h> <unistd.h> /*para alarm*/ volatile int count=0; /*definimos un ambiente donde guardar las variables*/ jmp_buf env; /*cuando llega una señal se ejecuta handler * y el numero de la señal se pasa como argumento */ void handler(int sig){ if(sig==SIGINT){ /*reseteamos el handler*/ signal(SIGINT,handler); count++; if(count==4){ printf("Este es tu cuarto CTRL+C\n"); } /*ejecutamos longjmp para reiniciar el proceso*/ if(count==5){ longjmp(env,1); } if(count==6){ printf("Adios!\n"); exit(0); } } } int main(int argc, char *argv[]){ int i=1; /* creamos el signal para el handler */ signal(SIGINT,handler); /* Almacenamos el ambiente actual: * variables, registros, stack pointer y framepointer * setjmp retorna 0 si almaceno de forma correcta. * * */ if(setjmp(env) != 0) printf("Aviso: 5 CTRL+C!\n"); printf("Holaaa \n"); for(;;){ printf("%d\n",i++); sleep(1); } return EXIT_SUCCESS; } ANEXOS AUX6 FORK #include<stdio.h> #include<unistd.h> #include<signal.h> #include<stdlib.h> int main() { int i = 0; int pid; int status = 0; printf("Estoy ejecutando el proceso principal\n"); pid = fork(); if (pid < 0) { fprintf(stderr,"No pude crear el proceso\n"); return(1); } printf("Estoy duplicado\n"); if (pid == 0) { signal(SIGINT,SIG_IGN); sleep(5); i = 10; printf("Soy el clon con i %d\n",i); exit(3); } else { /* verificar si el hijo sigue vivo */ while (waitpid(pid,&status,WNOHANG) == 0) { printf("El hijo aun vive\n"); sleep(1); } printf("Mi hijo murio con estado %d\n",WEXITSTATUS(status)); } } JMP #include <setjmp.h> main() { jmp_buf env; int i; i = setjmp(env); printf("i = %d\n", i); if (i != 0) exit(0); longjmp(env, 2); printf("Hello! :-)"); } AUX 7 Problema 1 Escriba un programa el cual lee un string como parámetro de entrada y envía los datos leídos a un proceso hijo el cual retorna la cantidad de bytes leídos. Problema 2 Se le pide implementar void pipeline(char *cmds[] , int size) Que recibe un arreglo de comandos y los ejecuta conectados por pipes, al igual que lo hace el shell cuando tipean una secuencia. Por ejemplo, la secuencia: % cat | sort | uniq | more se crea haciendo: cmds[0] = "cat"; cmds[1] = "sort"; cmds[2] = "uniq"; cmds[3] = "more"; pipeline(cmds, 4); Suponga que las primitivas nunca fallan y que los comandos existen. El primer comando lee desde la entrada estándar, y el último comando escribe en la salida estándar. No se requiere esperar a que los procesos mueran, basta con crearlos bien conectados con sus pipes. En el caso anterior, deben crear 3 pipes. 1 Problema 3 Implemente la función, int nfiles(char *path) que cuenta los archivos y directorios presentes en la ruta path. Puede asumir que no hay links ni puntos de montaje bajo path. P1 #include #include #include #include <stdio.h> <string.h> <sys/types.h> <unistd.h> #define BUFFER_SIZE 1024 #define READ 0 #define WRITE 1 int main(int argc, char *argv[]){ /*En caso que se quiera enviar un String... char write_msg[BUFFER_SIZE] = "CC3301-1 Programacion de Software de Sistema"; */ char *write_msg = argv[1]; char read_msg[BUFFER_SIZE]; int fd[2]; pid_t pid; /*Creamos el pipe * un pipe es de comunicacion unidireccional utilizando los fd que se pasan como argumento*/ if(pipe(fd) == -1){ fprintf(stderr, "Fallo el pipe"); return 1; } /*Creamos el proceso*/ pid = fork(); if(pid < 0) { fprintf(stderr, "Fallo el fork"); return 1; } if(pid > 0) { /*Soy el padre*/ /*Debo cerrar el extremo del pipe que no uso*/ close(fd[READ]); printf("Soy el padre y voy a escribir en el pipe: \n"); /*Escribimos en el pipe*/ write(fd[WRITE], write_msg, strlen(write_msg)+1); /*Cerramos el pipe*/ close(fd[WRITE]); } else { /*Soy el hijo*/ /*Cerramos el extremo que no uso*/ close(fd[WRITE]); /*Leemos desde el pipe*/ int n = read(fd[READ], read_msg, BUFFER_SIZE); printf("Soy el hijo y lei: %s.\n", read_msg); printf("El tamano del mensaje es: %d \n", n-1); /*Cerramos el pipe*/ close(fd[READ]); } return 0; } P2 #include <stdio.h> #include <unistd.h> #include <stdlib.h> #define READ 0 #define WRITE 1 int pipeline(char *cmds[], int ncmds) { int fdin, fdout; int fds[2]; int i; fdin = 0; for(i=0; i < ncmds-1; i++) { pipe(fds); fdout = fds[WRITE]; if(fork() == 0) { if( fdin != 0 ) { close(0); dup(fdin); close(fdin); } if( fdout != 1 ) { close(1); dup(fdout); close(fdout); } close(fds[READ]); execlp(cmds[i], cmds[i], NULL); exit(1); } if(fdin != 0) close(fdin); if(fdout != 1) close(fdout); fdin = fds[READ]; } /* Ultimo comando */ fdout = 1; if(fork() == 0) { if( fdin != 0 ) { close(0); dup(fdin); close(fdin); } close(fds[READ]); execlp(cmds[i], cmds[i], NULL); exit(1); } if( fdout != 1) close(fdout); if( fdin != 0 ) close(fdin); } char *cmds[] = {"cat", "sort", "uniq"}; main() { pipeline(cmds, 3); while(wait(NULL) > 0) ; } P3 #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <sys/types.h> <dirent.h> <string.h> <sys/stat.h> <unistd.h> int nfiles(char *); int main() { char dir[] = "."; printf("%d archivos bajo el directorio %s\n", nfiles(dir), dir); exit(EXIT_SUCCESS); } int nfiles(char *path) { DIR *dir; struct dirent *de; struct stat st; int count = 0; char *childpath; // obtengo el handler del directorio dir = opendir(path); if(!dir) { perror(path); exit(EXIT_FAILURE); } // itero sobre el directorio while((de = readdir(dir))) { // siempre hay que saltarse el . y el .., explicar cual es la unica carpeta en la que . y .. son iguales // también hay que hacer hincapié que siempre están ahà if(!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue; // sprintf con malloc incluido asprintf(&childpath, "%s/%s", path, de->d_name); // señalar diferencia entre stat y lstat if(stat(childpath, &st)) { perror(de->d_name); exit(EXIT_FAILURE); } // ojo con S_ISDIR if(S_ISDIR(st.st_mode)) count += nfiles(childpath); ++count; // ojo con el malloc del asprintf free(childpath); } // cierro el directorio closedir(dir); return count; } ANEXOS AUX 7 POPEN #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #define READ 0 #define WRITE 1 int popen_id; int fdpopen(char *cmd, int mode) { int fds[2]; if(pipe(fds) < 0) return -1; if((popen_id=fork()) == 0) /* soy el hijo */ { /* soy cmd */ if(mode == READ) { /* yo voy a escribir (ej: ls) */ close(fds[READ]); close(1); dup(fds[WRITE]); close(fds[WRITE]); } else { /* yo voy a leer (ej: wc) */ close(fds[WRITE]); close(0); dup(fds[READ]); close(fds[READ]); } execl("/bin/sh", "sh", "-c", cmd, NULL); exit(127); } if(popen_id < 0) return -1; /* Este es el padre */ /* Cerrar los fds que no vamos a usar */ close((mode==READ) ? fds[WRITE] : fds[READ]); return(fds[mode]); } int fdpclose(int fd) { int status; close(fd); waitpid(popen_id, &status, 0); return(status); } #define BUF_SIZ 1024 main() { int fd; char buf[BUF_SIZ]; int cnt; /* abrimos ls en lectura */ fd = fdpopen("ls", READ); if(fd < 0) { fprintf(stderr, "No pude abrir ls!\n"); perror("open"); exit(1); } /* mostramos la salida de ls */ while((cnt=read(fd, buf, BUF_SIZ)) > 0) if(write(1, buf, cnt) != cnt) exit(1); fdpclose(fd); printf("========================\n ingrese texto...\n"); fd=fdpopen("wc", WRITE); /* copiamos nuestra entrada a wc */ while((cnt=read(0, buf, BUF_SIZ)) > 0) if(write(fd, buf, cnt) != cnt) exit(1); fdpclose(fd); printf("Fin del Mundo-------\n"); } SYSTEM #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <sys/wait.h> int msystem(char *cmd) { int pid; int status; if( (pid=fork()) < 0 ) return(-1); if( pid == 0 ) /* Es el hijo */ { execl("/bin/sh", "sh", "-c", cmd, NULL); exit(127); } /* Es el padre: debemos esperar la muerte del hijo */ waitpid( pid, &status, 0 ); return(WEXITSTATUS(status)); } /* ejemplo de uso */ main() { int ret; ret = msystem("ls | more"); printf("done: %d\n", ret); } AUX 8 Pregunta 1 - Control 2 Otoño 2011 Queremos agregar a box un par de primitivas para escribir y leer un buffer en la caja en una sola operación: void putnbox(BOX *b, char *buf, int n); /* escribe los n chars de buf en box */ int getnbox(BOX *b, char *buf, int n); /* lee chars en buf. maximo n, retorna lo leido */ putnbox() siempre escribe los n bytes seguidos. Es decir, nunca debe poder entrar otro proceso haciendo put entre medio. Recuerde que n podría ser mayor que NBUFS. getnbox() se bloquea si no hay bytes en la caja. Una vez que hay bytes disponibles, los lee todos (hasta un máximo de n), copiándolos al buffer. Luego retorna el número de bytes copiados. Implemente ambas funciones utilizando las primitivas de sincronización que prefiera. Revise su código y explique si funciona para un productor y un consumidor; luego, si funciona para múltiples productores y consumidores simuláneos. Pregunta 2 - Control 2 Otoño 2011 Escriba las siguientes funciones (sin usar popen()): int more(); /* retorna el file descriptor para escribir a "more" por un pipe */ 1 void more_close(int fd); /* Cierra el fd y espera la muerte de "more" */ La idea es generar un file descriptor en el que podemos escribir y que está conectado por un pipe al comando /bin/more, para que vaya mostrando página por página lo que escribimos. Pregunta 3 - Control 2 Otoño 2011 En clases vimos la función: static jmp_buf ring; void clock(){ longjmp(ring, 1); } int call_with_timeout(int (*f)(), int timeout){ int res; struct sigaction sig, osig; memset(&sig, 0, sizeof(struct sigaction)); sig.sa_handler = clock; sig.sa_flags = SA_NODEFER; sigaction(SIGALRM, &sig, &osig); if(setjmp(ring) != 0) { sigaction(SIGALRM, &osig, NULL); return -1; } alarm(timeout); res = f(); alarm(0); sigaction(SIGALRM, &osig, NULL); return(res); } Modifíquela para que ahora también sobreviva si alguien genera una interrupción con ctrl-C durante la ejecución de la función f. En ese caso, debemos retornar -2 para diferenciar del timeout. Recuerde anular el timeout cuando hubo un ctrl-C. P1 /*typedef struct{ * * char buf[NBUFS]; * pthread_cond_t vacios, llenos; * pthread_mutex_t mutex, put; * int in, out; * int nvacios, nllenos; * }BOX; */ void putnbox(BOX *b, char *buf, int n){ pthread_mutex_lock(&b->put); pthread_mutex_lock(&b->mutex); while(n > 0){ while(b->nvacios==0) pthread_cond_wait(&b->vacios, &b->mutex); b->buf[b->in] = *buf++; n--; b->in = (b->in+1)%NBUFS; b->nllenos++; b->nvacios--; } pthread_cond_signal(&b->llenos); pthread_mutex_unlock(&b->mutex); pthread_mutex_unlock(&b->put); } void getnbox(BOX *b, char *buf, int n){ int cnt = 0; pthread_mutex_lock(&b->mutex); while(b->nllenos == 0) pthread_cond_wait(&b->llenos, &b->mutex); while(b->nllenos > 0 && n>0){ **buf++ = b->buf[b->out]; n--; cnt++; b->out = (b->out +1)%NBUFS; b->nllenos--; b->nvacios++; } pthread_cond_signal(&b->vacios); pthread_mutex_unlock(&b->mutex); return cnt; } P2 int mpid; int more(){ int fds[2]; pipe(fds); mpid = fork(); if(mpid < 0) return -1; if(mpid == 0){ /*Soy el hijo!*/ close(fds[1]); close(0); dup(fds[0]); close(fds[0]); execl("/bin/more", "more", NULL); fprint("error!"); exit(1); } close(fds[0]); return fds[1]; } void more_close(int fd){ close(fd); waitpid(mpid, NULL, NULL); } P3 void clock(){ longjmp(ring, 1); } void brk(int sig){ alarm(0); longjmp(ring, 2); } int call_with_timeout(int (*f)(), int timeout){ int res; int c; struct sigaction sig, osig1, osig2; memset(&sig, 0, sizeof(struct sigaction)); sig.sa_handler = clock; sig.sa_flags = SA_NODEFER; sigaction(SIGALRM, &sig, &osig1); sig.sa_handler = brk; sigaction(SIGINT, &sig, &osig2); if(c = setjmp(ring) != 0) { sigaction(SIGALRM, &osig1, NULL); sigaction(SIGINT, &osig2, NULL); if(c==1) return -1; else return -2; } alarm(timeout); res = f(); alarm(0); sigaction(SIGALRM, &osig1, NULL); sigaction(SIGINT, &osig2, NULL); return(res); }