CAPíTULO 2 Parallel Virtual Machine (PVM)
Transcripción
CAPíTULO 2 Parallel Virtual Machine (PVM)
PROLOG0 El objetivo por el cual decidimos desarrollar éste proyecto, fue la necesidad de implementar las soluciones que se dan para resolver los problemas de la programación concurrente, ya que estas soluciones se quedan en un nivel de abstracción que no nos permite captar con claridad sus alcances. Es decir cuando llevamos un curso completo de sistemas operativos nos enseñan cuales son los problemas típicos y como resolverlos usando distintos algoritmos, sin embargo nunca podemos programarlos y esto debido a que no contamos con los recursos necesarios ( computadoras de procesamiento paralelo ). No obstante existe un software de dominio publico llamado PVM (Parallel Virtual Machine, Máquina Virtual Paralela) que permite a un conjunto de computadoras conectadas en red, simular una computadora de procesamiento paralelo. Esto parecería una forma de obtener un poder de computo mucho más barato, ya que con un conjunto de computadoras de un costo mucho menor (80486 con LINUX) que el de una computadora paralela se pueden realizar los mismos procesos, sin embargo existen muchos factores que se tendrían que evaluar para comparar esta ventaja aparentemente importante. Para nuestros fines lo importante era lograr la sincronización entre procesos, que cooperaran en la solución de un problema aprovechando las ventajas de las distintas arquitecturas de computadoras que conforman una red. El material de este reporte se organiza en cuatro capítulos. La cobertura de estos es secuencia1 partiendo de la noción básica de procesos, la programación concurrente y los problemas que en ella se generan, con esto se busca dar las bases para comprender las eventualidades al programar en forma concurrente, en los demás capítulos se describe que es PVM, sus características y lo más importante, como utilizarlo para solucionar los problemas de la comunicación entre procesos. En el capítulo 1 se proporciona una visión de lo que es un proceso (los estados por los que pasa y las operaciones que se realizan sobre ellos), los problemas de sincronización relativos a la ejecución concurrente, las soluciones que se han dado a estos problemas, también se trata el abrazo mortal que debido a su importancia en la programación concurrente se describe a detalle y concluye con la presentación de algunos problemas clásicos de programación. En el capitulo 2 se describe que es PVM, como se obtiene, la forma en que se instala, el procedimiento para configurarlo y cuales son los modos en los que es posible operarlo, se complementa con algunas recomendaciones para resolver problemas y un breve comentario acerca de las limitantes de su operación. En el tercer capitulo se dan los detalles de implementación con PVM. Se explican las características de la programación que emplea, se describen las herramientas de las que se sirve para funcionar adecuadamente, las bibliotecas que utiliza, la forma en que realiza la comunicación y concluye con algunas consideraciones sobre el desempeño y la red. Para concluir se ofrece en el ultimo capitulo algunos ejemplos de programación utilizando PVM, consideraciones para escribir aplicaciones, los elementos que el programador puede utilizar, la forma en que puede realizar la compilación y ejecución de sus programas y el método de depuración utilizado en PVM. Como se puede observar en este reporte se hace mucho énfasis en el manejo de PVM y la razón es que si se logra comprender la forma en que funciona y las ventajas que ofrece para los programas concurrentes resultara más sencillo programar los algoritmos que resuelven los problemas de comunicación entre procesos. González Hernández Victor Adrián López Rivera Yolanda Zarate Cano José Manuel. Verano 1998. AGRADECIMIENTOS : A M I S PADRES : P o r d a r m e l a o p o r t u n i d a d d e ser u n a p r o f e s i o n i s t a y p o r t o d o s u a p o y o y c o m p r e n s i ó n en m i s momentos difíciles. A M I S HERMANOS : Por e s t a r conmigo y s o p o r t a r m i c a r á c t e r . Por d a r v i d a a m i h o g a r y p o r ser m i s h e r m a n o s . A MARCO ANTONIO VELÁZQUEZ : P o r todo s u a p o y o m o r a l y s e n t i m e n t a l d u r a n t e el t r a n s c u r s o d e m i c a r r e r a T.Q.M. A V I C T O R GONZÁLEZ Y MANUEL ZARATE : P o r t o d o s los m o m e n t o s q u e c o m p a r t i m o s j u n t o s y p o r d a r m e l a g r a t i t u d d e ser m i s m e j o r e s a m i g o s . G r a c i a s por soportarme. A MANUEL A G U I L A R CORNEJO : P o r q u e m á s q u e ser n u e s t r o a s e s o r , e s u n g r a n amigo. YOLANDA LOPEZ R I V E R A . A g r a d e z c o a Dios p o r p e r m i t i r m e l l e g a r a e s t e momen t o . A m i s p a d r e s A n a M a r í a y Amado p o r t o d o s u c a r i ñ o , comprensión y a p o y o en t o d o m o m e n t o a l o l a r g o d e m i carrera. A m i s h e r m a n o s Ana M a r í a , A n g e l i c a , A l m a , L u i s y Ricardo por soportarme y ayudarme. A M a r í a E l e n a por l l e n a r m i c o r a z ó n y a l e n t a r m e a s e g u i r a d e l a n t e en m i c a r r e r a . A m i s a m i g o s Y o l a n d a LÓpez y M a n u e l Zarate por c o m p a r t i r conmigo s u a m i s t a d y todos estos a ñ o s d e esfuerzo conjunto. A M a n u e l A g u i l a r C o r n e j o p o r ser m i g u í a y m o d e l o a s e g u i r como p r o f e s i o n i s t a , a d e m á s d e ser u n g r a n amigo y un g r a n a p o y o p a r a nosotros tres. " G r a c i a s M a n u e l p o r ser m i a m i g o " . A t o d o s m i s p r o f e s o r e s , q u e g r a c i a s a e l l o s hoy puedo d i s f r u t a r de este momento. G O N Z A L E Z HERNANDEZ V I C T O R A D R I A N . A m i mama que m e apoyo h a s t a el f i n a l . . JOSE MANUEL ZARATE CANO .. Int roducción El procesamiento en paralelo, es el método que consiste en tener varias tareas que resuelven un problema. Desde hace varios años se ha incrementado, la aceptación y la adopción del procesamiento en paralelo, debido ha la aparición de aplicaciones de “propósito general más complejas”, lo que ha provocado una demanda por mayor capacidad de ejecución, bajos costos y una productividad sosten ida. Los procesadores masivamente paralelos (MPPs) son ahora parte de las computadoras más poderosas en el mundo. Estas máquinas combinan desde algunos cientos hasta varios miles de CPUs en un gabinete conectado a cientos de Gigabytes de memoria. Los MPPs ofrecen un enorme poder computacional y son usados para resolver problemas computacionales. El segundo mayor desarrollo que afecta la solución de problemas científicos es el computo distribuido. El computo distribuido es un proceso donde un conjunto de computadoras conectadas en red, son usadas colectivamente para resolver problemas. La combinación de recursos computacionales puede exceder el poder de una sola computadora con gran poder de computo. En algunos casos, varios MPPs han sido combinados utilizando computo distribuido produciendo un poder computacional sin comparación. El factor más importante en el computo distribuido es el costo. Los grandes MPPs normalmente cuestan millones de dólares . En contraste los usuarios tienen un costo mucho menor en la ejecución de sus problemas con un conjunto de maquinas locales. Esto no es común para los usuarios de computo distribuido que utilizan las enormes MPP con su gran poder de computo ya que ellos aseguran que con estas máquinas que obtendrán los resultados en un tiempo mucho menor que el usuario que esta usando sus computadoras locales. Varios paradigmas han sido probados incluyendo memoria compartida, compiladores paralelos, y paso de mensajes. El modelo de paso de mensajes ha llegado a ser el paradigma de opción, desde la perspectiva del número y variedad de multiprocesadores que soporta, así como en términos de las aplicaciones, lenguajes, y sistemas de software que este usa. El sistema Parallel Virtual Machine (PVM) describe el uso del modelo de paso de mensajes para permitir a los programadores explotar el computo distribuido de acuerdo con los de tipos de computadoras, incluidas las MPPs que soporta. Un concepto clave en PVM es que éste crea una colección de computadoras que simula una gran máquina virtual. Conten ido Prólogo Agradecimientos Introducción ---, Capitulo 1. .-I Procesos 1.INociones Básicas De Proceso 1.I. 1 Estados De Un Proceso 1.I .2 El Bloque De Control De Proceso .3 Operaciones Sobre Los Procesos 1.I 1.2 Procesos En Sistemas Distribuidos 1.2.1 Introducción A Los Sistemas Distribuidos 1.2.2 Comunicación En Sistemas Distribuidos 1.2.3 Sincronización En Sistemas Distribuidos 1.2.3.1 Exclusión Mutua 1.2.4 Paso De Mensajes 1.3 Abrazos Mortales 1.3.1 Definición 1.3.2 Modelación De Un Abrazo Mortal 1.3.3 Características Del Abrazo Mortal 1.3.4 Prevención Del Abrazo Mortal 1.3.5 Evitando Abrazos Mortales 1.3.6 Recuperación Del Abrazo Mortal 1.4 Problemas De La Programación Concurrente _'' Capitulo 2. Parallel Virtual Machine (PVM). 2.1 Introducción 2.2 Obteniendo E Instalando Pvm 2.3 Configuración De La Maquina Virtual 2.3.1 La Consola Pvm 2.3.2 El Archivo Hostfile 2.3.3 Variables De Ambiente 2.3.4 Problemas AI Iniciar Pvm 2.4 Limitaciones De Recursos 2.4.1 En El Demonio Pvm 2.4.2 En La Tarea 2.5 Detalles De Implantación 2.5.1 Introducción 2.5.2 Características De Pvm 2.5.2.1 ldentificador De Tareas 2.5.2.2 Tolerancia A Fallas 2 5 2 . 3 Señalización 2.5.2.4 Comunicación 1 1 2 2 3 3 4 5 6 10 12 12 13 13 13 14 15 16 21 22 24 24 26 29 30 31 31 31 32 32 32 32 34 35 35 2.6 El Demonio Pvm 2.6.1 Inicio De Pvmd 2.6.2 Tabla De Hosts 2.6.3 Tabla De Tareas 2.6.4 Contexto De Espera 2.6.5 Detección Y Recuperación De Fallas 2.7 La Biblioteca De Programación 2.8 Comunicación 2.8.1 Comunicación Pvmd-Pvmd 2.8.2 Comunicación Tarea - Pvmd 2.8.3 Comunicación Pvmd - Tarea 2.8.4 Mensajes En El Pvmd 2.8.5 Codificadores De Mensajes 2.8.6 Funciones Que Manejan Empaquetamiento 2.8.7 Mensajes De Control 2.8.8 Ruteo Directo De Mensajes 2.8.9 Envío Múltiple 2.9 Consideraciones Generales De Desempeño 2.1O Consideraciones Particulares De La Red 2.1 1 Balance De Carga Capitulo 3. Aplicaciones bajo PVM. 3.1 Ejemplos De Programación De Pvm En Lenguaje C 3.2 Escribiendo Aplicaciones 3.3 lnterfaz De Usuario 3.3.1 Control De Procesos 3.3.2 Información 3.3.3 Configuración Dinámica 3.3.4 Señalización 3.3.5 Opciones De Colocación Y Obtención 3.4 Paso De Mensajes 3.4.1 Buffers De Mensajes 3.4.2 Empaquetamiento De Datos 3.4.3 Envío Y Recepción De Datos 3.4.4 Desempaquetamiento De Datos 3.5 Compilando Aplicaciones Pvm 3.6 Métodos De Depuración 3.7 Ejecutando Aplicaciones Pvm Capitulo 4 Solución al problema de la cena de los filósofos 4.1 Planteamiento 4.2 Implantación En Un Sistema Distribuido Usando Pvm Bibliografía 35 36 38 39 39 40 40 41 41 44 44 45 45 46 46 46 47 47 48 49 51 56 57 57 58 59 60 60 61 62 64 64 66 67 67 70 71 73 86 CAPíTULO I Procesos 1.1 Nociones básicas de proceso El término proceso fue utilizado por primera vez por los diseñadores del sistema Mulfics’ en los años sesenta. Desde entonces, el término proceso, utilizado a veces como sinónimo de tarea, ha tenido muchas definiciones ( H.M. Deitel,l993). A continuación se presentan algunas: 0 0 0 0 0 0 0 un programa en ejecución. una actividad asíncrona. el espíritu animado de un procedimiento el “centro de control de un procedimiento en ejecución. lo que se manifiesta por la existencia de un bloque de control del proceso ” en el sistema operativo. la entidad a la que se asignan los procesadores. la unidad “despachable ‘I ‘I ‘I ‘I ‘I. Aunque se han dado muchas otras definiciones, no hay una descripción universalmente aceptada, pero el concepto de “programa en ejecución“ parece ser el que se utiliza con mas frecuencia. Un programa es una entidad inanimada; sólo cuando un procesador le “infunde vidaJJ se convierte en la entidad “activa” que se denomina proceso. 1.I .I Estados de un proceso. Un proceso pasa por una serie de estados discretos. Varios eventos pueden ocasionar que un proceso cambie de estado. Se dice que un proceso se está ejecutando (es decir, se encuentra en estado de ejecución) si tiene asignada la CPU. Se dice que un proceso está listo (es decir, se encuentra en estado listo) si pudiera utilizar una CPU en caso de haber una disponible. Un proceso esta bloqueado (es decir, se encuentra en estado bloqueado) si está esperando que suceda algún evento (como la terminación de una €/S, por ejemplo) antes de poder proseguir su ejecución. ’ Sistema operativo de tiempo compartido, escrito principalmenteen un lenguaje de alto nivel ,desarrolladopor un grupo de instituciones en MIT ( Massachusetts Institute of Technology ). 2 Procesos Cuando se admite una tarea en el sistema, se crea el proceso correspondiente y se inserta normalmente al final de la cola de procesos listos. El proceso se desplaza poco a poco hacia el frente de la cola de procesos, a medida que los procesos que se encuentran antes que él completan su turno de uso de la CPU. Cuando el proceso llega al principio de la cola, se le asigna tiempo de CPU y entonces se dice que hay una transición de estado, del estado listo al estado de ejecución. 1.1.2 El Bloque de Control de Proceso La forma en que se manifiesta un proceso en un sistema operativo, es mediante un bloque de control de proceso (process control block, PCB) ó un descriptor de proceso. El PCB es una estructura de datos que contiene información importante acerca de un proceso, tal como: 0 0 0 0 0 0 0 0 0 el estado actual del proceso un identificador único del proceso un apuntador hacia el padre del proceso (es decir, hacia el proceso que lo creó) apuntadores a los hijos del proceso (es decir, a los procesos creados por él) la prioridad del proceso apuntadores hacia las zonas de memoria del proceso apuntadores a los recursos asignados al proceso un área para salvaguarda de los registros el procesador en que se está ejecutando el proceso (en un sistema de procesadores mÚIt¡ples) El PCB es un almacén central de información que permite al sistema operativo localizar toda la información importante acerca de un proceso. I.I .3 Operaciones sobre los Procesos Los sistemas que administran procesos deben ser capaces de realizar ciertas operaciones sobre procesos y con ellos. Tales operaciones incluyen: crear un proceso destruir un proceso suspender un proceso reanudar un proceso cambiar la prioridad de un proceso bloquear un proceso despertar un proceso despachar un proceso permitir que un proceso se comunique con otro (esto se denomina comunicación entre procesos) Procesos en Sistemas Distribuidos 3 I .2 Crear un proceso implica muchas operaciones tales como: 0 0 0 dar nombre al proceso insertarlo en la lista de procesos conocidos del sistema ( o tabla de procesos) determinar la prioridad inicial del proceso crear el bloque de control de proceso asignar los recursos iniciales al proceso, etc. Un proceso puede crear un nuevo proceso. Si lo hace, el proceso creador se denomina proceso padre, y el proceso creado, proceso hijo. Sólo se necesita un padre para crear un hijo. Destruir un proceso implica eliminarlo del sistema. Se le elimina de las tablas o listas del sistema, sus recursos se devuelven al sistema y su bloque de control de proceso se borra (es decir, el espacio de memoria ocupado por su PCB se devuelve al espacio de memoria disponible). Un proceso suspendido no puede proseguir sino hasta que lo reanuda otro proceso, el sistema efectúa las suspensiones para eliminar temporalmente ciertos procesos y así reducir la carga del sistema durante una situación de carga máxima. Reanudar o activar un proceso implica reiniciarlo a partir del punto en el que se suspendió. 1.2 Procesos en Sistemas Distribuidos I.2A Introducción a los Sistemas Distribuidos En el pasado, la mayoría de las computadoras trabajaba en forma aislada y la mayoría de los Sistemas Operativos eran diseñados para su ejecución en un solo procesador. Sin embargo, esta situación ha evolucionado gracias a dos avances tecnológicos. El primero fue el desarrollo de poderosos microprocesadores y el segundo fue la invención de redes de área local de alta velocidad (Local Area Network, LAN). Las redes de área local permitieron conectar docenas e incluso cientos de máquinas de tal forma que se pudiera transferir pequeñas cantidades de información entre ellas durante un milisegundo o un tiempo parecido(Tanembaum, 1996). El resultado de estos dos avances tecnológicos es que hoy en día es posible reunir sistemas de cómputo compuestos por un gran número de CPU’s, conectados mediante una red de alta velocidad. Éstos reciben el nombre genérico de Sistemas Distribuidos, en contraste con los Sistemas Centralizados,que constan de un único CPU. 4 Procesos “Un Sistema Distribuido es aquel que se ejecuta en una colección de máquinas sin memoria compartida, pero que aparece ante sus usuarios como una sola computadora” (Tanenbaum, 1996) Algunas de las características de un Sistema Distribuido son: Un mecanismo de comunicación global entre los procesos, de tal forma que todo proceso pueda comunicarse con cualquier otro. No es necesario que existan distintos mecanismos en cada máquina para la comunicación local ó remota. Debe existir un esquema global de protección. La administración de procesos debe ser la misma en todas partes. Los Sistemas Distribuidos deben diseñarse con cuidado, teniendo en cuenta los siguientes aspectos : La Transparencia : Ocultar la distribución a los usuarios e incluso de los programas de aplicación. La Flexibilidad : El diseño se debe hacer con la idea de facilitar los cambios futuros. La Confiabilidad : Disponibilidad de la información, la seguridad de la misma y tolerancia a fallos del sistema. El Desempeño : El tiempo de respuesta y el rendimiento. La Escalabilidad : Proporciona soporte al crecimiento del sistema. I.2.2 Comunicación en Sistemas Distribuidos La diferencia más importante entre un sistema distribuido y un sistema de un Único procesador es la comunicación entre procesos. En un sistema con un solo procesador, la mayor parte de la comunicación entre procesos supone de manera implícita la existencia de memoria compartida. En un sistema distribuido, no existe tal memoria compartida. Debido a la ausencia de memoria compartida, toda la comunicación en los Sistemas Distribuidos se basa en la transferencia de mensajes. Cuando el proceso A quiere comunicarse con el proceso 9, construye primero un mensaje en su propio espacio de direcciones, entonces ejecuta una llamada al sistema para que el Sistema Operativo busque el mensaje y lo envíe a través de la red hacia B. Para evitar el caos, A y B deben coincidir en el significado de los bits que se envíen. Para facilitar el trabajo con los distintos aspectos correspondientes a la comunicación, la Organización Internacional de Estándares (International Standard Organization, ISO), ha desarrollado un modelo de referencia que identifica en forma clara los distintos niveles de comunicación, les da nombres estandarizados y señala cuál nivel debe realízar cuál trabajo. Este modelo se llama el modelo de referencia para interconexión de sistemas abierfos (Day y Zimmerman, 1983), lo cual se abrevia por lo general con IS0 OSl(0pen System Interconection). Un sistema abierto es aquel preparado para comunicarse con cualquier sistema abierto mediante reglas estándar que gobiernan el formato, contenido y significado de los mensajes enviados y recibidos (Tanembaum, 1992). Estas reglas se formalizan en lo que se llama protocolos. I .2 Procesos en Sistemas Distribuidos 5 El Protocolo de Control de Transmisión (Transmition Control Protocol, TCP) y el Protocolo Internet (Internet Protocol, IP) proporcionan las reglas para la comunicación. Contienen los detalles referentes a los formatos de los mensajes, describen como responde una computadora cuando llega un mensaje y especifican de qué manera una computadora maneja un error u otras condiciones anormales. Un aspecto importante es que permite reflexionar sobre la comunicación por computadora de manera independiente de cualquier plataforma de red. Un protocolo de comunicaciones permite especificar o entender la comunicación de datos sin depender de un conocimiento detallado de un hardware de red. El TCP define un servicio clave proporcionado para una red de redes llamado entrega de flujo confiable. El TCP proporciona una conexión del tipo full duplex (en ambas direcciones), lo que les permite intercambiar grandes volúmenes de datos de manera eficaz. Debido a que se hacen pocas suposiciones sobre el sistema de entrega subyacente, el TCP es lo suficientemente flexible como para operar sobre una gran variedad de sistemas de entrega. Ya que proporciona un control de flujo, el TCP permite que el sistema cuente con una amplia variedad de velocidades para la comunicación. El Protocolo de Datagrama de Usuario (User Datagram Protocol, UDP) proporciona un servicio de entrega sin conexión y no confiable, utilizando el IP para transportar mensajes entre máquinas y agrega la capacidad para distinguir entre varios destinos dentro de un mismo host. El UDP es un protocolo sencillo en el sentido de que no aumenta de manera significativa la semántica del IP. Sólo proporciona a los programas de aplicación la capacidad para comunicarse, mediante el uso del servicio de entrega de paquetes, sin conexión y no confiable. Por lo tanto, los mensajes UDP se pueden perder, duplicar, retrasar o entregar en desorden; el programa de aplicación que utiliza el UDP debe resolver éstos problemas. Muchos programas que emplean el UDP no funcionan correctamente en una red de redes debido a que no manejan éstas condiciones. (Comer, 1995). I .2.3 Sincronización en Sistemas Distribuidos Aunque la comunicación es importante, no es todo lo que hay que considerar. Algo muy relacionado con esto es la forma en que los procesos cooperan y se sincronizan entre sí. Por ejemplo, la forma de implantar las regiones críticas o asignar recursos en un Sistema Distribuido. En los sistemas que sólo cuentan con un CPU, los problemas relativos a las regiones críticas, exclusión mutua y la sincronización se resuelven en general mediante métodos tales como los semáforos y los monitores. Estos métodos no son adecuados para su uso en los Sistemas Distribuidos, puesto que siempre se basan en la existencia de la memoria compartida. Por ejemplo, dos procesos que iteractuan mediante un semáforo deben poder tener acceso a éste. Si se ejecutan en la misma máquina, pueden compartir el semáforo almacenado en el núcleo y realizar llamadas al sistema para tener acceso a él. Sin embargo, si se ejecutan en máquinas distintas, este método ya no funciona, por lo que se necesitan otras técnicas. Incluso las cuestiones más sencillas, como el hecho de determinar si el evento A ocurrió antes o después del evento B requieren una cuidadosa reflexión. 6 Procesos 1.2.3.1 Exclusión Mutua Los procesos que trabajan juntos comparten con frecuencia un espacio común para almacenamiento, en el que cada uno puede leer o escribir. El espacio compartido puede estar en la memoria principal o ser un archivo compartido. En el caso de los Sistemas Distribuidos no existe tal memoria compartida, por lo que los problemas se reducen a compartir otros recursos (impresoras, buffers de comunicación, archivos, etc.). Las situaciones en las que dos o más procesos leen o escriben en ciertos datos compartidos y el resultado final depende de quién ejecuta qué y en qué momento, reciben el nombre de condiciones de competencia. La clave para evitar las condiciones de competencia relacionadas con la memoria compartida, archivos compartidos y cualquier otra cosa compartida, es determinar una forma de prohibir que más de un proceso lea o escriba en los datos compartidos a la vez. En otras palabras lo que necesitamos es la exclusión mutua (una forma de garantizar que si un proceso utiliza una variable o archivo compartidos, los demás procesos no puedan utilizarlos). Sin embargo, en algunas ocasiones un proceso puede tener acceso a los recursos compartidos o realizar labores críticas que puedan llevar a conflictos. Esa parte del programa, en la que se tiene acceso a los recursos compartidos se llama la región crítica. Si podemos arreglar las cosas de forma que no ocurra que dos procesos están al mismo tiempo en su región crítica, podremos evitar las condiciones de competencia. Aunque esta condición evita los conflictos, no es suficiente para que los procesos paralelos cooperen en forma correcta y usen de modo eficaz los datos compartidos. Necesitamos cuatro condiciones para poder obtener una buena solución: 1. Dos procesos no deben encontrarse al mismo tiempo dentro de sus secciones críticas. 2. No se deben hacer hipótesis sobre la velocidad o el número de CPU. 3. Ninguno de los procesos que estén en ejecución fuera de su sección crítica puede bloquear a otros procesos. 4. Ningún proceso debe esperar eternamente para entrar a su sección crítica. Los sistemas con varios procesos se programan comunmente mediante las regiones críticas. Cuando un proceso debe leer o actualizar ciertas estructuras de datos compartidas, primero entra a una región crítica para lograr la exclusión mutua y garantizar que ningún otro proceso utilice las estructuras de datos al mismo tiempo. Analizaremos ahora algunos ejemplos de implantación de las regiones críticas y la exclusión mutua en los Sistemas Distribuidos. 1.2 Procesos en Sistemas Distribuidos 7 Algoritmo Centralizado. La forma más directa de lograr la exclusión mutua en un Sistema Distribuido es similar a la forma en que se lleva a cabo en un sistema con un procesador. Se elige un proceso como coordinador. Siempre que un proceso desea entrar a una región crítica, envía un mensaje de solicitud al coordinador, donde indica la región crítica a la que desea entrar y pide permiso. Si ningún otro proceso está por el momento en esa región critica, el coordinador envía una respuesta otorgando el permiso. Cuando llega la respuesta, el proceso solicitante entra a la región crítica. Supongamos ahora que otro proceso, pide permiso para entrar a la misma región crítica. El coordinador sabe que un proceso distinto ya se encuentra en esta región, por lo que no puede otorgar el permiso. El método exacto utilizado para negar el permiso depende del sistema. El coordinador sólo se abstiene de responder, con lo cuál se bloquea el proceso que espera una respuesta. Otra alternativa consiste en enviar una respuesta que diga “permiso denegado”. Cuando el proceso sale de la región crítica, envía un mensaje al coordinador para liberar su acceso exclusivo. El coordinador extrae el primer elemento de la fila de solicitudes diferidas y envía a ese proceso un mensaje otorgando el permiso. Si el proceso está bloqueado, elimina el bloqueo y entra a la región crítica. Si ya se envió un mensaje explícito negando el permiso, entonces el proceso debe hacer un muestre0 del tráfico recibido o bloquearse posteriormente. De cualquier forma, cuando ve el permiso, puede entrar a la región crítica. El método centralizado tiene limitaciones. El coordinador es un punto de falla, por lo que si se descompone, todo el sistema se puede venir abajo. Si los procesos se bloquean por lo general después de realizar una solicitud, no pueden distinguir entre un coordinador muerto de un ”permiso denegado”, puesto en ambos casos no reciben respuesta. Además en un sistema de gran tamaño, un coordinador puede convertirse en un cuello de botella (congestionamiento de peticiones) para el desempeño. Algoritmo Distribuido. Con frecuencia, el hecho de tener un punto de falla es inaceptable, por lo cual algunos investigadores (Lamport, 1978)(Ricart y Agrawala, 1981) buscaron algoritmos distribuidos de exclusión mutua. El algoritmo resultante funciona como sigue: cuando un proceso desea entrar a la región crítica, construye un mensaje con el nombre de ésta, su número de proceso y la hora actual. Entonces envía el mensaje a todos los demás procesos y de manera conceptual a él mismo. Se supone que el envío de mensajes es confiable; es decir, cada mensaje tiene un reconocimiento. Si se dispone de una comunicación en grupo confiable, entonces ésta se puede utilizar en vez del envío de mensajes individuales. Cuando un proceso recibe un mensaje de solicitud de otro proceso, la acción que realice depende de su estado con respecto de la región crítica nombrada en el mensaje. 8 Procesos Hay que distinguir tres casos: 1. Si el receptor no está en la región crítica y no desea entrar en ella, envía de regreso un mensaje OK al emisor. 2. Si el receptor ya está en la región crítica, no responde, sino forma la solicitud en una fila. 3. Si el receptor desea entrar a la región crítica, pero no lo ha logrado todavía, compara la marca de tiempo en el mensaje recibido con la marca contenida en el mensaje que envió a cada uno. La menor de las marcas gana. Si el mensaje recibido es menor, el receptor envía de regreso un mensaje OK. Si su propio mensaje tiene una marca menor, el receptor forma la solicitud en una fila y no envía nada. Después de enviar las solicitudes que piden permiso para entrar a una región crítica, un procesador espera hasta que alguien más obtiene el permiso. Tan pronto llegan todos los permisos, puede entrar a la región crítica. Cuando sale de ella, envía mensajes OK a todos los procesos en su fila y elimina a todos los elementos de la fila. Como en el caso del algoritmo centralizado, la exclusión mutua queda garantizada sin bloqueo ni inanición. El número de mensajes necesarios por entrada es ahora de 2(n-1), en donde n es el número total de procesos en el sistema. Lo mejor es que no existe un punto de falla. Por desgracia, el único punto de falla es reemplazado por n puntos de falla. Si cualquier proceso falla, no podrá responder a las solicitudes. Este silencio será interpretado (incorrectamente) como negación del permiso, con lo que se bloquearán los intentos de los demás procesos por entrar a todas las regiones críticas. Puesto que la probabilidad de que uno de los n procesos falle es n veces mayor que la probabilidad de que falle un coordinador, se ha buscado reemplazar un algoritmo pobre con uno que es n veces peor y que requiere mayor tráfico en la red para funcionar. El algoritmo se puede mejorar de la siguiente manera: AI llegar una solicitud, el receptor siempre envía una respuesta, otorgando o negando el permiso. Siempre que pierda una solicitud o una respuesta, el emisor espera y sigue intentando hasta que le regresan una respuesta o el emisor concluye que el destino esta muerto. Después de negar una solicitud, el emisor debe bloquearse en espera de un mensaje de OK posterior. Otro problema con este algoritmo es que se debe utilizar una primitiva de comunicación en grupo; o bien, cada proceso debe mantener por sí mismo la lista de membresía del grupo, donde se incluyan los procesos que ingresan al grupo, los que salen de él y los que fallan. El método funciona mejor con grupos pequeños de procesos que nunca cambian sus membresías de grupo. En el algoritmo distribuido, todos los procesos participan en todas las decisiones referentes a la entrada en las regiones críticas. Si un proceso no puede manejar esta tarea, es poco probable que si todos los procesos la realizan sirva de algo. Sin embargo este algoritmo es más lento, más complejo, más caro y menos robusto que el algoritmo centralizado original, pero se mostró la posibilidad de un algoritmo distribuido, situación que no era obvia en un principio. 1.2 Procesos en Sistemas Distribuidos 9 Algoritmo de anillo de fichas. Un método completamente distinto para lograr la exclusión mutua en un sistema distribuido es el que se logra con el algoritmo de anillo de fichas el cual consiste de una red basada en un bus, en ésta red los procesos no tienen un orden inherente. En software, se construye un anillo lógico y a cada proceso se le asigna una posición en el anillo. Las posiciones en el anillo se pueden asignar en el orden numérico de las direcciones de la red o mediante algún otro medio. No importa como sea el orden, lo importante es que cada proceso sepa quien es el siguiente en la fila despues de él. AI inicializar el anillo, se le da al proceso O una ficha, la cual circula en todo el anillo. Se transfiere del proceso K al proceso K+1 (módulo el tamaño del anillo) en mensajes puntuales. Cuando un proceso obtiene la ficha de su vecino, verifica si intenta entrar a una región crítica. En ese caso, el proceso entra a la región, hace todo el trabajo necesario y sale de la región. Después de salir, pasa la ficha a lo largo del anillo. No se permite entrar a una segunda región crítica con la misma ficha. Si un proceso recibe la ficha de su vecino y no está interesado en entrar a una región crítica, sólo la vuelve a pasar. En consecuencia, cuando ninguno de los procesos desea entrar a una región crítica, la ficha sólo circula a gran velocidad en el anillo. Como es usual también este algoritmo tiene problemas. Si la ficha llega a perderse, debe ser regenerada. De hecho, es difícil detectar su pérdida, puesto que la cantidad de tiempo entre las apariciones sucesivas de la ficha en la red no está acotada. El hecho de que la ficha no se haya observado durante una hora no significa su pérdida; tal vez alguien la esté utilizando. El algoritmo también tiene problemas si falla un proceso, pero la recuperación es más sencilla que en los demás casos. Si pedimos un reconocimiento a cada proceso que reciba la ficha, entonces se detectará un proceso muerto si su vecino intenta darle la ficha y fracasa en el intento. En este momento, el proceso muerto se puede eliminar del grupo y el poseedor de la ficha puede puede enviar ésta por encima de la cabeza del proceso muerto al siguiente miembro, o al siguiente miembro despues de éste, en caso necesario. Por supuesto esto requiere que todos mantengan la configuración actual del anillo. Algoritmos de elección. Muchos de los algoritmos distribuidos necesitan que un proceso actúe como coordinador, iniciador, secuenciador o que desempeñe de cierta forma algún papel esencial. En general, no importa cuál de los procesos asuma esta responsabilidad especial, pero uno de ellos debe hacerlo. Si todos los procesos son idénticos, sin caracteristica alguna que los distinga, no existe forma de elegir uno de ellos como especial. En consecuencia, supondremos que cada proceso tiene un número único; Por ejemplo, su dirección en la red (suponiendo que existe un proceso por cada máquina). En general, los algoritmos de elección intentan localizar al proceso con el máximo número de proceso y designarlo como coordinador. Los algoritmos difieren en la forma en que lo llevan a cabo. 10 Procesos Además, también supondremos que cada proceso sabe el número de proceso de todos los demás. Lo que el proceso desconoce es si los procesos están activos o inactivos. El objetivo de un algoritmo de elección es garantizar que al iniciar una elección, ésta concluya con el acuerdo de todos los procesos con respecto a la identidad del nuevo coordinador. 1.2.4 Paso de Mensajes Las regiones críticas y los monitores son extrapolaciones de los semáforos. Todos ellos proveen estructuras de control para acceder a variables comunes a todos los procesos. Los monitores son versiones centralizadas. Cada proceso accede a variables comunes y se asegura de que lo haga de manera Única. Este requerimiento resulta natural en una computadora o en un sistema con memoria común. ¿Qué pasa en un sistema distribuido? En él las computadoras están físicamente dispersas y por lo tanto la memoria de cada nodo no tiene acceso sino muy limitado a la memoria de otro nodo. Esto dificulta mucho la implantación de monitores, regiones críticas y semáforos. En un sistema distribuido tenemos conectadas muchas computadoras independientes que mandan y reciben mensajes. Estos mensajes son enviados y recibidos sin la menor sincronización. Lo que necesitamos es un protocolo que nos permita la sincronización de estos mensajes. Una extrapolación de semáforos permite un enfoque llamado paso de mensajes (message passing), el cual puede ser visto como el extender los semáforos para implantar el mandar datos así como la sincronización. Cuando usamos el paso de mensajes para sincronización y comunicación, los procesos envían y reciben mensajes en vez de leer y escribir en variables compartidas. La comunicación es lograda por que un proceso al recibir un mensaje, recibe también valores del proceso que se lo envió. La sincronización se obtiene ya que el mensaje es recibido después de haber sido enviado restringiendo la ocurrencia de estos dos sucesos. Un mensaje es enviado al ejecutar: Envía lista-expresiones A Destino El mensaje contiene los valores de las expresiones en lista-expresiones en el momento que se ejecuta. Destino da al programador control sobre donde va el mensaje, y de aquí, que proceso puede recibir el mensaje. Un mensaje es recibido al hacer: Recibe lista-variables De Fuente Donde lista-variables es una lista de variables a donde irán a dar la lista-expresiones vista anteriormente. La fuente permite decidir de donde será recibido el mensaje así como que proceso generó la información solicitada. La recepción del mensaje efectúa la asignación de valores a las distintas variables y posteriormente destruye el mensaje permitiendo liberar canales para la recepción de otro mensaje. El diseño de primitivas para Procesos en Sistemas Distribuidos 11 1.2 paso de mensajes conlleva los problemas de la definición de la semántica de las primitivas ¿Cómo se definen fuente y destino?. Y también el problema de los protocolos de sincronización. Existen varias soluciones para estos problemas las cuales veremos a continuación.Tomados juntos Fuente y Destino definen un canal de comunicación. Necesitamos el darle algún nombre a estos canales para poder saber de donde viene un mensaje y a donde van. Se han dado varias soluciones. La mas sencilla es el nombrar a los canales de comunicación del mismo nombre que los procesos que los utilizan. A esta estrategia se le llama Nombramiento Directo (Direct Naming ). De tal manera que: Envía ele-prod A Consumidor envía un mensaje que solo puede ser recibido por el proceso consumidor. De manera análoga: Recibe ele-prod De Productor permite recibir un mensaje enviado por el proceso productor. El nombramiento directo es fácil de implementar y de usar. Hace posible a un proceso controlar el tiempo en el que recibe un mensaje de otro proceso. La solución es muy natural y sencilla de programar. Este modelo permite el encadenamiento de procesos, llamado también pipeline (tubería). Se tienen a varios procesos concurrentes en los que los datos de salida sirven de datos de entrada a otro. Se le llama pipeline por que los datos fluyen como el agua de una tubería. ¿Qué sucede si tenemos a más de un proceso que produce cero más de uno que consume?. Tendríamos que enviar desde cada proceso productor o recibirla desde cada proceso consumidor. Pero sucede que el nombramiento directo solo nos permite recibir mensajes de un solo proceso. Supongamos que tenemos un manejador de impresora que recibe en forma de mensaje las peticiones de impresión. El manejador debería ser capaz de recibir mensajes de cualquier proceso. Por lo que no podríamos implantarlo usando nombramiento directo. Pensemos en una tienda ahí llega cualquier cliente, algún proceso, el cual es atendido por un servidor, un proceso como el manejador de la impresora. A este esquema se le denomina cliente - servidor. Para el cual no sirve el enfoque de nombramiento directo. Podemos pensar que cada cliente deja una nota donde especifica el trabajo que desea que se le efectúe. Por otro lado habrá un server que tomará las notas y las atenderá en el orden en que llegaron. Definimos entonces un buzón (mailbox) donde cada cliente deja su nota al server. Por desgracia esto es bastante costoso de implantar si no se tiene una red especial de comunicaciones. Cuando un mensaje es enviado, tiene que esperar a que sea efectuado un recibe por parte del buzón. Después, se tiene que dar a conocer a todos los procesos que un nuevo mensaje ha llegado al buzón y una vez que es tomado por alguien se tiene que notificar también. Un tipo especial de buzones lo forman los denominados Puertos (Ports) en los cuales sólo un proceso recibirá los mensajes puestos en él. Existe una implantación de puertos donde al ser enviado un mensaje sólo un proceso puede ser despertado por este hecho, pero cualquier otro proceso puede acceder al puerto. Se asume que cada proceso verá si 12 Procesos existe nueva información dentro del puerto y también cada proceso se hace cargo de tomar sólo los mensajes que le corresponden. La implantación de los puertos no es tan costosa como la del buzón, Esta también es flexible ya que se tiene un puerto asociado con cada proceso así como puertos del sistema bien definidos donde están los mensajes disponibles para todos los procesos. 1.3. Abrazos Mortales I.3.1 Definición En un ambiente de multiprogramación, muchos procesos compiten por un número determinado de recursos. Un proceso pide los recursos, y si no todos están disponibles en ese momento, entonces debe esperar. Puede suceder que el proceso se quede esperando para siempre ya que los recursos que se necesitan fueron tomados primero por otros procesos que también están esperando. Si esto se da entre los demás procesos ninguno de los cuales puede proseguir por estar esperando los recursos que esta esperando otro que también esta esperando, tenemos un abrazo mortal. Esta sifuación delicada puede ocasionar la perdida y debe ser evitada en lo posible o manejada adecuadamente una vez que aparezca. 1.3.2 Modelación de un Abrazo Mortal. Supongamos que tenemos un sistema el cual consiste de un número finito de recursos que se distribuyen entre los diferentes procesos que se ejecutan. Los recursos se pueden dividir en diferentes tipos, cada uno con un cierto número de instancias del mismo. El CPU, la memoria y los archivos son algunos ejemplos de recursos. Si hay dos CPU en el sistema entonces el recurso tipo CPU tiene dos instancias. De manera análoga, el recurso impresora puede tener cinco instancias. Un proceso debe pedir un recurso antes de utilizarlo y soltarlo una vez usado. Uno puede pedir tantos recursos como necesite. Es claro que este número no debe exceder el número disponible en el sistema. En condiciones normales, el proceso da los siguientes pasos para utilizar un recurso: 1. Petición. Si el recurso no se puede conseguir inmediatamente (si, por ejemplo, el recurso lo esta utilizando otro proceso), entonces el proceso debe esperar hasta que le sea otorgado. 2. Uso. El proceso utiliza el recurso. 3. Liberación. El proceso libera el recurso para que este pueda ser utilizado por otros procesos. La petición y liberación de recursos varían de acuerdo al tipo de recursos. Algunas se harán mediante el uso de llamadas al sistema, otras mediante el uso de instrucciones wait y signal (Francisco Manuel Márquez, 1994). Una tabla en el sistema lleva el registro de la asignación que se la ha dado a los recursos. Se manejan colas para los distintos tipos de recursos, de tal suerte que, si un proceso llega a pedir un recurso que esta siendo utilizado por alguien más se agrega a la cola. Abrazos Mortales 13 1.3 1.3.3. Características del Abrazo mortal Para el estudio de los abrazos mortales es necesario estudiar primero sus características para poder definir estrategias de prevención y manejo. Lo primero es saber bajo que condiciones se dan. Un abrazo mortal se da si y solo si las siguientes condiciones se dan en el sistema. 0 0 Exclusión Mutua. AI menos uno de los recursos que esta involucrado no es compatible; es decir, un y solo un proceso puede usar al recurso a la vez. Si alguien más lo pide, el proceso que hizo la petición espera hasta que ha sido Iiberado. Condición de Espera. Debe existir un proceso que tiene en posesión al menos un recurso y esta esperando recursos adicionales que están en posesión de otros procesos. No Apropiación. Los recursos no pueden ser apropiados; esto es, el recurso solo puede ser liberado, de manera voluntaria por el proceso que lo esta utilizando, una vez terminado su uso. Cola Circular. Debe existir un conjunto de procesos esperando {Po,P, P,} tal que Poesta esperando un recurso agarrado por Pi, P, esta esperando uno agarrado por P2 ........ Pn.f esta esperando un recurso agarrado por P, y Poesta esperando un recurso agarrado por Po. Estas condiciones son necesarias y suficientes. No se necesita que haya otras condiciones y faltando alguna de ellas no se dará el abrazo mortal. Utilizar este hecho permite mucho del análisis de técnicas de prevención y manejo. 1.3.4 Prevención del abrazo mortal. Como se dijo anteriormente para que el abrazo mortal se dé es necesario que se cumplan las cuatro condiciones necesarias y suficientes. AI prevenir que al menos una de ellas se dé estaremos previniendo la aparición del abrazo mortal. rn Exclusión mutua.- La exclusión mutua no puede eliminarse de recursos. Pero se W debe mantener al mínimo, así por ejemplo, las operaciones de lectura pueden realizarse normalmente sobre cualquier recurso sin necesidad de utilizarlo de manera única. Condición de espera.- Se puede utilizar un protocolo que permita al proceso obtener sus recursos al inicio de su ejecución. Esto permite garantizar que cuando un proceso está pidiendo sus recurso no tenga ningún otro. Otro protocolo puede permitir a los procesos pedir recursos sólo cuando no tiene ninguno. Esto se logra soltando los recursos una vez utilizados y pidiéndolos cada vez que se utilicen. Este protocolo puede ser demasiado burocrático. Estos protocolos desgraciadamente no son muy útiles ya que ambas estrategias evitan la correcta utilización de los recursos. Si un proceso necesita utilizar un recurso sólo una vez, con el primer protocolo lo mantendra ocupado durante mucho tiempo. Por el otro lado, si lo necesita muchas veces, el segundo protocolo aumentará la carga de .- 14 Procesos pedir y liberar el recurso, existiendo la posibilidad de que el proceso no pueda ejecutarse si el recurso en cuestión es muy popular. En general, cada proceso tendrá recursos que utiliza mucho y otro recurso que casi no. Por ello estos protocolos tienen valor poco práctico. No apropiación.- Una forma de prevención es no permitir que un proceso se apropie de recursos de otros procesos mientras se encuentran en estado de espera. Esto se utiliza sobre todo en el caso de la memoria, ya que la apropiación no causa ningún daño al proceso víctima. Sin embargo, no es utilizable en el caso de recursos donde el proceso se encuentra con operaciones a medio realizar; como puede ser el caso de las impresoras, las cintas o los archivos en disco. Cola circular.- Para asegurarse de que no exista nunca la cola circular, se puede dar una orden en la cual se piden los recursos. Existe una función que para cada recurso nos da un número natural, que representa el orden del recurso. Los procesos sólo pueden solicitar recursos cuyo orden es cada vez mayor. 1.3.5 Evitando abrazos mortales Un método que permitiría evitar al abrazo mortal sin la perdida en la utilización del sistema sería el siguiente: si se conoce toda la información sobre la utilización de recurso que va utilizar un proceso se puede encontrar la secuencia de asignaciones que eviten el abrazo mortal. Así, si sabemos que el proceso P va a utilizar primero ta cinta, después un archivo y finalmente la impresora, podemos saber si debemos aceptar la petición del proceso Q que está utilizando la impresora y pide utilizar la cinta. Existen varios métodos que funcionan de esta manera. Los requerimientos de información que es necesario conocer previamente varían en cada uno de ellos. El más simple de ellos necesita que se declaren cuales son las necesidades máximas de cada proceso. Dada esta información es posible construir un algoritmo que nunca llega a un estado de abrazo mortal. El algoritmo verifica dinámicamente que nunca exista la condición de cola circular. Se define el estado del sistema como la asignación de recursos, las necesidades máximas de ellos y los que hay disponibles. Un estado es seguro si existe una secuencia de asignación que de los recursos a cada proceso y evite el abrazo mortal. En forma más precisa se puede decir que un estado es seguro sólo si existe una secuencia segura. Una secuencia { P I ,P,2........, Pn } es segura para el estado presente de asignaciones si es posible darle los recursos que necesita al proceso Picon los disponibles y los que tienen los procesos Pj b'j< i. Es decir, si los que necesita el recurso Pi aún no están disponibles puede esperar hasta que terminen todos los Pj Una vez que se le han entregado eventualmente terminará y liberará los recursos que tiene, permitiendo que el proceso Pi+, tenga todos los recursos que necesita y así sucesivamente. , Un estado seguro no es un estado de abrazo mortal. Y un estado de abrazo mortal es un estado inseguro. Sin embargo, no todos los estados inseguros son estados de abrazo mortal, un estado inseguro puede llevar a un abrazo mortal. Abrazos Mortales 15 1.3 1.3.6 Recuperación del abrazo mortal Cuando se determina que existe un abrazo mortal, existen varias alternativas. Una posibilidad es informar al operador de lo que ha sucedido y dejar que él lo maneje manualmente. La otra posibilidad es que el sistema se recupere automáticamente. Hay dos formas de romper un abrazo mortal: rn La primera es matar uno o más procesos para romper la cola circular. Una vez que se decide terminar los procesos se pueden hacer dos cosas. En cualquiera de ellas los recursos de los procesos terminados son apropiados por el sistema. 0 Matar a todos /os procesos en abrazo mortal. Con ello se eliminará el abrazo mortal, pero el costo es muy alto. Puede ser que algunos de los procesos llevase mucho tiempo y necesiten ser vueltos a ejecutarse. 0 Matar un proceso a la vez hasta que se elimine el abrazo mortal. Este método utiliza mucho tiempo de CPU ya que después de que cada proceso es matado debe verificar si aún existe el abrazo mortal. Como ya se señaló el algoritmo de detección es costoso. 0 Inanición. La inanición es un problema que surge al quitarle recursos a los procesos. Debemos asegurarnos de que no existan procesos a los que siempre se les quiten los recursos. Si la elección se da en base a un factor de costo, entonces puede ser que un proceso sea elegido siempre como víctima. A fin de evitar esto se puede guardar información adicional de cada proceso como el número de veces que ha sido reiniciado. Y añadir al costo este factor. La segunda es apropiarse de un recurso que este en abrazo mortal. Un mejor esquema puede lograrse combinando la prevención y recuperación. Este se da gracias a las distintas características de los recursos, algunos que son fáciles de apropiar, otros que permiten ser ordenados y así. Supongamos que tenemos un sistema con los siguientes tipos de recursos: Recursos internos. Recursos usados por el sistema, como el bloque de control de procesos. Memoria central. Memoria usada por los procesos. Recursos de los trabajos. Periféricos asignables como cintas y archivos . Espacios de intercambio. Espacio en la memoria secundaria para los procesos. Con cada tipo se pueden utilizar distintos esquemas como sigue: Recursos internos. Aquí se puede utilizar prevención utilizando ordenamiento. Memoria central. Ya que este es un recurso que se puede apropiar fácilmente se puede utilizar prevención por apropiación. 0 Recursos de los trabajos. Estos recursos se pueden administrar con el algoritmo del banquero. 0 Espacio de intercambio. Se puede asignar preasignación ya que es posible saber el máximo utilizado de antemano. Así varios esquemas se pueden combinar a fin de obtener una solución efectiva al problema del abrazo mortal a un bajo costo. 16 Procesos 1.4 Problemas de la Programación Concurrente. Los ejemplos más ilustrativos en donde se da solución a los problemas más comunes de la programación concurrente son ( Ben - Aril 1990 ): I.Mergesort. Ordenación de un arreglo de N entradas, en forma ascendente o descendente. 0 Solución. Como es un algoritmo secuencial, nosotros ordenamos cada mitad del arreglo y luego unimos los resultados. Si tenemos un sistema con varios procesadores, los ordenamientos pueden hacerse en paralelo para obtener un mejor desempeño. Para implementar el paralelismo requerimos sincronización y comunicación. Cada paso de unión debe ser sincronizado con la producción de un nuevo resultado desde uno de los ordenamientos. 2. Buffer limitado. Hay dos técnicas usadas para limitar el tamaño del buffer. 0 0 Solución 1. La primera es el buffer circular, el cual es como el buffer infinito, excepto que el indice es calculado modulo el tamaño del arreglo. Solución 2. Otro método para limitar el tamaño del buffer es hacer notar que una vez que un dato ha sido usado, este espacio nunca es usado, así que debemos usarlo en vez de solicitar un nuevo indice. Para el problema de productor-consumidor con buffer limitado, la sincronización es necesaria para asegurarnos de que el consumidor puede ser detenido mientras espera el siguiente buffer que le mande el productor y el productor puede ser detenido si no hay buffers en el pool. 3. Lectores y Escritores. Es similar a el problema de exclusión mutua en varios procesos que estan compitiendo para accesar a la sección crítica. En este problema, sin embargo, dividimos los procesos en dos clases: Lectores: procesos que no requieren excluir a los demás. Escritores: procesos los cuales requieren excluir a los otros procesos, ya sean lectores o escritores. El problema es una abstracción de acceso a base de datos, donde no hay peligro en tener varios procesos leyendo al mismo tiempo, pero escribir o cambiar los datos debe hacerse al mismo tiempo, pero escribir o cambiar los datos debe hacerse bajo exclusión mutua para asegurar consistencia. 0 Solución. En esta solución, el primer lector que obtiene el acceso a la base de datos lleva a cabo un down en el semáforo db. Los siguientes lectores sólo incrementan un contador rc. Al salir los lectores, éstos decrementan el contador y eJ úJtirno en salir hace un up al semáforo, lo cual permite entrar a un escritor bloqueado, si es que existe. Problemas de la Programación Concurrente 17 1.4 Una hipótesis explícita en esta solución es que los lectores tienen prioridad sobre los escritores. Si surge un escritor mientras varios lectores se encuentran en la base de datos, el escritor debe esperar. Pero si aparecen nuevos lectores, de forma que exista al menos que exista al menos un lector en la base de datos, el escritor deberá esperar hasta que no haya más lectores interesados en la base de datos. (Tanenbaum, 1992) 4. Filósofos comensales. Este problema se explicará con más detalle en el capítulo 4. 5. Multiplicación matricial. Multiplicación de matrices de N * N. 0 Solución. El proceso i-ésimo (l<i<N) realiza de manera independiente la multiplicación del renglón i-ésimo por la columna i-ésima. Un proceso N+l realiza el acomodo de los resultados en la matriz resultante. 6. La suma de un conjunto de N números. 0 0 0 Solución 1: Dos procesos calculan de manera independiente la suma de N/2 números de un conjunto. Un tercer proceso recibe las dos sumas parciales y calcula la solución final. Solución 2: Crear un árbol binario de procesos de profundidad d. Este árbol tiene 2d niveles, cada uno de los cuales calcula la suma de N/Zd números y pasa el resultado parcial a su padre. Un proceso interior suma los valores pasados a este por sus hijos. En la raíz proceso la salida del resultado final. Solución 3: Un conjunto de k procesos tiene acceso a el conjunto entero de números. Un proceso remueve dos números, los suma y regresa el resultado a el conjunto. Cuando hay solamente un número en el conjunto, el programa termina y regresa ese valor. 7. El programa lee registros de 80- caracteres y escribe el dato como registros de 125 caracteres. Un blanco extra es puesto después de cada registro de entrada y un par de asteriscos (**) es reemplazado por un signo de exclamación (!).. O Solución: Usar procesos de árbol, uno para leer y descomponer la entrada, uno para filtrar los pares de asteriscos y uno para componer los registros de salida. 8. Cálculo del coeficiente binomial: (n k) = n (n-I) ...(n-k+l) Ax2 ...k Solución 1: Un proceso calcula el numerador y un segundo procesos calcula el denominador. Un tercer proceso hace la división. 18 Procesos Solución 2: Notar (probar) que i! divide a j(j+l) ...U+ ¡-I). El proceso numerador puede recibir resultados parciales de el proceso denominador y hacer la división inmediatamente, guardando los resultados intermedios desde que llegan a ser grandes también. Por ejemplo: 1x2 divide a 10x9, 1x2~3 divide a 10x9~8, y así continuamente. 9. Dar dos árboles binarios con niveles etiquetados, verificar si la secuencia de etiquetas es la misma en cada árbol. Por ejemplo, los dos árboles definidos por la expresión (a,(b,C)) y ((a,b),C) tienen la misma secuencia de niveles. 0 Solución: Crear dos procesos para recorrer los árboles concurrentemente. Ellos nos enviaran las hojas etiquetadas en el orden encontrado para un tercer proceso de comparación. 10. Sean S y T dos conjuntos disjuntos de números donde s y t son dos elementos elementos en los conjuntos correspondientes. Dar un programa el cuál modifica los dos conjuntos, tal que S contiene los s miembros más pequeños de SUT y T contiene los t números más grandes de S UT. 0 Solución 1: El proceso Ps encuentra los elementos más grandes en S y los envía para el proceso Pt, el cuál encuentra los números más grandes en T y los envía al proceso Ps. Solución 2: Crea SUT. Permite a el proceso Ps extraer los s elementos más pequeños de SUT y Pt extrae los t elementos más grandes de SUT. 11. El Juego de la Vida. Un conjunto de células es arreglado en un arreglo rectangular (potencialmente finito) tal que cada célula tiene ocho vecinos (horizontalmente, verticalmente y diagonalmente). Cada célula esta “viva” o “muerta”. Dado un conjunto finito inicial de células vivas, calcular la configuración obtenida después de una secuencia de generaciones. Las reglas para del paso de una generación a la siguientes son: (a) Si una célula está viva y tiene menos de dos vecinas vivas, ésta muere. (b) Si ésta tiene dos o tres vecinas vivas, ésta continua con vida. (c) Si tiene cuatro o más vecinas vivas, ésta muere. (d) Una célula muerta con exactamente tres vecinas vivas llega a vivir. Solución: Cada célula es simulada por un proceso. Hay dos problemas a resolver. Primeramente, el cálculo de la siguiente generación debe ser sincronizado, por ejemplo, la modificación de cada célula de ser en base a un estado de las células vecinas en la misma generación. Además, una gran estructura de datos de procesos y canales de comunicación debe ser creado. Problemas de la Programación Concurrente 19 1.4 0 ...u+ Solución 2: Notar (probar) que i! divide a j(j+l) ¡-I). El proceso numerador puede recibir resultados parciales de el proceso denominador y hacer la división inmediatamente, guardando los resultados intermedios desde que llegan a ser grandes también. Por ejemplo: 1x2 divide a 10x9, 1 x 2 ~ divide 3 a 10x9~8, y así continuamente. 9. Dar dos árboles binarios con niveles etiquetados, verificar si la secuencia de etiquetas es la misma en cada árbol. Por ejemplo, los dos árboles definidos por la expresión (a,(b,C)) y ((a,b),C) tienen la misma secuencia de niveles. 0 Solución: Crear dos procesos para recorrer los árboles concurrentemente. Ellos nos enviaran las hojas etiquetadas en el orden encontrado para un tercer proceso de comparación. I O . Sean S y T dos conjuntos disjuntos de números donde s y t son dos elementos elementos en los conjuntos correspondientes. Dar un programa el cuál modifica los dos conjuntos, tal que S contiene los s miembros más pequeños de SwT y T contiene los t números más grandes de S UT. Solución 1: El proceso Ps encuentra los elementos más grandes en S y los envía para el proceso Pt, el cuál encuentra los números más grandes en T y los envía al proceso Ps. Solución 2: Crea SUT. Permite a el proceso Ps extraer los s elementos más pequeños de SUT y Pt extrae los t elementos más grandes de SuT. 11. El Juego de la Vida. Un conjunto de células es arreglado en un arreglo rectangular (potencialmente finito) tal que cada célula tiene ocho vecinos (horizontalmente, verticalmente y diagonalmente). Cada célula esta “viva” o “muerta”. Dado un conjunto finito inicial de células vivas, calcular la configuración obtenida después de una secuencia de generaciones. Las reglas para del paso de una generación a la siguientes son: (a) Si una célula está viva y tiene menos de dos vecinas vivas, ésta muere. (b) Si ésta tiene dos o tres vecinas vivas, ésta continua con vida. (c) Si tiene cuatro o más vecinas vivas, ésta muere. (d) Una célula muerta con exactamente tres vecinas vivas llega a vivir. 0 Solución: Cada célula es simulada por un proceso. Hay dos problemas a resolver. Primeramente, el cálculo de la siguiente generación debe ser sincronizado, por ejemplo, la modificación de cada célula de ser en base a un estado de las células vecinas en la misma generación. Además, una gran estructura de datos de procesos y canales de comunicación debe ser creado. 20 Procesos 12. El problema es escribir un disco servidor que minimice el arreglo rectangular del tiempo de búsqueda de un drive de disco. Un sencillo servidor podría satisfacer requerimientos para datos en orden decreciente de distancia de una posición actual del brazo. Desafortunadamente, éste disco servidor podría dejar "morir de hambre (no atender por un periodo largo de tiempo las solicitudes que hace al procesador) a un requerimiento para datos si los requerimientos llegan también rápidamente. " Solución: Mantener dos colas de requerimientos: una para requerimientos de datos de números de track menores que la posición actual y uno para requerimientos con números de track mayores. Satisfacer todos los requerimientos de una cola antes considerando los requerimientos de la otra cola. Asegurarnos de que un paso de requerimientos para datos de el track actual no causa Inanición. 0 13. Calcular todos los números primos desde 2 a n. Notar (probar) que si k no es un primo, este es divisibles por un primo p(k) <= dk + 1. 0 Solución 1: Destinar un proceso para cada número desde k hasta n. Verificar si k es divisible por cualquier número menor o igual a dk + 1. 0 Solución 2: Destinar procesos para cada k y tener que borrar todos los múltiplos de k en el conjunto desde 2 hasta n. 0 Solución 3: Destinar un procesos para borrar todos los múltiplos de 2. Cuando un número se descubre que es primo por todos los procesos existentes, designa un nuevo proceso para borrar todos los múltiplos de este primo. ' 0 Solución 4: Dividir el conjunto en i bloques de tamaño n/i. Designar un proceso para cada bloque. Comenzar calculando los primos en el primer bloque. Cuando un primo k ha sido calculado, podemos liberar todos los procesos conteniendo números arriba de k CAPíTULO 2 Parallel Virtual Machine (PVM) 2.1 Introducción PVM es un software que permite a una red de computadoras heterogéneas UNIX, ser usada como una computadora paralela (Manual de Referencia de PVM ). Distintos problemas computacionales como la ordenación de arreglos, la solución a una suma de N números, entre otros, pueden ser resueltos utilizando un conjunto de computadoras. Ei desarrollo de PVM inicia en el verano de I989 en el Oak Riúge National Laboratory (ORNL) y es ahora un proyecto de investigación en marcha que involucra a las siguientes Universidades y dependencias norteamericanas' : Universidad de Emory, ORNL, Universidad de Tennessee, Universidad Carnegie Mellon, Departamento de Energía, Fundación Nacional de Ciencia, Estado de Tennessee y Centro de Supercómputo de Pittsburgh. Debido a su naturaleza experimental, el proyecto PVM, produce software que es utilizado por investigadores de la comunidad científica y otros centros de enseñanza. Este software es y ha sido distribuido libremente con el interés del avance de la ciencia y es usado en todo del mundo. Bajo PVM un usuario define una colección de computadoras con un solo procesador para simular una gran computadora de memoria distribuida; proporciona las funciones para iniciar tareas en una máquina virtual2y permite a éstas sincronizarse y comunicarse. Una tarea se define en PVM como una unidad de cómputo, análoga a un proceso UNIX. Las aplicaciones en Fortran 77 Ó C pueden ser paralelizadas utilizando código común de paso de mensajes. Múltiples tareas de una aplicación pueden cooperar para resolver un problema en paralelo pasando y recibiendo mensajes. PVM proporciona funciones para explotar mejor la arquitectura de cada computadora en la solución de problemas, maneja todas las conversiones de datos que pueden ser requeridas si dos computadoras usan diferentes representaciones de punto flotante ó enteros. Además, permite a la máquina virtual ser interconectada por una variedad de redes diferentes. ' http://www.netlib2.cs.utk.edu * El Término Máquina Virtual es usado para identificar a una computadora 16gica de memoria distribuida. 21 22 Parallel Virtual Machine El sistema PVM está compuesto por dos partes : La primera parte del sistema la conforma un demonio3 llamado pvmd3, algunas veces abreviado como pvmd, el cual reside en todas las máquinas que conforman la máquina virtual. pvmú3 puede ser diseñado por cualquier usuario que cuente con un login y un password, y que pueda instalar el demonio en una máquina. Cuando el usuario va a ejecutar una aplicación, debe crear una máquina virtual para iniciar PVM, las aplicaciones pueden ser ejecutadas desde el shell de UNIX. La segunda parte del sistema la conforma una biblioteca de rutinas de interfaz PVM, ubicada dentro del archivo libpvm3.q la cual contiene rutinas de paso de mensajes, creación de procesos, coordinación de tareas, y modificación de la máquina virtual. Los programas de aplicación deben ser ligados con esta biblioteca para poder utilizar PVM. 2.2 Obteniendo e Instalando PVM. PVM no requiere de privilegios especiales para ser instalado. Cualquier persona que posea un login puede hacerlo. PVM-ARCH es usada a lo largo de este reporte para representar el nombre de la arquitectura que PVM usa para una computadora dada. La siguiente tabla muestra todos los nombres de PVM-ARCH y los correspondientes tipos de arquli TABLA 2.1 Son procesos que siempre se están ejecutando. ( W. Richard Stevens, 1996 ) Obteniendo e Instalando PVM 23 2.2 Hay varias formas de obtener el software y la documentación. La guía de usuario, el código fuente de PVM 3, las páginas de ayuda, XPVM, y apuntadores a otros paquetes PVM relacionados, se encuentran disponibles en netlib. Netlib es un servicio de distribución de software ubicado en Internet. Hay varias maneras de obtener software de netlib. La primera es con una herramienta llamada xnetlib. Xnetlib es una interface X Windows que permite al usuario navegar y solicitar la transferencia de PVM a la computadora del usuario. Para obtener xneflib envíe un e-mail a [email protected] con el mensaje send xnetlib.shar from xnetlib o un ftp anónimo a cs.utk.edu pub/xnetlib. Los archivos netlib pueden también ser obtenidos por un ftp anónimo a netlib2.cs.utk.edu. Ubicándose en el directorio pvm3. El archivo índex describe los archivos en este directorio. El software PVM puede ser solicitado por e-mail. Para recibir este software enviar un e-mail a [email protected] con el mensaje: send índex from pvm3. Un manejador automático de correo electrónico regresará una lista de los archivos y demás instrucciones adicionales por e-mail. La ventaja de este método es que cualquier persona con acceso email a Internet puede obtener el software. Los archivos de código fuente, que consumen alrededor de un Megabyte cuando se desempacan, están disponibles en el formato tar uudecodedcompresed. Para instalar PVM, coloque el archivo pvm3.3.ü.far.z.u~ en el directorio donde usted quiere que resida el código fuente. Por default PVM asume que éste es instalado en su directorio $HOME/pvm3, pero este puede ser instalado en una área más centralizada como /usr/local/pvm3. Para desempacar el código fuente siga los siguientes pasos: % uudecode pvm3.3.0.tar.z.u~ % u n c o m p r e s s pvm3.3.0.tar.Z % tar xvf pvm3.3.0.tar. PVM usa dos variables de ambiente PVM-ARCH y PVM-ROOT que se colocan en el archivo .cshrc. Aquí damos un ejemplo para PVM-ROOT: sefenv PVM-ROOT /HOMUmsr/u2/kohl/pvm3 El método recomendado para colocar PVM-ARCH, es anexar la línea PVM-ROOT//i6/cshrc.sfub en el archivo .cshrc. La línea anterior debe ser anexada después de la declaración de la variable de ambiente PATH. Este archivo automáticamente determina PVM-ARCH para éste host y es particularmente Útil cuando el usuario comparte un fi/esystem4 común ( tal como NFS, Network File System ) a través de arquitecturas diferentes. 4 Es una parte del Arbol de archivos UNIX, que consiste en un directorio, subdirectonos y archivos ( Nemeth, 1996). 24 Parallel Virtual Machine El código fuente de PVM se obtiene con directorios y makef7/es5 para distintas arquitecturas. La construcción para cada tipo de arquitectura se realiza automáticamente tecleando: make dentro del directorio PVM-ROOT. El makefile determina automáticamente libfp~m3.a~ pvmgs y en cuál arquitectura se está ejecutando y construirá pvmd3, libp~m3.a~ libgpvm3.a. Éste coloca todos los archivos en pvm3Aib/PVM_ARCH con excepción de pvmgs, el cual es colocado en PVM-ROOT/bin/PVM-ARCH. PVM busca los archivos ejecutables del usuario en la dirección default $HOM€(pvm3/bin/PVM_ARCH. Si PVM es instalado en otra dirección como /usrAoca/ para todos los usuarios, entonces cada usuario podría también crear el directorio $HOM€(pvrn3/binn'VM_ARCH para colocar sus propios ejecutables. Por ejemplo: si una aplicación de usuario PVM espera producir una tarea llamada Tarea1 en una estación SPARC llamada tlallocl, entonces en tlallocl tendría que haber un archivo ejecutable $ H O M € ( p v r n 3 / b i n / ~ U r e a1. Este default puede ser cambiado para un path distinto en el hostfi/e. Ver Sección 2.3.2. 2.3 Configuración de la Máquina virtual 2.3.1 La Consola PVM. La consola PVM llamada pvm, es la Única tarea PVM, que permite al usuario iniciar, preguntar y modificar el estado de la máquina virtual iterativamente. La consola puede iniciar y detener su ejecución múltiples veces en cualquiera de los host que conforman la máquina virtual, sin afectar a PVM o a cualquier aplicación que pueda estar ejecutándose. Cuando inicia la consola pvm, comprueba si PVM ya se está ejecutando, y si no automáticamente ejecuta pvmd en este host, pasando a pvmd las opciones de la línea de comandos y el archivo hostfile. De esta forma, PVM no necesita estar ejecutándose para que se pueda inicializar la consola. pvm bd cdebugmask>j[hoMIej pvm -n<hostname> La opción -d habilita la máscara de depuración ( número hexadecimal que corresponde a los bits de depuración de pvmd.c ) . ~~ SArchivoque ejecuta el comando make. Ver manual de UNIX, Ver tabla 2.1. 6 Configuración de la Máquina Virtual 25 2.3 La opción -n es utilizada para especificar un nombre alterno para el pvmd principal (en caso de que el hostname' no sea igual a la dirección IP que se desea). Ésta opción es útil si el host tiene múltiples redes conectadas a él, tales como FDDl o ATM, y se desea que PVM use una red en particular. Una vez inicializada la consola, imprime el prompt: y acepta comandos desde la entrada estándar (teclado). Los comandos disponibles en la consola son: add Seguido por uno o más nombres de hosts, agrega éstos a la máquina virtual. alias Define o lista los alias de comandos. conf Lista la configuración de la máquina virtual, incluyendo nombre del host, identiificador de la tarea pvmd y tipo de arquitectura. delete Seguido por uno o más nombres de hosts, los elimina de la máquina virtual. Los procesos PVM ejecutándose en los hosts eliminados son perdidos. echo Repite argumentos. halt Termina todos los procesos PVM incluyendo la consola y los demonios, deteniendo la ejecución de PVM. help Puede ser usado para obtener información acerca de cualquiera de los comandos de la consola. La opción help seguida por un comando de la consola pvm, lista todas las opciones y banderas disponibles para éste comando. id Imprime en la consola el ID de la tarea. jobs Lista los procesos que se ejecutan en la máquina virtual. kill Termina cualquier proceso PVM. mstat Muestra el estado de los host especificados. ps -a Lista todos los procesos que se encuentran actualmente en la máquina virtual, sus direcciones, sus ID's de tareas, y los ID's de sus tareas padre. pstat Muestra el estado de un sólo proceso PVM . quit Sale de la consola dejando a los demonios y tareas PVM en ejecución. 7 Cada computadora en la red debe de taner un nombre unico. 26 Parallel Virtual Machine reset Elimina todos los procesos PVM excepto la consola, reinicia todas las tablas internas y colas de mensajes PVM. Los demonios son dejados en un estado estacionario' . setenv Despliega o coloca variables de ambiente. sig Seguido por un número de señal correspondiente. y tid, envía la señal a la tarea con el tid spawn lnicializa una aplicación PVM. Las opciones incluyen: -count Número de tareas, por default 1. -(host) Se produce en un host , por default cualquiera. -(PVM-ARCH) Se produce en un hosts de tipo PVM-ARCH. -? Habilita depuración. - > Redirecciona la salida de una tarea a la consola. - > file Redirecciona la salida de una tarea a un archivo especificado en file. ->>fi/e Redirecciona la salida de una tarea anexándola a un archivo especificado en file. unalias Deja de definir un alias de comando. version Imprime la versión de libpvm que es usada. La consola lee el archivo $HOME/.pvmrc antes de leer comandos desde el tty 'O , así puede hacer cosas como: alias ? help alias h help alias j jobs setenv PVM-EXPORT DISPLAY $ imprime mi id echo nuevo shell pvm id Los dos métodos más comunes para ejecutar PVM3 son: iniciar la consola pvm y agregar hosts manualmente (pvm también acepta un argumento de nombre de host opcional), ó iniciar pvmd con un hosffi/e. 2.3.2 El archivo hostfile. El archivo hosffile define la configuración inicial de los hosts, que PVM combina para crear la máquina virtual. Este archivo también contiene información acerca de los hosts que el usuario desee agregar posteriormente a la configuración. * Ver Sección 1.1.1 'O ver figura 5.2 de (Nemeth, 1996). tipo de terminal Configuración de la Máquina Virtual 27 2.3 Cada usuario de PVM podrá tener su propio hostfile, el cuál describe su propia máquina virtual personal. El archivo hostfile, en su forma más sencilla, es sólo una lista de nombres de hosts, un nombre por línea. Las líneas en blanco son ignoradas, y las líneas que comienzan con un # son líneas de comentario. Estos símbolos ( # ) permiten al usuario comentar su hostfile y también proporcionar una forma rápida de modificar la configuración inicial. Un ejemplo de una configuración, se ve a continuación: # mi primer configuración tlalloc4 tlalloc2 xanum.uam.mx alpha.cs.cinvestav.mx xhara2. uam.mx hermes.cs.uh.edu Las máquinas que pertenecen a la misma red local, no es necesario especificar su dirección completa. Existen varias opciones que pueden ser especificadas en cada línea después del nombre del host. Las opciones son separadas por un espacio en blanco. Opciones: lo = userid Permite al usuario especificar un login alternativo para este host; de otra manera, se usará el login inicial de la máquina. so = pw Causará que PVM pida al usuario accese a este host con su password. Esto es útil en el caso donde el usuario tenga un UID" y un password distinto en el sistema remoto. PVM usa rsh por default para inicializar demonios pvmd remotos, pero cuando pw es especificado, PVM usará rexec() en vez de rsh. dx = location-of-pvmd Permitirá al usuario especificar una dirección distinta a la default de pvmd para este host. Este es Útil si alguien va a usar su propia copia personal de pvmd. ep = paths-to-user-executables Esta opción permite al usuario especificar una serie de rutas de acceso para buscar sus archivos ejecutables. Múltiples rutas son separados por dos puntos. Si ep= no es especificado, entonces PVM busca las tareas de aplicación en $HOMElpvm3/binlPVM-ARCH. sp = value Especifica la velocidad relativa del host comparada con otro en la configuración. El rango de posibles valores es 1 a 1'000,000 con 1 O00 como default. 11 numero entero que identifica un usuario y es asignado por el root. 28 Parallel Virtual Machine bx = locationof-debugger Especifica cual es el script’* de depuración que se invocará en este host si la depuración es requerida en la rutina spawn. Por default el depurador está en pvrn3Aib/debugger 13. wd = workingdirectory Especifica un directorio de trabajo en el cual todas las tareas producidas en este hosf se ejecutarán. El default es $HOME. so = ms14 Especifica que el usuario iniciará manualmente un pvmd esclavo en este host. Es útil si los servicios de red rsh y rexec() están deshabilitados, pero existe conectividad IP. Cuando usamos esta opción se verá en el tty de pvmd3: [t80040000] ready Fri Oct 17 78:47:47 7997 **** Manual startup **** Login to “honk” and type: pvm3/lb/pvmd -s -do -nhonk I 80a9ca95:0cb6 4096 2 80a95c43:OOOO Type response: En honk15después de teclear la línea dada, se observa: ddpro<23I2>arch<ALPHA>ip<80a95~43:0a8e>mtu<4096>el cuál se transmitirá para el pvmd principal. En este punto verá: Thanks y los dos pvmds están listos para comunicarse. Si el usuario desea colocar cualquiera de las opciones arriba citadas como default para un conjunto de hosts, entonces puede colocar estas opciones en una línea con un * al principio. Las opciones defaults tendrán efecto para todos los hosts siguientes hasta que haya otra línea default. Los host que el usuario no quiere agregar en la configuración inicial, pero que serán utilizados posteriormente, pueden ser especificados en el hosM/e poniendo al principio de estas líneas un &. Un ejemplo del desplegado de hostfile y más de estas opciones son mostradas a continuación: ~ 12 archivo que contiene un conjunto de instrucciones que son ejecutadas por el shell. l 3 La variable de ambiente PW-DEBUGGER puede ser declarada en lugar de bx. l 4 manual startup (ms). l 5 honk es el nombre de una máquina. .- Configuración de la Máquina Virtual 29 2.3 Hostfile. # Las líneas de comentarios son iniciadas con # # Las lineas en blanco son ignoradas gstws ipsc dx = /usr/geistlpvrn3/lib/l860/pvmd3 ibmi.scri.fsu.edu lo = gst so = pw # Las opciones default son colocadas con * para los siguientes host * ep = $sun/probleml : -/nla/mathlib sparky # azure.epm.ornl.gov midnight.epm.ornl.gov # reemplaza las opciones default con los nuevos valores para los host * lo = gageist so = pw ep = problem1 thud.cs.utk.edu speedy.cs.utk.edu # Las máquinas por agregar posteriormente, son especificadas con & # estas sólo necesitan listarse si se requieren. & sun4 ep = problem1 & castor dr = /usr/local/bin/pvmd3 & dasher.cs.utk.edu lo = gageist & elvis dr = -/pvmYlib/SUN4/pvmd3 2.3.3 Variables de ambiente PVM usa dos variables de ambiente cuando se inicializa y se ejecuta. Cada usuario PVM necesita utilizar estas dos variables para poder disponer de PVM. La primera es PVM-ROOT, la cual es establecida para la localización del directorio pvm3. La segunda es PVM ARCH, la cual informa a PVM la arquitectura del host, y de esta manera, decide que archivos ejecutables debe escoger del directorio PVM-ROOT. PVM mejora el uso del ambiente y lo mantiene en cada una de los hosts, donde éste sea distinto. Por ahora esto permite a una tarea exportar cualquier parte de su ambiente a sus tareas hijo colocando PVM-EXPORT a las variables que serán exportadas a través de spawnI6 Por ejemplo: PVM-EXPORT = D1SPLAY:SHELL exporta las variables DISPLAY y SHELL a las tareas hijo. l6 (Ver Sección 2.3.1.) ___ 30 Parallel Virtual Machine 2.3.4 Problemas al iniciar PVM. Si PVM tiene un problema al iniciar, desplegará un mensaje de error ya sea en la pantalla o en el archivo /tmp/pvm/.<uid>. Esta sección tiene como objetivo, ayudar a la interpretación de los mensajes de error y explicar cómo resolver estos problemas. Si el mensaje dice: ft80040000]Can Y start pvmd Las posibles razones son: 0 0 0 que tu archivo host, en el hosf remoto no contenga el nombre del host desde el cual tu estás iniciando PVM. no tener PVM instalado en un host. no tener PVM-ROOT bien declarado en el mismo host Para confirmar que el archivo .hots esta colocado correctamente en la máquina remota, teclea: % rsh remote.host ‘Is’ Para verificar el último punto teclee: % rsh remotehost ‘prinfenv ’ Si PVM es eliminado manualmente o detenido anormalmente ( por ejemplo con la caída del sistema ) entonces, verifica que exista el archivo /tmp/pvmd.<uiá>. Este archivo es usado para autentificar y existe solamente cuando PVM se esta ejecutando. Si obtienes un mensaje que dice: [fB00400000]L ogh Jncorrect Entonces probablemente significa que no hay una cuenta en la máquina remota con tu login. Si hay un login distinto en la máquina remota, se puede fijar el otro con la opción: /o=opfion en el hosffile. Si obtienes cualquier mensaje desconocido, entonces checa el archivo .cshrc. Es importante que el usuario no tenga cualquier WS en el archivo .cshrc porque interferirá con la inicialización de PVM. Limitaciones de Recursos 31 2.4 2.4. Limitaciones de recursos. Las limitaciones de recursos impuestas por el Sistema Operativo y el hardware son las misma para las aplicaciones PVM. Siempre que sea posible, PVM evita colocar limites explícitos, en vez de esto regresa un mensaje de error cuando los recursos se han terminado. Naturalmente, la competencia entre usuarios en el mismo host y en la red afectan algunos límites dinámicamente. 2.4.1 En el demonio PVM. El número de tareas que cada pvmd puede manejar está limitado por dos factores: el número de procesos ermitidos a cada usuario por el Sistema Operativo, y el número de archivos descriptoresI? disponibles en el pvmd. El pvmd puede llegar a un embotellamiento si todas las tareas tratan de comunicarse unas con otras, a través de él. El pvmd usa asignación de memoria dinámica para almacenar paquetes, en la ruta entre tareas, hasta que la tarea receptora acepta los paquetes estos son acumulados por el pvmd en una cola de Primeras Entradas, Primeras Salidas (First lnput First Output, FIFO). El control de flujo no es impuesto por el pvmd, éste almacenará todos los paquetes dados a él, hasta que no pueda obtener más memoria. 2.4.2 En la Tarea Igual que pvmd, una tarea tiene un límite en el número de tareas con las que se puede conectar directamente. Cada una de las rutas directas a una tarea tienen conexiones TCP (bidireccionales), y consume cada una un descriptor de archivo. Así con un límite de 64 archivos abiertos, una tarea puede establecer conexiones directas con otras 60 tareas. Este límite únicamente tiene efecto cuando usamos ruteo directo tarea-tarea. Los mensajes direccionados vía los pvmds, solamente usan la conexión default pvmd-tarea. El tamaño máximo de un mensaje PVM está limitado por la cantidad de memoria disponible para la tarea. Los mensajes generalmente son empaquetados utilizando datos existentes en la memoria, y éstos pueden residir en ella mientras son empaquetados y enviados. El mensaje más grande posible que una tarea puede enviar debe ser menor que la mitad de la memoria disponible. Si un número no determinado de tareas es enviado a un sólo destino, todas al mismo tiempo, la tarea destino Ó el pvmd pueden ser sobrecargados, porque éstos tratan de almacenar los mensajes. 17 Estructuras de datos que guardan información del proceso. 32 Parallel Virtual Machine 2.5 Detalles de Implantación. 2.5.1 Introducción En ésta sección describimos los detalles de implantación de PVM en una máquina de un sólo CPU. El sistema PVM es portable a cualquier versión de UNIX, y a las MPPs ( Message Passing Machines Whit Many Processors, Máquinas de Paso de Mensajes con Muchos Procesadores). PVM permite la construcción de aplicaciones con tolerancia a fallas. A fin de mantener a PVM tan portable como sea posible, se evitó el uso de características de los sistemas operativos y de los lenguajes de programación. Se asume que los sockets” están disponibles para la comunicación entre procesos y que cada host de la máquina virtual puede conectarse directamente a cualquier otro host usando protocolos IP ( TCP y UDP ). Esto es, el demonio pvmd debe ser capaz de enviar un paquete a otro demonio pvmd en un sólo paso. Los requerimientos para la conectividad completa del IP pueden ser eliminados especificando las rutas y ésto permite a los demonios pvmd mandar mensajes. Note que algunas máquinas MPPs no tienen disponibles los sockets en el procesamiento de nodos, pero los tienen en el front-end . (donde el pvmd se ejecuta). ’’ PVM proporciona rutinas que permiten a los procesos llegar a ser tareas PVM y volver a ser procesos normales nuevamente. Estas son rutinas para adicionar y eliminar hosts de la máquina virtual, para iniciar y terminar tareas PVM, para enviar señales a otras tareas PVM, para obtener información acerca de la configuración de la máquina virtual y de las tareas que se están ejecutando actualmente bajo PVM . 2.5.2 Características de PVM 2.5.2.1 ldentificador de tareas Todas las tareas que corren bajo PVM son representados por un identificador de tareas ( task identifier, tid ), éste es utilizado para la identificación de procesos, cada tarea tiene un valor único el cual es proporcionado por el demonio pvmd local. PVM contiene varias rutinas que regresan el valor del tid, de tal forma que se puedan identificar las tareas en el sistema. Éstas rutinas son pvm-myfjd(), pvm-spawn(), pvm-parent(), pvm-bufinfo(), pvm-tasks(), pvm-tidtohost() y pvm-gettid(). 18 l9 Crea un punto terminal para conectarse a un canal y devuelve un descriptor. ( Márquez,l993) Seccidn de entrada. 2.5 Detalles de Implentación 33 El tid es un entero de 32 bits, para direccionar demonios pvmd y grupos de tareas en una máquina virtual. El tid identifica a una Única tarea en la máquina virtual, sin embargo los tids son reciclados cuando no son usados por largo tiempo. El tid contiene cuatro campos como se muestra en la figura 2.1, sin embargo el tamaño de los campos puede cambiar ( de acuerdo a la configuración de la máquina virtual ). Puesto que el tid contiene mucha información, está diseñado para colocar dentro de él un tipo de datos entero más grande. 31 31 3029 SG 16 24 8 17 H O O L FIGURA 2.1 El campo L abarca los primeros 18 bits. El campo H los siguientes 12 y los campos G y S los dos ultimos. Los campos SIG y H tienen significado global, esto es, cada pvmd de una máquina virtual los interpreta de la misma forma. El campo H contiene el número de host relativo a la máquina virtual. Cuando la máquina inicia, cada pvmd es configurado con un número de host único, distinto de cero, partiendo del espacio de dirección de la máquina. El pvmd con número cero en el campo HIes usado dependiendo del contexto, ya sea para diferenciar al demonio local pvmd ó a la copia del demonio local llamado pvmd del demonio maestro pvmd. El número máximo de hosts en la máquina virtual está limitado a 2H -1. La relación entre el número de host y cada host, es conocido por todos los demonios. Los mensajes son direccionados al demonio pvmd colocando el bit S y el campo del host, y llenando de ceros el campo L. En las versiones posteriores de PVM, éste bit debería de ser cedido al campo H Ó L. Cada pvmd asigna un significado local al campo L ( cuando el campo H es igual a su propio número de host ). El demonio pvmd contiene un mapeo entre el valor L y el ldentificador de procesos UNIX. De la misma forma que el número de hosts, el número de tareas por host, está limitado de acuerdo al tamaño de su campo TID. Puesto que el campo L permite 18 bits, a lo más 264143 tareas pueden existir concurrentemente en un host. En puertos multiprocesadores el campo L es frecuentemente subdividido, por ejemplo, en un campo partición (P), un campo de número de nodo (N) y el bit de localización W. Como se muestra en la Figura 2.2 31 24 31 30 29 SG 16 18 17 16 H w O 8 10 P FIGURA 2.2 N O 34 Parallel Virtual Machine El campo N abarca los primeros 11 bits. El campo P los siguientes 6 , W el 17, H del 18 al 29 y los campos G y S los dos ultimos. El campo P especifica una partición de la máquina (algunas veces llamada “tipo de proceso” Ó “trabajo”), en el caso donde pvmú puede manejar múltiples particiones MPP. El campo N determina un nodo de CPU específico en una partición. El bit W indica si una tarea es ejecutada en un nodo MPP Ó en un procesador del host (servicio de nodo). La colocación del bit W puede ser determinada por la salida de ps -a desde la consola de pvm. Puesto que la salida de tid en ps, es un número hexadecimal, el quinto dígito de la derecha contiene el bit W. La siguiente es una tabla de estados para determinar si el bit W es cero o uno. bit W tarea corriendo en: contenido del O I nodo de computadora MPP nodo de un host 0,1,4,5,8,9,C,d 2,3,6,7,a,b,e,f !jth dígito del tid Por ejemplo, si el tid es 60001, entonces sabes que tu tarea es ejecutada en el nodo de una MPP. Los campos del fiú se conforman como sigue: S G H L Naturalmente, los TID’s son ocultados por la aplicación y el programador deberá intentar predecir sus valores o modificarlos. no 2.5.2.2 Tolerancia a fallas Si un host falla, PVM automáticamente lo detectara y lo dará de baja de la máquina virtual; el estado de los host puede ser solicitado por la aplicación, y si se requiere, un host de reemplazo puede ser anexado por la aplicación. También es responsabilidad del desarrollador de aplicaciones, hacer éstas tolerantes a las fallas de los hosts. PVM no intenta recobrar tareas que hallan sido terminadas por la falla de un host. Otro uso de la tolerancia a fallas, es la de agregar más hosts cuando sea necesario; por ejemplo en un fin de semana cuando el nivel de actividad de una institución disminuye, la aplicación puede adicionar más host para aumentar su poder de cómputo. 2.5 Detalles de Implentación 35 2.5.2.3 SeñaIización PVM proporciona dos métodos para el envío de señales entre distintas tareas PVM. 0 0 El primero consiste en enviar una señal UNIX. El segundo, notifica a una tarea acerca de un evento por medio de un mensaje, este tiene una etiqueta para un usuario específico. Hay varios eventos que se pueden notificar a las tareas en PVM, entre ellos están: la finalización de una tarea, la perdida o falla de un host, y la adición de un host. 2.5.2.4 Comunicación PVM proporciona rutinas para empaquetar y enviar mensajes entre tareas. El modelo asume que: 0 cualquier tarea puede enviar un mensaje a cualquier otra. 0 no hay un límite para el tamaño. 0 no hay número máximo de tales mensajes. En todos los hosts hay limitaciones físicas de memoria, lo cual limita el espacio del buffer, el modelo no se restringe a las limitaciones de una máquina en particular y asume que dispone de memoria suficiente. Ésto permite enviar bloques asincrónicamente, recibirlos del mismo modo y funciones para recibir no-bloques. Hay opciones en PVM 3 que requieren que los datos sean transferidos directamente de una tarea a otra. En este caso, si el mensaje es extenso, el emisor puede retener el mensaje hasta que el receptor este listo para recibirlo. Un no-bloque recibido, inmediatamente regresa ya sea con el dato ó con la bandera de que el dato no llegó, mientras que un bloque recibido regresa sólo cuando el dato está en el buffer de recepción. Una rutina puede ser llamada para que retorne información acerca de los mensajes recibidos. El modelo PVM garantiza que el órden de los mensajes se preserva. Si la tarea 1 envía un mensaje A a la tarea 2, después la tarea 1 envía un mensaje B a la tarea 2, el mensaje A llega a la tarea 2 antes que el mensaje B. 2.6 El Demonio PVM Una vez que el pvmd es ejecutado en cada host de la máquina virtual y los pvmds son configurados para trabajar juntos. Los pvmds de un usuario no interactúan con los de otros. El pvmd fue diseñado para ejecutarse bajo un UID no privilegiado que ayude al usuario a reducir los riesgos de seguridad y para minimizar el impacto de un usuario PVM con los demás. El demonio pvmd sirve como ruteador y controlador de mensajes. Esto proporciona un punto de contacto entre cada uno de los hosts, así como autentificación, control de procesos y detección de fallas. Los demonios inactivos, ocasionalmente mandan un mensaje a los 36 Parallel Virtual Machine demás demonios, para verificar su alcance; cuando un pvmd no responde es marcado como deteriorado. Los pvmds son más resistentes que los componentes de una aplicación y continúan ejecutándose si el programa es interrumpido, facilitando la depuración. El primer pvmd ( iniciado manualmente ) es llamado el pvmd maestro, mientras los otros ( iniciados por el maestro ) son llamados pvmds esclavos. En la mayoría de las operaciones, todos los pvmds son considerados iguales. Únicamente el maestro puede iniciar nuevos pvmds esclavos y adicionarlos a la configuración de la máquina virtual. De la misma forma, sólo el maestro puede eliminar esclavos de la máquina virtual. Si el pvmd maestro pierde contacto con un esclavo, éste marca al esclavo como dañado y lo suprime de la configuración. Si un esclavo pvmd pierde contacto con el maestro, el esclavo por sí mismo se dará de baja. Este algoritmo asegura que la máquina virtual no llegue a ser particionada y continué ejecutándose como dos máquinas virtuales. No hay actualmente forma de que el maestro deje sus funciones a otro pvmd, así que éste es siempre parte de la configuración. Las estructuras de datos más importantes son las tablas de hosts y las tablas de tareas, las cuales describen la configuración de la máquina virtual y la localización de las tareas ejecutándose bajo pvmd. Además de ellas están las colas de mensajes y paquetes, y contextos de espera para almacenar información del estado de las multitareas en el pvmd. Cuando se inicia el pvmd se configura así mismo, ya sea como un maestro ó como un esclavo, dependiendo de sus argumento en la línea de comandos. AI momento de que pvmd crea y liga sockets para comunicarse con las tareas y con otros pvmds, abre un archivo de errores e inicializa las tablas. Para un pvmd maestro, la configuración puede incluir lectura del hostfile y determinación de parámetros default, tales como el nombre del host (hostname). Un pvmd esclavo obtiene sus parámetros de la línea de comandos y envía una línea de datos de regreso al proceso inicial para incluirse en la tabla de hosts. Después de configurarse así mismo el pvmd entra en un ciclo en la función work(). El corazón del ciclo work() es una llamada a select() que prueba todos los recursos de entrada para el pvmd ( tareas locales y la red ). Los paquetes que llegan son recibidos y enrutados a sus destinos. Los mensajes direccionados a pvmd son redireccionados a las funciones loclentry(), netentry() Ó schedentry(). 2.6.1 Inicio de pvmd AI adicionar un nuevo host a la máquina virtual, nuestro principal objetivo es obtener un pvmd ejecutándose con bastante información (por ejemplo, el identificador del pvmd maestro) para configurarlo completamente como los anteriores. Varios mecanismos están disponibles para iniciar un pvmd dependiendo del sistema operativo. Naturalmente nosotros queremos usar un método que sea disponible, seguro, rápido y fácil de instalar. Queremos evitar teclear passwords todo el tiempo, pero no queremos ponerlos en un archivo donde éstos puedan ser descubiertos. Los sistemas operativos no conocen todos estos criterios. Inetd2' daría rapidez, inicialización confiable, 2o Arquitectura de red. El DemonioPVM 37 2.6 pero requeriría que el administrador del sistema instalara PVM en cada uno de los hosts donde sería usado. lnicializar el pvmd con rlogin o telnet permitirá accesar a cualquier host con servicios rsh o conexión IP, no requeriendo privilegios especiales para ser instalado. La principal desventaja es el esfuerzo que se requiere para obtener un programa que se comunique y un script que trabaje confiablemente. Dos sistemas ampliamente disponibles son rsh y rexeco. Nosotros usamos ambos para cubrir muchas de las características requeridas. rsh es un programa, el cual puede ser usado por el pvmd para ejecutar comandos en un host remoto sin un password. Esto puede ser hecho colocando un password común en los hosts (requiriendo los servicios del administrador del sistema) o creando un archivo .rhost en el host remoto. El uso de rsh es frecuentemente impedido por tener un alto riesgo de seguridad, su uso es restringido al archivo .rhost. La alternativa rexec(1, es una función compilada dentro de pvmd diferente a rsh la cual no requiere password; rexecg requiere que el usuario proporcione uno a tiempo de ejecución, ya sea tecleando éste o colocándolo en un archivo .netrc (esto es, en realidad una mala idea). Cuando el pvmd maestro recibe un mensaje DM-ADD, éste crea una nueva entrada en la tabla de hosts. Las descripciones del host son guardadas en una estructura waitc-add ligada a un contexto de espera, y aún no adicionado a la tabla de hosts. Entonces crea una copia de pvmd ( pvmd‘ ), la cual hace el trabajo rudimentario, pasando a éste una lista de hosts y comandos a ejecutar. Cualquiera de los pasos en el proceso de inicio ( por ejemplo obtener la dirección IP del host, comenzar un shell, etc. ) pueden bloquear la ejecución por segundos o minutos, y el maestro pvmd debe ser capaz de responder a otros mensajes durante este tiempo. El pvmd’ tiene el número de host cero y se comunica con el pvmd maestro a través del protocolo pvmd-pvmd, aunque éste nunca se comunica con los pvmds esclavos. La operación de inicio tiene un contexto de espera en el pvmd maestro. En caso de interrupción del pvmd’, el pvmd maestro recibe una señal SIGCHLD, entonces llama a hostfailentry(), para dar de baja a pvmd‘. pvmd’ usa rsh o rexec() ( o inicialización manual ) para comenzar un pvmd en cada host nuevo, pasa parámetros al pvmd y obtiene una línea de información sobre la configuración. Cuando finaliza pvmd’ envía un mensaje DM-STARTACK de regreso al pvmd maestro; que contiene las líneas de configuración o los mensajes de error. El maestro analiza el resultado y completa los descriptores de host almacenados en el contexto de espera. Los resultados son regresados en un mensaje DM-ADDACK. Los hosts iniciados son configurados en la máquina usando el protocolo de actualización de tabla ( DM-HTUPD ). El diálogo de configuración entre pvmd’ y un nuevo esclavo es similar a lo siguiente: pvmd -> slave: [exec/ $PVM-ROO?Xb@md -s -dd-honkf dOa9w950f5a4096 3 dOa95c430000 slave ->pvrndI’ ddpro ~2312, arch <ALPHA>ip<dOa95~~Ob3f>mfnc4096> pvmd-> slave: €OF 38 Parallel Virtual Machine Los parámetros del pvmd maestro ( máscara de depuración, índice en la tabla de hosts, direcciones IP y MTU ) y el esclavo ( nombre del host, índice en la tabla de hosts y dirección IP ) son pasadas en la línea de comandos. El esclavo responde con su configuración ( número de revisión del protocolo pvmd-pvmd, arquitectura del host, direcciones IP y MTU), espera un EOF del pvmd’ y se desconecta del pipe 27 , poniéndose en un estado de ejecución de prueba (runstate = PVMDSTARTUP). Si recibe el resto de la información ( de la configuración ) desde el pvmd maestro, fuera de tiempo ( DDBAILTIME por default cinco minutos ) inicia su ejecución normal. De otra manera, este asume que hay algunos problemas con el maestro y termina. Si una tarea especial llamada hoster ”, tiene registrada con el pvmd maestro una prioridad para recibir los requerimientos DM-ADD, el sistema de inicialización normal no es usado. En lugar de bifurcar el pvmd’ un mensaje SM-STHOST es enviado a la tarea del “ hoster ”. Esto debe iniciar el proceso remoto como se describe arriba (usando cualquier mecanismo si éste quiere), paso de parámetros y colección de respuestas, entonces envía un mensaje SM-STHOSTACK devuelta a pvmd. Si la tarea del hoster falla durante una operación de adición, el pvmd usa el contexto de espera para recuperarse. Este asume que ninguno de los procesos fueron iniciados y envía un mensaje DM-ADDACK indicando un error del sistema. I‘ 2.6.2 Tabla de hosts La tabla de hosts es una estructura de datos y describe la configuración de la máquina virtual. Hay una tabla por cada demonio pvmd y son sincronizadas a través de todos los pvmds. La configuración de la máquina puede decaer durante un tiempo como hosts caigan Ó sus redes lleguen a estar desconectadas. Las tablas de hosts de los pvmds esclavos son modificadas por comandos del pvmd maestro usando mensajes. La operación de borrado es muy simple cuando se recibe un mensaje. La operación de adición es hecha más cuidadosamente, en tres fases internas para garantizar una disponibilidad global de los nuevos hosts sincronizados. Recibiendo un mensaje, cada esclavo conoce la identidad del nuevo pvmd, y el nuevo pvmd conoce la identidad de los previamente existentes. Cuando varios hosts son adicionados a la vez, el trabajo es hecho en paralelo y la tabla de hosts es actualizada al mismo tiempo, permitiendo que la operación completa se realice en menos tiempo que para un solo host. Los descriptores de host ( hostd ) pueden ser compartidos por varias tablas de hosts, esto es, cada hostd tiene un contador de referencia, de cuantas tablas de hosts lo incluyen. Cuando la configuración de la máquina cambia, la descripción para cada hosts ( excepto los adicionados y borrados ) permanece igual. Canales de comunicación entre procesos. El DemonioPVM 39 2.6 Las tablas de hosts tienen múltiples usos: describen la configuración de la máquina describen las colas de paquetes almacenadas 0 describen los buffers de mensajes. .Permiten a los pvmd manipular conjuntos de hosts, por ejemplo cuando escogemos un host candidato en cual se produce una tarea, Ó actualiza la configuración de la máquina virtual. 0 0 2.6.3 Tabla de tareas Cada pvmd mantiene una lista de todas las tareas que maneja. Cada tarea sin tomar en cuenta su estado, es un miembro de la lista, ordenada por ctid ( ID de tareas). Muchas tareas son también guardadas en una segunda lista, ordenada por fpid. La cabecera de ambas listas es un descriptor de tareas ficticio, apuntado al localizador de tareas global ( locltasks ). Puesto que el pvmd frecuentemente necesita buscar una tarea por TID o PID, deberá ser más eficiente para mantener estas dos listas como arboles balanceados. 2.6.4 Contexto de espera Los contextos de espera ( waitcs ) son usados por el pvmd para almacenar información del estado cuando un hilo de la operación 22 debe ser interrumpido. El pvmd no es realmente multi - hilado, pero puede realizar operaciones concurrentemente. Por ejemplo, cuando un pvmd tiene una llamada del sistema (desde una tarea) , algunas veces interactúa con otro pvmd. Puesto que éste sirve como un ruteador de mensajes, no puede bloquearse mientras espera que el pvmd remoto le responda. Cuando la contestación llega, el pvmd usa la información guardada en el waifcs para completar la llamada del sistema y contestar ( a la tarea ) . Los waifcs son numerados serialmente, y el número es enviado en la cabecera del mensaje con la solicitud y regresa con la contestación. El waitc incluye unos campos extra para manejar la mayoría de los casos restantes, y un apuntador, wa-spec el cual puede apuntar a un bloque de datos extra en casos especiales. Algunas operaciones necesitan más de una fase de espera, esto puede ser en serie o en paralelo, o aún anidadas ( si el pvmd remoto tiene que hacer otra solicitud ). En el caso paralelo un waitc es creado para cada hosts remoto. Todas las operaciones paralelas que existen en múltiples hosts son conjunciones: un grupo de waitcs semejantes termina, esperando que cada waitc en el grupo haya terminado. Finalmente, cuando el waitc que termina es sólo uno en su grupo, la operación está completa. ** Es la unidad básica de ejecucibn así como el objeto de trabajo más pequeño, y se ejecuta en el contexto de una tarea, comparte los recursos de la tarea con otros hilos. 40 Parallel Virtual Machine 2.6.5 Detección y recuperación de fallas Desde el punto de vista de pvmd, la tolerancia a fallas significa que éste puede detectar cuando un pvmd remoto es dado de baja y recuperarse sin que se caiga el sistema. Si el pvmd con falla es el maestro, se tiene que bajar el sistema. Desde el punto de vista de las tareas, la detección de fallas significa que cualquier operación que involucra una baja de hosts regresa una condición de error, en vez de suspenderse. La detección de fallas se origina en el protocolo pvmd-pvmd, cuando un paquete esta sin reconocerse durante tres minutos. La función hostfailentry() es llamada, está revisa waitlist y termina cualquier espera involucrada con la falla del host. 2.7 La Biblioteca de programación. La biblioteca libpvm es una colección de funciones que permiten a las tareas tener interfaz con el pvmd y con las demás tareas. Contiene funciones para empaquetar y desempaquetar mensajes. Una vez que ejecuta llamadas al sistema, PVM usa las funciones de mensajes para enviar solicitudes de servicio al pvmd y recibir respuestas. La biblioteca de programación está escrita en C y de ahí que naturalmente soporta aplicaciones C y C++, e incluye muchas funciones para la programación de la interfaz. En la primer llamada a cualquier función libpvm, se llama a pvmbeatasko para inicializar el estado de la biblioteca y conectar la tarea a su pvmd. Los detalles de conexión son ligeramente distintos entre tareas anónimas (no creadas por pvmd) y tareas creadas por spawn. El pvmd publica las direcciones del socket, donde serán escuchadas para su conexión, en /tmp/pvmd.<uid>. Este archivo contiene una línea tal como “7f000001:06f7” y es como un atajo; las tareas creadas, heredan la variable de ambiente PVMSOCK, la cuál contiene la misma información. Una tarea creada con spawn necesita un segundo bit de datos para reconectarse satisfactoriamente, es decir, su ID de proceso. Cuando una tarea es producida por el pvmd, un descriptor de tarea es creado durante la fase exec. El descriptor es necesario, por ejemplo, para esconder cualquier mensaje que llegue para la tarea antes de que sea completamente reconectada y esté lista para recibirlo. Durante la reconexión, la tarea identifica al pvmd por medio de su PID. Si la tarea es siempre el hijo de pvmd, entonces este podría usar su PID como el regresado por getpid(), para identificarse. Para permitir que otros procesos intervengan, tales como depuradores, el pvmd pasa una variable de ambiente, PVMEPID a la tarea, la cual usa éste valor en vez de su PID real. La tarea también pasa su PID real, de ahí que puede ser controlado por el pvmd por medio de señales. pvmbeatask() crea un socket TCP y hace una conexión propia con el pvmd. Cada uno debe proporcionar su identidad a el otro, para prevenir que un usuario distinto entre al sistema. El pvmd y las tareas crean un archivo en su propio /tmp y éste es modificado sólo por sus UID. Intentan escribir cada uno en los archivos del otro para verificar sus propios archivos y cambiarlos. Si esto tiene éxito, han probado sus identidades. Note que esta autenticación es solamente tan fuerte como el filesystem y la autoridad del root en cada máquina. . La Biblioteca de Programación 41 2.7 Un número serial de protocolo, es comparado cuando se quiera que una tarea se conecte a su pvmd o a otra tarea. Éste número debe ser incrementado cuando un cambio en el protocolo haga a éste incompatible con la versión anterior. Desconectar es mucho más sencillo. Esto puede ser hecho por un close desde cualquier terminal, por ejemplo por la salida del proceso tarea. La función pvm-exit() ejecuta un shutdown limpio, tal que el proceso puede ser conectado de poco tiempo después (obtendría un diferente TID ). 2.8 Comunicación. La comunicación PVM es en base a los protocolos Internet TCP y UDP. Los manejadores de protocolo PVM se ejecutan como procesos normales ( pvmds y tareas ), sin modificar el sistema operativo. Naturalmente, el desempeño de paso de mensajes realizado es degradado por esta estrategia. El desempeño debería de ser mejor si el código fuera integrado en el kernel, o alternativamente, la interfaz de red fuera disponible a los procesos, pasando por alto el kernel. Sin embargo, cuando se ejecuta en Ethernet, los efectos de su gasto general son mínimos. El desempeño es determinado por la calidad del código de red en el kernel. Cuando se ejecuta en redes más rápidas Ó ruteos directos tarea-tarea mejora el desempeño, minimizando el número de saltos entre uno y otro. Esta sección describe cómo y dónde TCP y UDP son empleados, y describe los protocolos PVM construidos sobre ellos. Hay tres casos a considerar: 0 0 0 comunicación pvmd -pvmd comunicación tarea pvmd comunicación pvmd -tarea - 2.8.1 Comunicación Pvmd-Pvmd Los demonios PVM se comunican con los otros a través de sockets UDP. Un UDP es un servicio de envío no confiable, el cual puede perder, duplicar o reordenar paquetes, nosotros necesitamos un mecanismo con reconocimiento y recuperación. UDP impone un límite en la longitud de un paquete, el cual solicita a PVM fragmentar mensajes grandes. Usando UDP nosotros construimos un servicio confiable que envía paquetes con una secuencia, y recibe una capa de mensajes, proporcionando una conexión similar a los pasos TCP, pero con registros limitados. Si se utiliza TCP, hay tres factores que lo hacen inapropiado: 0 Primero, la máquina virtual debe ser capaz de escalar a cientos de hosts. Cada conexión TCP abierta consume un descriptor de archivo en el pvmd, y algunos sistemas operativos !imitan el número de archivos abiertos a 32. Un sólo socket UDP puede enviar y recibir, cualquier número de sockets remotos UDP. 42 Parallel Virtual Machine Una máquina virtual compuesta de N hosts necesita hasta N(N-1)/2 conexiones, las cuales son caras de establecer. Puesto que la identidad de cada host en una máquina virtual es conocida, nuestro protocolo puede ser inicializado desde un estado correcto sin una fase de conexión. Finalmente, el servicio de paquetes pvmd-pvmd debe ser capaz de detectar cuando los pvmds remotos ó los hosts se han perdido ó la red se ha caído. Para realizar esto, necesitamos colocar temporizadores fuera de la capa del protocolo colocado. Todos los parámetros y valores de default para la comunicación pvmd-pvmd son definidos en el archivo ddpr0.h. También están especificados los código de mensaje para los puntos de entrada (DM-XXX). Un número serial (DDPROTOCOL) es verificado siempre que un pvmd es adicionado a la máquina virtual. Éste debe ser incrementado si se realiza un cambio en el protocolo que lo hace incompatible con las versiones anteriores. Las cabeceras para paquetes y mensajes son mostrados en las figuras 2.3 y 2.4. Valores de múltiple-byte son enviados en "el órden de bytes de la red", esto es, primero el byte más significativo. Byte O 1 2 ................................................. .......................................................... . ................................... < 3 ............................................................................................. TID fuente ..................................................................................................................................... ! Número de secuencia Número Ack (de reconocimiento) j ...................................................................................................................................... Sin uso ...................................................................................................................................... FIGURA 2.3 Cabeceras de paquetes pvmd- pvmd Los campos fuente y destino almacenan los TlDs de la fuente y el destino final del paquete, sin importar la ruta que éste toma. ..................................................................................................................................... Código del mensaje ..................................................................................................................................... i Codificación del mensaje o Número de Contexto de Espera Remoto j ..................................................................................................................................... FIGURA 2.4 Cabecera del mensaje 2.8 Comunicación 43 Los números de secuencia y reconocimiento inician en 1 e incrementan su valor hasta 65535. Éstos son inicializados en la tabla de hosts para nuevos hosts, de ahí que la conexión no necesita ser establecida explícitamente entre pvmds. Las bits de bandera son definidos como sigue: SOM,EOM. Marcan el primer y último fragmento de un mensaje. Los fragmentos que intervienen tienen limpios ambos bits. Éstos son usados por las tareas y por el pvmd para detectar límites de mensaje. DAT. Significa que los datos están contenidos en el paquete y el número de secuencia válido. Si el paquete es de longitud cero, también debe ser enviado. ACK . Significa que el campo de número de reconocimiento es válido. Este bit puede ser combinado con el bit DAT para llevar reconocimiento de un paquete de datos. Sin embargo actualmente el pvmd genera un paquete de reconocimiento para cada paquete de datos, tan pronto como ,éste es recibido, a fin de obtener más seguridad en los datos. FIN. Señala que el pvmd ha terminado la conexión. Un paquete con el bit FIN colocado ( y DAT limpio ) señala la primera fase de un showdown ordenado. Cuando un reconocimiento llega, un paquete final es enviado con ambos bits FIN y ACK colocados. El estado de una conexión entre pvmds es guardado en una entrada en la tabla de hosts ( estructura hostd ). El estado de una paquete es guardado en su estructura pkt. Los paquetes en espera de ser enviados a un hosts son encolados en hd'tdx en forma FIFO. Los paquetes pueden ser creados en tareas locales Ó por el pvmd y son anexados a la cola por el código de ruteo. No son usadas colas de recepción porque los paquetes que entran son pasados inmediatamente a través de otras colas de envío ó son reensamblados en mensajes (o descartados). Cuando el mensaje es reensamblado completamente, el pvmd pasa éste a la función netentry(), la cual lo despacha a su punto de entrada apropiado. Para probar el desempeño sobre redes de alta latencia, el protocolo permite la salida de múltiples paquetes en una conexión, así que dos o más colas son requeridas. hd-opq almacena listas de paquetes sin reconocimiento, hd-rxq almacena paquetes recibidos fuera de la secuencia, hasta que éstos puedan ser aceptados. Cuando el paquete arriba al pvmd destino, cada uno genera un paquete de reconocimiento y lo envía de regreso al transmisor. La diferencia de tiempo entre enviar un paquete y recibir un reconocimiento, es usado para estimar el tiempo de ida y vuelta al host remoto. Cuando el reconocimiento de un paquete llega, es removido del hd-opq y descartado. Cada paquete sin reconocimiento tiene un temporizador que lleva la cuenta, y es reenviado hasta que es admitido por el pvmd remoto. Si un paquete se extingue debido a que se terminó su tiempo, se asume que el host remoto Ó el pvmd fueron dados de baja o están incomunicados, y el pvmd ya no intenta enviar paquetes, llamando a la función hostfailentry(). 44 Parallel Virtual Machine - 2.8.2 Comunicación tarea Pvmd Una tarea se comunica con su pvmd sobre una conexión TCP. UDP debe parecer más apropiado, porque ya es un servicio de envío de paquetes, mientras que TCP es un protocolo fluido. Desafortunadamente UDP no es confiable, ya que puede perder paquetes. Puesto que un sistema desconfiable requiere un mecanismo de recuperación (con temporizadores) en ambas terminales y porque una suposición en el diseño es que la tarea no pueden ser interrumpidas mientras se realizan ES,solo se puede usar TCP. Nota: originalmente hemos usado datagramas de dominio UNIX, para la conexión pvmd tarea. Mientras esto parece ser confiable, depende de la implantación del sistema operativo, éste protocolo no es ampliamente disponible como TCP. - 2.8.3 Comunicación pwmd tarea El sistema de envío de paquetes entre pvmd y tareas es mucho más sencillo que entre dos pvmds porque TCP ofrece un envío confiable. El pvmd y la tarea mantienen una cola FIFO de paquetes destinados para cada uno e intercambia lectura y escritura en la conexión TCP. La principal desventaja cuando usamos TCP para una pareja pvmd-tarea es que el número de llamadas al sistema necesarias para transferir paquetes entre una tarea y pvmd, aumenta. Sobre UDP, sólo sendto() y recvfrom() serán necesarios para transferir un paquete. Puesto que TCP no proporciona marcas de registro (para distinguir paquetes de uno u otro), tenemos que enviar el total de paquetes en la cabecera. Así un paquete también puede ser enviado por una sola llamada write(), pero debe ser recibido por dos llamadas read(), la primera para obtener la cabecera y la segunda para obtener los datos. Sin embargo, cuando hay mucho de tráfico en la conexión pvmd tarea, una optimización sencilla puede reducir el número promedio de llamadas de lectura hasta una por paquete. Así, cuando leemos el cuerpo del paquete, el número de lecturas solicitadas aumenta por el tamaño de la cabecera del paquete, éste puede tener éxito para obtener ambos, el cuerpo del paquete actual y la cabecera del paquete siguiente, al mismo tiempo. - La cabecera del paquete es mostrada en la figura 2.5. Los números de secuencia no son necesarios, y las únicas banderas son SOM y €OM, las cuales son usadas en el protocolo pvmd-pvmd. Comunicación 45 2.8 1 Byte O 2 3 ..................................................................................................................................... TID destino ................................................................... .............................................................. TID fuente .................................................................................................................................... Longitud del paquete ........................................................................................................ ........................... i i i E : S i 0 ; 0 : M ! M! . Sin uso FIGURA 2.5 2.8.4 Mensajes en el Pvmd Las funciones pkinf() y pksfr() anexan enteros y cadenas terminadas en NULL, respectivamente, dentro de un mensaje. Las correspondientes funciones de desempaquetamiento son upkint() y upksfr(). Los enteros sin signo son empaquetados con su mismo signo, pero son desempaquetados usando upkuinf(). Otra función, upksfra//oc(), asigna espacio dinámicamente para la cadena que ésta desempaca. Todas éstas funciones usan funciones de bajo nivel byfepk() y byfeupk() para leer y escribir bytes para y desde mensajes. Los mensajes son enviados llamando a la función sendmessage(), la cual enruta el mensaje a su dirección destino. Para un destino remoto, los fragmentos de mensajes son unidos para empaquetarlos y enviarlos por la capa de ruteo de paquetes. Si un pvmd programa una solicitud y se escoge así mismo como destino, no tiene que tratar el mensaje en forma distinta, si no que envía el mensaje como es usual y espera una contestación, la cual viene inmediatamente. Los mensajes nuevos son reensamblados desde los paquetes por /oc/inpkf() si es desde una tarea, Ó por nefinpkf() si son de otro pvmú.Una vez reensamblados, el punto de entrada apropiado es llamado ( /oc/enfry(),netenfry(), o schedenfry() ). Las únicas funciones que los pvmús hacen automáticamente son pings a otros pvmús para verificar la red y borrar hosts dados de baja de la configuración de la máquina. 2.8.5 Codificadores de mensajes Libpvm proporciona un conjunto de funciones para empaquetar cada tipo de datos dentro del mensaje y recobrar éstos en otra terminal. Cualquier tipo de datos puede ser empaquetado dentro del mensaje en uno de varios formatos de codificación. Cada buffer de mensajes almacena un vector de funciones para codificar/decodificar todos los tipos, el cual es inicializado cuando el buffer es creado. 46 Parallel Virtual Machine Actualmente hay cinco conjuntos de codificadores (y decodificadores) definidos. El vector codificador/decodificador usado en un buffer es determinado por el formato del parámetro pasado en pvm-mkbuf() cuando se crea un nuevo mensaje, y por el campo de codificación de la cabecera del mensaje cuando el mensaje es recibido. Los dos más comúnmente usados, empaquetan datos dentro de “raw“ (host nativo) y formatos “default” (XDR). En “Inplace”, el dato es dejado en el lugar hasta que el mensaje es enviado. La codificación “foo” puede empaquetar sólo enteros y cadenas de caracteres, y debe ser usado cuando se compone un mensaje para el pvmd. Finalmente los decodificadores “alien” serán instalados cuando un mensaje recibido no puede ser desempaquetado porque su formato no es igual a cualquiera de los decodificadores disponibles en la tarea. Esto permite al mensaje ser almacenado o enviado, pero cualquier intento por leer un dato de éste resultará en un error. 2.8.6 Funciones que manejan empaquetamiento Cuatro funciones manejan todo el tráfico de paquetes en la entrada y salida de libpvm. Mroute() es llamada por funciones de alto nivel, tales como pvm-send() y pvm-recv() para enviar y recibir mensajes. Éste establece cualquier ruta necesaria antes de llamar a mxfer(). Mxfer() bloquea la transmisión hasta que un mensaje es recibido o hasta que termine su temporizador. Ésta llama a mxinpuf() para copiar fragmentos de mensajes dentro de la tarea y los reensambla en un mensaje. Pvmmctl() es llamado por mxinput() siempre que un mensaje de control es recibido. 2.8.7 Mensajes de Control Los mensajes de control son enviados como mensajes regulares a una tarea. Cuando la tarea lleva un mensaje de control, en vez de encolarlo, lo pasa a la función pvmmcfl() y entonces lo descarta. Como loc/enfry() en el pvmd, pvmmcfl() es un punto de entrada en la tarea, causando que tome una acción. La principal diferencia es que los mensajes de control no pueden siempre ser usados para obtener la atención de las tareas, puesto que ésta debe de estar en mxfer(), enviando ó recibiendo los mensajes. Las siguientes etiquetas de mensajes de control son definidas. Las primeras tres son usadas por los mecanismos de ruteo directo. Los futuros mensajes de control pueden ser usados para hacer cosas tales como colocar depuradores y máscaras de seguimiento en la tarea conforme ésta se ejecute. Etiqueta TC-CONREQ TC-CO NACK TC-TASKEXIT TC-NOOP TC-OUTPUT Significado Solicitud de conexión Reconocimiento de conexión Tareas que salieron/no han salido No hacer nada Reclama salida estándar de los hijos Comunicación 47 2.8 2.8.8 Ruteo directo de mensajes El ruteo directo permite a una tarea enviar mensajes a cualquier otra tarea a través de una liga TCP, evitando que la cabecera se copie en ellos a través de los pvmds. Este mecanismo es implementado enteramente en libpvm, tomando ventaja de las facilidades de la notificación y de mensajes de control. Por default, cualquier mensaje enviado a otra tarea es ruteado al pvmd, el cual lo encamina a su destino. Si el ruteo directo está disponible (pvmrouteopt=PvmRouteDirect) cuando un mensaje (direccionado a una tarea) es pasado a mroute(), intenta crear una ruta directa, si no existe ya. La ruta puede ser no valida ( si el destino no existe) o bien, concedida Ó refutada por la tarea destino. 2.8.9 Envío Múltiple Libpvm proporciona la función pvm-mcast(), para enviar un mensaje a varios destinos simultáneamente, esperando que utilice menos tiempo que con varias llamadas pvm-send(). La implantación actual permite rutear varios mensajes a través de pvmd y utiliza una salida de l : N para simplificar la propagación de la tolerancia a fallas. El problema es estar seguro que la falla de un host no causará la pérdida de cualquier mensaje. La capa de ruteo de paquetes de la pvmd coopera con iibpvm para enviar en forma simultánea un mensaje. 2.9 Consideraciones generales de desempeño. No hay limitaciones para los estilos de programación en PVM de los que un usuario puede hechar mano para resolver una problemática. Cualquier control específico y estructura puede ser implementada en PVM mediante el uso apropiado de las construcciones. Por otro lado hay ciertas consideraciones que el programador de aplicaciones debe tener presente cuando programe cualquier sistema de paso de mensajes. La primer consideración, es la granularidad de las tareas. Esto es típicamente medido como la razón entre el número de bytes recibidos por un proceso y el número de operaciones de punto flotante realizadas en el mismo. Haciendo algunos cálculos sencillos respecto a la velocidad computacional de las máquinas en la configuración PVM y el ancho de banda en la red, un usuario puede obtener una aproximación de la granularidad de la tarea que trata de utilizar. Una mayor granularidad y la más alta velocidad implican frecuentemente un aumento en el paralelismo disponible. La segunda consideración, es el número de mensajes enviados. El total de bytes recibidos puede ser enviado en muchos mensajes pequeños o en pocos mensajes grandes. Mientras usar pocos mensajes grandes reduce el tiempo de reconstrucción del mensaje, esto no siempre causa la disminución del tiempo total de ejecución. Hay casos donde pequeños mensajes pueden traslaparse con otra operación, de ahí que su cabecera es marcada. La capacidad para traslapar mensajes con cálculos y el número Óptimo de mensajes por enviar depende de la aplicación. 48 Parallel Virtual Machine Una tercer consideración es, si se debe convertir la aplicación a paralelismo funcional ó a paralelismo de datos. Definimos el paralelismo funcional como máquinas diferentes desarrollando tareas diferentes en una configuración PVM. Por ejemplo una supercomputadora de vector puede resolver una parte de un problema adaptado para vectorización, un multiprocesador puede resolver otra parte del problema adaptado a paralelización y una estación de trabajo gráfica puede visualizar los datos generados, en tiempo real. Cada máquina desarrolla diferentes funciones ( posiblemente sobre los mismos datos ). En el modelo de paralelismo de datos, el dato es particionado y distribuido entre todas las máquinas en la configuración PVM. Las operaciones ( frecuentemente similares ) son realizadas en cada conjunto de datos y la información es pasada entre los procesos hasta que el problema esté resuelto. El paralelismo de datos ha sido común en multiprocesadores de memoria distribuida, porque éste requiere sólo escribir un programa paralelo que es ejecutado en todas las máquinas y porque muchas veces puede ser escalable a un centenar de procesadores. 2.1 O Consideraciones particulares de la red. Hay consideraciones adicionales para quien desarrolla aplicaciones en paralelo y desea ejecutarlas sobre una red de máquinas. Su programa paralelo estará compartiendo la red con otros usuarios. Estos ambientes multiusuarios y/o multitareas afectan la comunicación y el desempeño computacional de sus programas en forma compleja. Primero considere los efectos de tener distinto poder computacional en cada máquina de la configuración. Esto puede ser debido a que se tiene una colección heterogénea de máquinas en la máquina virtual, las cuales difieren en su velocidad computacional. Sólo, entre diferentes marcas de estaciones de trabajo se puede tener dos ordenes de magnitud y poder computacional distintos. En supercomputadoras puede ser más grande la diferencia. Pero, aún si el usuario especifica una colección de máquinas homogéneas, él puede ver grandes diferencias en el desempeño de cada máquina. Esto es causado por sus propias multitareas Ó por las tareas de otros usuarios en un subconjunto de las máquinas que se encuentran en la configuración. Si el usuario divide su problema en partes idénticas una para cada máquina entonces, la consideración de arriba puede afectar adversamente su desempeño. Su aplicación se ejecutará tan lenta como la tarea en la máquina más lenta. Si las tareas se coordinan con cada una de las otras, entonces aún las máquinas más rápidas se alentarán por esperar los datos de las tareas lentas. La segunda consideración son los efectos de retardo de mensajes grandes a través de la red. Esto puede ser causado por la distancia entre las máquinas si se está empleando una red de área amplia ó por la congestión causada por los programas propios ó por los de otros usuarios de la red. Considerando que las redes Ethernet tienen un bus. Así que sólo un mensaje puede estar en el bus en cualquier momento. Si la aplicación está diseñada para que cada una de estás tareas sólo envié mensajes a su tarea vecina, se podría asumir que no hay congestión. En un multiprocesador de memoria distribuida, como en el Intel Paragon, no habría congestión y todos los envíos Consideraciones particulares de la red 49 2.10 deberían procesarse en paralelo. Pero sobre una Ethernet los envíos son conducidos serialmente, variando el tiempo de retardo (latencias) en los mensajes que llegan de tareas vecinas. Otras redes tales como Token Ring, FDDl y HiPPI, todas tienen propiedades que pueden causar variación en la latencia. El usuario debe determinar si la tolerancia de la latencia debe ser diseñada dentro de su algoritmo. La tercer consideración es que el desempeño y la efectividad en el ancho de banda de la red, cambia dinámicamente conforme más usuarios comparten sus recursos. Una aplicación puede obtener muy buena velocidad durante su ejecución y una pobre velocidad con la misma ejecución pocos minutos después. Durante la ejecución de una aplicación ésta puede tener un patrón de sincronización fuera de lo normal, causando que algunas tareas estén esperando datos. En el peor caso, un error de sincronización puede existir cuando en una aplicación la carga dinámica de la máquina fluctúa en una forma particular. Como tales condiciones no se pueden reproducir fácilmente, éstos errores son muy difíciles de encontrar. 2.11 Balance de Carga En un ambiente de redes multiusuario, hemos encontrado que el balance de carga puede ser el factor de desempeño más importante. Hay muchos esquemas de balance de carga para programas paralelos. En está sección mostraremos los tres más comunes e importantes esquemas utilizados en redes de computadoras. El método más sencillo, es el de balance de carga estático. En este método el problema es dividido y las tareas son asignadas a los procesadores sólo una vez. La partición de datos puede ocurrir fuera de línea, antes que el trabajo sea comenzado Ó puede ocurrir como un paso anterior a la aplicación. El tamaño de las tareas Ó el número de tareas asignadas a una máquina, puede variar a causa del distinto poder de cálculo en las máquinas. Puesto que todas las tareas pueden ser activadas desde el inicio, éstas pueden comunicarse y coordinarse con cualquier otra. En una red ligeramente cargada, el balance de carga estático puede ser bastante efectivo. Cuando el balance de carga está variando, el esquema de balance de carga dinámico es requerido. El método más común es llamado el paradigma de Pool of Task ( Banco de tareas ). Éste es típicamente implementado en un programa maestro/esclavo, donde el programa maestro crea y almacena el pool y cede tareas a los programas esclavo conforme van terminando. El pool es usualmente implementado como una cola, y si las tareas varían en tamaños entonces las tareas más grandes son colocadas cerca de la cabeza de la cola. Con este método todos los procesos esclavos están ocupados como tantas tareas haya en el pool. Un ejemplo del paradigma del pool de tareas puede ser visto en el programa xep proporcionado con el código fuente bajo pvm3/xep. Puesto que las tareas se inician y se detienen arbitrariamente con este método, éstas son mejor adaptadas a aplicaciones que no requieren comunicación entre programas esclavo, y sólo se comunican con el maestro y con los archivos. Un tercer esquema de balance de carga, no usa un proceso maestro, requiere que en un tiempo determinado todos los procesos reexaminen y redistribuyan su carga de trabajo. 50 Parallel Virtual Machine Algunas implantaciones nunca se sincronizan con todos los procesos, pero en cambio distribuyen su exceso de carga solamente con sus vecinos. Otras esperan hasta que un proceso señale que su balance de carga ha obtenido alguna tolerancia mayor antes de ir a una redistribución de carga y esperar un tiempo fijo. CAPíTULO 3 Aplicaciones bajo (PVW 3.1 Ejemplos de programación de PVM en lenguaje C . Esta sección contiene dos programas de ejemplo, cada uno ilustra una forma distinta de organizar aplicaciones en P V M 3. Estos ejemplos y otros son proporcionados con el código fuente en PVM-ROOT/examples. El primer ejemplo es un modelo maestro/esclavo con comunicación entre esclavos. El segundo es un programa con el modelo un programa - múltiples datos (Single Program Multipie Data, SPMD). En el modelo maestro/esclavo, el programa produce y direcciona un número específico de programas esclavo, los cuáles realizarán las operaciones. P V M no está restringido a éste modelo. Por ejemplo, cualquier tarea P V M puede inicializar procesos en otras máquinas. Pero un modelo maestro/esclavo es un paradigma de programación útil y sencillo de ilustrar. El maestro llama a pvm-myfid(), la cuál, como es la primer llamada PVM, registra ésta tarea en el sistema PVM. Éste entonces, llama a pvm-spawn() para ejecutar un número dado de programas esclavo en otras máquinas. El programa maestro contiene un ejemplo de envío de mensajes en PVM. El maestro envía a los esclavos el número de esclavos comenzados y una lista de todos los tids de los esclavos. Cada esclavo llama a pvm-mytid() para determinar su ID de tarea en la máquina virtual, luego usa el dato para enviarlo a el maestro, para crear un orden único de O a nproc-I . Subsecuentemente, pvm-send() y pvm-recv() son usados para pasar mensajes entre procesos. Cuando terminan, todos los programas P V M llaman a pvm-exif() para desconectar cualquier sócket de los procesos, restablecer buffers de I10 y guarda el estado de los procesos que se están ejecutando. En modelo SPMD, sólo hay un programa y no existe un programa maestro directamente. Tales programas son algunas veces llamados hostless. En el segundo ejemplo, el usuario comienza la primer copia del programa, verificando pvm-parent(), ésta 51 52 Aplicaciones bajo PVM copia puede determinar que ha sido creada por PVM y que debe ser la primer copia. Ésta después produce múltiples copias de sí misma y las pasa el arreglo de ti&. Hasta este punto, cada una de las copias es igual y puede trabajar su partición de datos en colaboración con los otros procesos. Usando pvm-parent() evita comenzar el programa SPMD desde la consola pvm, porque pvm-parent() regresará el fid de la consola. Este tipo de programas SPMD, debe ser comenzado desde el prompt de UNIX. VERSIÓN MAESTRO/ESCLAVO PROCESO MAESTRO # include pvm3.h” #define SLAVENAME “slavel” void main(void)( int mytid; int tids[32]; int n; int nproc; int i; int who; int msgtype; float datos[ 1001; resuIt[32]; mytid=pvm-mytido; /* identificador de tareas */ /* identificador de esclavos */ /* número de datos */ /* número de esclavos */ /* contador para el ciclo for */ /* de que esclavo se trata */ /* etiqueta de mensajes */ /* arreglo de datos */ /* resultado de los esclavos */ /* asigna a mytid el TID de el proceso en la máquina virtual, este TID es único */ puts(“ ¿Cuantos programas esclavos quieres (1-32): ? scanf(“%d”,&nproc); pvm-s pawn(SLAVENAM E,(char**)O, O,”” ,nproc,tids); /* comienza nproc cop¡as de ‘I); un archivo ejecutable en la máquina virtual, SLAVENAME tiene el nombre de este archivo ejecutable; este archivo debe de estar colocado en $HOME/pvm3/bin/SGI ya que es ahí donde PVM busca por default los archivos ejecutables, si no se quiere dejar ahí entonces se debe de indicar cual es el nuevo path para este host en el archivo rhosts, (char**)O es un puntero a un arreglo de argumentos de tareas, la bandera O es debido a que cualquier host de la máquina virtual puede iniciar la tarea por lo que PVM escoge el host mas apropiado, las en ellas se pondría el host si la bandera anterior fuera distinta de cero, nproc es el número de esclavos que escogió el usuario y finalmente tids es el arreglo de tids para que PVM lo llene con los tids de los esclavos creados */ ‘I” n= 100; inicializa-datos(); /* número de datos a procesar */ /* inicializa datos */ 3.1 Ejemplos de programación de PVM en lenguaje C 53 /* transmite datos iniciales para tareas esclavos */ /* limpia el buffer y crea uno nuevo para empaquetar pvm-initsend( PvmDataRaw); un nuevo mensaje, como los mensajes sólo se enviaran entre la misma máquina entonces se usa PvmDataRaw */ /* empaqueta el buffer de mensajes activo, en el pvm-pkit(&nproc, 1 ,I ) ; primer argumento se pone el arreglo a enviar, en este caso se pondrá el número de procesos esclavos iniciados, el segundo argumento es el número total de items a enviar y el tercer argumento el número de pasos para empaquetar el item */ pvm-pkit(&n,l , I ) pvm-pkfloat(dato,n, 1); pvm-mcast(fiús,nproc,O); /* se empaqueta el número de datos */ /* se empaqueta el arreglo de datos de punto flotante en un sólo paso */ /* Multi envía estos datos a el buffer activo de mensajes con lo que cada esclavo recibe una copia de los datos, fids es el arreglo de identificadores de esclavos con etiqueta cero */ /* espera resultados de los esclavos */ mstype =5; /* etiqueta del maestro */ for (¡=O;icnproc;i++){ /* recibe un mensaje, bloquea un proceso hasta que un mensaje con etiqueta msgtype ha arribado esto debido a la etiqueta -1 pvm-recv() esta aceptando un mensaje desde cualquier proceso quien ha igualado msgtype */ pvm-upkit(&who,l , I ) ; /* desempaqueta el TID de el esclavo que envío sus datos */ pvm-upkfloat(&result[who], 1,l); /* desempaqueta los resultados de el esclavo who */ printf(“ Conseguí %f desde % d\n “,result[who],who); pvmexit (); } /* fin for */ pvm-recv(- 1,msgtype); }/* fin maestro */ 54 Aplicaciones bajo PVM PROCESO ESCLAVO # include “ pvm3.h” void main(void){ int mytid; int tids[32]; int n; int m; int nproc; int i; int master; int msgtype; float datos[ 1001; float result[32]; /* identificador de tareas */ /* identificador de esclavos */ /* número de datos */ /* que esclavo soy */ /* número de esclavos */ /* contador para el ciclo for */ /* identificador para el maestro */ /* etiqueta de mensajes */ /* arreglo de datos */ /* resultado de los esclavos */ mytid=pvm-m ytid() ; /* registra el proceso en la máquina virtual */ /* Recibe datos del programa maestro */ msgtype=O; pvm-recv(-I ,msgtype); pvm-upkint(&nproc,l ,1); pvm-upkint(fids,nproc, 1); pvm-upkint (&n,l , I ) ; pvm-upfloat(dato,n, 1); /* etiqueta */ /* recibe datos de cualquier proceso */ /* desempaqueta el número de esclavos */ /* desempaqueta el arreglo de tids */ /* desempaqueta el número de datos */ /* desempaqueta el arreglo de datos */ /* Determina que esclavo es */ for(¡=O; i<nproc;i++){ if(myt id==ti&[ ¡I){ me=¡; break; 1 /* trabaja con los datos */ result = works( me,n,datos,tids,nproc); /* envía el resultado a el maestro */ pvm-initsend( PvmDataDefault); pvm pkint(&me,l ,I); pvmIpkfloat(&result,l ,I); msgtype =5 ; master = pvm-parent(); /* limpia el buffer y se prepara para empaquetar un nuevo mensaje */ /* empaqueta que esclavo soy */ /* empaqueta el resultado */ /* retorna el TID de el proceso que produjo esta tarea en este caso este proceso fue creado por el proceso mastro */ Ejemplos de programación de PKM en lenguaje C 55 3.1 pvm-send(master,msgtype); /* envía los datos en el buffer activo master es el destino con etiqueta msgtype */ pvmexit(); } /* fin esclavo */ VERSION SPMD #define NPROC 4 #include “pvm3.h” void main(void) { int mytid; int fids[NPROC]; int me; int i; /* Número de copias a generar *I mytid = pvm-mytido; fiús[O]=pvm-parent(); if (fiús[O] < O) { fids[O]=mytido; me = O; I* Se registra en PVM *I /* Registra si es el padre o un hijo */ /* ldentificador de tareas *I /* Arreglo de fiús*/ /* ldentificador de las tareas producidas */ /* Contador del ciclo for *I I* Entonces yo soy el padre *I I* Inicio copias de mi mismo *I pvm-spawn(“sped”, (char **)O, O, “”,NPROC-l ,&fids[l]); /* Envía el arreglo de fids*/ pvmjnitsend(PvmDataDefau1t); /* a los hijos *I pvm-p kint (fids,NPROC,I); pvm-mcast(&fids[ 11, NPROC-1,O); } /* Entonces, soy un hijo *I else{ /* Recibe el arreglo de fids*/ pvm-recv( fids[O],O); pvm-upkint( fids,NPROC, 1); for(i=l ; i<NPROC;i++) if(mytid == tiail ) {me = i; break; } /* Todas las NPROC tareas son iguales ahora e e e 1 56 Aplicaciones bajo PVM dowork(int me,int *fids,int nproc){ int token,dest,count=l ,stride=l ,msgtag=4; if( me==O ){ token = fiús[O]; pvm-initsend( PvmDataDefault ); pvm-pkint ( &token,count,stride ); pvm-se nd ( ti&[ me+1] ,msgtag); pvm-recv(fids[nproc- I], msgtag); else{ 1 1 pvm-recv( fiús[me-I 1, msgtag); pvm-upkint(&token,count,stride); pvm-initsend( PvmDataDefault); pvm-p kint(&token,count ,stride); dest=(me==nproc-I)? fiús[O]:fiús[me+l]; pvm-send(dest,msgtag); 1 3.2 Escribiendo aplicaciones. Los programas de aplicación vistos en PVM son ejemplos generales y flexibles de programación paralela que soportan el modelo de paso de mensajes. Éstos programas pueden ser ejecutados en tres diferentes niveles: rn Modo transparente, en el cual las tareas son automáticamente ejecutadas en los hosts más apropiados ( generalmente los menos cargados ). Modo dependiente de la arquitectura, en el cual el usuario puede indicar las arquitecturas especificas en las que determinadas tareas serán ejecutadas. Modo de bajo nivel, en el cual un host particular puede ser especificado para ejecutar determinadas tareas. Tal estratificación permite flexibilidad, mientras conserva la habilidad para explotar el potencial particular de cada máquina en la red. Los programas de aplicación bajo PVM tienen control arbitrario y estructuras dependientes. Es decir, en cualquier punto en la ejecución de una aplicación concurrente, los procesos existentes pueden tener vínculos entre ellos, y también, cualquier proceso puede comunicarse y/o sincronizarse con cualquier otro. Esto se permite para la forma más usual de programación paralela, MlMD (Multiple Instruction Multiple Data, Múltiples instrucciones múltiples datos), pero en la práctica muchas aplicaciones concurrentes son más estructuradas. Dos estructuras típicas son el modelo SPDM, en el cual todos los procesos son idénticos y el modelo de maestro/esclavo, en el cual un conjunto de procesos esclavos realizan el trabajo para uno o más procesos maestros. Interfaz de Usuario 57 3.3 3.3 lnterfaz de Usuario. En esta sección daremos una breve descripción de las rutinas de la biblioteca de usuario en PVM3.3. Esta sección esta organizada según el tipo de función de cada rutina. ¿Cómo el usuario debe tomar ventajas de esta funcionalidad, y de las rutinas C que pertenecen a esta función?. En PVM 3 todas las tareas son identificadas por un entero que es proporcionado por el demonio local pvmd ( tid ). Éste es similar al identificador de procesos ( PID ) en sistemas UNlX excepto que el tiú tiene codificado en él la dirección del proceso en la máquina virtual. Esta codificación permite a las rutinas una comunicación más eficiente, y permite una integración más eficaz de los multiprocesadores. Todas las rutinas PVM son escritas en C . Las rutinas en C++ pueden ser ligadas con las bibliotecas PVM. Las rutinas en Fortran pueden llamar a estas funciones a través de una interfaz Fortran 77 proporcionada con el código fuente de PVM3. Esta interfaz traslada argumentos, los cuales son pasados por referencia en Fortran. 3.3.1 Control de Procesos int tid = pvm-mytid(void) La rutina pvm-mytid() registra esta tarea dentro de PVM, en su primer llamada y genera un único fiú si el proceso no fue generado con pvm-spawn(). Este regresa el tiú de esta tarea, y puede ser llamado múltiples veces. Cualquier llamada que se realice en PVM (no sólo pvm-mytid ) registra una tarea en PVM , si la tarea no ha sido registrada antes con otra llamada. int info = pvm-exit(void) La rutina pvm-exit() comunica al demonio local pvmd, que esta tarea abandona la máquina virtual. Esta rutina no termina la tarea, la cuál puede continuar su ejecución como cualquier otro proceso UNlX sin utilizar P VM. Inf numt = pvm-spawn(char *task, char **argv, int flag, char *where, int ntask, int "tids) La rutina pvm-spawn() inicializa ntask copias de un archivo task ejecutable en la máquina virtual, argv es un apuntador a un arreglo de argumentos para task con el fin del arreglo inicializado con NULL. Si la tarea no tiene argumentos entonces argv es NULL. El argumento flag es usado para especificar algunas opciones, las cuales son: 58 Aplicaciones bajo PVM PvmTaskDefa ult PvmTaskDefault PvmTaskHost PvmTaskArch PVM escoge donde producir tareas PVM escoge donde producir tareas El argumento where especifica un host para producirse ahí. El argumento where especifica una arquitectura PVM-ARCH para producirse ahí. Comienza estas tareas bajo un depurador La llamada PVM en estas tareas genera una busqueda de datos Iniciará tareas bajo un nodo MPP front-endkervice Comienza tareas bajo un conjunto de host complemento PvmTaskDebug PvmTaskTrace PvmMppFront PvmHostCompl En numf es colocado el número de tareas producidas exitosamente o un código de error si las tareas no pueden ser inicializadas. Si las tarea fueron iniciadas, entonces pvm-spawn() regresa un vector de fids de tareas producidas y si algunas tareas no pueden ser iniciadas, los códigos de error correspondientes, serán colocados en las Últimas ( nfask numf ) posiciones del vector. Pvm-spawn() también puede iniciar tareas en multiprocesadores. En el caso de la Intel-¡PSC/860, se aplican las siguientes restricciones: cada llamada spawn obtiene un subcubo de tamaño nfask y carga el programa task en todos estos nodos. La iPSC1860 OS tiene una asignación limite de 10 subcubos para todos los usuarios, de ahí que es mejor comenzar un bloque de tareas en una iPSC/860 con sólo una llamada a pvm-spawn() en vez de varias llamadas. Dos bloques diferentes de tareas producidas separadamente en la iPSC/860, pueden comunicarse con cada una de las otras, más rápido que cualquier otra tarea PVM, aún cuando están en subcubos separados. int info = pvm-kill(int fid) La rutina pvm-kill termina las tareas PVM identificadas por fid. Esta rutina no está diseñada para eliminar la tarea que está llamando ésta función, lo cual debería ser realizado por la llamada pvm-exit( ) seguida por exif(). 3.3.2 Información int fid = pvmjarent(void) La rutina pvm-parenf() regresa el fid del proceso que generó esta tarea Ó el valor de PvmNoParenf si no fue creada por pvm-spawn(). int pstat = pvmjsfat(.int fid) La rutina pvm-psfaf regresa el estado de una tarea PVM identificada por fid. Esta rutina regresa PvmOk si la tarea se está ejecutando, PvmNoTask si no, Ó PvmBadParent si el fides inválido int mstat = pvm-mstat(char *host) Interfaz de Usuario 59 3.3 La rutina pvm-mstat regresa PvmOk si el host está activo. PvmHostíile si el host esta incomunicado o PvmNoHosf si el host no está en la máquina virtual. Esta información puede ser útil cuando se crean aplicaciones con tolerancia a fallas. int info = pvm-conf¡g(int *nhost,int *narch,struct pvmhostinfo **hostp) La rutina pvm-config regresa información acerca de la máquina virtual incluyendo el número de host, nhost, y el número de formatos de datos distintos, narch. hostp es un apuntador a un arreglo de estructuras pvmhostinfo. El arreglo es de tamaño nhost. Cada estructura pvmhostinfo contiene el tid del demonio pvmd, el nombre del host, nombre de la arquitectura y la velocidad relativa del CPU, en la configuración. PVM no usa o determina el valor de la velocidad. El usuario puede colocar éste valor en el hosffile y recuperar este valor con pvm-config(), para usarlo en una aplicación. int info = pvm-tasks(int which, int *ntask,struct pvmtaskinfo ** taskp) La rutina pvm-tasks regresa información acerca de las tareas PVM ejecutándose en la máquina virtual. El entero which especifica cuáles tareas regresarán información. Las opciones presentes son: 0 0 0 (O), lo que significa que todas las tareas regresarán información, el tid de un pvmd, lo que significa que sólo las tareas ejecutándose en ese host regresan información. ó el tid de una tarea, lo que significa que sólo esa tarea devolvera información. El número de tareas es regresado en ntask. taskp es un apuntador a un arreglo de estructuras pvmtaskinfo. El arreglo es de tamaño ntask. Cada estructura taskinfo contiene el tid, el fid del pvmd, el tid de la tarea padre, y el nombre del archivo producido. ( PVM no conoce el nombre del archivo de tareas iniciadas manualmente). int dtid = pvm-tidtohost(int tid) Si un usuario necesita conocer en que host se está ejecutando una tarea especifica, la función pvm-fiútohost() proporciona esa información. 3.3.3 Configuración Dinámica int info = pvm-addhosts( char **hosts, int nhost, int *infos) int info = pvm-delhosts( char **hosts, int nhost, int *infos) Estas rutinas agregan o eliminan un conjunto de hosts en la máquina virtual. En info es regresado el número de hosts agregados exitosamente. El argumento infos es un arreglo de longitud nhost que contiene el código de estado para cada host individual, que ha sido agregado o eliminado. Esto permite al usuario verificar si sólo uno de los hosts de un 60 Aplicaciones bajo PVM conjunto causó algún problema y así evitamos agregar o eliminar el conjunto entero de hosts otra vez. 3.3.4 Señalización int info = pvm-sendsig( int tid, int signum) pvm-sendsig() envía una señal signum a otra tarea PWM identificada por tid. int info = pvm-notify(nt what, int msgtag, int cnt, int *fids) La rutina pvm-nofify() solicita a PVM que informe a la tarea que invocó a pvm-notfly(), la ocurrencia de alguno de los siguientes eventos: Pvm TaskExit PvmHostDelete PvmHostAdd Notifica si una tarea sale Notifica si un host es borrado (o ha fallado) Notifica si un host es adicionado En respuesta a la solicitud de notificación, algún número de mensajes son enviados por PVM de regreso a la tarea solicitante. Los mensajes son etiquetados con el código ( msgtag ) proporcionado por pvm-notify. Si el host donde una tarea A está siendo ejecutada falla, y una tarea B ha solicitado si la tarea A ya terminó, entonces la tarea B será notificada de lo que sucedio aún cuando la salida fuera causada indirectamente. 3.3.5 Opciones de colocación y obtención int oldVal= pvm-setoptfint what, int val) int val = pvm-getopt(int what) Las rutinas pvm-setopt( ) y pvm-getopt( ) son funciones de propósito general, que permiten al usuario colocar u obtener opciones del sistema PWM. En PWM 3, pvm-setopt() puede ser usada para colocar varias opciones incluyendo: impresión automática de mensajes de error, nivel de depuración y método de ruteo de comunicación para todas las subsecuentes llamadas PWM. pvm-setopt() regresa el valor previo de colocación en oldval. En PWM 3.3 el argumento what puede tomar los siguientes valores: Opción Significado PvmRoute PvmDebugMask PvmAufoErr PvmOutputTid 1 2 3 4 PvmOutputCode Pvm TraceTid 5 6 Política de ruteo Mascaras de depuración Reporte automático de errores Dispositivo de salida estándar para tareas hijos Salida de la etiqueta de mensaje (msgtag) Señala el dispositivo para tareas hijos Paso de Mensajes 61 3.4 PvmTraceCode PvmFragSize PvmResvTids 7 8 9 Señala etiqueta de mensaje ( msgfag ) Tamaño del fragmento de mensaje. Permite a los mensajes ser enviados para etiquetas y fids reservados. Pvm-sefopf() puede colocar varias opciones de comunicación en PVM, tales como método de ruteo ó tamaños de fragmento a usar. Esta rutina puede ser llamada múltiples veces durante una aplicación, para colocar selectivamente ligas de comunicación directa de tarea a tarea. Su uso típico es llamar a esta función después de pvm-myf¡d(). 3.4 Paso de mensajes: El envío de un mensaje está compuesto de tres pasos en PVM : 0 0 0 Primero, un buffer de envío debe de ser inicializado mediante una llamada a pvm-initsend() o pvm-mkbufo. Segundo, el mensaje debe ser " empaquetado dentro de este buffer usando cualquier número y combinación de las rutinas pvm-pk*. Tercero, el mensaje completo es enviado a otro proceso llamando a la rutina pvm-send() ó llamadas múltiples con la rutina pvm-mcasf(). " Además, hay funciones de comunicación colectiva que operan sobre un grupo entero de tareas. PVM también proporciona la rutina, pvm-psend(), la cual combina los tres pasos en una sola llamada. Esto permite la posibilidad de hacer más rápidas las implantaciones internas. pvm-psend() sólo empaqueta y envía un arreglo contiguo de un sólo tipo de datos. pvm-psend() usa su propio buffer de envió, así que esto no afecta a un buffer que ha sido empaquetado para usarse con pvm-sendo. Un mensaje es recibido por llamadas a rutinas que reciben ya sea un bloque Ó un nobloque y entonces desempaqueta cada uno de los elementos empaquetados en el buffer de recepción. Las rutinas de recepción pueden ser usadas para aceptar: 0 cualquier mensaje cualquier mensaje desde una fuente específica cualquier mensaje con una etiqueta específica mensajes con etiqueta y fuente dadas. Hay también una función de prueba que verifica si un mensaje ha llegado, pero aún no se ha desempacado. 62 Aplicaciones bajo P V M 3.4.1 Buffers de mensajes Las siguientes rutinas para manejo de buffers de mensajes, son requeridas sólo si el usuario desea manejar múltiples buffers de mensajes dentro de una aplicación. No se requieren múltiples buffers para la mayoría de los pasos de mensajes entre tareas. En PVM 3 hay un buffer de envío y uno de recepción activos por proceso en cualquier momento. El desarrollador de aplicaciones puede crear cualquier número de buffers de mensajes e intercambiarlos entre ellos para empaquetar y enviar datos. Las rutinas de empaquetamiento, desempaquetamiento, envío y recepción sólo afectan a los buffers activos. Inf bufid = pvm-mkbuf( inf encoding) La rutina pvm-rnkbuf crea un nuevo buffer de envío vacío y especifica el método de codificación usado para empaquetar mensajes. Este regresa el identificador de buffer, bufid. Las opciones de codificación son: PvmDafaDefaulf. La codificación XDR es usada por default, porque PVM no sabe si el usuario va a agregar otra máquina heterogénea antes que se envíe este mensaje. Si el usuario sabe que el siguiente mensaje será enviado a una máquina que entiende el formato original entonces, él puede usar la codificación PvmDafaRaw y ahorrar costos de codificación. PvmDafaRaw. La codificación no se hace, los mensajes son enviados en su formato original. Si el proceso de recibimiento no puede leer éste formato entonces, éste regresará un mensaje de error durante el desempaquetamiento. PvmDafalnPlace. Los datos son dejados en su lugar. El buffer sólo contiene el tamaño y el apuntador de cada elemento ha ser enviados. Cuando pvm-send() es llamado, los elementos son copiados directamente fuera de la memoria del usuario. Esta opción disminuye el número de veces que el mensaje es copiado, sólo si el usuario no modifica los elementos entre el tiempo en que son empaquetados y el tiempo en que éstos son enviados. Otro uso de esta opción es empaquetar una vez y, modificar y enviar algunos elementos ( arreglos ) múltiples veces durante una aplicación. int bufid = pvm-inifsend(inf encoding) La rutina pvm-inifsend() limpia el buffer de envíos y crea uno para empaquetar un mensaje nuevo. El esquema de codificación usado por este empaquetamiento es colocado por encoding. El nuevo identificador de buffer es regresado en bufid. Si el usuario esta usando un sólo buffer de envíos, entonces la función pvrn-inifsend() debe ser llamada antes de empaquetar un nuevo mensaje dentro del buffer, de otra manera el mensaje en el buffer se fusionará con el nuevo mensaje. Paso de Mensajes 63 3.4 int info = pvm-freebuf(int bufid) La rutina pvm-freebuf() libera el buffer con identificador bufid. Esto se debe realizar después, de que el mensaje ha sido enviado y ya no se necesita el buffer. La llamada a la función pvm-mkbuf() crea un buffer para un nuevo mensaje si se requiere. Ninguna de estas llamadas es requerida cuando usamos pvm-inifsend(), la cuál realiza estas funciones por el usuario. int bufid = pvm-getsbuf(void) La rutina pvm-getsbut() regresa el identificador del buffer activo de envío de mensajes. int bufid = pvm-getrbuf(void) La rutina pvm-getrbuf() regresa el identificador del buffer activo de recepción de mensajes. int oldbuf = pvm-setsbuf( int bufid) Esta rutina coloca el identificador del buffer de envio activo en bufid, guarda el estado del buffer previo y regresa su identificador en oldbuf. int oldbuf = pvm-setrbuf(int bufid) Esta rutina coloca el identificador del buffer de recepción activo en bufid, guarda el estado del buffer previo y regresa su identificador en oldbuf Si bufid es cero en pvm-sefsbuf() Ó pvm-sefrbuf() entonces, se guarda el buffer actual y no hay buffer activo. Esta innovación puede ser usada para guardar el estado actual de los mensajes de una aplicación, de ahí que una biblioteca matemática ó una interfaz gráfica, las cuáles también usan mensajes PVM no interferirán con el estado de los buffers de la aplicación. Después de que éstas terminan, los buffers de la aplicación pueden ser reiniciados. Esto es Útil para enviar mensajes sin reempaquetarlos, usando las rutinas de buffer de mensajes. Esto es ilustrado por los siguientes fragmentos: bufid = pvm-recv( src, fag); oldid = pvm-setsbuf( bufid ); info = pvm-send( dsf, fag ); info = pvm-freebuf(o/did ); 64 Aplicaciones bajo PVM 3.4.2 Empaquetamiento de Datos Cada una de las siguientes rutinas tipo C empaqueta un arreglo de datos, de un tipo conocido, dentro del buffer activo de envíos. Estas rutinas pueden ser llamadas múltiples veces para empaquetarse en un sólo mensaje. Así un mensaje puede contener varios arreglos cada uno con un tipo de datos distinto. No hay límite para la complejidad de los mensajes empaquetados, pero una aplicación debe desempaquetar los mensajes exactamente como éstos fueron empaquetados. Las estructuras de C deben ser pasadas empaquetando sus elementos individuales. Los argumentos para cada una de las rutinas son un apuntador al primer elemento a ser empacado, el argumento nitem es el número total de elementos a empaquetar de éste arreglo y stride es el método a usar cuando empacamos. Una excepción es pvm-pkstr(), la cuál por definición empaqueta una cadena de caracteres terminada con NULL, de ahí que no necesita los argumentos nitem Ó stride. int info = pvm-pkbyte int info = pvm-pkcplx int info = pvm-pkdcplx int info = pvm-pkdouble int info = pvm-pkfloat inf info = pvm-pkint int info = pvm-pklong int info = pvm-pkuinf int info = pvm-pkushort int info = pvm-pkulong int info = pvm-pklong int info = pvm-pkshort int info = pvm-pkstr int info = pvm-packf ( char *CPl ( float *XPl (double *ZPJ (double *dPl (float *fp, (int *nPJ ( long *nPJ (unsigned int ( unsigned short ( unsigned long ( long (short (char *cp) (const char *fmt, .. int nitem, int stride) int nitem, inf striúe) int nitem, int sfride) int nitem, int stride) int nitem, int stride) int nitem, int stride) int nitem, int stride) *np, int nitem, int stride) *np, int nitem, int sfride) *np, inf nitem, int stride) *np, int nitem, int stride) *np, int nitem, int stride) PVM también proporciona una rutina de empaquetamiento pvm-packf() que usa un formato de expresión prinff-like ( cómo imprimir ) para especificar qué datos se empaquetan y cómo se empaquetan dentro del buffer de envíos. Todas las variables son pasadas como direcciones si los argumentos count y stride son especificados; de otra manera se asume que las variables tienen valor fijo. 3.4.3 Envío y recepción de datos. int info = pvm-sená(int tid, int msgtag); La rutina pvm-send() etiqueta el mensaje con un identificador entero msgtag y envía éste inmediatamente al proceso con identificador tid. int info = pvm-mcast(int *tids, int ntask, int msgtag) Paso de Mensajes 65 3.4 La rutina pvm-mcasf() etiqueta el mensaje con un identificador entero msgfag y difunde el mensaje a todos las tareas especificadas en el arreglo de tids ( excepto el mismo ). El arreglo de tids es de longitud nfask. int info = pvmjsend( int fid, int msgtag, void *vp, int cnt, int type) La rutina pvm-psend() empaqueta y envía un arreglo de un tipo de datos especificado a la tarea identificada con tid. En C el argumento type puede tener los siguientes valores: PVM-STR PVM-BYTE PVM-SHORT PVM-I NT PVM-LONG PVM-USHORT PVM-U LONG PVM-FLOAT PVM-CPLX PVM-DOUBLE PVM-DCPLX PVM-U INT Estos nombres están definidos en pvm3/inc/ude/pvm3.h. int bufid = pvm-recv( int fid, int msgfag) Ésta rutina de recibimiento de bloques espera hasta que un mensaje con etiqueta msgtag llegue desde una tarea con identificador fid. Un -1 en msgtag Ó en fid significa que todos los mensajes son recibidos no importando su etiqueta Ó fid. Éste coloca el mensaje en un nuevo buffer de recepción activo que es creado a menos que se haya guardado con una llamada a pvm-sefrbuf(). Si el buffer de recibimiento ya fue creado previamente es borrado. int bufid = pvm-nrecv( int fid, int msgtag) Si el mensaje solicitado no ha llegado, entonces la rutina de recepción de no-bloques pvm-nrecv() regresará cero en bufid. esta rutina puede ser llamada múltiples veces para el mismo mensaje, para verificar si éste ha llegado mientras realiza trabajo Útil entre llamadas. Cuando no puede realizarse trabajo útil, la rutina pvm-recv() de recepción de bloques puede ser llamada para el mismo mensaje. Si un mensaje con etiqueta msgfag ha llegado desde una tarea con identificador fid, pvm-nrecv() coloca este mensaje en un nuevo buffer de recepción activo, el cuál regresa el identificador de este buffer. El buffer de recepción activo previo es limpiado, si no es que éste ha sido guardado con la llamada a pvm-setribuf(). Un -1 en msgtag Ó en tid significa que todos los mensajes son recibidos no importando su etiqueta Ó tid. int bufid = p v m j r o b e ( int fid, int msgtag) Si el mensaje solicitado no ha llegado, entonces pvm-probe() regresa bufid=O. Sino, regresa un bufid diferente de cero, pero no es recibido el mensaje. Ésta rutina puede ser llamada múltiples veces para un mismo mensaje, para verificar si éste ha llegado. Con la función pvm-bufinfo() y el bufid obtenido se tiene información del mensaje antes de recibirlo. 66 Aplicaciones bajo PVM int info = pvm-bufinfo( int butid, int *bytes, int *msgtag, int *tid) int bufid = pvm-trecv( int tid, int msgtag, struct timeval *tmout) La rutina pvm-bufinfo() regresa msgtag, el tid de la tarea fuente, y la longitud en bytes del mensaje identificado por bufid. PVM proporciona una función de recepción de mensajes a destiempo. Considerar el caso donde un mensaje nunca va ha llegar (debido a un error o una falla),la rutina pvm-recv() se bloquearía. Cuando pasa cierta cantidad de tiempo, el usuario ya no espera recibir el mensaje. La rutina pvm-trecv() permite especificar un periodo de tiempo de espera. Si el periodo de tiempo de espera es muy grande, pvm-trecv() actuará como pvm-recv(). Si el periodo de tiempo de espera es cero, actúa como pvm-nrecv(). Asi, pvm-trecv() llena el intervalo entre las funciones de recepción de bloques y de no-bloques. int info = pvmjrecv( int tid, int msgtag, void *vp, int cnt, int type, int *did,int *dag, int *rcnt) La rutina pvm-precv() combina las funciones de recepción y desempaquetamiento de un bloque. Esta función no regresa un bufid, regresa el valor actual de tid, msgtag y cnt en rtid, rtag y rcnt respectivamente. 3.4.4 Desempaquetamiento de datos Las siguientes rutinas de C desempaquetan varios tipos de datos del buffer de recepción. En una aplicación deben igualar a sus correspondientes rutinas de empaquetamiento en tipo, número de elementos y pasos. nitem es el número de elementos a desempacar y stride son los pasos. int info = pvm-upkbyte int info = pvm-upkcplx int info = pvm-upkdcplx int info = pvm-upkdouble int info = pvm-upkfloat int info = pvm-upkint int info = pvm-upklong int info = pvm-upkuint int info = pvm-upkushort int info = pvm-upkulong int info = pvm-upklong int info = pvm-upkshort int info = pvm-upkstr int info = pvm-unpackf ( char *CPJ ( float *XP, ( double *ZPJ ( double *dp, (float *fPf (int *nP, ( long *np, (unsigned int ( unsigned short ( unsigned long ( long (short ( char *cp) (const char *fmt, ... int nitem, int stride) int nitem, int stride) int nitem, int stride) int nitem, int stride) int nitem, int stride) int nitem, int stride) int nitem, int stride) *np, int nitem, int stride) *np, int nitem, int stride) *np, int nitem, int stride) *np, int nitem, int stride) *np, int nitem, int stride) La rutina pvm-unpackf() usa un formato de expresión printf-like ( cómo imprimir ) para especificar qué y cómo desempaquetar los datos del un buffer de recepción. Compilando Aplicaciones P V M 67 3.5 3.5 Compilando aplicaciones PVM Un programa en lenguaje C que se ejecuta bajo PVM, necesita ser ligado con libpvm3.a. Los programas de ejemplo y el archivo makefile son proporcionados con el código fuente en el directorio $HOMUpvm3/examples. El archivo Reaúme que se encuentra en ese directorio, describe como construir y ejecutar los ejemplos. Makefile muestra como las aplicaciones en C y Fortran deben ser ligados con las bibliotecas de PVM, también contiene información acerca de las bibliotecas adicionales requeridas para algunas arquitecturas. Un programa make, independiente de la arquitectura es proporcionado con PVM. Éste script se encuentra en $HOM€/pvm3/lb/aimk; al ejecutarlo, automáticamente reconocerá la clase de arquitectura en la que se encuentra y en base a ello agregará las bibliotecas correctas. Para construir cualquiera de los ejemplos: Ir al directorio $HOMUpvm3/lib. Teclear: % aimk $HOMUpvm3/examples/nombre~ejemplo Se creará el programa ejecutable y aimk lo colocará en: $HOMUpvm3/bin/P VM- ARCH Para compilar tu programa tienes que moverlo al directorio: $HOMUpvm3/bin/P VM-A RCH y teclear: % cc nombre-programa. c libpvm.a -o nombre-ejecutable y se creará el programa ejecutable nombre-ejecutable 3.6 Métodos de Depuración En general, la depuración de programas paralelos es mucho más difícil que la depuración de programas seriales. No solamente son varios programas ejecutándose simultáneamente, si no que al interactuar pueden generar errores. Por ejemplo un proceso puede recibir un dato erróneo que posteriormente cause problemas, como la división por cero. Otro ejemplo es deadlock ( estancamiento ), donde un error de programación causa que todos los tareas esperen mensajes. Todas las rutinas PVM devuelven una condición de error, si algún error ha sido detectado durante su ejecución. 68 Aplicaciones bajo P V M A continuación presentamos una lista de los códigos de error y su significado. Código de error Significado Por default PVM imprime las condiciones de error detectadas en las rutinas PVM. La rutina pvm-setopt() permite habilitar la opción de reporte automático. Imprime el diagnostico de las tareas inicializadas, y es visualizado usando el redireccionamiento de la consola o por la llamada a pvm-catchouf() en la tarea inicializada (frecuentemente la tarea maestro). pvm-catchout() causa que la salida estándar de todas las tareas inicializadas subsecuentemente aparezca en la salida estándar de la tarea inicial. Las tareas PVM pueden ser iniciadas manualmente bajo cualquier depurador serial estándar, por ejemplo úbx.stúouf, su salida estándar siempre aparece en la ventana en la que fueron comenzadas. . Las tareas PVM que son generadas con spawn pueden ser iniciadas bajo un depurador, colocando la opción flag, para incluir a PvmTaskDebug en la llamada pvm-spawn(), por default PVM ejecutará el script PVM_ROOT/lib/debugger. Éste script se inicializa en una ventana xterm en el host donde se inició PVM y las tareas son ejecutadas bajo un depurador en esta ventana. La tarea que es depurada, puede ser ejecutada en cualquiera de los hosts en la máquina virtual, especificando los argumentos flag y where de pvm-spawn(). El usuario puede crear su script de depuración y personalizarlo incluyendo el depurador de su preferencia Ó aún mejor, un depurador paralelo si éste está disponible. El usuario puede decir a PVM donde se encuentra este script usando la opción (bx=) en el Hosffile. Métodos de Depuración 69 3.6 Los mensajes de diagnostico enviados a sfúerr desde una tarea no aparecerán en la pantalla del usuario. Todos serán mandados a un archivo de la forma /fmp/pvml.<uid> en el host donde fue inicializado PVM. Los mensajes de sfdouf pueden aparecer en este archivo. Las tareas que son inicializadas desde la consola PVM pueden tener sus sfdouf(y todas las sfúouf de sus tareas hijos) redirecionadas a la ventana de la consola o en un archivo separado. La rutina pvm-sefopf() permite al usuario el uso de un conjunto de máscaras de depuración que mandan su salida a /fmp/pvml.<uid>. Por default el nivel de depuración es: "sin mensajes del depurador". El nivel de depuración puede ser cambiado varias veces dentro de una aplicación. Los mensajes del depurador describen solamente qué está realizando PVM y no que aplicación se está ejecutando. Los tres pasos recomendados para depurar programas son: 0 0 Primero, si es posible ejecute el programa como un proceso sencillo y depúrelo como cualquier programa serial. El propósito de este paso es detectar errores lógicos y señalarlos cómo no relacionados al paralelismo. Segundo, ejecute el programa usando 2 a 4 tareas en una sola máquina. PVM ejecutará estos tareas como multitareas. El propósito de este paso es verificar la sintaxis de comunicación y la lógica, Por ejemplo en un programa se utilizó una etiqueta de mensaje igual con 5 en el envío, pero el receptor espera por un mensaje con etiqueta igual a 4. Un error muy común descubierto en este paso, es el uso de etiquetas de mensaje que no son únicas. Para ilustrar esto, imagine que una misma etiqueta es siempre utilizada. Un proceso recibe algún dato inicial en tres mensajes separados, pero no tiene manera de determinar cuál de los mensajes tiene qué datos. PVM regresa cualquier mensaje que iguale la solicitud de la fuente y la etiqueta, esto es hasta que el usuario se asegura que este par identifica en forma única el contenido del mensaje. Los errores de etiquetas duplicadas son muy difíciles de depurar, porque esto es sensitivo para efectos de sincronización y podrían no ser reproducibles de ejecución en ejecución. Si el error no puede ser determinado por el código de error de PVM ó en un mensaje, entonces el usuario puede obtener un control completo del depurador en su programa, iniciando una o todas sus tareas bajo el depurador. El tercer paso es ejecutar los mismos 2 a 4 tareas en varias máquinas. El propósito de este paso es verificar los errores de sincronización que se han producido por los retardos de la red. La clase de errores frecuentemente descubiertos en este paso son sensibles al algoritmo de orden de los mensajes recibidos, y programas que se estancan debido a errores lógicos sensitivos a los retardos de la red. Otra vez el control completo del depurador puede ser obtenido en este paso, pero puede no ser Útil, porque el depurador puede pasar por alto o enmascarar errores que aparecerán posteriormente 70 Aplicaciones bajo P V M 3.7 Ejecutando aplicaciones PVM. Una vez que PVM es ejecutado, una aplicación que usa rutinas PVM puede ser inicializada desde el prompt de comandos de UNIX, en cualquiera de los hosts de la máquina virtual. Una aplicación no necesita ser inicializada en la misma máquina que el usuario elige para iniciar PVM. Stdouf y stderr aparecen en la pantalla para todas las tareas PVM inicializadas manualmente' . Los mensajes de error producidos por las tareas son escritos en el archivo de errores estándar, /tmp/pvmd.aid > en el host donde PVM fue inicializado. La forma más fácil de ver la salida estándar de las tareas PVM es usando un cambio en la dirección de salida, que está disponible en la consola PVM2. Si la salida esfándar no es redireccionada en la consola PVM, entonces esta salida también irá a el archivo /fmp/pvmd.a i d >. Cuando los usuarios quieren ejecutar sus programas con un valor que es menor a la prioridad que tienen, sobrecargan menos las estaciones de trabajo. Hay un par de formas para realizar esto. El primer método, inicia tu programa en un script. Este es un ejemplo de un script de dos líneas: $!/bin/sh exec nice -10tu-programa $* Entonces cuando se produce el script, ejecutará el programa en el nivel deseado. El segundo método es llamando a la función UNIX setphrity(' en tu programa. No es necesario iniciar una nueva sesión PVM para cada aplicación que se quiera ejecutar, aunque será necesario reiniciar PVM si un proceso falla. I 2 Hay tareas que no son inicializadas manualmente, P.e. en los programas maestrdesclavo. Spawn, con la opción ->. CAPíTULO 4 La cena de los filósofos 4.1 Planteamiento. En 1965, Dijsktra planteo y resolvió un problema de sincronización llamado el problema de la cena de los filósofos. Desde entonces, todas las personas que idean cierta primitiva de sincronización intentan demostrar lo maravilloso de la nueva primitiva al mostrar su elegancia para resolver el problema de la cena de los filósofos. El problema se puede enunciar de la manera siguiente. Cinco filósofos se sientan a la mesa. Cada uno tiene un plato de espagueti. El espagueti es tan escurridizo, que un filosofo necesita dos tenedores para comerlo. Entre cada dos platos hay un tenedor. La vida de un filosofo consta de periodos alternados de comer y pensar. (Esto es una abstracción, incluso para los filósofos, pero las demás actividades son irrelevantes en este caso). Cuando un filosofo tiene hambre, intenta obtener un tenedor para su mano izquierda y otro para su mano derecha, alcanzando uno a la vez y en cualquier orden. Si logra obtener los dos tenedores, come un rato y después deja los tenedores y continua pensando. La pregunta clave es: se puede escribir un programa para cada filosofo que lleve a cabo lo que se supone debería y nunca se detenga?. Para sistemas con un procesador o con memoria compartida se tiene una solución, la cual no tiene bloqueos ni inanición, es correcta y permite el máximo paralelismo para un numero arbitrario de filósofos. Utiliza un arreglo, state, para llevar un registro de la actividad de un filosofo: si esta pensando, comiendo o hambriento (intenta obtener los tenedores ). Un filosofo puede comer solo si los vecinos no están comiendo. Los vecinos del i-esimo filosofo se definen en las macros LEFT y RIGHT. En otras palabras, si i=2, entonces LEFT=I y RIGHT=3. El programa utiliza un arreglo de semáforos, uno por cada filosofo, de manera que los filósofos hambrientos pueden bloquearse si os tenedores necesarios están ocupados. Observe que cada proceso ejecuta el procedimiento philosopher como código principal, pero 71 72 La Cena de los Filósofos los demás procedimientos, take-forks, putforks y test son procedimientos ordinarios y no procesos separados. (Tanembaum, 1993) #include "prototypes.h" 5 #define N #define LEFT (i-l)%N #define RIGHT (i+l)%N O #define THINKING #define HUNGRY 1 #define EATING 2 typedef int semaphore; int state[N]; semaphore mutex=l; semaphore s[N]; /* numero de filósofos */ /* numero del vecino izquierdo de i */ /* numero del vecino derecho de i */ i* el filosofo esta pensando */ /* el filosofo intenta conseguir los tenedores */ /* el filosofo esta comiendo */ /* los semáforos son un caso particular de int */ /* arreglo para llenar un registro del estado de cada quien */ i* exclusión mutua para las regiones criticas *I i* un semáforo por filosofo */ void philosopher (int ¡) { while (TRUE){ think(); take-forks(¡); ea0 put-forks(¡); /* i: de cual filosofo se trata ( desde O hasta N-1 ) */ /* se repite por siempre */ /* el filosofo esta pensando */ /* obtiene dos tenedores o se bloquea */ /* mmm..., espagueti! *I i* coloca los tenedores en la mesa */ 1 } void take-forks(int i) { down(&mutex); state[i]=HUNGRY; test(¡); up(&mutex); down(&s[i]); /* i: de cual filosofo se trata ( desde O hasta N-I ) */ void put-forks(int i) { down(&mutex); state[¡] = THINKING; test(LEFT); test(R1GHT); up(&mutex); /* i: de cual filosofo se trata ( desde O hasta N-1 ) */ 1 /* entra a la región critica *I /* registra el hecho de que el filosofo i tiene hambre */ /* intenta tomar 2 tenedores *I /* sale de la región critica */ /* se bloquea si no consiguió los tenedores */ /* entra a la región critica */ /* el filosofo ha terminado de comer *I /* ve si el vecino izquierdo puede comer ahora */ /* ve si el vecino derecho puede comer ahora *I i* sale de la región critica *I 1 /* i: de cual filosofo se trata ( desde O hasta N-1 */ void test(int i) { if state[i]=HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) { 1 state[¡] == EATING; up(&s[il); Implantación en un Sistema Distribuido usando PVM 73 4.2 4.2 Implantación en un Sistema Distribuido usando PVM Pensemos ahora en que tenemos un conjunto de hosts conectados mediante una red local, cualquier host puede obtener información de los demás por medio de paso de mensajes. Imaginemos ahora, que podemos conectar a esta red en un anillo virtual(Fig.4.1), en donde cada host representa un filosofo, entonces tenemos una representación virtual de una mesa en donde se tienen a los filósofos esperando comer, cada uno de ellos tiene conexión directa a sus vecinos derecho e izquierdo, pero nuestro problema ahora tiene un cambio: es un sistema distribuido real, es decir, sin memoria compartida. M.-Maestro. F.- FilOSOfO. S.-Recibe Solicitudes. E.- Estados. Anillo Virtual. Ahora debemos replantear nuestro problema. No podemos implementar semáforos para que cada filosofo pueda entrar a su región critica, porque como hemos visto en el Capitulo 1 debemos usar de manejo de regiones criticas para sistemas distribuidos. Para implantar la cena de los filosos comensales usamos el modelo maestro/esclavo con comunicación entre esclavos, donde el programa produce y direcciona cinco filósofos esclavos, los cuales se comunican e informan al maestro cuando han logrado entrar a la región critica. El maestro les envía una lista de todos los tids de los esclavos y un arreglo con el nombre de los hosts en el anillo, posteriormente cada filosofo crea a su vez dos procesos hijos, uno llamado RECIBE-SOLICITUDES y otro llamado ESTADOS los cuales tienen la función de procesar la solicitud de exclusión mutua y de mantener actualizado el estado del filosofo ( si esta comiendo, pensando o con hambre ) respectivamente. El programa está 74 La Cena de los Filósofos basado en la solución que dio Dijsktra haciendo las modificaciones para un sistema distribuido. Para lograr la exclusión mutua en un sistema distribuido, nos basamos en el algoritmo distribuido propuesto por Glenn Ricart y Agrawala que analizamos en la sección 1.2.3.2 El algoritmo envía solo 2*(N-1) mensajes (de ida y vuelta))=, donde N es el número de nodos en la red por invocación a la sección critica. Aquí cabe hacer una aclaración ya que el algoritmo envía una solicitud de entrada a todos los nodos de la red y sólo en el caso de que reciba autorización de todos, puede entrar a la sección critica, en nuestro caso esto no es necesario ya que para que un filosofo pueda entrar a la región critica es necesario sólo que sus vecinos derecho e izquierdo le den la autorización para entrar, de ahí que si tenemos cinco filósofos dos pueden estar dentro de la región critica, por lo cual mediante álgebra sencilla podemos ver que es necesario solo 4 mensajes por invocación, para N filósofos. Dentro del algoritmo de exclusión mutua se usa un semáforo binario para lograr exclusión mutua de una base de datos compartida en éste host, el semáforo fue implantado usando mecanismos de comunicación entre procesos( inter process comunication, IPC). El mecanismo IPC de semáforos implantado en el UNlX System V es una generalización del concepto de semáforo descrito por Dijkstra, ya que va a permitir manejar un conjunto de semáforos mediante el uso de un identificador asociado. También vamos a poder realizar operaciones signal y waif que actualizan de manera atómica todos los semáforos asociados bajo un mismo identificador. Un semáforo en UNlX System V se compone de los siguientes elementos: 0 El valor del semáforo. El identificador del Último proceso que manipuló el semáforo. El número de procesos que hay esperando a que un valor del semáforo se incremente. El número de procesos que hay esperando a que el semáforo tome el valor de cero. La base de datos compartida se implantó usando igualmente mecanismos IPC. La memoria convencional que puede direccionar un proceso a través de su espacio de direcciones virtuales es local a ese proceso y cualquier intento de direccionar esa memoria desde otro proceso va a provocar una violación de segmento. Para solucionar este problema, UNlX System V brinda la posibilidad de crear zonas de memoria con la característica de poder ser direccionada por varios procesos simultáneamente. Esta memoria va a ser virtual, por lo que sus direcciones físicas asociadas podrán variar con el tiempo. Esto no va a plantear ningún problema, ya que los procesos no generan direcciones físicas, sino virtuales, y es el Kernel el encargado de traducir de unas a otras( Márquez, 1994). Presentamos el algoritmo a continuación. Implantación en un Sistema Distribuido usando P V M 75 4.2 ..................................................................................................... I* MAESTRO I* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . #include "pvm3.h" #include <header.h> void main(void){ int val; int mytid; int tids[NFILO]; int who; int msgtag0=0; int msgtag1=1; int estadistica[NFILO]; I *I *I I /* bandera para checar cuantos procesos se crearon "1 I* identificador de tarea para el maestro *I I* arreglo de tids para un esclavo filoso *I I* que filosofo reporta su estado "1 i^ etiqueta del proceso maestro *I 1" etiqueta de fin de procesos *I r" estadistica de cuantas veces se ha entrado a la *I P sección de critica *I I* del for "1 I* arreglo auxiliar *I int i; int tids2[1]; char **args; char *host[NFILO]; I* arreglo para los hosts *I I* apuntador a un arreglo de tareas *I args=(char **)O; host[O]="iris50';host[l]="iris51 host[2]="ir¡s52"; host[3]="iris53";host[4]="iris54"; I* asigna un tid al maestro *I mytid=pvm-m *id(); for(i=O;i<NFILO;i++){ val=pvm_spawn(ESCLAVE_FILO,args,PvmTaskHost,host[i],1 ,&tids2); i^ crea un filosofo por host *I if(val==1) tids[i]=tids2[1]; else printf("No se inicio el filoso %d en %s \n",i,host[i]); 'I; 1 printf("Posiblemente Iniciaron todos los filósofos"); r" crea el buffer de envío *I pvm-initsend(PvmDataRaw); I* empaqueta el arreglo de tids *I pvm-pkint(tids,NFILO,l); P empaqueta el arreglo de hosts *I pvm-pkbyte( host,NFILO,1); pvm-mcast(tids,NFILO,msgtagO);i^ envía los datos empacados a los filósofos *I I* ESPERA RESULTADOS DE LOS FIL&3OFOS *I for(i==O;i<lTEFW;i++){ pvm-recv(-1 ,-1); pvm_upkint(&who,l,l); estadistica[who]=estadistica[who]+l ; printf(" El filosofo %d ha comido %d veces \n",who,estadistica[who]);} pvmexit(); 1 76 La Cena de los Filósofos ........................................................................................................ I I* FILOSOFO MAiN *I I* *I ........................................................................................................ I #include "pvm3.h" #include "header.h" void main(void){ I* para el tid del padre *I int parent; I* etiqueta para recibir los datos del padre *I int msgtag0=0; int msgtag8=8; I* del for *I int i; P que filosofo soy *I int me; I* tid de cada proceso ESTADO *I int dest; I* tid para cada proceso RECIBE-SOLICITUDES *I int recibe; I* tid del filosofo *I int mytid; I* arreglo de tids de los filosofos *I int tids[NFILO]; P arreglo de host *I char host[NFILO]; P arreglo de tids de los procesos ESTADO *I int estado-tids[NFILO]; I* arreglo de tids de los procesos RECIBE-SOLICITUDES *I int solicitudes-tids[NFILO]; I* validaciones *I int va1,vall; char **args; args=(char **)O; I* determina que filosofo soy *I mytid=pvm-mytid; I* obtiene el tid del padre *I parent=pvm-parent(); I* recibe datos del padre *I pvm-recv(parent,msgtagO); I* desempaqueta el arreglo de tids *I pvm-upkint(tids,NFILO, 1); I* desempaqueta el arreglo de hosts*/ pvm-upkbyte(host, NFILO,1); P busca que filosofo soy *I for(i=O;i<NFILO;i++){ if(mytid==tids[i]){ /* es el filosofo i *I me=¡; break;} 1 I* CREA UN PROCESO RECIBE-SOLICITUDES PARA ESTE HOST *I val=pvm~spawn(RECIBE~SOLICITUDES,args,PvmTaskHost, host[me],l ,&recibe); I* CREA UN PROCESO ESTADOS PARA ESTE HOST *I val 1=pvm-spawn( ESTADOS,args,PvmTaskHost,host[me],1,&dest); if(val!=l) printf("No se inicio el proceso recibe-solicitudes en %s \n",host[me]); if(val1!=l) printf("No se inicio el proceso estados en %s\n",host[me]); I* empaqueta el arreglo de tids *I pvm-pkint(tids,NFILO,l ); I* empaqueta quien soy *I pvm-pkint(&me,l ,l); P se envían al proceso RECIBE-SOLICITUDES *I pvm-send(reci be,msgtag8); llena-estado-tids(me,tids,dest,estado-tids); llena-solici-tids(me,tids,recibe,solicitudes-tids); philosopher(me,dest,tids,estado-tidslsolicitudes-tids); 1 Implantación en un Sistema Distribuido usando P V M 77 4.2 ......................................................................................................... I I* *I I* DOWN *I I* *I I* *I ....................................................................................................... I down(int semid, struct sembuf operacion, int Sharet-vars) { I* el semáforo que hay que modificar *I operacion.sem-num=Sharet-vars; I* como se le asigna un valor negativo significa *I operacionsem-op=-1 ; I* que se realizara una operacion wait *I operacion sem-flag=O; i" bandera *I i" semop realiza operaciones atómicas sobre los *I semop(semid,&operacion,1); I* semáforos que hay asociados bajo el *I I* identificador semid *I 1 ........................................................................................................ I* I* I* I* UP I *I *I *I *I I ........................................................................................................ up(int semid, struct sembuf operacion, int Sharet-vars) { operacion.sem-num=Sharet-vars; I* el semáforo que hay que modificar *I operacion.sem-op=l ; I* como se le asigna un valor positivo significa *I I* que se realizara una operacion signal *I operacion sem-flag=O; I* bandera *I semop(semid,&operacion,1); I* semop realiza operaciones atómicas sobre los *I I* semáforos que hay asociados bajo el *I i" identificador semid *I 1 ..................................................... I I* *I I* LLENA ESTADO TlDS *I I* *I *I I* ........................................................................................................ I void Ilenaestado-tids(int me,int *tids, int dest, int *estado-tids){ int msgtag7=7; P'etiqueta *I int i; i" del for *I int who; i" quien da su tid *I estado-tids[me]=dest; P'lleno mi lugar en el arreglo *I pvm-pkint(&dest,l ,l); I* empaqueto el tid de mi proceso estado *I pvm-pkint(&me,l ,l); I* empaqueta quien soy *I pvm~mcast(tids,NFILO,msgtag7); i" envío a todos los procesos el tid menos a mi *I i" mismo *I ..................... ESPERA ELTID DE LOS DEMAS PROCESOS *****************I for(i=O;i<NFILO-1 ;i++){ pvm-recv(-1 ,msgtag7); pvm-upkint(&dest, 1,l); pvm-upkint(&who, 1,í); estado-tids[who]=dest ; I* espero NFILO-1 mensajes *I I* recibo de cualquier proceso *I I* desempaqueto el tid del proceso estado *I /* desempaqueto quien me lo manda *I I* lleno el arreglo con el tid *I 78 L a Cena de los Filósofos ........................................................................................................ I I* /* LLENA SOLICITUD TlDS I* I* ........................................................................................................ *I *I */ *I I void llena-solicitids(int me,int *tids,int recibe,int *solicitudes-tids){ P etiqueta *I int msgtag6=6; I* del for *I int i; I* quien da su tid *I int who; I* lleno mi lugar en el arreglo *I solicitudes-tids[me]=recibe; i* empaqueto el tid de mi proceso solicitud *I pvm-pkint(&recibe, 1,l); P empaqueta quien soy *I pvm-pkint(&me, 1,l); P envío a todos los procesos el tid menos a *I pvm-mcast(tids, NFIL0,msgtagG); I* mi mismo *I ..................... ESPERA ELTID DE LOS DEMAS PROCESOS *****************I for(i=O;i<NFILO-1 ;i++){ pvm-recv(-1 ,msgtag6); pvm-upkint(&recibe,l ,l); pvm-u pkint (&who,1,1); solicitudes-tids[who]=reci be; 1 1 I* espero NFILO-1 mensajes *I I* recibo de cualquier proceso *I P desempaqueto el tid del proceso estado *I P desempaqueto quien me lo manda *I I* lleno el arreglo con el tid *I ........................................................................................................ / /* I* /* I* RECIBE RESPUESTAS ....................................................................................................... */ *I *I *I I void recibe-respuestas(int me,int *solicitudes-tids)( I* vecino derecho *I int right; I* vecino izquierdo *I int left; int msgtag3=3; I* etiqueta *I I* bandera de solicitud *I int solicitud=4; P respuesta enviada por el vecino izquierdo *I int respuecta-I; /* respuesta enviada por el vecino derecho *I int respuesta-r; I* vecino derecho modulo NFILO *I right=(i+l)%NFILO; I* vecino izquierdo modulo NFILO *I left=(¡-l)%NFILO; I********* ESPERA PERMISO PARA ENTRAR DEL FILOSOFO IZQUIERDO*************/ pvm~recv(solicitudes[leftJ,msgtag3); pvm-upkint(respuesta-I, 1); I*********ESPERA PERMISO PARA ENTRAR DEL FILOSOFO DERECHO **************I pvm~recv(solicitudes[right],msgtag3); pvm-upkint(respuesta-r, 1); 1 4.2 Implantación en un Sistema Distribuido usando P V M 79 ......................................................................................................... I* I* ENTRA EXCLUSION MUTUA I* I I *I *I *I I DataBase *entraexclusion-mutua(int me,int *tids,int *solicitudes-tids)P entra a la región critica *I /* vecino derecho *I int right; I* vecino izquierdo *I int left; int msgtag3=3; P etiqueta *I P identificador del semáforo *I int semid; I* identificador de la memoria compartida *I int shmid; i* bloque de memoria compartida *I int memory; I* apuntador a la memoria compartida *I DataBase *Datos; I* estructura que indica las operaciones *I struct sembuf operacion; I* que vamos a llevar a *I I* cabo sobre el semáforo *I I* es la llave que indica a que grupo de *I key-t llave; P semáforos queremos acceder *I I* tipo semáforo *I typedef int semaphore; P semáforo binario que garantiza que solo un *I semaphore Shared-va rs; I* proceso accesa a las variables compartidas */ Ilave=ftok("archivo",k) ; I* Creación de una llave para acceder a los *I /* mecanismos de comunicación entre procesos *I P (Interprocess comunication, iPC)*/ semid==semget(llave,l ,IPCCREAT I 0600); I* petición de un identificador para el semáforo *I I* con permisos de lectura y escritura para el I* usuario *I memory=sizeof(DataBase); I* determina el tamaño de la memoria compartida *I shmid=shmget(lPC~PRIVATE,rnemory,lPC~CREAT~O600); P identificador de la memoria *I if(shmid==-1)(perror("error al crear la memoria");exit(-1);) if(semid==-1) I* no se creo el identificador *I {perror("error en la asignación semget"); exit(-1) ;} I* salimos del proceso con este error *I semctl(semid,Shared-vars,SElVAL,1); I* Inicialización del semáforo *I down(semid,operaciones,S hared-vars); *)-l);l* unión de la zona de direcciones I* if(Datos==(DataBase *)shmat(shmid,O,O)==(DataBase I* compartida nuestro espacio de *I I* direcciones virtuales *I {perror("error al unir la memoria compartida");exit(-1);) Datos->shmid=shmid; Datos->sect-critica=TRUE; I* quiere entrar a la sección critica *I Datos->secu-del-nodo=Datos->secu_mac_alta+l; /* obtiene la secuencia mas alta */ up(semid,operaciones,Shared-vars); right=(¡+ 1)%NFILO; i" vecino derecho modulo NFILO *I left=(i-l)%NFILO; P vecino izquierdo modulo NFILO *I pvm-initsend(PvmDataRaw); I* crea el bufer de envío *I pvm-pkint(&Datos->secu-del-nodo,1,l); I* empaqueta la secuencia del nodo *I pvm-pkint(&me,l ,l); I* empaqueta quien soy *I pvm~pstruc(Datos,5,1); i" empaqueta los datos de memoria compartida*/ pvm~send(solicitudes~tids[left],msgtag3); P envía la solicitud al filosofo izquierdo *I pvm~send(solicitudes~tids[right],msgtag3);I* envía la solicitud al filosofo derecho *I recibe-respuestas( me,solicitudes-tids); P recibe a autorización de entrada *I return (Datos); 1 80 La Cena de los Filósofos ........................................................................................................ / /* */ /* SALE EXCLUSION MUTUA */ /* *I /* */ ........................................................................................................ / void sale-exclusion-mutua(int me, int &tids,DataBase *Datos){ i" sale de la región critica */ /* vecino izquierdo */ int lefi = (me-l)%NFILO; /* vecino derecho */ int right = (me+l)%NFILO; i" etiqueta de respuesta */ int respuesta=7; int msgtag3=3; /* etiqueta */ i" sale de la región critica */ Datos->sect-critica=FALSE; i" si se marco para entrar después a la *I if(Datos->arreglo-resp[lefi]) { i" región critica al vecino izquierdo *I /* desmarca el hecho que espera respuesta *I Datos->arreglo-resp[lefi]=FALSE; /* se empaqueta la respuesta */ pvm-pkint(&respuesta, 1,l); /* este filosofo manda respuesta a vecino */ pvrn-~end(tids[left],msgtag3); /* izquierdo */ 1 /* si se marco para entrar después a la *I /* región critica al vecino derecho *I Datos->arreglo-resp[right]=FALSE; i" desmarca el hecho que espera respuesta */ /* se empaqueta la respuesta */ pvm-pkint(&respuesta,l ,I); I* este filosofo manda respuesta a vecino */ pvm-send(tids[right] ,msgtag3); /* derecho */ if(Datos->arreglo-resp[right]) { 1 ........................................................................................................ / /* */ /* CAMBIA ESTADO *I I* */ /* *I ........................................................................................................ / int cambia-estado(int state,+int solicitud, int dest, int who){ int msgtag2=2; i" crea el bufer de envío */ pvm-initsend(PvmDataRaw); i" quien solicita *I pvm-pkint(&who,l , I ) ; /* empaqueta el estado a cambiar *I pvm-pkint(&state,l ,I); /* empaqueta la solicitud */ pvm-pkint(&solicitud, 1,l); /* envía el estado a dest */ pvm-send(dest, msgtag2); if(solicitud==TRUE){ I*************** ESPERA EL RESULTADO DE LA SOLICITUD ...................... pvm-recv(dest, msgtag2); pvm-upkint(&state,l ,I); return( *state ); 1 1 / I* solo de destino recibe */ I* desempaqueta el estado */ i" regresa el estado solicitado */ 4.2 Implantación en un Sistema Distribuido usando P V M 81 ........................................................................................................ I * *I I* TEST *I I* *I I* *I ...................................................................................................... I void test(int i,int *tids,int *estado-tids){ /* vecino derecho *I int right; I* vecino izquierdo *I int leít; /* estado del i-esimo filosofo *I int estado-¡; /* estado del filosofo izquierdo *I int estado-I; I* estado del filosofo derecho *I int estador; /* vecino derecho modulo NFILO *I right=(i+l)%NFILO; i* vecino izquierdo modulo NFILO *I left=(¡-l)%NFILO; I* que estado tengo *I estado~i=cambia~estado(-2,TRUE,estado~tids[i],tids[i]); estado~l=cambia~estado(-2,TRUE,estado~tids[le~],tids[leít]); /* que estado tiene el filosofo left *I estado~r=cambia~estado(-2,TRUE,estado~tids[right],tids[right]);/* que estado tiene el filosofo derecho *I if(estado-¡==HUNGRY && estado-l!=EATING && estado-r!=EATING) cambia~estado(EATING,FALSE,estado~tids[i],tids[i]); I* el filosofo puede comer *I 1 I I */ TAKE FORKS *I *I *I ..................................................................................................... / DataBase *take-forks(int me,int dest,int *tids,int *estado-tids,int *solicitudes-tids){ I* obtiene dos *I DataBase *Datos; /* tenedores *I Datos=entra_exclusion-mutua(me,tids,solicitudes-tids) I* entra a la región critica *I cambiaestado(HUNGRY,FALSE,dest,O); I* el filosofo me tiene hambre *I I* intenta tomar dos tenedores *I test(me,tidc,estado-tids); sale-exclusion-mutua(me,tids,Datos); /* sale de la región critica *I return Datos; I* /* I* I* 1 ....................................................................................................... I *I I* I* PUT FORKS *I *I I* *I I* ........................................................................................................ I coloca los dos *I void put-forks(int me,int dest,int *tids,int *estadotids,int *solicitudestids){ I* /*tenedores *I int right; I* vecino derecho *I I* vecino izquierdo *I int left; I* memoria compartida *I DataBase "Datos; right=(me+1)%NFILO; /* vecino derecho modulo NFILO *I left=(me-l)%NFILO; I* vecino izquierdo modulo NFILO *I Datos=entra-exclusion_mutua(me,tids,solicitudes-tids) I* entra a la región critica *I I* cambiaestado(THINKING,FALSE,dest,O); el filosofo me tiene hambre *I test(right,tids,estado-tids); test(left,tidc,estado-tids); sale-exclusion-mutua(me,tids,Datos); 1 I* sale de la región critica *I 82 La Cena de los Filósofos ........................................................................................................ / /* *I I* PHILOSOPHER *I I* */ I* *I ........................................................................................................ / void philosopher(int me,int dest,int *tids,int *estado-tids,int *solicitudes-tids); DataBase *Datos; int j=O; while(j<lTERA){ cambiaestado(THINKING,FALSE,dest,O); /* el filosofo me esta pensando */ Datos=take_forks(me,dest,tids,estado~tids,solicitudes~tids);); I* obtiene dos tenedores o se i* bloquea */ i* el filosofo me esta comiendo */ cambia-estado( EATING,FALSE,dest,O); put-forks(me,dest,tids,estado-tids,solicitudes-tids); i* regresa ambos tenedores */ shmcti(Datos-xhmid,IPC-RMI D,O); /* borrado de la zona de memoria compartida */ j++; 1 pvm-halt(); 1 /*terminar con todos */ ........................................................................................................ I* RECIBE-SOLI CI TUDES MAIN / *I ........................................................................................................ / #include "pvm3.h" #include "header.h" void main(void){ recibe-solicitudes(); 1 4.2 Implantación en un Sistema Distribuido usando P V M 83 ...................................................................................................... I* I* I* I* RECIBE SOLlCl TUDES ........................................................................................................ I *I *I *I *I I void recibe-solicitudes(void){ I* bandera de respuesta */ int respuesta=7; i* es verdadera si no se puede contestar inmediatamente*/ int diferir; I* tid del filosofo padre *I int parent; int msgtag3=3; I* etiqueta *I I* etiqueta *I int msgtag8=8; i* quien hace la solicitud para entrar a la región critica *I int who; i* bandera de exclusión mutua *I int mutex; I* de que i-esimo filosofo soy hijo *I int me; I* arreglo de tids de los filósofos *I int tids[NFILO]; I* la secuencia del nodo que hace la solicitud *I int k; DataBase *Datos; parent=pvm-parent(); I* obtiene el tid del proceso padre *I pvm-recv(parent, msgtag8); I* recibe datos del proceso padre (filosofo ¡)*I pvm-upkint(tids,NFILO, 1); I* recibo el arreglo de tids *I pvm-upkint(&me, l,l,); I* recibo el numero de filosofo de quien soy hijo *I pvm-recv(-I ,msgtag3); I* recibe solicitudes de cualquier proceso *I pvm-upkint(&k, 1,l); I* desempaqueta la secuencia de quien hace la solicitud *I pvm-upkint(&who,l,l ,); /* desempaqueta quien hace la solicitud */ pvm~upstruck(Datos,5,1); i* recibe los datos de la zona de memoria compartida *I Datos->secu-mas-alta=Maximo( me,tids,Datos->secu-mas-alta,k);l* obtiene la máxima secuencia en toda la red *I down(mutex); I* obtiene la autorización para entrar a la sección critica *I diferir=Datos->secc-critica&& ((k>Datos->secu_del-nodo)lI(k==Datos->secudeI_nodo && who>me)); I* obtiene permiso para entrar a la sección critica en *I I* la red *I up (mutex); i^ sale de la sección critica de este host *I if (diferir){ I* si diferir es TRUE no puedo entrar a la sección critica *I I* de la red *I Datos->arreglo-resp[who]=TRUE; I* lo marco para contestarle posteriormente *I 1 else{ pvm-pkint(&respuesta,l ,l); r' autorizo a who entrar a la sección critica de la red *I pvm~send(tids[who],msgtag3); 1 1 84 La Cena de los Filósofos ........................................................................................................ / /* */ /* MAXI MO */ /* */ /* */ ........................................................................................................ I int maximo(int me,int *tids,int Datos->secu-mas-alta){ i" etiqueta */ int msgtag9=9; E* del for */ int i; i" quien solicita */ int who; I* secuencia de quien solicita */ int secuwho; I* empaqueta quien es */ pvm-pkint(&me,l ,l); pvm-pkint(&Datos->secu-mas-alta, 1,I); i" empaqueta la secuencia mas alta */ i" envía la información a todos */ pv-mcast(tids, NFILO,msgtag9); for(¡=O; i<NFILO;i++){ /* recibe la secuencia de los demás */ pvm-recv(-1 ,msgtag9); I* desempaqueta quien la envió *I pvm-upkint(&who, 1,l); /* desempaqueta la secuencia */ pvm-upkint(secu-who, 1,l); if(secu-who>Datos->secu-mas-alta) I* verifica la mas alta de todos *I Datos->secu-mas-aIta=secuwho; 1 return(Datos->secu-mas-alta); 1 ........................................................................................................ I* /* ESTADOS I* / */ */ */ /* */ ........................................................................................................ / void estado(void){ I* de quien recibe la solicitud */ int who; /* etiqueta de solicitud */ int solicitud; I* estado del filosofo *I static int Estado; I* etiqueta */ int msgtag2=2; I* se ejecuta por siempre */ for(; ;I{ i" recibe de cualquiera *I pvm-recv(-1 ,msgtag2); /* desempaqueta quién envia */ pvm-upkint(&who,l ,I); /* desempaqueta la variable estado */ pvm-upkint(&state,l , I ) ; /* desempaqueta la solicitud */ pvm-upkint(&solicitud, 1,I); r' solicitud no es TRUE cuando solo */ if(solicitud!=TRUE) I* se desea cambiar a un nuevo estado */ /* Estado ahora tiene el nuevo estado *I Estado=state; /* solicitud es FALSE cuando se desea *I else{ /* que se mande el estado del filosofo *I I* crea un buffer de envío */ pvm-initsend(PvmDataRaw); I* empaqueta el estado del filosofo *I pvm-pkint(&Estado,l ,I); /* envía el estado a quien lo solicitó *I pvm~send(who,msgtag2);} 1 1 main(){ estado();} /* procedimiento principal */ Implantación en un Sistema Distribuido usando P V M 85 4.2 HEADER.H #include <stdio.h > #include <sys/types.h > #include <sys/ipc.h > #include <sys/sem.h > #include <sys/shm.h > #define ##define #define #define #define #define #define #define #define #define #define ESCLAVE-FILO RECIBE-SOLICITUDES ESTADOS NFILO ITEM END FALSE TRUE THINKINK EATING HUNGRY typedef struct data{ int secu-del-nodo=O; int secu-mas-alta=O; int secc-critica; int arreglo-resp [NFILO]; int Share-vars=TRUE; int shmid; }Dataease; "slave-filo" "recibe-sol" "estados" 5 20 2 O 1 2 3 4 P archivo esclavo */ r archivo recibe-sol */ r' archivo estados *I /* numero de filososfos */ P iteraciones */ P bandera de termino*/ r falso */ r^ verdadero *I /* pensando *I /* comiendo */ Phambre */ /* estructura de datos compartidos */ BIBLIOGRAFÍA 0 BEN-ARI, M., “Principles of Concurrent and Distribuited Programming”, la Edición. Editorial Prentice Hall, Gran Bretaña, 1990. O COMER, D. , “TCPAP, Principios basicos, protocolos y arquitectura”, 3a Edición. Editorial Prentice Hall, México 1996. O 0 O DAY,J.D., y ZIMMERMAN, H., ” The OS1 Reference Model” , Proc. of the IEEE, vol. 71, Diciembre, 1983 DEITHEL, “Introducción a los Sistemas Operativos”, 2a. Edición. Editorial Addison-Wesley, U.S.A, 1993 HAHN, H., “UNIX sin fronteras”, la. Edición. Editorial Mc. Graw Hill, México 1995. LAMPORT, L., “Time Clocks, and the Ordering of Events in a Distribuited System” , Commun of the ACM, vol. 21, pp. 558-564, Julio 1978 O MARQUEZ G., F., “Unix , Programación Avanzada”, 2a Edición. Editorial Addison-Wesley Iberoamericana, Wilmington, Delaware, E.U.A., 1994. O MILAN, MILENKOVIC, “Sistemas Operativos: Conceptos y Diseño”, la. Edición. Editorial Mc. Graw Hill, España, 1988 0 NEMETH, E., ”Unix System Administration Handbook”, Editorial Addison Wesley, 1995 0 PARKER, SYBIL P., “Diccionario Mc. Graw Hill de Computación Bilingüe English-Spanish”, la. Edición. Editorial Mc. Graw Hill, México, 1984 O PVM MANUAL REFENCE. ( hffp://www.netlib2.cs.utk.edu ) 0 RICART, G. y AGRAWALA, A. K., “An Optimal Algoritm for Mutual Exclution in Computer Networks”, Commun. of the ACM, vol. 24, pp. 9-17, Enero 1981 O STEVENS, RICHARD W., “Advanced Programming in the UNIX Environment”, 12a. Edición. Editorial Addison-Wesley, Massachusetts, USA, 1996 O TANEMBAUM, ANDREW S., “Sistemas Operativos Modernos”, la. Edición en español. Editorial Prentice Hall, México, 1993 0 TANEMBAUM, ANDREW S., ”Sistemas Operativos Distribuidos”, 1a. Edición en español. Editorial Prentice Hall, México, 1995 0‘ TANEMBAUM, ANDREW S., “Redes de Ordenadores”, la. Edición en español. Editorial Prentice Hall, México, 1992 86