Modulo de Sisncronizacion
Modulo de Sisncronizacion
Modulo de Sisncronizacion
El mdulo de sincronizacin y comunicacin se estructura en los siguientes apartados: Introduccin Concurrencia, comparticin y coordinacin Condicin de carrera Coordinacin implcita vs. Coordinacin explcita Modelo Productor Consumidor Modelo Lectores Escritores Modelo Cliente Servidor
INTRODUCCIN
Este mdulo incide fundamentalmente en los servicios POSIX para la sincronizacin y la comunicacin, tanto de procesos ligeros como pesados. Unas veces sern servicios del sistema operativo y otros sern servicios proporcionados a nivel de biblioteca (por ejemplo por la biblioteca pthread). Se utilizarn los siguientes programas, que debern descargarse de la web y compilar para su ejecucin:
Carreras.c Concurr_prc.c Lector.c Compart_pth.c CPUs_prc.c TCP_c.c Compart_prc.c CPUs_pth.c TCP_s.c Concurr_pth.c Escritor.c Times_C.c
Los servicios y funciones de biblioteca utilizados en dichos programas son los siguientes:
accept bzero execvp fork getpid lseek open pthread_cancel pthread_cond_wait pthread_mutex_init pthread_setcanceltype send socket toupper alarm calloc exit fprintf htons memcpy perror pthread_cond_destroy pthread_create read shutdown strlen usleep write atoi close fcntl getenv kill mmap poll pthread_cond_init pthread_join recv signal sysconf wait bind connect fflush gethostbyname listen munmap printf pthread_cond_signal pthread_mutex_destroy sched_yield sizeof times waitpid
CONCURRENCIA
En un sistema UNIX la concurrencia sucede a muchos niveles. Actualmente, en la mayora de los sistemas operativos modernos encontramos las siguientes caractersticas relacionadas con su capacidad de concurrencia: Sistema multiproceso: el sistema permite lanzar mltiples mandatos o aplicaciones, que, al ejecutar, sern mltiples procesos. Todos los procesos del sistema ejecutarn concurrentemente. El sistema crea la ficcin de una CPU (virtual) exclusiva para ejecutar cada proceso. Un sistema puede ser multiproceso incluso cuando slo dispone de una nica CPU. En este ltimo caso, de acuerdo a la planificacin del SO, los procesos intercalan su ejecucin en la nica CPU existente. Sistema multiusuario: mltiples usuarios pueden conectarse y trabajar concurrentemente sobre el sistema. Sistema multiprocesador: el sistema explota todos los recursos de cmputo disponibles. Si la mquina dispone de varias CPUs, ninguna quedar ociosa mientras existan procesos listos para ejecutar. Podr haber en ejecucin paralela real tantos hilos de ejecucin como procesadores tenga la mquina. Ciertas tecnologas modernas ofrecen incluso ms: HyperThreading permite dos hilos de ejecucin por procesador. DualCore encapsula dos procesadores completos en un nico Chip. QuadCore encapsula cuatro procesadores completos en un nico Chip. Captura de seales: un proceso puede decidir ejecutar una funcin cuando el sistema notifique un evento dado, va una determinada seal. La ejecucin de la funcin asociada ser asncrona (y en cierto sentido concurrente) respecto de la del hilo principal del proceso. Proceso multihilo: un hilo del proceso puede crear otro. Todos los hilos del proceso comparten un nico espacio de memoria. Se usan tcnicas de programacin concurrente, utilizando servicios ofrecidos por una biblioteca o por el sistema operativo. Biblioteca multihilo: conjunto de servicios para programacin multihilo, sin que el sistema operativo se entere. La biblioteca multiplexa el nico hilo que el sistema operativo ofrece por proceso. Si se bloquea un hilo se est bloqueando el nico hilo del proceso. Sistema multihilo: el sistema ofrece el conjunto de servicios para la programacin multihilo. El sistema operativo gestiona que un proceso tenga ms de un hilo. Todos
los hilos del proceso comparten un nico espacio de memoria. Si un hilo se bloquea, los dems hilos del proceso no se ven afectados. Un proceso multihilo, sobre un sistema multihilo, sobre un multiprocesador, podra estar ejecutando simultneamente en paralelo en ms de una CPU, esto es, obteniendo tasas de utilizacin de CPU de ms del 100%. Este mismo proceso multihilo (usando una biblioteca multihilo), sobre un sistema monohilo, ejecutara como mximo al 100% de CPU, aunque el sistema sea multiprocesador. Para que la realizacin de esta primera tanda de preguntas ofrezca resultados interesantes, se recomienda encarecidamente que las realice en la mquina triqui.fi.upm.es. Se pide que se introduzca usted en un sistema UNIX donde tenga cuenta abierta. Un sistema UNIX es multiproceso y multiusuario.
Analicemos ahora el tipo de sistema con el que est trabajando. El directorio /proc, como ya habamos visto anteriormente, recoge informacin sobre el sistema. Entre esta informacin se encuentra las caractersticas del procesador, en concreto, en el ficherocpuinfo. processor.- nos indica el nmero de procesadores virtuales que contiene la mquina. physical id.- nos proporciona el identificador fsico de cada uno de los procesadores que contiene la mquina. Como es de esperar, si varios procesadores tienen el mismo physical id, es porque se trata del mismo procesador fsico. cpu cores.- nos permite conocer el nmero de ncleos que existen por procesador fsico. Un valor de '1' indicar, por tanto, que el procesador contiene un nico ncleo. Si el numero de procesadores virtuales es mayor que el nmero de procesadores fsicos mltiplicado por el nmero de ncleos que tiene cada procesador, entonces la mquina utiliza hyper-threading . La tecnologa de hyperthreading estar soportada si el flag ht est presente en el campo flags y si se utiliza kernel SMP. Dada la informacin contenida en /proc/cpuinfo , conteste a las siguientes preguntas:
Adems de la informacin proporcionada por /proc/cpuinfo, proporcionamos dos programas CPUs_pth y CPUs_prc, que nos van a permitir conocer (en cierto modo) la capacidad de computo del sistema (entendida como nmero total de incrementos de una variable de 64 bits) que pueden conseguir el nmero de procesos (o hilos) indicado en el nmero de segundos establecidos. Como se puede imaginar, este no es un mtodo fiable,
puesto que depende de la carga (de procesos) a la que est sometida la mquina. Si en el sistema hay otros procesos que usan intensivamente la CPU es fcil que las medidas que usted obtenga sean muy dispares. Analice el cdigo del programa CPUs_prc. Comprobar que se crean tantos procesos (mediante fork) como se indique en el primer argumento, y que cada uno de estos procesos estar en ejecucin durante un tiempo determinado por el segundo argumento. Compile y ejecute el programa, por ejemplo, con los argumentos 1 10:
alumno@maquinaLinux~/PracticasAnalisis/ModuloSinCom$ ./CPUs_prc 1 10 Total work done by 1 process in 10 seconds is = 498176612
Adems podemos utilizar el programa Times_C.c que nos va a permitir conocer el tiempo real que un proceso ha estado en ejecucin, y adems el tiempo de procesador (usuario) que ha utilizado el proceso. Como es lgico pensar, si nuestro proceso es multihilo (como es el caso en CPUs_prc.c ), y nuestro sistema es multiprocesador, podr suceder que cada uno de los hilos de ejecucin se asignen a procesadores diferentes por lo que el tiempo de procesador (usuario) ser superior al tiempo real.
alumno@maquinaLinux~/PracticasAnalisis/ModuloSinCom$ ./Times_C ./CPUs_prc 1 10 Total work done by 1 process in 10 seconds is = 4982684141 Times_C: Real:10.000" User:10.000" Syst:0.000"
El resultado muestra como durante 10 segundos, 1 proceso en ejecucin ha logrado realizar un total aproximado de 5000 millonesde sumas. Cunto cmputo pueden realizar el doble de procesos en la mitad de tiempo? El mismo o la mitad? Esto depende de si tenemos una nica CPU o tenemos ms (y obviamente de la carga del sistema).
alumno@maquinaLinux~/PracticasAnalisis/ModuloSinCom$ ./Times_C ./CPUs_prc 2 5 Total work done by 2 process in 5 seconds is = 4979025368 Times_C: Real:5.010" User:10.000" Syst:0.000"
Resulta que efectivamente se alcanza una cantidad de cmputo prcticamente idntica (del orden de los 5000 millones). De ello podramos deducir que nuestra mquina es, al menos, un biprocesador. Adems vemos como el tiempo de procesador (en modo usuario) es el doble que el tiempo real, lo cual implica que cada proceso ha estado ejecutando en un procesador diferente. Recuerde que estamos haciendo todo este tipo de pruebas sobre un sistema multiproceso y multiusuario, luego las medidas de tiempos que podamos hacer sern siempre aproximadas y prcticamente irrepetibles, al estar sometidas al indeterminismo del resto de la carga en el sistema. El programa CPUs_pth.c presenta la misma funcionalidad que el programa CPUs_prc.c, pero utilizando procesos ligeros o threads. Siguiendo el mismo procedimiento que para CPUs_prc.c, podemos determinar si nuestro sistema es multihilo o si por el contrario el soporte para los threads es mediante una biblioteca multihilo.
Ciertos programas que ya se utilizaron en el captulo de procesos nos permiten observar cmo sucede la concurrencia en o entre los procesos existentes en un sistema. Nos estamos refiriendo a: Compart_*, Concurr_*, UnSegundo.c y Ventanilla_*.
CONDICIN DE CARRERA
El acceso concurrente sin coordinacin a recursos compartidos puede NO producir los resultados esperados. En sistemas multiproceso, no es posible garantizar la velocidad relativa de los diferentes procesos en ejecucin, es decir, no vamos a conocer a priori el orden en que se ejecutarn los procesos en nuestro sistema. Este hecho provoca que un acceso no coordinado a los recursos conpartidos por los procesos pueda producir resultados diferentes a los esperados. Este fenmeno se conoce como condicin de carrera. Se ha desarrollado el programa Carreras.c que pretende demostrar la existencia de condiciones de carrera, cuando no existe coordinacin en el acceso a un recurso compartido por varios procesos. Analice el cdigo del programa Carreras.c
C-01.C-02.C-03.C-04.C-05.C-06.C-07.C-08.C-09.C-10.C-11.C-12.C-13.C-14.C-15.C-16.C-17.C-18.C-19.C-20.C-21.C-22.C-23.C-24.C-25.C-26.C-27.C-28.C-29.C-30.C-31.C-32.C-33.C-34.void shared_free(void * ptr, unsigned size) { munmap(ptr, size); } void * shared_alloc(unsigned size) { void * ptr = NULL; int fd = open("/dev/zero", O_RDWR); if (fd < 0) return NULL; ptr = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); close(fd); return ptr; } #define X(a) { (a); (a); (a); (a); (a); }
int main(void) { int i, v; volatile int * Variable = shared_alloc(sizeof(int)); for (i = 0; i < 50; i++) { Variable[0] = 0; switch(fork()) { case -1: perror(MYNAME": fork()"); exit(1); case 0: for (v=0;v<10000;v++) X(X(Variable[0]++)); exit(0); default: for (v=0;v<10000;v++) X(X(Variable[0]--)); wait(NULL); }
C-35.C-36.C-37.C-38.C-49.C-40.- }
Si se analiza el cdigo de dicho programa, se comprobar que en ningn momento se utiliza de manera explicita ningn mecanismo que permita el acceso de manera coordinada al recurso compartido. Sin embargo, el sistema operativo se encarga de garantizar que mientras un proceso est accediendo al fichero, ninguno ms pueda modificarlo. Coordinacin explcita: es aquella que realiza explcitamente el programador, utilizando los mecanismos de sincronizacin adecuados, proporcionados por el lenguaje o por el Sistema Operativo, para conseguir que no sucedan condiciones de carrera entre procesos concurrentes que est programando y que estn compartiendo recursos. En clase se ven ejemplos de la utilizacin de diversos mecanismos de sincronizacin para la implementacin de determinados modelos clsicos de programacin concurrente.
int n_datos; int buffer[BUFF_SIZE]; pthread_mutex_t mutex; pthread_cond_t no_lleno, no_vacio; void Productor(void) { int i, dato; for (i = 0; i < TOTAL_DATOS; i++) { /*Producir el dato*/ dato = i; pthread_mutex_lock(&mutex); while (n_datos == BUFF_SIZE) pthread_cond_wait(&no_lleno, &mutex); buffer[i % BUFF_SIZE] = dato; n_datos++; printf ("PRODUCTOR: %d \n", dato); pthread_cond_signal(&no_vacio); pthread_mutex_unlock(&mutex); } } void Consumidor(void) { int i, dato; for (i = 0; i < TOTAL_DATOS; i++) { pthread_mutex_lock(&mutex); while (n_datos == 0) pthread_cond_wait(&no_vacio, &mutex); dato = buffer[i % BUFF_SIZE]; n_datos--; pthread_cond_signal(&no_lleno); pthread_mutex_unlock(&mutex); /*Consumir el dato*/ printf ("CONSUMIDOR: %d fflush(stdout); } printf ("\n"); fflush(stdout); } int main(void)
\n", dato);
PC-51.- { PC-52.pthread_t th1, th2; PC-53.PC-54.pthread_mutex_init(&mutex, NULL); inicial */ PC-55.pthread_cond_init(&no_lleno, NULL); PC-56.pthread_cond_init(&no_vacio, NULL); PC-57.pthread_create(&th1, NULL, (void*)Productor, NULL); */ PC-58.pthread_create(&th2, NULL, (void*)Consumidor, NULL); PC-59.pthread_join(th1, NULL); terminacin */ PC-60.pthread_join(th2, NULL); PC-61.pthread_mutex_destroy(&mutex); */ PC-62.pthread_cond_destroy(&no_lleno); PC-63.pthread_cond_destroy(&no_vacio); PC-64.PC-65.return 0; PC-66.- }
/* Situacin
/* Arranque
/* Esperar
/* Destruir
E-13.E-14.E-15.E-16.E-17.E-18.E-19.E-20.E-21.E-22.E-23.E-24.E-25.E-26.E-27.E-28.E-29.E-30.- }
fl.l_start = 0; fl.l_len = 0; fl.l_pid = getpid(); fd = open("BD", O_RDWR); for (cnt = 0; cnt < 10; cnt++) { fl.l_type = F_WRLCK; fcntl(fd, F_SETLKW, &fl); lseek(fd, 0, SEEK_SET); read(fd, &val, sizeof(int)); val++; lseek(fd, 0, SEEK_SET); write(fd, &val, sizeof(int)); fl.l_type = F_UNLCK; fcntl(fd, F_SETLK, &fl); } return 0;
Los programas Escritor.c y Lector.c estn pensados para ser ejecutados de manera simultanea. En principio, podrn existir simultaneamente tantos procesos escritores y lectores como queramos. Ejecute la siguiente secuencia. Primero creamos el archivo BD (que estar vacio) y a continuacin lanzamos la ejecucin simultanea de 3 procesos lectores y dos procesos escritores.
alumno@maquinaLinux~/PracticasAnalisis/ModuloSinCom$ > BD alumno@maquinaLinux~/PracticasAnalisis/ModuloSinCom$ ./Lector & ./Escritor & ./Lector & ./Escritor & ./Lector ...
TS-06.TS-07.TS-08.TS-09.TS-10.TS-11.TS-12.TS-13.TS-14.TS-15.TS-16.TS-17.TS-18.TS-19.TS-20.TS-21.TS-22.TS-23.TS-24.TS-25.TS-26.TS-27.TS-28.TS-29.TS-30.TS-31.TS-32.TS-33.TS-34.TS-35.TS-36.TS-37.TS-38.TS-39.TS-40.TS-41.TS-42.TS-43.TS-44.TS-45.TS-46.TS-47.TS-48.TS-49.TS-50.TS-51.TS-52.TS-53.TS-54.TS-55.TS-56.TS-57.TS-58.TS-59.TS-60.TS-61.TS-62.TS-63.TS-64.TS-65.TS-66.TS-67.-
void Timeout_Control(int signo) { int timeout; char * text = "TIMEOUT!...Terminating now.\n"; if (!signo) { text = getenv("TIMEOUT"); if (text) { signal(SIGALRM, Timeout_Control); timeout = atoi(text); if (timeout >= 0) alarm(timeout); } } else { write(2, text, strlen(text)); exit(0); } } void SIGCHLD_Handler(int signo) { signal(SIGCHLD, SIGCHLD_Handler); if (signo == SIGCHLD) waitpid(-1, NULL, WNOHANG); } int main(int argc, char * argv[]) { int sd, cd, size, ret; char ch; short int port; struct sockaddr_in s_ain, c_ain; Timeout_Control(0); SIGCHLD_Handler(0); sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sd == -1) perror(argv[0]); if (argc > 1) else port = atoi(argv[1]); port = 7777;
bzero((char *) &s_ain, sizeof(s_ain)); s_ain.sin_family = AF_INET; s_ain.sin_addr.s_addr = INADDR_ANY; s_ain.sin_port = htons(port); /* 7 == echo port */ ret = bind(sd, (struct sockaddr *) &s_ain, sizeof(s_ain)); if (ret == -1) perror(argv[0]);
TS-68.listen(sd, 5); TS-69.while (1) TS-70.{ TS-71.size = sizeof(c_ain); TS-72.cd = accept(sd, (struct sockaddr *) &c_ain, &size); TS-73.switch (fork()) TS-74.{ TS-75.case -1: TS-76.perror("echo server"); TS-77.return 1; TS-78.case 0: TS-79.close(sd); TS-80.while (recv(cd, &ch, 1, 0) == 1) TS-81.{ TS-82.ch = toupper(ch); TS-83.send(cd, &ch, 1, 0); TS-84.} TS-85.close(cd); TS-86.return 0; TS-87.default: TS-88.close(cd); TS-89.} /* switch */ TS-90.} /* while */ TS-91.- } /* main */
En este ejemplo se utiliza el argumento 18231 para lanzar el proceso servidor, pero puede utilizar otros valores.
Utilizaremos el programa TCP_c.c como proceso cliente. Este programa puede recibir dos parmetros, uno o ninguno. Realice un par de conexiones interactivas con su servidor, desde la mquina local. Y compruebe el funcionamiento del modelo cliente-servidor.
Si se est trabajando en la mquina triqui.fi.upm.es, a travs de varios terminales putty, asegrese que en ambos terminales estn conectado al mismo nodo (triqui3 o triqui4). Recuerde que hemos arrancado el programa servidor como un demonio, esto es, en segundo plano y a la escucha en un canal de comunicacin con direccin conocida. Para evitar que se nos quede este proceso latente en el sistema (consumiendo el puerto al que est asociado) deberemos matarlo.
Ejecute el mandato fg, para traerlo a primer plano y luego ^C para matarlo.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloSinCom$ fg ./TCP_s 18231 ^C [1234] Interrupted
O bien, consulte su PID con un ps y luego lncele una seal con el mandato kill.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloSinCom$ kill -9 1234 [1234] Killed
Intentemos ahora medir las prestaciones de este tipo de comunicacin local. Cree y tenga a mano un fichero de 1 Mega byte (por ejemplo, puede utilizar el programa Robot_B que se utiliz en el mdulo de ficheros). Veremos cuanto tiempo (aproximado) tarda en trasferirse en ida y vuelta este fichero. Para medir el tiempo utilizaremos el mandato Times_C.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloSinCom$ ./Times_C ./TCP_c 127.0.0.1 18231 < 1MB.dat > /dev/null Times_C: Real:X.xxx" User:XX.xxx" Syst:XX.xxx"
Intentemos ahora medir las prestaciones de la comunicacin remota. Busque una mquina remota donde pueda compilar y ejecutar el servidor TCP (digamos que se llama triqui.fi.upm.es). Lncelo en dicha mquina remota y desde la mquina local (que debe ser otra), desde la que est realizando ests prcticas, vuelva a contactar y a medir el tiempo de transferir un megabyte.
Times_C
./TCP_c
No olvide terminar los procesos servidores que haya arrancado. De otro modo no podrn arrancar otro en el mismo puerto.