Unidad VI. Sistemas Operativos Capitulo VI Arquitectura del sistema de archivos Las características que posee el sistema de archivos de UNIX son las siguientes: • • • • • • Posee una estructura jerárquica. Realiza un tratamiento consistente de los datos de los archivos. Puede crear y borrar archivos Permite el crecimiento dinámico de los archivos Protege los datos de los archivos. Trata los dispositivos y periféricos (terminales, unidades de cinta, etc.) como si fuesen archivos El kernel del sistema trabaja con el sistema de archivos a un nivel lógico y no trata directamente con los discos a nivel físico. Cada dispositivo es considerado como un dispositivo lógico que tiene asociados unos números de dispositivo (minor number y major number). Estos números se utilizan para indexar dentro de una tabla de funciones, los cuales tenemos que emplear para manejar el driver del disco. El driver del disco se va a encargar de transformar las direcciones lógicas del sistema de archivos a direcciones físicas del disco. Estructura del sistema de archivos Bloque de superbloque lista de inodes boot 1. 2. 3. 4. bloque de datos Bloque de boot. Se localiza típicamente en el primer sector, y puede contener el código de boot o de arranque. Este código es un pequeño programa que se encarga de buscar el sistema operativo y cargarlo en memoria para inicializarlo. Superbloque. Describe el estado del sistema de archivos. Contiene información acerca de su tamaño, total de archivos que puede contener, qué espacio queda libre, etc. Lista de nodos índice (inodes). Esta lista tienen una entrada por cada archivo, donde se guarda una descripción del mismo; situación del archivo en el disco, propietario, permisos de acceso, fecha de actualización, etc. Bloque de datos. Ocupa el resto del sistema de archivos. En esta zona es donde se encuentra situado el contenido de los archivos a los que hace referencia la lista de inodes. El superbloque El superbloque contiene, entre otras cosas, la siguiente información: • Tamaño del sistema de archivos. 1 Unidad VI. Sistemas Operativos • • • • • • • • Lista de bloques libres disponibles. Índice del siguiente bloque libre en la lista de bloques libres. Tamaño de la lista de inodes. Total de inodes libres. Lista de inodes libres. Índice del siguiente inode libre en la lista de inodes libres. Campos de bloqueo de elementos de las listas de bloques libres y de inodes libres. Estos campos se emplean cuando se realiza una petición de bloqueo o de inode libre. Flag para indicar si el superbloque ha sido modificado o no. En la memoria del sistema se cuenta con una copia del superbloque y de la lista de inodes, para realizar de forma eficiente el acceso a disco. Existe un demonio (syncer) que se encarga de realizar la actualización en disco de los datos de administración que se encuentran en memoria, este demonio se levanta al iniciar el sistema. Naturalmente antes de apagar el sistema también hay que acualizar el superbloque y las tablas de inodes del disco, el encargado de llevar a cabo lo anterior es el programa shutdown. Nodos índices (inodes) Cada archivo en un sistema UNIX tiene asociado un inode. El inode contiene información necesaria para que un proceso pueda acceder al archivo. Esta información incluye: propietario, derechos de acceso, tamaño, localización en el sistema de archivos, etc. La lista de inodes se encuentra situada en los bloques que hay a continuación del superbloque. Durante el proceso de arranque del sistema, el kernel lee la lista de inodes del disco y carga una copia en memoria, conocida como tabla de inodes. Las manipulaciones que haga el subsistema de archivos sobre los archivos van a involucrar a la tabla de inodes pero no a la lista de archivos, ya que la tabla de inodes está cargada siempre en memoria. La actualización periódica de la lista de inodes del disco la realiza un demonio del sistema. Los campos que componen un inode son los siguientes: • • • • • • Identificador del propietario del archivo. La posesión se divide entre un propietario individual y un grupo de propietarios y define el conjunto de usuarios que tienen derecho de acceso al fichero. El superusuario tiene derecho de acceso a todos los ficheros del sistema. Tipo de archivo. Los archivos pueden ser ordinarios de datos, directorios, especiales de dispositivos y tuberías. Tipo de acceso al archivo. Da información sobre la fecha de la última modificación del archivo, la última vez que se accedió a él y la última vez que se modificaron los datos de su inode. Número de enlaces del archivo. Representa el total de los nombres que el archivo tiene en la jerarquía de directorios. Entradas para los bloques de dirección de los datos de un archivo. Si bien los usuarios tratan los datos de un archivo como si fuesen una secuencia de bytes contiguos, el kernel puede almacenarlos en bloques que no tienen por qué ser contiguos. En los bloques de dirección es donde se especifican los bloques de disco que contienen los datos del archivo. Tamaño del archivo. Los bytes de un archivo se pueden direccionar indicando un offset a partir de la dirección de inicio del archivo (offset 0) . 2 Unidad VI. Sistemas Operativos Hay que hacer notar lo siguiente: 1. El nombre el archivo no queda especificado en su inode. 2. Existe una diferencia entre escribir el contenido de un inode en disco y escribir el contenido del archivo. El contenido del archivo (sus datos) cambia sólo cuando se escribe en él. El contenido de un inode cambia cuando se modifican los datos del archivo o la situación administrativa del mismo (propietario, permisos, enlaces, etc.). La tabla de inode contiene la misma información que la lista de inodes, además de la siguiente información: • • • • • El estado del inode, que indica o Si el inode está bloqueado; o Si hay algún proceso esperando a que el inode quede desbloqueado; o Si la copia del inode que hay en memoria difiere de la que hay en el disco; o Si la copia de los datos del archivo que hay en memoria difieren de los datos que hay en el disco (caso de la escritura en el archivo a través del buffer caché). El número de dispositivo lógico del sistema de archivos que contiene al archivo. El número de inode. Punteros a otros inodes cargados en memoria. El kernel enlaza los inodes sobre una cola hash y sobre una lista libre. Un contador que indica el número de copias del inode que están activas (por ejemplo, porque el archivo está abierto por varios procesos). 3 Unidad VI. Sistemas Operativos Figura. Tabla de direcciones de bloque de un inode Tipos de archivos en UNIX En UNIX existen cuatro tipos de archivos: • Archivos ordinarios, también llamados archivos regulares o de datos. • Directorios • Archivos de dispositivos, conocidos también como archivos especiales • Tuberías o pipes Archivos ordinarios Los archivos ordinarios contienen bytes de datos organizados como un arreglo lineal. Las operaciones que se pueden hacer sobre los datos de uno de estos archivos son: • Leer o escribir cualquier byte de este archivo. • Añadir bytes al final del archivo, aumentando su tamaño. 4 Unidad VI. Sistemas Operativos • Truncar el tamaño de un archivo a cero bytes, esto es como si borrásemos el contenido del archivo. Las operaciones siguientes no están permitidas: • • • Insertar bytes en un archivo, excepto al final. Borrar bytes de un archivo, excepto el borrado de bytes con la puesta a cero de los que ya existen. Truncar el tamaño de un archivo a un valor distinto de cero. Los archivos ordinarios, como tales, no tienen nombre y el acceso a ellos se realiza a través de los inodes. Directorios Los directorios son los archivos que nos permiten darle una estructura jerárquica a los sistemas de archivos de UNIX. Su función fundamental consiste en establecer la relación que existe entre el nombre de un archivo y su inode correspondiente. En algunas versiones de UNIX, un directorio es un archivo cuyos datos están organizados como una secuencia de entradas, cada una de las cuales contiene un número de inode y el nombre de un archivo que pertenece al directorio. Al par inode­nombre de archivo se le conoce como enlace (link). Offset 0 16 32 48 64 inode nombre del archivo 45 . 2 .. 2384 345 4921 inittab mount shutdown .... ...... ....... Figura. Ejemplo de estructura del directorio /etc para UNIX System V El kernel maneja los datos de un directorio con los mismos procedimientos con que se manejan los datos de los archivos ordinarios, usando la estructura inode y los bloques de acceso directo e indirectos. Los procesos pueden leer el contenido de un directorio como si se tratase de un archivo de datos, sin embargo no pueden modificarlos. El derecho de escritura en un directorio está reservado al kernel. Los permisos de acceso a un directorio tiene los siguientes significados: • • • Permiso de lectura. Permite que un proceso pueda leer ese directorio. Permiso de escritura. Permite a un proceso crear una nueva entrada en el directorio o borrar alguna ya existente. Esto se puede realizar a través de las llamadas: creat, mknod, link o unlink. Permiso de ejecución. Autoriza a un proceso para buscar el nombre de un archivo dentro del directorio. 5 Unidad VI. Sistemas Operativos Desde el punto de vista del usuario, vamos a referenciar los archivos mediante su path name. El kernel es quien se encarga de transformar el path name de un archivo a su inode correspondiente. Archivos especiales. Los archivos especiales o archivos de dispositivos permiten a los procesos comunicarse con los dispositivos periféricos (discos, cintas, impresoras, terminales, redes, etc.). Existen dos tipos de archivos de dispositivos: archivos de dispositivos modo bloque y archivos de dispositivos modo carácter. Los archivos de dispositivos modo bloque se ajustan a un modelo concreto: el dispositivo contiene un arreglo de bloques de tamaño fijo (generalmente múltiplo de 512 bytes) y el kernel gestiona un buffer caché (implementado vía software) que acelera la velocidad de transferencia de los datos; ejemplos típicos de estos dispositivos son: los discos y las unidades de cinta. En los archivos de dispositivos modo carácter la información es vista por el kernel o por el usuario como una secuencia lineal de bytes; la velocidad de transferencia de los datos entre el kernek y el dispositivo se realiza a baja velocidad, dado que no se involucra al buffer caché; ejemplos típicos de estos dispositivos son: las terminales serie y las líneas de impresora. Los módulos del kernel que gestionan la comunicación con los dispositivos se conocen como drivers de dispositivos (device drivers). El sistema también puede soportar dispositivos software (o seudo dispositivos) que no tienen asociados un dispositivo físico. Por ejemplo, si una parte de la memoria del sistema se gestiona como un dispositivo, los procesos que quieran acceder a esa zona de memoria tendrán que usar las mismas llamadas al sistema que hay para el manejo de archivos, pero sobre el archivo de dispositivo /dev/mem (archivo de dispositivo genérico para acceder a memoria). En esta situación, la memoria es tratada como un periférico más. Como ya hemos visto anteriormente, los archivos de dispositivos, al igual que el resto de los archivos, tienen asociado un inode. En el caso de los archivos ordinarios o de los directorios, el inode nos indica los bloques donde se encuentran los datos de los archivos, pero en el caso de los archivos de dispositivos no hay datos a los que referenciar. En su lugar, el inode contiene dos números conocidos como major number y minor number. El major number indica el tipo de dispositivo de que se trata (disco, cinta, terminal, etc.) y el minor number indica el número de unidad dentro del dispositivo. En realidad, estos números los utiliza el kernel para buscar dentro de unas tablas una colección de rutinas que permiten manejar el dispositivo. Esta colección de rutinas constituyen realmente el driver del dispositivo. Tuberías o pipes 6 Unidad VI. Sistemas Operativos Un pipe es un archivo con una estructura similar a la de un archivo ordinario. La diferencia principal con éstos es que los datos de un fifo son transitorios. Los fifos se utilizan para comunicar dos procesos. Lo normal es que un proceso abra el fifo para escribir en él y otro para leer de él. Los datos escritos en el fifo se leen en el mismo orden en el que fueron escritos (de ahí su nombre fifo – first in first out). La sincronización del acceso al fifo es algo de lo que se encarga el kernel. El almacenamiento de los datos en un fifo se realiza de la misma forma que en un archivo ordinario, excepto que el kernel sólo utiliza entradas directas de la tabla de direcciones de bloque del inode del fifo. Por lo tanto, un fifo podrá almacenar 10 Kbytes a lo más. 7 Unidad VI. Sistemas Operativos Lectura de directorios Veamos primero algunas funciones que necesitaremos para programar, basados en el Manual del Programador de Linux. opendir ­ abre un directorio SINOPSIS #include <sys/types.h> #include <dirent.h> DIR *opendir(const char *nombre); DESCRIPCIÓN La función opendir() proporciona un identificador de bloque al directorio utilizado por las demás funciones de directorio. Devuelve un apuntador al flujo de directorio o NULL si ocurre un error. El flujo se sitúa en la primera entrada del directorio. ERRORES EACCES Permiso denegado. EMFILE El proceso está usando demasiados descriptores de fichero. ENFILE Hay demasiados ficheros abiertos en el sistema. ENOENT El directorio no existe o nombre es una cadena vacía. ENOMEM Memoria insuficiente para completar la operación. ENOTDIR El nombre no es un directorio. readdir ­ lee una entrada de un directorio SINOPSIS #include <sys/types.h> #include <dirent.h> struct direct *readdir (DIR *dirp) Cada llamada subsecuente a readdir devuelve un apuntador a una estructura que contiene información sobre la siguiente entrada del directorio. La función readdir devuelve NULL cuando llega al final del directorio. Se debe utilizar rewinddirpara volver a empezar, o closedir para terminar. 8 Unidad VI. Sistemas Operativos La estructura dirent se declara como sigue: struct dirent { long d_ino; /* número de nodo­i */ off_t d_off; /* ajuste hasta el dirent */ unsigned short d_reclen; /* longitud del d_name */ char d_name [NAME_MAX+1]; /* nombre fichero (acabado en nulo) */ } d_ino es un número de nodo­i. d_off es la distancia desde el principio del directorio hasta este dirent. d_reclen es el tamaño de d_name, sin contar el carácter nulo del final. d_name es un nombre de fichero, una cadena de caracteres terminada en nulo. Ejemplo: Programa para imprimir la lista de archivos contenidos en un directorio. #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dirent.h> #include <errno.h> void main (int argc, char *argv[ ]) { DIR *directorio; struct dirent *entradadir; if (argc !=2 ) { fprintf (stderr, “Use: %s nombre_directorio \n”, argv[0]); exit (1); } if ( (directorio = opendir (argv[1]) )== NULL) { fprintf (stderr, “No puedo abrir el directorio %s. Error %s\n”, argv[1], strerror(errno)); exit(1); } while ( (entrada_dir = readdir (directorio) ) ! = NULL) printf (“%s\n”, entradadir ­>d_name); closedir (directorio); exit(0); } 9 Unidad VI. Sistemas Operativos 1 Unidad VI. Sistemas Operativos Acceso a archivos especiales Entrada/salida sobre terminales Los terminales son dispositivos especiales que trabajan en modo carácter. Todo programa que se ejecuta en UNIX tiene asociados 3 descriptores de archivo que le dan acceso a su terminal de control. Estos descriptores son: 0 para la entrada estándar, 1 para la salida estándar y 2 para la salida estándar de errores. El archivo de dispositivo que permite a un proceso acceder a su terminal de control es dev/tty. Si el sistema no reservase de forma automática los descriptores anteriores, podríamos hacerlo nosotros mediante las siguientes llamadas: close (O); open ("/dev/tty", O_RDONLY); /* Reserva del descriptor 0. */ close (l); open ("/dev/tty", O_WRONLY); /* Reserva del descriptor 1. */ close (2); open ("/dev/tty", O_WRONLY); /* Reserva del descriptor 2. */ En el sistema hay un terminal especial llamado consola (dispositivo /dev/console) que es empleado durante el arranque para sacar los mensajes relativos a este proceso. Cada usuario que inicia una sesión de trabajo interactiva, lo hace a través de un terminal. Este terminal tiene asociado un archivo de dispositivo que localmente se puede abrir como /dev/tty, pero que visto por otros usuarios tiene la forma /dev/ttyXX, donde XX representa dos dígitos. Como ejemplo para ilustrar el acceso a terminales, vamos a escribir una versión simplificada de la orden write. Esta orden se emplea para enviar mensajes a los usuarios que hay conectados al sistema. Su forma de uso es: $ write usuario Línea de texto 1 Línea de texto 2 . . Línea de texto n ^D [Fin de archivo] Esta secuencia hará que usuario reciba, a través de su terminal, las n líneas de texto que le enviamos. Para poder enviar el mensaje, tenemos que saber si el usuario existe y si tiene iniciada sesión de trabajo. También hay que conocer cuál es el archivo de dispositivo que tiene asociado su terminal. Para obtener respuesta a estas dos preguntas, hay que consultar el archivo /etc/utmp. Este archivo es gestionado por el sistema y contiene información administrativa de los procesos que hay ejecutándose en un instante determinado. Para hacer la lectura de utmp independiente de su estructura, vamos a emplear la función estándar getutent. Su declaración es: #include <sys/types.h> #include <sys/utmp.h> struct utmp *getutent ( ) Con cada llamada a getutent se lee un registro del archivo /etc/utmp. Si el archivo no está abierto, la llamada se encarga de abrirlo. Después de leer un registro, la función devuelve un puntero 1 Unidad VI. Sistemas Operativos a una estructura del tipo utmp, definida en el archivo de cabecera <sys/utmp.h>. Cuando getutent llega al final del archivo, devuelve un puntero NULL. La definición de la estructura utmp es la siguiente: struct utmp { char ut_user[8]; /* Nombre del usuario. */ char ut_id[4]; /* Identificador de /etc/inittab. char ut_line[12], /* Nombre delarchivo de dispositivo asociado (console, ttyXX, lnNXX, etc ... ) */ pid_t ut_pid /*ldentificador del proceso. */ short ut_type; /* Tipo de entrada: EMPTY RUN_LVL BOOT TIME OLD_TIME NE W_TIME IN1T_PROCESS LOGIN_PROCESS USER_PROCESS DEAD_PROCESS ACCOUNTING */ struct exit_status { short e_terminatio; /*Estado de terminación del proceso.*/ short e_exit; /*Estado de salida del proceso. */ }ut_time; /* Se aplica a las entradas cuyo tipo es DEAD_PROCESS.*/ unsigned short ut_reserved1; /* Reservada para usos futuros.*/ char ut_host[16]; /* Nombre del host. */ unsigned long ut_addr; /* Dirección internet del host. */ }; La forma de averiguar si un usuario está o no conectado al sistema, es buscar una entrada del archivo utmp cuyo campo ut_user coincida con el nombre del usuario que buscamos. Para saber cuál es el archivo de dispositivo que tiene asociado su terminal, tenemos que utilizar el campo ut_line. A continuación mostrarnos el código del programa mensaje_para, que es equivalente a la orden estándar write. Prograrna Envío de mensajes a un usuario (mensaje_para.c) #include <stdio.h> #include <fcntl.h> #include <utmp.h> main (int argc, char *argv[ ]) int tty; char terminal [20], mensaje [256], *logname; 1 Unidad VI. Sistemas Operativos struct utmp *utmp, *getutent( ); if (argc != 2) { fprintf (stderr, "Forma de uso: %s usuario\n", argv[0]); exit (­1); } /* Consultamos si el usuario está en sesión. */ while (( utmp = getutent ( )) != NULL && strncmp (utmp­>ut­user, argv[1], 8) != 0); if (utmp = = NULL) { printf ("EL USUARIO %s NO ESTÁ EN SESIÓN.\n", argv[0]); exit (0); } /* Apertura del terminal del usuario. */ sprintf (terminal, "/dev/%s", utmp­>ut_line); if ((tty = open (terminal, O_WRONLY))= = ­1) { perror (terminal); exit (­1); } /* Lectura de nuestro nombre de usuario. */ logname = getenv ("LOGNAME"); /* Aviso al usuario que va a recibir nuestro mensaje. */ sprintf (mensaje, "\n\t\tMENSAJE PROCEDENTE DEL USUARIO %s\07\07\07\n", logname); write (tty, mensaje, strlen (mensaje)); /* Envío del mensaje.*/ while (gets (mensaje) != NULL) { write (tty, mensaje, strlen (mensaje)); sprintf (mensaje, "\n"); write (tty, mensaje, strlen (mensaje)); } sprintf (mensaje, "\n<FIN DEL MENSAJE>\n"); write (tty, mensaje, strlen (mensaje)); close (tty); exit (0); } 1