INGENIERÍA TÉCNICA INFORMÁTICA DE SISTEMAS
Curso Académico 2005 / 2006
Proyecto Fin De Carrera
SERVIDOR DE FICHEROS
9P EN JAVA
Autor: Jaime Garzón Pérez -
[email protected]Tutor: Fco. J. Ballesteros -
[email protected]UNIVERSIDAD
Resumen
El presente proyecto describe el diseño y la implementación de un servidor de ficheros en red 9P multiplataforma.
Los protocolos de sistemas de ficheros en red permiten crear sistemas de ficheros distribuidos. Una determinada máquina tiene los recursos, que son puestos a disposición de más máquinas a través de un protocolo.
El protocolo 9P es el protocolo de ficheros de red integrado en el sistema operativo PLAN 9. Es un protocolo de nivel de aplicación, generalmente empleado sobre TCP. En cada transacción cliente-servidor de 9P se intercambian mensajes, el cliente envía un mensaje de petición al servidor, T-mensaje, y el servidor envía un mensaje de respuesta al cliente, R-mensaje. Las comunicaciones guardan estado y el servidor recuerda las acciones del cliente.
Los objetivos para esta implementación son, fundamentalmente, que podamos establecer comunicaciones con los clientes 9P (ceñirnos al protocolo), que sea una implementación portable entre arquitecturas distintas y que tenga un rendimiento adecuado.
El diseño del servidor 9P implementado aquí es modular. Principalmente existe un componente encargado de las comunicaciones cliente-servidor, y otro componente que desempeña la labor de acceder a los recursos que se están sirviendo.
La selección de Java, como lenguaje para implementar el diseño modular anterior, no fue algo arbitrario. Java tiene una portabilidad excelente entre distintas plataformas, con el diseño adecuado se pueden implementar aplicaciones fácilmente modificables y portables entre distintas arquitecturas.
Las pruebas hechas al software resultante han resultado satisfactorias, los resultados verificaron los objetivos propuestos. Hemos probado el servidor en varias plataformas diferentes, sin modificar su implementación, con resultados satisfactorios. También hemos probado varios clientes de 9P
RESUMEN 4
ÍNDICE 5
1. INTRODUCCIÓN 6
1.1.SERVIDORES DE FICHEROS EN RED 6
1.1.1. Espacio de nombres 6
1.1.2. Ejemplos reales de Sistemas de ficheros de red 7
1.2.PLAN 9 8
1.3.PROTOCOLO 9P 9
1.3.1. Mensajes 9P 10
1.3.2. Ejemplo de sesión 9P 18
1.4.JAVA 21
2. OBJETIVOS DEL PROYECTO 22
2.1.MOTIVACIÓN 22
2.2.REQUISITOS. 23
2.3.METODOLOGÍA DE DESARROLLO:DESARROLLO ESPIRAL 24
3. DISEÑO 26
3.1.SERVIDOR DE FICHEROS 9P 26
3.1.1. Procesado de mensajes 27
3.1.2. Interfaz de acceso a recursos 28
4. IMPLEMENTACIÓN 29 4.1.PAQUETE P9P 30 4.1.1. Clase server9P 31 4.1.2. Clase msg9P 32 4.1.3. Clase rpc9P 34 4.1.4. Clase type9P 35 4.2.PAQUETE PFS 38 4.2.1. Clase pfile 38 4.2.2. Clase pfid 41 4.2.3. Clase fidtable 42 5. PRUEBAS 44 5.1.PRUEBAS DE J2SE 44 5.2.PRUEBAS DE J2ME 49 6. CONCLUSIONES 51 BIBLIOGRAFÍA 54
CAPÍTULO 1
1.
Introducción
1.1. Servidores de ficheros en red
Los sistemas de ficheros en red permiten acceder a ficheros y directorios remotos de la misma forma que se accedería a ficheros locales, estos sistemas son llamados
Sistemas de ficheros distribuidos1. Generalmente estos ficheros son organizados dentro de una estructura de ficheros, lo que llamamos Espacio de Nombres.
En la figura 1-1 vemos cómo recursos existentes bajo el punto P en la máquina A pueden ser accedidos por la máquina B. La máquina A tiene los recursos de forma local, estos recursos están distribuidos y son accesibles mediante la red. La máquina B tiene los recursos en remoto, pero accesibles desde su Espacio de Nombres, creando la abstracción de que son locales.
figura 1-1 Ejemplo de jerarquía de ficheros en red Máquina A
P
Máquina B
Los nombres, de un espacio de nombres, pueden representar ficheros locales de un sistema de ficheros, ficheros virtuales que puede generar el sistema operativo (con algún propósito), ficheros ubicados en máquinas remotas, etc. Generalmente el espacio de nombres suele tener una estructura tipo árbol.
Transparencia e integración
Puede pensarse que cualquier medio que nos permita acceder a un fichero en una máquina remota es un sistema de ficheros de red. Pero no es así, entendemos un sistema de ficheros en red cuando los recursos distribuidos entran a formar parte del espacio de nombres que tenemos en el cliente.
El uso de un espacio de nombres permite que, tanto los ficheros locales como los remotos, sean tratados por igual. El sistema operativo se encarga de crear una capa de abstracción, traduciendo operaciones realizadas sobre ficheros en llamadas a procedimientos remotos o RPC (Remote Procedure Call)2. Esta capa de abstracción suele estar integrada en el núcleo del sistema, las aplicaciones de usuario no llegan a ser conscientes de que acceden a máquinas remotas, el sistema operativo se encarga de todo.
1.1.2. Ejemplos reales de Sistemas de ficheros de red
El protocolo de sistemas de red más difundido es el “Network File System“(NFS) de Sun Microsystems, implementado para numerosas plataformas basadas en UNIX. Tiene una total transparencia para las aplicaciones de usuario puesto que suele estar implementado a nivel de Kernel haciendo uso de las RPC.
El protocolo 9P también es un protocolo de ficheros en red, parecido a NFS, que se usa en el sistema operativo PLAN 9. Se basa también en llamadas a procedimientos remotos. Ambos protocolos guardan muchas semejanzas, como su estructura cliente-servidor o el uso de RPCs.
La diferencia fundamental entre NFS y 9P es que los mensajes de NFS son auto contenidos, cada petición de un cliente contiene toda la información que el servidor necesita para realizar la acción requerida. En una petición de 9P la información que necesita el servidor no está toda especificada dentro de la propia petición, ha podido ser indicada con anterioridad, existe un recuerdo de las acciones anteriores.
Por tanto, NFS es un protocolo que no guarda estado, al contrario de 9P, que realiza sus operaciones dentro de sesiones donde el cliente y el servidor mantienen cierta información común. El hecho de que NFS no guarde estado le hace robusto a fallos en la comunicación, pero al tener que indicar en cada mensaje más información hace que sea más pesado. El protocolo 9P es más ligero en sus transmisiones que NFS.
1.2. Plan 9
Plan 9 es un sistema operativo de libre distribución desarrollado por Bell Labs. Se inició a finales de los años ochenta por Ken Thompson, Rob Pike, Dave Presotto y Phil Winterbottom. En su página oficial podremos encontrar toda la documentación necesaria sobre su diseño e implementación.3
El desarrollo ha continuado hasta hoy día por múltiples programadores. La versión actual del sistema es la cuarta edición, distribuida bajo la licencia Lucent Public
License versión 1.02.4
El objetivo que tiene Plan 9 es solventar los típicos problemas de los sistemas clásicos de una forma simple y clara. Los sistemas basados en Unix se han ido adaptando a las ideas nuevas. Originalmente, lo normal era que una sola máquina centralizaba todos los procesos que competían por el tiempo de ejecución y/o recursos. Las nuevas tendencias tienden a un procesamiento distribuido, abaratando costes y obteniendo mejor rendimiento. Ya sea porque se tienen muchos procesadores en la misma máquina o porque se emplean múltiples máquinas, tenemos el problema de coordinar los recursos. Esto ha provocado muchas complicaciones a la hora de adaptar los sistemas clásicos.
Para lograr esta transparencia PLAN 9 tiene tres axiomas:
· Todos los recursos son accesibles desde una estructura jerárquica, como si de ficheros se tratasen, ya sea un recurso de memoria, capacidad de cálculo o cualquier tipo de dispositivo, estará representado en esta jerarquía. Todo es tratado como un fichero.
· Existe un protocolo estándar llamado 9P para acceder a los recursos. Plan 9 quiere ofrecer facilidad a la hora de coordinar y dar acceso a los recursos disponibles en una red. Mediante el uso de 9P los procesos tienen acceso a los recursos.
· Existen múltiples vistas de la jerarquía de los recursos. Cada proceso tiene una visión privada y personalizada de los recursos del sistema, esto se llama
Espacio de Nombres de un proceso.
Un ejemplo de cómo podría ser un sistema distribuido construido de esta forma sería el mostrado en la figura 1-2, donde vemos múltiples recursos distribuidos interconectados por conexiones de alto rendimiento. Las máquinas A, B, C y D hacen uso de los recursos. Podría darse el caso de que mientras A ejecuta procesos complejos
figura 1-2 Recursos distribuidos en un sistema Plan 9
Plan 9 tiene mecanismos para personalizar la vista de los recursos públicos, de manera que parezcan cercanos y locales.
1.3. Protocolo 9P
Como ya hemos dicho, 9P es un protocolo de ficheros de red. Se basa en llamadas a procedimientos remotos. Cada operación es una transacción cliente-servidor y utiliza la estructura básica donde el cliente realiza una petición y es respondido por el servidor. Podemos encontrar todos los detalles de la implementación de este protocolo en el Capítulo 5 del manual de Plan 9.5
Existen distintos tipos de mensajes para cada acción que desee realizar el cliente 9P. Cuando el cliente envíe un mensaje (T-mensaje) el servidor responderá con el mensaje de respuesta correspondiente (R-mensaje). La figura 1-3 muestra este comportamiento.
figura 1-3 Mensajes 9P entre el Cliente y el Servidor
Tanto el cliente 9P como el servidor 9P deben guardar estado de las acciones realizadas. Cuando un cliente se conecta a un servidor se establece un canal de comunicación bi-direccional, al que llamaremos sesión 9P.
Dentro de una sesión el cliente y el servidor guardan unos identificadores de ficheros comunes entre ellos, también conocidos como FIDS (File Identifier
Descriptors). Estos identificadores o FIDS son “punteros” a ficheros del servidor. El
cliente hace uso de un FID para indicar sobre qué fichero quiere ejecutar una determinada acción. Esto puede verse en la figura 1-4.
CPU CPU CPU CPU
CPU CPU CPU CPU
HD HD HD HD
HD HD HD HD
Recursos Distribuidos
Recursos Tiempo Cálculo Recursos de Almacenamiento
ß 9P à ß 9P à ß 9P à Conexión de RED Conexión de RED Alto Rendimiento A B C D Cliente 9P Servidor 9P Petición 9P (T-mensajes) Respuesta 9P (R-mensajes)
figura 1-4 Uso de descriptores FID entre el cliente y el servidor de ficheros 9P
Cada fichero tiene un identificador único dentro del sistema, que no puede repetirse, denominado QID. Dos QID son iguales si, y sólo si, identifican al mismo fichero en el sistema de ficheros. Pueden suceder que un fichero tenga múltiples FIDs haciéndole referencia, pero sólo tendrá un QID. Algunos R-Mensajes retornan el QID de un fichero.
Aunque el protocolo 9P esté diseñado para su uso en conexiones de red, también se usa dentro de Plan 9 de forma local. Mediante este mecanismo Plan 9 normaliza el acceso a los ficheros, sin hacer distinciones entre los ficheros locales y remotos.
A la hora de usar 9P mediante una conexión de red, los mensajes 9P usan protocolos de transporte. Originalmente 9P usaba el protocolo de transporte IL6, implementado en PLAN 9, pero lo más usual es usar el protocolo TCP7 por lo extendido que está.
1.3.1. Mensajes 9P
El protocolo 9P tiene diferentes tipos de mensajes para cada operación a realizar. Podemos dividir los mensajes en dos grupos, los mensajes generados por el cliente (mensajes de petición T-mensajes) y los mensajes generados por el servidor como respuestas (mensajes de respuesta R-mensajes). A continuación vemos una tabla con todos los mensajes de petición y su correspondiente respuesta.
Petición (cliente) Respuesta (servidor) Tversion Rversion Tauth Rauth Tattach Rattach Twalk Rwalk Topen Ropen Tcreate Rcreate Servidor 9P DISCO Cliente 9P Conexión 9P FID FID FID FID FID FID
Además de los mensajes anteriores, existe un tipo de mensaje que sólo puede ser generado por el servidor como réplica a cualquier T-mensaje del cliente, éste mensaje es
Rerror, útil para indicar al cliente un error en la petición requerida.
La estructura común de un mensaje 9P es la siguiente:
size[4] TIPO_MENSAJE tag[2] LISTA_DE_CAMPOS
El campo size indica el tamaño completo del mensaje.
Todos los mensajes de 9P tienen un identificador de mensaje o tag, utilizado para relacionar las peticiones con su respuesta correspondiente y evitar ambigüedades a la hora de interpretar una respuesta del servidor.
El valor TIPO_MENSAJE especifica el tipo de mensaje. Dependiendo del tipo que
sea, el elemento LISTA_DE_CAMPOS contendrá unos parámetros u otros, según la naturaleza del mensaje.
A nivel de transporte, un mensaje 9P es una secuencia de Bytes. Por ello es importante que se conozca el tamaño del mensaje y de cada uno de los elementos que lo componen. Como ya hemos dicho, el parámetro size describe el tamaño del mensaje
completo, como podemos ver en la estructura, size está indicado como “size[4]”, lo que
indica que el propio dato ocupa 4 bytes. También podemos ver que el valor tag ocupa 2 bytes.
A continuación vemos una descripción de cada tipo de mensaje, de su función y de cómo está formado.
Mensajes “Rerror”
La estructura del un mensaje Rerror es la siguiente: Rerror: size[4] Rerror tag[2] ename[s]
El mensaje Rerror se usa por el servidor para notificar una anomalía a la hora de interpretar o ejecutar otro mensaje. Se envía con una descripción del problema.
Mensajes “version"
A continuación vemos la estructura interna de Tversion y de Rversion. Tversion: size[4] Tversion tag[2] msize[4] version[s] Rversion: size[4] Rversion tag[2] msize[4] version[s]
El mensaje Tversion es el primero que debe enviar el cliente una vez establecido el canal de comunicación. Su función es negociar algunos aspectos de la nueva conexión. Con este mensaje se indica qué versión del protocolo se está usando y qué tamaño máximo pueden tener los mensajes. El servidor debe responder con el mensaje de respuesta correspondiente Rversion.
El cliente indicará la versión del protocolo que desea usar, respondiéndole el servidor con la misma versión, o una anterior en el caso de no poder usar la solicitada por el cliente. Actualmente sólo existe una versión de 9P activa, pero el protocolo está diseñado para mantener la compatibilidad en un futuro.
Respecto al tamaño de los mensajes, es posible que el medio de transmisión limite este parámetro. El cliente indicará el tamaño máximo que desea, respondiéndole el servidor con el mismo tamaño o uno menor.
En el caso de que no se entienda el mensaje Tversion, o que la negociación sea inaceptable para el servidor, éste responderá con un Rerror, indicando los motivos del error.
Mensajes “auth” y “attach”
La estructura de los mensajes Tattach y Rattach es la siguiente:
Tattach: size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s] Rattach: size[4] Rattach tag[2] qid[13]
Los mensajes Tattach tienen por objetivo pedir al servidor un acceso al sistema de ficheros. Para ello proponen un FID como raíz en el servidor y el servidor les retornará un identificador único de fichero, también llamado QID, en un mensaje Rattach.
Tauth es un mensaje que propone un FID al servidor para poder autentificarse a
través de dicho FID. El servidor responderá mediante Rattach. El FID propuesto por el cliente se empleará para el proceso de autentificación, que queda delegado a un protocolo de autentificación que no forma parte de 9P. En el caso de que el servidor no requiera autentificación responderá con un mensaje Rerror.
Ambos mensajes, Tattach y Tauth, contienen el nombre de usuario uname y un nombre que indica el árbol de ficheros al que se desea acceder aname.
Mensajes “clunk”
La estructura de los mensajes clunk es la siguiente: Tclunk: size[4] Tclunk tag[2] fid[4] Rclunk: size[4] Rclunk tag[2]
El mensaje Tclunk pide al servidor que elimine el FID indicado del conjunto de FIDS actualmente activos en la conexión. El servidor retornará Rclunk si fue posible realizar la operación, o Rerror en caso contrario. Cabe destacar que esto no eliminará el fichero del servidor, sólo elimina una referencia al mismo.
Los FIDs pueden ser reutilizados una vez eliminados, excepto en el caso de que se retorne Rerror, el FID no podrá ser usado más.
Mensajes “flush”
La estructura de los mensajes flush es la siguiente: Tflush: size[4] Tflush tag[2] oldtag[2] Rflush: size[4] Rflush tag[2]
El cliente puede quedarse esperando una respuesta del servidor que, por algún motivo, éste no logra enviar. Puede que una lectura se bloquease esperando datos. Para solventar estas situaciones de bloqueo, el cliente puede enviar al servidor un mensaje de tipo Tflush, que forzará al servidor a cancelar la petición referida por el mensaje. Si todo fue correcto el servidor lo comunicará con un Rflush.
Dentro del mensaje Tflush encontraremos el tag del mensaje que tiene que ser cancelado oldtag.
Mensajes “open” y “create”
A continuación vemos la estructura de los mensajes Topen y Ropen. Topen: size[4] Topen tag[2] fid[4] mode[1]
Ropen: size[4] Ropen tag[2] qid[13] iounit[4]
La estructura de los mensajes Tcreate y Rcreate es la siguiente.
Tcreate: size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1] Rcreate: size[4] Rcreate tag[2] qid[13] iounit[4]
Estos mensajes permiten al cliente crear y/o abrir un fichero.
Topen indica al servidor que se abra el fichero indicado por el FID. El servidor
retornará un Ropen con el QID del fichero abierto. La apertura se efectuará en el modo indicado por el cliente a través del parámetro mode.
Con Tcreate el cliente pide al servidor que cree un archivo con el nombre name, con los permisos indicados por el parámetro perm. El nuevo fichero será creado dentro del fichero apuntado por el FID indicado por el cliente, por lo que será necesario que el FID apunte a un directorio. Si la creación fue correcta, el servidor retornará el QID del nuevo fichero. Además de crear un nuevo fichero también se efectuará una apertura sobre él, por tanto, es necesario especificar el modo de apertura con el parámetro mode.
Cabe destacar que la operación Tcreate implica también la modificación del FID propuesto por el cliente, que de ser el directorio destino, pasa a ser el nuevo archivo creado.
El servidor hace uso del parámetro iounit para indicar al cliente cuál es número máximo de bytes que garantiza que pueden ser leídos o escritos, de forma que el cliente se adaptará a la hora de escribir o leer información.
Mensajes “read” y “write”
La estructura de los mensajes Tread y Rread es:
Tread: size[4] Tread tag[2] fid[4] offset[8] count[4] Rread: size[4] Rread tag[2] count[4] data[count]
La estructura de los mensajes Twrite y Rwrite es:
Twrite: size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count] Rwrite: size[4] Rwrite tag[2] count[4]
Estos dos mensajes son el mecanismo de lectura y escritura en los ficheros remotos. Con ellos el cliente podrá acceder a la información y modificarla. Ambas operaciones se efectúan siempre en un FID, que debe esta abierto anteriormente en el modo adecuado (no se podrá escribir un fichero abierto para lectura).
El mensaje Tread es el encargado de leer de los ficheros remotos. Si la operación se ha podido realizar sin problemas, se retornará un conjunto de datos en un mensaje
Rread. Con Twrite el cliente pide al servidor que escriba unos datos en un fichero, si
todo fue correcto el servidor retornará un mensaje Rwrite.
Ambos mensajes de petición tienen dos parámetros que permiten indicar a partir de que byte se realizará la operación, el valor offset, y qué número de bytes se van a leer o escribir, el valor count. El mensaje Twrite tiene otro campo que contiene el bloque de información que pretende escribirse, el valor data.
Los mensajes de respuesta del servidor tienen un parámetro que indica el número de bytes que han sido leídos o escritos, el valor count. Para el caso de las lecturas,
Rread retornará también la información requerida por el cliente en el parámetro data. Como en todos los mensajes, puede suceder que el servidor no comprenda la petición o esta no pueda ser realizada. El servidor responderá con un mensaje Rerror a este tipo de peticiones, indicando la descripción del error.
Mensajes “remove”
La estructura de los mensajes Tremove y Rremove es: Tremove: size[4] Tremove tag[2] fid[4] Rremove: size[4] Rremove tag[2]
El mensaje Tremove pide al servidor que elimine un fichero, para ello, el cliente indica el FID que referencia al fichero que se desea eliminar. Si la operación se completó correctamente, el servidor lo indicará con un Rremove, de lo contrario, el servidor retornará un Rerror.
Mensajes “stat” y “wstat”
Los campos característicos de los mensajes Tstat y Rstat: Tstat: size[4] Tstat tag[2] fid[4]
Rstat: size[4] Rstat tag[2] stat[n]
La estructura de los mensajes Twstat y Rwstat es:
Twstat: size[4] Twstat tag[2] fid[4] stat[n] Rwstat: size[4] Rwstat tag[2]
Estos mensajes están relacionados con los metadatos de los ficheros. Los ficheros tienen una serie de información almacenada sobre si mismos, como puede ser el tamaño o la fecha del último acceso, los campos de la estructura STAT8 representan estos metadatos.
El mensaje Tstat pide al servidor que retorne los metadatos de un fichero indicado por un FID. El servidor retornará Rstat con una estructura de tipo stat, que contiene variada información acerca del FID requerido.
Cuando le cliente quiere editar los metadatos de un fichero escribe los nuevos metadatos mediante el mensaje Twstat, que envía al servidor una estructura stat y un fid que debe ser actualizado. En el caso de que sea posible efectuar el cambio de los metadatos, el servidor retornará un mensaje Rwstat.
La estructura STAT se compone de los siguientes campos de datos:
Al igual que los mensajes, la estructura STAT es una secuencia de bytes con los datos colocados de forma consecutiva.
Mensajes “walk”
A continuación vemos la estructura de los mensajes Twalk y Rwalk.
Twalk: size[4] Twalk tag[2] fid[4] nfid[4] nwname[2] nwname*(wname[s]) Rwalk: size[4] Rwalk tag[2] nwqid[2] nwqid*(qid[13])
El mensaje Twalk pide al servidor que asigne un fichero a un nuevo FID. El cliente parte de un FID origen e indica la ubicación del fichero destino a partir de ese FID. El servidor retornará los QIDs de todos los ficheros que recorrió para llegar al lugar indicado (si el fichero destino existe).
El FID que el cliente ofrece como inicio de la ruta no es modificado. El servidor usará un nuevo FID, parámetro newfid, para apuntar al fichero destino de la ruta.
El camino a recorrer se indica por una lista de nombres contenida en wname. Es muy habitual que el cliente quiera duplicar un FID, de forma que envía una petición
Twalk con una lista de nombres vacía, y el servidor asume que el fichero donde apunta
el FID origen es el mismo que el fichero destino.
size[2] Tamaño total en bytes de la estructura STAT type[2] Campo para uso interno del Sistema Operativo.
dev[4] Campo para uso interno del Sistema Operativo.
qid.type[1] Vector de bits correspondiente al modo de apertura del fichero. qid.vers[4] Versión del fichero.
qid.path[8] Identificador único del fichero dentro del sistema de ficheros. mode[4] Permisos y modos.
atime[4] Marca de tiempo del último acceso.
mtime[4] Marca de tiempo de la última modificación. length[8] Tamaño del fichero en bytes.
name[ s ] Nombre del fichero.
uid[ s ] Nombre del usuario propietario.
gid[ s ] Nombre del grupo al que pertenece el fichero.
1.3.2. Ejemplo de sesión 9P
Supongamos que tenemos un servidor 9P sirviendo la estructura de ficheros indicada en la figura 1-5. En este ejemplo, un cliente se conecta a nuestro servidor para realizar la siguiente acción “leer el contenido de un fichero y escribirlo en otro fichero nuevo”.
figura 1-5 Estructura de ficheros que sirve el Servidor 9P
A continuación veremos paso a paso las acciones que realiza el cliente y las respuestas por parte del servidor. Estas acciones están reflejadas en la figura 1-6, donde vemos cada mensaje de petición y la respuesta.
FASE 1: Establecimiento de la sesión 9P.
1. El cliente inicia la conexión indicando el tipo de versión de 9P y el tamaño de los mensajes. La respuesta del servidor confirma la versión y el tamaño.
2. El cliente trata de establecer un canal para autentificarse. El servidor indica que no está permitido autentificarse.
3. El cliente ofrece un FID para atarlo con la raíz del sistema de ficheros, retornando el servidor el QID del fichero raíz.
Tras estos mensajes, el cliente ha establecido una conexión con el servidor y obteniendo un FID (287) que identifica la raíz del sistema de ficheros.
FASE 2: Apertura del fichero “/dir3/file1”
4. El cliente ofrece un FID para atarlo al fichero “/dir3/file1”. 5. Apertura del fichero “/dir3/file1” para lectura.
El cliente abre el fichero origen en el modo lectura. Después de estos mensajes se
/ dir1/ dir2/ dir3/ file1/ | file2/ dir4/ file3/
FASE 3: Creación del fichero “/dir1/copia1”
6. El cliente ofrece un FID para atarlo al fichero (directorio) “dir1”.
7. Se verifica la existencia del fichero “/dir1/copia1” atándolo a un FID. El servidor responde con un Rerror, el fichero no existe.
8. Con esta acción el cliente crea una copia de un FID. El FID 291 y el 292 están asignados al mismo fichero físico.
9. El cliente pide al servidor que se cree el fichero “/dir1/copia1” y le asigna el FID 292.
10. El cliente deshecha el FID 291.
Tras estos mensajes, el cliente ha logrado crear el fichero destino “/dir1/copia1”. Se sostienen los FID 287 y 289 anteriores junto con uno nuevo, el 292 que representa el nuevo fichero creado.
FASE 4: Lectura y Escritura.
11. Lectura del fichero “/dir3/file1”. El servidor retorna con un bloque de datos indicando que ha leído 22 bytes.
12. Escritura de 22 bytes al fichero “/dir1/copia1”.
13. El cliente trata de leer más datos del fichero “/dir3/file1”, el servidor responde con un mensaje sin datos. La lectura se da por finalizada.
Se realiza la transferencia de información de un fichero a otro. Todos los descriptores anteriores se mantienen activos.
FASE 5: Cierre de los ficheros
14. El cliente deshecha el FID 289. 15. El cliente deshecha el FID 292.
La siguiente figura muestra las peticiones del cliente, columna de la izquierda, y las respuestas correspondientes por parte del servidor, columna de la derecha. La comunicación 9P corresponde al ejemplo anterior.
Tread fid=289 offset=0 count=8192
Rread count=22 data={datos} Twrite fid=292 offset=0 count=22 data={..}
Tclunk fid=289 Tclunk fid=292 Tread fid=289 offset=22 count=8192
Rclunk Rread count=0 Rwrite count=22 Rclunk 11 12 13 14 15 Respuestas SERVIDOR 9P Tversion msize=8216 version=”9P2000”
Rversion msize=8216 version=”9P2000” Tauth afid=287 uname=”glenda”
Twalk fid=287 newfid=289 wname=”dir3/file1” Topen fid=289 mode={lectura}
Twalk fid=287 newfid=291 wname=”dir1”
Twalk fid=291 newfid=292 wname=” ” Tattach fid=287 afid=-1 uname=”glenda”
Twalk fid=291 newfid=292 wname=”copia1”
Tcreate fid=292 name=”copia1”
Rwalk wqid=[ ]
Rerror ename=”File not found” Rwalk wqid=[QID]
Ropen qid=QID Rwalk wqid=[QID, QID] Rattach qid=QID
Rerror ename=”No auth required”
Tclunk fid=291 Rcreate qid=QID Peticiones CLIENTE 9P 10 9 8 7 5 6 4 3 2 1 Rclunk 1 2 3 4 5
1.4. JAVA
Es necesario plantearse qué lenguaje es más conveniente para implementar el servidor 9P. El lenguaje seleccionado debe permitir cumplir los requisitos que hemos impuesto para la aplicación, no nos valdría de nada usar un lenguaje que sólo exista en una arquitectura si lo que pretendemos es tener portabilidad.
La elección del lenguaje ha sido JAVA9. Su capacidad de portabilidad está llevada al extremo de funcionar sin cambios en cualquier plataforma, que tenga una máquina virtual de java implementada y tenga las librerías que se utilicen.
Java es un lenguaje orientado a objetos con una sintaxis inspirada en C. Incorpora conceptos como sincronización y gestión de tareas dentro del lenguaje, así como toda una colección de clases ya implementadas para ofrecer un entorno de desarrollo completo.
Java basa su filosofía en una máquina virtual (Virtual Machine o VM). La implementación de una aplicación es igual para dos sistemas diferentes, ya que la implementación está hecha para la VM. El código fuente de Java es compilado para la VM, que lo interpreta (En la figura 1-7).
figura 1-7 Programa de JAVA interpretado por la VM.
Existen varias versiones de JAVA, las más interesantes para nuestra aplicación son J2SE (Edición estándar) y J2ME (Edición reducida). La edición estándar de Java se usa en PCs, estaciones de trabajo o incluso servidores, está diseñada para máquinas sin muchas limitaciones en lo que a recursos se refiere. La edición reducida de Java nos ofrece la posibilidad de ejecutar programas en Java en arquitecturas con recursos reducidos, como teléfonos móviles o agendas. La implementación del servidor deberá funcionar en las dos versiones J2SE y J2ME.
El gran problema de Java es su rendimiento. Un programa hecho en Java debe ser interpretado por la VM, que a su vez lo ejecuta en el sistema real. Si usamos un lenguaje de programación como C, el programa estará compilado para la arquitectura del sistema, obteniendo mejor rendimiento. En Java el código será interpretado, no obstante, el rendimiento parece aceptable para nuestro propósito.
Programa de JAVA
VM de Java
Sistema Operativo
Interfaz de Sistema Interfaz de VM
CAPÍTULO 2
2.
Objetivos del proyecto
El protocolo 9P está muy integrado en PLAN 9. Nos interesaría que PLAN 9 pudiese acceder a recursos de otras máquinas con arquitecturas y sistemas muy diferentes. Por ello queremos implementar un servidor 9P para otros sistemas.
2.1. Motivación
Plan 9 no es un sistema operativo muy difundido hoy por hoy. La gran mayoría de sistemas que están implantados no ofrecen un modo de acceso claro a sus recursos, para las máquinas con Plan 9.
Implementando un servidor 9P que pueda ejecutarse en otros sistemas diferentes de Plan 9, potenciaremos la capacidad de éste para usar recursos de otras máquinas.
En la figura 2-1 vemos un ejemplo de cómo un cliente 9P, en un sistema Plan 9, accede a recursos de otras plataformas, que los distribuyen mediante el protocolo 9P.
LINUX MacOS Windows
PLAN 9
Cliente 9P
Además existen otras motivaciones, por ejemplo, supongamos que tenemos un sistema con Plan 9 y varios dispositivos como agendas personales o teléfonos móviles. Entendemos que todos los periféricos tienen acceso al mismo medio de transmisión que el sistema con Plan 9. En este caso, poder acceder a los recursos de estos periféricos desde Plan 9 no nos aportaría gran beneficio (en lo que a cantidad de recursos se refiere), pero nos ofrecería movilidad.
Podemos imaginar un programa hospedado en el sistema con Plan 9. Este programa está haciendo uso de todos los recursos del sistema con Plan 9, pero podría ser que esté recaudando información de todos los pequeños periféricos a los que tiene acceso, o incluso podría cambiar su comportamiento dependiendo de unos parámetros que estén ubicados dentro de un periférico (con lo que lograríamos algo parecido a un control remoto).
2.2. Requisitos.
Una vez identificado el problema, debemos fijarnos unos objetivos o requisitos que deberíamos cumplir para que la implementación del servidor resulte útil.
Protocolo 9P como estándar:
Si pretendemos que el servidor sirva correctamente a todos los clientes 9P que se conecten a él, debemos ajustarnos a la especificación del protocolo 9P.
Portabilidad:
Puesto que pretendemos que el servidor pueda ejecutarse en múltiples máquinas debemos hacer un diseño altamente portable, que no sea costoso implementar para múltiples plataformas.
Fácil mantenimiento:
Ya que de un protocolo se trata, y puede estar sujeto a futuras ampliaciones, sería un requisito deseable que la modificación fuese lo más sencilla posible. Un diseño modular y claro facilitará este requisito.
Eficacia:
Es necesario que el rendimiento cumpla unos límites. No nos vale de nada un servidor que tarde un tiempo excesivo en responder a una petición que debería ser prácticamente instantánea.
2.3. Metodología de desarrollo: Desarrollo espiral
Las metodologías de desarrollo ayudan a desarrollar un producto de software, siguiendo unas pautas para producir el software deseado. Estas metodologías pueden ser tan simples como sentarse a hablar sobre la aplicación o tan sofisticadas como usar lenguajes formales y diagramas, para especificar el desarrollo del software. Las metodologías de software dividen el desarrollo de un producto en cuatro fases fundamentales: análisis, diseño, desarrollo y verificación.
Existen muchos métodos de desarrollo para producir una aplicación desde su fase de análisis al producto final. Una de las metodologías mas comunes es el desarrollo espiral basado en prototipos.
La metodología de desarrollo espiral se basa, fundamentalmente, en generar prototipos de la aplicación y retomar a las fases de análisis, diseño y desarrollo tras cada prototipo, para mejorar o añadir nuevas funcionalidades al producto. Es, por tanto, una metodología iterativa, que retorna a las fases anteriores del desarrollo para iniciar un nuevo ciclo. Con cada ciclo la aplicación está más acabada. En la figura 2-2 podemos ver una representación del modelo.
figura 2-2 Metodología de desarrollo espiral
Una breve explicación, paso a paso, de la metodología mostrada en la figura 2-2 sería como sigue:
· El proyecto se inicia con un análisis previo del problema, pudiendo usar las herramientas necesarias para realizar el análisis, como entrevistas al usuario, especificaciones de requisitos, textos descriptivos del problema que es necesario resolver. Análisis Diseño Desarrollo Evaluación Inicio 1º Prototipo Software Prototipo Final de Software Pruebas
· Finalmente pasamos a evaluar el prototipo. Si falla en algún aspecto retornaremos a la fase de análisis, realizaremos un nuevo diseño e implementaremos un nuevo prototipo.
Las iteraciones de las fases de desarrollo se producirán hasta que lleguemos al producto final. Con cada iteración iremos encontrando prototipos más cercanos al software final, siendo también en cada nuevo ciclo los cambios menos frecuentes.
Para el desarrollo del presente proyecto hemos seguido esta metodología. La fase de análisis fue realizada, fundamentalmente, con entrevistas verbales con el tutor del proyecto y lectura de la documentación del protocolo 9P.
Tras las entrevistas se procedió a realizar un diseño, elaborado generalmente mediante diagramas de interacción entre entidades y tratando de cumplir a las ideas captadas en el análisis. La implementación de los prototipos se realizó directamente en un lenguaje de programación, siendo más concretos, los prototipos han sido realizados en JAVA.
La evaluación de los prototipos tuvo dos fases:
· Verificar que el software generado cumple las características del diseño.
· Analizar los prototipos junto con el tutor del proyecto.
Con la evaluación se rechazaba el prototipo o parte de él, retornando la fase de análisis para solucionar los problemas.
CAPÍTULO 3
3.
Diseño
Es importante tener un diseño claro y definido, los beneficios superan con creces el esfuerzo invertido en el diseño. Si desarrollamos aplicaciones partiendo de un diseño adecuado obtendremos mejores resultados con menor esfuerzo. Posteriores modificaciones también se ven afectadas por el diseño, un diseño que permita este tipo de cambios alargará el tiempo de vida de la aplicación.
Dividiendo el problema, en problemas más sencillos, podemos trazar un camino hasta un diseño modular y claro, esto es conocido como diseño descendente.
En los diseños descendentes existen varios niveles de diseño. Cuanto más profundo sea el nivel nos revelará más información de cómo funciona la aplicación.
3.1. Servidor de Ficheros 9P
Podemos ver nuestro servidor 9P como una caja negra que realiza unas determinadas acciones (sin especificar como estas se llevan a cabo).
En el caso que nos atañe, tenemos un servidor que extrae la información (mensaje 9P) de una conexión. El mensaje es procesado e interpretado como un mensaje de petición, y tras realizarse las operaciones requeridas, se retornará un mensaje de respuesta. Desde este punto de vista, el servidor es un objeto que media entre la conexión de red y los recursos, como vemos en la figura 3-1.
Servidor de Ficheros 9P Petición 9P Respuesta 9P Recursos Recursos Cliente 9P RED
Las tareas del servidor se pueden dividir en dos grandes grupos:
· Gestión de los mensajes y gestión de la comunicación.
· Ejecución de las acciones requeridas en los recursos.
En la figura 3-2 vemos cómo podría repartirse el trabajo del servidor, con cada entidad encargándose de una interfaz externa diferente.
figura 3-2 División de tareas del servidor
3.1.1. Procesado de mensajes
Cuando un cliente se conecta con un servidor 9P, se inicia entre ellos una Sesión. Esta sesión es una comunicación cliente-servidor, donde ambos almacenan información de mensajes anteriores. Para un diseño más óptimo, deberíamos ofrecer la posibilidad de que existan múltiples sesiones 9P. Aunque esto es algo que puede no ser posible dependiendo del medio de transporte usado en la implementación.
En la figura 3-3 vemos como existe una entidad que gestiona las Sesiones 9P, cuando un cliente inicia una conexión se creará una sesión. Cada sesión es una entidad independiente que recibe mensajes 9P y responde a ellos.
figura 3-3 Entidad para el procesado de mensajes 9P Servidor de Ficheros 9P Interfaz de acceso a Recursos Procesado de Mensajes 9P 9P Recursos I/O Procesado de Mensajes 9P Sesión 9P (conexión) Interfaz de acceso a Recursos Receptor de nuevas conexiones Mensajes 9P Mensajes 9P Sesión 9P (conexión) RED
Dentro de la entidad Sesión 9P tendremos varios agentes que realizarán distintas acciones. Uno se encarga de extraer información del canal de comunicación y otro agente se encargará de procesar el mensaje, como vemos en la figura 3-4.
figura 3-4 Entidad “Sesión 9P”
Recordemos que un mensaje 9P es una secuencia de bytes, dependiendo del tipo de mensaje su estructura variará. Los datos están colocados uno tras otro en la secuencia, la estructura del mensaje es “aplanada”. Tendremos un agente encargado de extraer o insertar estos mensajes del canal de comunicación. Una vez extraído un mensaje, éste es interpretado y reestructurado por la segunda entidad. Lo que llamaremos “desaplanado”.
La entidad que hemos denominado Petición/Respuesta se encargará de transformar esa información en un mensaje 9P de tipo petición T-Mensaje, ejecutar la RPC correspondiente y generar un mensaje 9P de tipo respuesta R-Mensaje.
3.1.2. Interfaz de acceso a recursos
La segunda entidad importante que constituye nuestro servidor es una interfaz que permite acceder a los recursos de una forma homogénea. Pueden existir peculiaridades a la hora de acceder a un recurso en concreto, para normalizar estas peculiaridades a una interfaz común, existe un modulo encargado de ello. Este módulo ofrece una interfaz de acceso a ficheros como interfaz a los recursos.
En la figura 3-5 podemos observar como trabaja este diseño, donde simultáneas Sesiones de 9P pueden acceder a los recursos. Dentro de cada mensaje 9P, que quiere acceder a un fichero, existe un identificador de este fichero llamado FID. Estos FIDs serán gestionados por esta entidad, que los traducirá en referencias a objetos que representarán a los recursos.
Sesión 9P (conexión) Petición / Respuesta
RPC
T-Mensaje
R-Mensaje Lectura y escritura
de mensajes. Interfaz de acceso a Recursos Mensajes 9P RED
CAPÍTULO 4
4.
Implementación
A la hora de implementar el diseño la programación orientada a objetos nos facilita la adaptación. Muchas de las entidades descritas anteriormente serán implementadas con una clase de Java o un conjunto de ellas. A continuación veremos una descripción global de la implementación del servidor.
La implementación propuesta es estructuralmente similar al diseño planteado, pero no todas las entidades están implementadas directamente como clases, en ocasiones se han unido varias entidades para formar una clase de la implementación, o una entidad ha quedado dividida en varias clases.
En cuanto al procesado de mensajes, hemos implementado varias clases que cumplen el papel de las entidades propuestas por el diseño:
· En el diseño tenemos una entidad llamada “Petición/Respuesta” (que podemos ver en la figura 3-3 del diseño). Esta entidad está implementada mediante las tres clases tmsg9P, rmsg9P y rpc9P. Estos clases están encargadas de extraer un mensaje 9P, ejecutar su llamada a procedimiento remoto y retornar el mensaje de respuesta.
· El diseño nos describe una entidad encargada de establecer conexiones de red y mantener sesiones abiertas (ver figura 3-4). Ésta es una estructura típica de un servidor que acepta múltiples conexiones, la hemos implementado mediante una clase llamada server9P.
La interfaz de acceso a recursos está descrita con las siguientes clases:
· La clase fidtable representa la entidad “Conjunto de descriptores” de la figura 3-5 del diseño. Una instancia de esta clase será una tabla que contenga descriptores de ficheros, representados por objetos de la clase pfid.
· La clase pfile es la representación de un fichero, y es equivalente a la entidad “Interfaz tipo fichero” que podemos ver en la figura 3-5 del diseño.
En la figura 4-1 podemos ver la interacción de todas estas clases al formar un servidor de ficheros 9P completo.
figura 4-1 Diagrama interacción de clases
Un ejemplo de cómo interactúan estas clases entre sí sería el siguiente: 1. Un mensaje es recibido por server9P.
2. El mensaje es procesado por una instancia de tmsg9P.
3. La acción requerida por el mensaje se delega a una RPC, representada por una instancia de la clase rpc9P.
4. La RPC consultará la tabla de descriptores (fidtable), para obtener el descriptor de fichero necesario, para ejecutar la acción requerida por el mensaje.
5. La RPC ejecuta las acciones sobre el descriptor de ficheros pfid.
6. Las instancias de la clase pfid acceden a la representación del fichero pfile, para ejecutar las acciones requeridas por la RPC.
7. El objeto pfid retorna el resultado a la RPC.
8. La instancia de la clase rpc9P genera un mensaje de respuesta rmsg9P. 9. El mensaje de respuesta se envía al objeto server9P.
10. Finalmente, server9P envía la respuesta por el canal de comunicación. Dada la distinta naturaleza de las clases, éstas están agrupadas en dos paquetes:
· Clases relacionadas con los mensajes 9P, empaquetadas en el paquete P9P.
· Clase relacionadas con el sistema de ficheros, empaquetadas PFS.
4.1. Paquete P9P
Las clases que incluye este paquete están relacionadas con el protocolo 9P. Existe una clase de tipo mensaje 9P llamada msg9P y otra clase que representa una RPC
server9P tmsg9P rmsg9P fidtable pfid pfid rpc9P pfid pfile
Objetos encargados del tratamiento de mensajes Objetos encargados del
sistema de ficheros 1 2 3 4 5 6 7 8 9 10
La clase principal de este paquete es server9P. Esta clase proporciona una implementación de un servidor 9P, gestionando las comunicaciones cliente-servidor y procesando los mensajes del protocolo. Está construida a partir de las otras clases del paquete, como son msg9P y rpc9P.
4.1.1. Clase server9P
La clase server9P es un objeto encargado de esperar conexiones TCP y de leer los mensajes 9P del canal de comunicación TCP. La interfaz de uso es muy simple, sólo existe un método llamado ejecutar( ), que nos permite iniciar la escucha y recepción de mensajes. Es una clase abstracta que delega sus métodos a dos sub-clases, una desarrollada para J2SE y la otra para la versión ligera de Java J2ME (figura 4-2).
figura 4-2 Jerarquía de clases de server9P
Una instancia de esta clase es capaz de servir múltiples clientes 9P de forma simultánea. Para ello lanza un hilo de ejecución para cada cliente nuevo que quiera conectar con el servidor. Cada hilo de ejecución se encargará de la recepción y transmisión de los mensajes 9P por la conexión TCP, esto se muestra en la figura 4-3.
figura 4-3 Funcionamiento global del servidor 9P
El constructor de esta clase define varios parámetros de funcionamiento del servidor 9P. La forma del constructor es la siguiente:
server9P (int maxcon, int maxfid, int p, boolean show, pfile f) El valor p establece el puerto TCP de escucha del objeto server9P.
El objeto pfile contenido en f (que representa un fichero) indica la raíz del sistema
de ficheros que va a servir el objeto server9P. Para más información acerca de este objeto podemos consultar el apartado “4.2.1 Clase pfile” de la presente memoria.
server9PSE server9PME server9P Server 9P ThreadConection ThreadConection ThreadConection Recepción Conexiones 9P 9P 9P
El parámetro maxcon establece el número máximo de conexiones simultáneas
admitidas por el servidor, maxfid determina el número máximo de descriptores reservados que podrán tener cada cliente en el servidor.
Por último, el campo show establece si la instancia de server9P retornará mensajes por la salida estándar del sistema. Útil para depuración.
4.1.2. Clase msg9P
La clase msg9P representa un mensaje 9P. Dado que existen múltiples tipos de mensajes diferentes, existen sub-clases mensaje para cada tipo de mensaje 9P que podamos encontrar. En la figura 4-4 observamos la jerarquía de clases que sustenta la clase msg9P.
figura 4-4 Árbol de clases de la clase msg9P
Todas las sub-clases que dependen de msg9P heredan la siguiente interfaz:
Métodos de la clase msg9P
void write(ByteArrayOutputStream buf) void read (byte[] msg, int i)
String toString() msg9P Tauth9P Tattach9P Tversion9P Tcreate9P Topen9P Tremove9P Twalk9P Tstat9P Twstat9P Tflush9P Tclunk9P Tread9P Twrite9P Tmsg9P P Rauth9P Rattach9P Rversion9P Rcreate9P Ropen9P Rremove9P Twalk9P Rstat9P Rwstat9P Rflush9P Rclunk9P Rread9P Rwrite9P Rmsg9P P Rerror9P
La clase msg9P es abstracta, al igual que sus sub-clases Tmsg9P y Rmsg9P. Los métodos write( ), read( ) y toString( ) sólo podrán ser invocados para instancias de mensajes 9P concretos, puesto que no tienen sentido si el tipo de mensaje 9P no está totalmente definido. El método estático setTypeMsg( ) nos sirve para especificar una instancia de un mensaje 9P. En la figura 4-5 podemos ver cómo estos métodos interactúan con un objeto msg9P.
figura 4-5 Diagrama interacción clase msg9P
El método estático SetTypeMsg( ) retorna un objeto msg9P de un tipo concreto. El tipo de mensaje retornado dependerá del Byte que se ofrezca como parámetro en el método. Si consultamos la especificación de los mensajes 9P recordaremos que el primer byte (excluyendo los dos bytes que determinan el tamaño del mensaje) define el tipo de mensaje. Por medio de este método podemos generar objetos msg9P de un tipo concreto basándonos en el primer byte del mensaje recibido por la conexión.
El método read( ) lee un mensaje 9P de un vector de Bytes, para almacenarlo en un objeto msg9P. El método write( ) escribe el mensaje 9P, que se almacena en un objeto msg9P, dentro de un buffer de Bytes. Mediante estos dos métodos, se realiza el aplanado y desaplanado de los mensajes, para su envió o recepción por el canal de comunicaciones.
El método toString( ) simplemente retorna una breve descripción del mensaje 9P que contiene el objeto. Se usa para depuración.
Dado que cada tipo de mensaje tiene una estructura diferente, las clases concretas que los representan adquieren campos de datos basados en la estructura. Así pues, la clase Tversion9P debe tener los campos de datos de un mensaje Tversion.
Recordando como es un mensaje 9P de tipo Tversion:
Tversion[1] Tag[2] Msize[4] Version[S]
El mensaje Tversion9P deberá tener las propiedades tag, msize y version. Para no crear confusión, estos datos se llaman de la misma forma dentro del objeto Tversion9P, y esta regla es aplicable a las demás clases de mensajes.
msg9P 54 F0 06 A6 FF 00 00 F0 4D FF 16 C4 .. .. .. .. 65
Mensaje 9P como cadena de Bytes read()
write()
msg9P.SetTypeMsg()
Crea una instancia de msg9P
Descripción del mensaje
Las propiedades de cada mensaje son de tipos diferentes como se puede ver en la especificación de los mensajes 9P. En términos de programación, existen datos numéricos de distintos tamaños y cadenas de datos. Para asegurar una interpretación adecuada e independiente de la arquitectura de estos valores, tenemos tipos específicos de datos, pertenecientes a la clase type9P.
4.1.3. Clase rpc9P
La clase rpc9P representa una llamada a procedimiento remoto de un mensaje 9P. La implementación actual es de lado del servidor 9P, las RPC implementadas están asociadas a T-mensajes y retornan R-mensajes como respuestas.
Para cada sub-clase de tmsg9P existe una clase concreta de tipo rpc9P. La figura 4-6 muestra la jerarquía de clases.
figura 4-6 Árbol de clases de la case rpc9P
La interfaz que heredan las sub-clases de la clase abstracta rpc9P es la siguiente: Métodos de la clase rpc9P
static rpc9P getrpc(tmsg9P tmsg) rmsg9P process(fidtable fidt)
El método de clase getrpc( ) retorna una instancia de un objeto rpc9P concreto de
rpc9Pattach rpc9Pauth rpc9Pclunk rpc9Pcreate rpc9Pflush rpc9Popen rpc9Pread rpc9Pwstat rpc9Premove rpc9Pstat rpc9Pversion rpc9Pwalk rpc9Pwrite rpc9Pattach rpc9Pauth rpc9Pclunk rpc9Pcreate rpc9Pflush rpc9Popen rpc9Pread rpc9Pwstat rpc9Premove rpc9Pstat rpc9Pversion rpc9Pwalk rpc9Pwrite rpc9P
recursos. Por ello es necesario el parámetro fidtable en la llamada al método process( ). El objeto fidtable se analiza más adelante y representa un sistema de ficheros.
El método process( ) es el encargado de ejecutar una RPC. Para que un objeto
rpc9P pueda invocar este método tiene que ser una instancia concreta de una de las
sub-clases de rpc9P (no tendría sentido ejecutar una RPC indefinida).
El método process( ) retornará un mensaje 9P de tipo rmsg9P como respuesta a la petición realizada por un cliente.
figura 4-7 Diagrama interacción de la clase rpc9P
En la figura 4-7 podemos ver como interactúa un objeto rpc9P con el resto de entidades. Un ejemplo de lo que sucede en la figura podría ser que tmsg9P represente un mensaje de tipo Topen, dado este mensaje se usaría el método getrpc( ) para obtener un objeto rpc9P correspondiente a un Topen (1). Posteriormente, se usaría el método process( ) para ejecutar la RPC (2), usando como parámetro un objeto fidtable (representante del sistema de ficheros). Finalmente, se retornaría una respuesta en un mensaje rmsg9P, que podría ser un Ropen o un Rerror, si algo fue mal.
4.1.4. Clase type9P
La clase type9P implementa los tipos de datos que podemos encontrar dentro de los mensajes 9P. En la especificación del protocolo encontramos datos de tipo numérico de varios tamaños, cadenas de caracteres y estructuras de datos. Cada tipo de dato está codificado de una forma determinada dentro del mensaje, tal como se define en la especificación de 9P. En la figura 4-8 podemos ver la jerarquía de clases que desciende de type9P.
figura 4-8 Árbol de clases de la case type9P
int9P type9P byte9P short9P long9P String9P data9P qid9P stat9P rpc9P.getrpc( )
Crea una instancia de rpc9P
rmsg9P process( ) tmsg9P 1 2 rpc9P fidtable
Todos las sub-clases que dependen de type9P heredan la siguiente interfaz: Métodos de la clase type9P
void write(ByteArrayOutputStream buf) void read (byte[] msg, int i)
int len() String toString()
El método write( ) escribe el valor del dato dentro de un buffer de bytes. El método read( ) lee el tipo de dato de un array de bytes, a partir de la posición i. Por último, len( ) retorna el tamaño del dato.
El método toString( ) retorna una representación del dato, dentro de una cadena de caracteres.
Los datos almacenados por estas clases son de carácter diverso, al igual que sus campos de datos internos.
Diferenciamos tres tipos de clases que heredan de type9P:
· Tipos numéricos: Clases byte9P, short9P, int9 y long9P.
· Tipos cadena: Clases String9P y data9P.
· Estructuras: Clases qid9P y stat9P.
Tipos numéricos.
Estas clases abarcan todas las representaciones numéricas que existen en los mensajes 9P. Los tamaños numéricos que aparecen en los mensajes son de 1, 2, 4 y 8 Bytes, representados por las clases byte9P, short9P, int9P, long9P respectivamente.
En la especificación de mensajes 9P los valores numéricos son UNSIGNED (sin signo). Están escritos de la forma little-endian, el byte menos significativo primero. Esta forma de codificación de los valores no debe preocuparnos, ya que, los métodos read( ) y write( ) de cada tipo se encargan de transformarlos.
Todos estos tipos tienen un campo llamado value, donde encontramos el valor
numérico almacenado por el objeto.
Tipo cadena
Estas clases se usan para almacenar texto o bloques de datos. Existen dos clases de este tipo, String9P y data9P, diferenciadas en que el tamaño del String9P es auto contenido, como especifica el protocolo 9P. En cambio data9P es una ristra de tamaño desconocido, por ello tiene un método read( ) diferente que especifica el tamaño de datos que se van a leer:
public void read(byte[] b, int i, int sz)
Dentro del campo value encontraremos la cadena de caracteres que almacenan estas clases.
TIPO Campo
String9P String value
data9P String value
Estructuras
Los mensajes 9P tienen una estructura muy parecida, por ello existen estructuras de datos con múltiples campos que aparecen en muchos mensajes.
Las clases qid9P y stat9P representan estas estructuras. Están construidas a partir de los tipos básicos anteriores. La clase qid9P almacena el QID de un fichero, un identificador único. La clase stat9P almacena los metadatos del fichero, información sobre los atributos del fichero.
Los tipos anteriores sólo tenían un campo donde almacenaban la información, estas estructuras tienen múltiples campos de información.
La siguiente figura muestra los campos asociados a estas estructuras:
TIPO Campo
qid9P byte9P type
int9P vers
long9P path
stat9P short9P type
int9P dev qid9P qid int9P mode int9P atime int9P mtime long9P length String9P name String9P uid String9P gid String9P muid
4.2. Paquete PFS
El paquete PFS alberga las clases relacionadas con el acceso a los recursos del sistema. Está implementado como una interfaz de acceso a ficheros en un disco, aunque internamente pueda tratarse de otros recursos.
Podemos distinguir principalmente tres clases importantes: pfid, pfile y fidtable. Éstas clases están estrechamente relacionadas y su función es implementar un sistema de ficheros y descriptores independiente. La figura 4-9 muestra cómo interactúan estas clases para generar un sistema de ficheros.
figura 4-9 Diagrama interacción clases del paquete PFS
La clase pfile es la representación de un fichero, pero al igual que en un sistema operativo, para poder acceder al objeto pfile es necesario un descriptor de fichero, que es lo que representa la clase pfid. La clase fidtable es una tabla de descriptores pfid disponibles para el acceso a recursos.
fidtable pfid pfid pfid pfid pfid pfid pfid pfid pfile raiz pfile pfile pfile pfile pfile pfid pfid
En la siguiente tabla vemos los métodos asociados de la clase pfile. Métodos de la clase pfile
boolean isDir() boolean isReadable() boolean isWritable() boolean isExecutable() String getname() stat9P getStat() qid9 getqid()
void wstat(stat9P newst)
pfile create(String name, long perm) pfile walk(String s)
void remove()
pfile[] getListOfNodes()
El método create( ) crea un pfile nuevo de nombre name, los permisos del nuevo fichero pfile vienen dados por el parámetro perm, esta clase retorna el objeto pfile creado, permitiendo que creemos nuevos objetos en la jerarquía de objetos pfile. El parámetro perm definirá (entre otras cosas) si el nuevo objeto creado es de tipo
directorio o tipo fichero. El método remove( ) elimina el pfile, siempre que no tenga descendencia en la jerarquía, es el otro mecanismo que tenemos para gestionar la estructura.
El método walk( ) retorna un objeto pfile requerido mediante el parámetro s, que define una ruta de búsqueda dentro de la jerarquía de objetos pfile.
El método wstat( ) permite modificar las propiedades de un fichero, en función de las indicadas en la estructura STAT que pasamos como parámetro al método, esto nos permite modificar valores tales como el nombre de fichero.
Algunos métodos son puramente informativos, nos proporcionan información del objeto pfile retornando verdadero o falso según las propiedades del pfile. Estos métodos son isDir( ), que retorna verdadero si el objeto es la representación de un fichero tipo directorio, isReadable( ) e isWritable( ), informan de si el objeto puede ser leído o escrito respectivamente, y por último isExecutable( ), retorna verdadero si el fichero tiene la propiedad de ser ejecutable.
Otros métodos informativos son getname( ), que retorna una cadena con el nombre del fichero pfile, getStat( ), que retorna una estructura de tipo stat9P con información acerca del fichero, y getqid( ), retornando una estructura de tipo qid9P con el QID del fichero.
Por último, getListOfNodes( ) retorna una tabla de objetos pfile con los hijos del
figura 4-10 Diagrama interacción de la clase pfile
La clase pfile es una clase abstracta, no especifica el tipo de recurso que estamos abstrayendo en un fichero. Pueden implementarse sub-clases de pfile que accedan a cualquier recurso que nos interese. Actualmente sólo existen dos subclases que se sirven de la interfaz de la superclase pfile. En la figura 4-11 vemos la jerarquía de clases de
pfile.
figura 4-11 Diagrama clases de pfile
La sub-clase pfileramfs crea una abstracción de fichero en memoria y pfiledisk crea una capa de abstracción sobre ficheros reales. Ambas clases heredan todos los métodos de pfile por lo que su interfaz es idéntico.
Muchos recursos están organizados de una forma jerárquica, el caso más claro son los sistemas de ficheros. Los objetos pfile pueden imitar esta característica creando estructuras con los recursos, para ello existen dos clases de objetos pfile, “directorios” y “ficheros”.
Podría darse el caso de que queramos generar un tipo de pfile que ofrezca acceder a la información de un sensor de temperatura, como si de un archivo se tratase. Una forma de representar la información del sensor es un fichero que contenga la temperatura del sensor, podríamos llamarlo “/temperatura” y podría estar ubicado en la raíz del sistema de ficheros. Este objeto pfile no permitiría crear mas ficheros, ni modificar el fichero “/temperatura”. A través de la lectura del contenido del único
pfileramfs pfiledisk pfile
pfile
Métodos informativos:
isExecutable( ), isReadable( ), isWriteable( ) isDir( ), getname( ), getstat( ), getqid( )
nuevo pfile pfile walk ( ) create ( ) remove ( ) wstat ( )
intentásemos acceder al fichero “/file1”, los objetos pfile se encargarían de construir el nuevo nodo de la estructura para dicho fichero.
figura 4-12 Generación de estructuras pfile bajo demanda
4.2.2. Clase pfid
La clase pfid representa un descriptor de fichero. Una instancia de pfid permite acceder a un objeto pfile.
Un pfile puede ser referenciado por varios pfids y cada uno de ellos puede estar haciendo uso del fichero de diversas formas. Los métodos que pueden ser invocados en un pfile son los siguientes.
Métodos de la clase pfid
boolean isOpen() qid9P getqid() stat9P getStat()
pfile getfile()
void setfile(pfile f) pfile walk (String s)
pfile create (String name, long perm) void open (short nmode)
void close()
byte[] read (long offset, long count) int write (long offset, byte[] data) void remove() / /dir1 /dir2 /dir4 /file4
?
?
//file1 /dir1 /dir2
/dir3 /dir4 /file2 /file3 /file4 FICHEROS REALES ESTRUCTURA DE OBJETOS PFILE