WikiC2 - U

Anuncio
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);
}
Descargar