Capitulo VI Arquitectura del sistema de archivos

Unidad VI. Sistemas Operativos Capitulo VI Arquitectura del sistema de archivos Las características que posee el sistema de archivos de UNIX son las ...
0 downloads 3 Views 966KB Size
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         #include         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    #include 

  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  #include  #include  #include  #include  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   #include   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  .  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   #include   #include   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\n");  write (tty, mensaje, strlen (mensaje));  close (tty);  exit (0);  }

1