# El sistema de ficheros virtual (VFS) de Linux (II)

Autor: Juan Antonio Martínez Castaño
E-mail: jantonio@dit.upm.es
Web: http://www.dit.upm.es/~jantonio

### Copyright

Este artículo es Copyright 1999 de Juan Antonio Martínez Castaño y se distribuye bajo las siguientes condiciones:

- Su distribución mediante medios electrónicos es libre, siempre y cuando se conserve el texto íntegro y en su formato HTML original, haciendo especial mención a la conservación del mensaje de copyright
- El autor y dueño del copyright cede los derechos de publicación impresa a Prensa Técnica S.L., autorizando a ésta a realizar las modificaciones al texto que considere oportunas para su publicación
- La distribución o copia, total o parcial, en cualquier medio impreso por parte ajena a Prensa Técnica S.L. Está expresamente prohibida
- Ningún particular podrá poner este fichero en un servidor Web de acceso público sin el consentimiento expreso del autor y de Prensa Técnica S.L.

### Indice

- Entradilla
- Presentación
- Introducción
- Operaciones de Montaje y Desmontaje
- Navegando por el Sistema de Ficheros
- Operaciones de Entrada/Salida
- Temas Avanzados
- Conclusiones
- Referencias

### Entradilla

Continuando con la serie dedicada al Linux Virtual File System, vamos a estudiar en esta entrega el funcionamiento del VFS, desde el procedimiento de montaje hasta como se llega a las funciones de E/S sobre el dispositivo

### Presentación

En el número Anterior de Linux Actual, se procedió a describir el por qué de la existencia del Sistema de Ficheros Virtual de Linux, elementos de que contaba un filesystem, y la operación de registro de un sistema de ficheros en el núcleo. En este artículo, continuando la serie, vamos a describir el proceso de montaje y desmontaje, y los diversos mecanismos de que Linux se vale para navegar a través del sistema de ficheros

### Introducción

Hagamos un pequeño resumen de la entrega anterior:

- El Sistema de Ficheros Virtual de Linux es una capa que utiliza el núcleo Linux para abstraer y uniformizar el acceso a los diferentes tipos de sistemas de ficheros. En un lenguaje orientado a objetos, podemos decir que el VFS es una clase, y cada sistema de ficheros una clase derivada. Del mismo modo, cada sistema de ficheros montado se puede considerar como una instancia de alguna de las subclases de sistemas de ficheros

  El Sistema de Ficheros Virtual de Linux es una capa que utiliza el núcleo Linux para abstraer y uniformizar el acceso a los diferentes tipos de sistemas de ficheros.

- Esta jerarquía se muestra en el núcleo Linux en forma de arrays de punteros a funciones. Dichos arrays son rellenados con los datos correctos en función de cada sistema de ficheros
- Para que Linux pueda utilizar un sistema de ficheros, es precisa la operación de registro, por la que el sistema de ficheros es añadido a la lista de fs reconocidos por el núcleo, a la vez que se le informa al núcleo del punto de entrada del manejador del sistema de ficheros
- En un sistema de ficheros distinguimos tres estructuras de datos fundamentales: Superbloques, inodos, y ficheros. Los primeros contienen los datos esenciales de cada sistema de ficheros montado, así como los manejadores de entrada a dicho sistema de ficheros. Los inodos contienen información sobre cada entrada de datos del sistema de ficheros, tipo, permiso, propietario, tamaño, fechas, punteros a los bloques de datos...
  Los ficheros no son sino secuencias de bloques que contienen los datos referenciados por un inodo
- Los directorios no son sino ficheros que contienen una tabla de asociaciones nombre-inodo
- Existe un último elemento, denominado el "kernel caché", manejado directamente desde el VFS. En la versión 2.2 del núcleo, distinguimos caché de inodos, de directorios y de bloques. Existen a su vez cachés diferentes para lectura y escritura

Antes de seguir, es recomendable que el lector tenga a mano el número anterior de Linux Actual, y repase cuidadosamente las estructuras de datos y operaciones de registro de sistemas de ficheros descritos en él. En el CD-Rom que se acompaña a la revista se incluye la documentación existente en la actualizad sobre el VFS, así como diversos ejemplos de sistemas de ficheros, alguno de ellos desarrollado por el autor. Donde sea posible, se remitirá al código fuente del núcleo Linux, utilizándose -a menos que se diga lo contrario- la versión 2.12 del núcleo Linux

Una vez preparados, podemos continuar:

### La operación de montaje

La operación siguiente a la del registro del sistema de ficheros es la de montaje. la operación de montaje se realiza de la siguiente manera

- La llamada al sistema mount() comprueba primero los permisos y que la operación puede ser realizada
- A continuación se busca en la lista de sistemas de ficheros registrados el tipo especificado. En el caso de que no se encuentre registrado, se busca el módulo cargable correspondiente a dicho sistema de ficheros y se procede a su carga y registro. En caso de no encontrar dicho fs se genera un informe de error
- El VFS crea una estructura de datos correspondiente a un nuevo superbloque, rellenando los campos correspondientes al inodo padre, puntos de montaje, permisos, etc.
- El VFS llama a la rutina read_super() del sistema de ficheros correspondiente, pasando como parámetro la nueva estructura de datos creada. read_super() completa la estructura de datos, inicializa punteros y demás estructuras necesarias de la nueva entrada del sistema de ficheros. La principal estructura es el puntero a la lista de operaciones con el superbloque.
  A la hora de programar filesystems, es necesario recordar que no podemos hacer suposiciones sobre el punto de montaje: el núcleo genera una estructura superbloque por cada punto de montaje de cada sistema de ficheros. todos los superbloques correspondientes a un sistema de ficheros dados se inicializan desde la rutina read_super() siendo la única diferencia, el puntero a la estructura superbloque pasada en cada llamada
- Una vez inicializada la estructura, read_super() retorna, bien un puntero a la estructura superbloque recibida, o bien NULL indicando el error

Con la operación mount() cada sistema de ficheros personaliza un filesystem dado en una instancia del mismo

Echemos un vistazo a la lista de operaciones que se pueden realizar con el superbloque. Recordemos que el superbloque suele contener información sobre el sistema de ficheros, punteros a los inodos, etc. Del mismo modo debe contener los métodos de acceso al inodo raíz del sistema de ficheros. Lo usual es que las operaciones sobre el inodo raíz sean ligeramente distintas a las del resto de los inodos ( pues intervienen punteros al superbloque, así como a estructuras ajenas al sistema de ficheros ). Por ello es normal que existan una familia de inode_operations y de root_inode_operations. El listado 1 ilustra la lista de operaciones sobre el superbloque:

    struct super_operations {
            void (*read_inode) (struct inode *);
            void (*write_inode) (struct inode *);
            void (*put_inode) (struct inode *);
            void (*delete_inode) (struct inode *);
            int (*notify_change) (struct dentry *, struct iattr *);
            void (*put_super) (struct super_block *);
            void (*write_super) (struct super_block *);
            int (*statfs) (struct super_block *, struct statfs *, int);
            int (*remount_fs) (struct super_block *, int *, char *);
            void (*clear_inode) (struct inode *);
            void (*umount_begin) (struct super_block *);
    };
Listado 1: Operaciones sobre el superbloque

Distinguimos entre operaciones sobre el inodos y sobre el superbloque. todas las operaciones sobre inodos y ficheros pasan por la lectura de los datos del inodo. Para ello, existen una serie de entradas en la lista de operaciones sobre el superbloque, que permiten la lectura, escritura, asignación o liberación de inodos. La estructura inode que el VFS pasa al superbloque contiene información sobre el inodo deseado. Un inodo especial es el inodo raíz, o root_inode
Recordemos que en el 99% de los casos, el inodo raíz corresponde casi siempre a un directorio, denominado directorio raíz; pero no siempre tiene por qué ser así: en los casos de redirectores ( loop devices, cryptfs, etc ) las estructuras no corresponden a directorios, sino que redirigen las operaciones a otros comandos
Del mismo modo, aunque insistiremos posteriormente sobre ello, las operaciones, se realizan sobre el inodo, no sobre los ficheros, directorios ( según corresponda ) asociados al inodo. Es muy importante que el lector distinga las operaciones sobre inodos de las operaciones sobre los ficheros asociados

- Operaciones sobre inodos
  - read,write - leer o escribir datos del inodo
  - put,delete - son dos operaciones muy similares, y normalmente ambas realizan la misma función: dar por eliminado el inodo correspondiente. La primera indica que el inodo ya no será usado y que se deben realizar todas las labores de limpieza necesarias. La segunda efectúa el borrado físico de dicho inodo
  - notify_change Esta operación es invocada por el VFS cada vez que se realiza un cambio sobre el inodo. Sirve para aquellos casos en que hay que realizar una sincronización con el estado actual (se verá con detalle al tratar el VFS caché)
  - clear_inode Se utiliza para "limpiar" los datos de un inodo antes de informar al VFS cache de que dicho inodo está limpio y es reutilizable
- Operaciones sobre el superbloque
  - statfs Devuelve el estado del superbloque
  - put,write al igual que put y delete ambas operaciones son similares: put libera los datos del superbloque, y write invalida el superbloque. La operación de invalidación corresponde a la operación de desmontaje umount()
  - umount_begin es una función comodin que se utiliza en algunos sistemas de ficheros que dependen de algún demonio que se ejecute en espacio de usuario. Sirve para informar al demonio de que se va a llevar a cabo una operación de desmontaje

Las operaciones sobre inodos realizadas en el superbloque no tienen sino la misión de proporcionar al VFS punteros a posteriores operaciones. Empecemos pues a navegar por el sistema de ficheros

Es preciso saber distinguir entre operaciones sobre inodos y operaciones sobre ficheros

La figura 1 ilustra las estructuras y punteros que maneja el VFS para poder mantener y operar con los diversos sistemas de ficheros montados en el sistema

Figura 1:Estructuras utilizadas en las operaciones de mount()

### Navegando por el Sistema de Ficheros

Una vez montado el sistema de ficheros, la primera operación sobre él es la lectura del inodo raíz. El descriptor del superbloque contiene un identificador del root_inode_number, que es utilizada por el VFS para acceder al primer inodo del sistema de ficheros
Vamos a ilustrar el proceso con un ejemplo: tenemos el un sistema de ficheros montado sobre /mnt/mi_sistema, y dentro de él queremos acceder al fichero directorio1/mi_fichero. Esto es: queremos leer el fichero /mnt/mi_sistema/directorio1/mi_fichero . El listado 2 indica las estructuras de operaciones que se pueden realizar con inodos y ficheros.

    struct file_operations {
    	loff_t (*llseek) (struct file *, loff_t, int);
    	ssize_t (*read) (struct file *, char *, size_t, loff_t *);
    	ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
    	int (*readdir) (struct file *, void *, filldir_t);
    	unsigned int (*poll) (struct file *, struct poll_table_struct *);
    	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    	int (*mmap) (struct file *, struct vm_area_struct *);
    	int (*open) (struct inode *, struct file *);
    	int (*flush) (struct file *);
    	int (*release) (struct inode *, struct file *);
    	int (*fsync) (struct file *, struct dentry *);
    	int (*fasync) (int, struct file *, int);
    	int (*check_media_change) (kdev_t dev);
    	int (*revalidate) (kdev_t dev);
    	int (*lock) (struct file *, int, struct file_lock *);
    };

    struct inode_operations {
    	struct file_operations * default_file_ops;
    	int (*create) (struct inode *,struct dentry *,int);
    	int (*lookup) (struct inode *,struct dentry *);
    	int (*link) (struct dentry *,struct inode *,struct dentry *);
    	int (*unlink) (struct inode *,struct dentry *);
    	int (*symlink) (struct inode *,struct dentry *,const char *);
    	int (*mkdir) (struct inode *,struct dentry *,int);
    	int (*rmdir) (struct inode *,struct dentry *);
    	int (*mknod) (struct inode *,struct dentry *,int,int);
    	int (*rename) (struct inode *, struct dentry *,
    			struct inode *, struct dentry *);
    	int (*readlink) (struct dentry *, char *,int);
    	struct dentry * (*follow_link) (struct dentry *, struct dentry *, unsigned int);
    	int (*readpage) (struct file *, struct page *);
    	int (*writepage) (struct file *, struct page *);
    	int (*bmap) (struct inode *,int);
    	void (*truncate) (struct inode *);
    	int (*permission) (struct inode *, int);
    	int (*smap) (struct inode *,int);
    	int (*updatepage) (struct file *, struct page *, unsigned long, unsigned int, int);
    	int (*revalidate) (struct dentry *);
    };
Listado 2: Operaciones sobre inodos y ficheros

1. El primer paso es la lectura del root_inode. Para ello, el VFS llama a la rutina read_inode() del array de operaciones del superbloque, indicando que va a leer el inodo raíz. read_inode() devuelve una estructura inode, que a su vez incluye un puntero a una estructura inode_operations que contiene los punteros a las operaciones que se pueden realizar con el root inode. En nuestro caso el root inode corresponde a un directorio. Recordemos que cada estructura inode_operations incluye un puntero a una estructura file_operations que indica las operaciones que se pueden realizar con el fichero asociado a dicho inodo
2. El VFS recibe el puntero al inodo, y encuentra que es un directorio ( el directorio "." que corresponde al directorio raíz del sistema de ficheros ). Por consiguiente la primera operación es leer el contenido de dicho directorio, para poder seguir buscando el fichero pedido. Recordemos que un directorio no es sino un fichero, luego por consiguiente invocará a la siguiente secuencia de funciónes
   - inode_operations->file_operations->open()
   - inode_operations->file_operations->readdir()
   - inode_operations->file_operations->readdir()
   - [...]
   - inode_operations->file_operations->close()
3. Readdir() va obteniendo una lista de parejas nombre-inodo. El sistema de caché de directorios va almacenando las sucesivas entradas. Al encontrar el fichero deseado ( /mnt/mi_sistema/directorio1 ) el VFS obtiene el inodo buscado y vuelve a ejecutar sb->read_inode() para obtener información del inodo encontrado
4. Puesto que "directorio1" es a su vez un directorio, se repite el proceso anterior haciendo un readdir() sobre directorio1, hasta encontrar la entrada "mi_fichero" que era lo que estabamos buscando. En todo este proceso el sistema de caché del VFS ha ido almacenando todos los inodos y directorios encontrados, de manera que no son necesarias nuevas operaciones de entrada/salida para recuperar la información.
5. Finalmente, el VFS efectua un open() y un read() sobre "mi_fichero" que era la operación deseada

Notese que en ningún momento el VFS ha necesitado saber nada de las características del sistema de ficheros subyacente. Todas las operaciones se realizan mediante punteros a funciones, y es el código de cada sistema de ficheros en particular el que realiza el trabajo. Todos los detalles quedan ocultos al VFS que se dedica únicamente a gestionar el caché y a secuenciar los accesos. Esta separación de tareas y simplicidad en el proceso es el corazón de la potencia del Virtual File System de Linux

Figura 2: Resumen de operaciones con el SuperBloque

### Operaciones de entrada salida

Deliberadamente hemos omitido los procesos de entrada/salida de datos de la explicación anterior, pero el lector tendrá claro que los datos tienen que salir de algun sitio... es necesario un interfaz con los dispositivos físicos y un mecanismo de copia de datos desde / hacia el soporte físico. En el ejemplo anterior, estas operaciones se han realizado en los siguientes casos:

- Cada vez que hay que leer la información de un inodo
- Cada vez que hay que analizar el contenido de un directorio
- En el proceso de lectura del fichero

¿ Cómo se realizan estas operaciones ?. El lector recordará del número anterior de Linux Actual, que en el descriptor del sistema de ficheros había una entrada que indicaba al núcleo que dicho sistema de ficheros requería un dispositivo físico. Este flag es hace que el núcleo permita que el sistema de ficheros pueda acceder a dispositivos físicos, habilitando las operaciones de bloqueo/desbloqueo del proceso asociado a la operación de entrada salida.

Entre las estructuras del superbloque se deben encontrar las herramientas adecuadas para poder acceder a los bloques de datos del dispositivo asociado que correspondan a los inodos. Del mismo modo cada inodo contiene una lista de bloques asociados al fichero indexado por el inodo. Un tercer conjunto de datos nos indica la lista de bloques libres y ocupados en el sistema de ficheros. Con estas herramientas, el sistema de ficheros es capaz de solicitar operaciones de entrada/salida sobre el driver correspondiente.

Recordamos al lector que las entradas y salidas sobre un sistema de ficheros son operaciones de acceso en modo bloque, esto es, se leen o escriben de una vez un conjunto de datos. Del mismo modo la estructura del superbloque contiene información sobre el tamaño de los bloques que maneja el sistema de ficheros dado.

Para la realización de las operaciónes de lectura/escritura, se dispone de las primitivas bread() y bwrite(). Se recuerda al lector que estas operaciones son bloqueantes: el proceso asociado a la operacion de E/S queda a la espera de la ejecución, pudiendo serle cedido el control de ejecución de la CPU a otro proceso. El sistema de ficheros deberá ser capaz de ser diseñado de manera que sea reentrante: esto es, que varios procesos puedan estar ejecutándose concurrentemente sobre un sistema de ficheros dado. El núcleo Linux dispone de diversas primitivas de bloqueo y exclusión mutua que facilitan esta tarea

### Temas avanzados

Aparte de las operaciones de lectura y escritura existen otro tipo de operaciones que se pueden realiar con las diversas estructuras de un sistema de ficheros. Vamos a describir alguna de ellas:

1. Cuando un inodo corresponde a un enlace simbólico, el VFS puede realizar, bien la lectura del enlace en sí, o bien resolver el enlace, buscando el fichero asociado. Para el primer caso, hay que tener en cuenta que es política habitual que para el caso de enlaces simbólicos, y siempre que la longitude del nombre del fichero asociado al enlace sea menor que el tamaño reservado en el inodo a los punteros a los bloques ( que dado que en este caso no se usan, pues un enlace simbólico no es un fichero, sino un puntero a un fichero ) sean dedicados a almacenar el nombre del fichero al que apunta el enlace. Este sistema -para algunos algo truculento-,permite ahorrar una operación de entrada/salida a la hora de seguir el enlace.
   La operación realizada para resolver el enlace simbólico se indexa mediante la función follow_link de la estructura inode_operations del inodo correspondiente al enlace simbólico
2. En ocasiones, especialmente cuando se busca un acceso inmediato a los datos de un fichero, como pueda ser en el caso de una base de datos, o en la carga de un fichero ejecutable, se realiza la operación de "mapeado en memoria" del fichero. Consiste en hacer que el fichero pueda ser accesible desde el proceso que lo solicita como si fuera parte del mapa de memoria del proceso.
   El núcleo Linux provee de una función mmap() por defecto, que permite utilizar los datos de bloques del descriptor del fichero contenido en el inodo asociado, para realizar la operación de mapeo. En el caso de que no se pueda utilizar dicha estructura, debido a que no se siga un sistema de bloques/inodos, el usuario deberá proveer su propia rutina mmap() que debe retornar el bloque asociado a la posición de memoria pedida por el Virtual File System.
   Si se desea utilizar la función generic_mmap() prevista por el núcleo Linux, ésta deberá ser incluída en la estructura inode_operations del inodo asociado. Recuérdese que sólo se pueden ejecutar aplicaciones binarias que residan en sistemas de ficheros que soporten la función mmap()
3. En algunos sistemas de ficheros, las operaciones sobre inodos y ficheros no implican de forma tan directa un acceso al dispositivo físico: en el caso de un loop filesystem, éste simula un dispositivo de modo bloque para lo que no es sino un sistema de acceso en modo carácter a un fichero
4. En otros casos, no hay una correspondencia real inodo-fichero: en el caso de los sistemas de red, los inodos son generados al vuelo bajo demanda. El NFS guarda una tabla de inodos virtuales y su correspondencia, existiendo unos mecanismos de validación y de control de consistencia.
   En otros casos, como el ejemplo que se incluye en el CD-Rom del "tar filesystem" la lista de inodos es generada al vuelo al realizar la operación de montaje. En el caso del "ROM Filesystem" un programa "mkromfs" genera off-line un fichero que corresponde al futuro sistema de ficheros, conteniendo la información de inodos y bloques precalculada.... cada implementación tiene sus propios detalles y trucos. Precisamente y gracias al VFS podemos obviar todas estas diferencias y tratar de forma unificada todos los sistemas de ficheros que Linux soporta

La figura 3 ilustra un resumen de operaciones realizables con inodos

Figura 3: Resumen de operaciones con inodos

### Resumen. Conclusiones

En este capítulo dedicado al Virtual File System hemos descrito las operaciones de montaje y desmontaje, viendo cómo navega Linux entre los diversos componentes de un sistema de ficheros dados. Se han visto los mecanismos de las operaciones con inodos y ficheros, y se ha hecho una introducción a cómo se maneja el caché desde el VFS

Para cuendo el lector lea este artículo, estará posiblemente disponible la versión 2.4 del núcleo Linux. Si bien la metodología de trabajo con el VFS no cambia drásticamente con la nueva versión, el uso y gestión del VFS caché ha sido reestructurado por completo, añadiendole mucha mayor funcionalidad, rapidez y efectividad. La principal mejora respecto a la versión 2.2 del núcleo es que ya no existen diferencias entre el caché de lectura y el de escritura. Del mismo modo, las rutinas de validación de entradas en el caché se han reescrito y optimizado, arreglándose diversos problemas que surgían con las operaciones del caché en sistemas multiprocesador

La versatilidad y la potencia del Virtual File System de Linux permite a nuestro sistema operativo favorito en uno de los más potentes y flexibles. Muchos fabricantes de otros sistemas operativos tipo UNIX están empezando a portar sus sistemas de ficheros a Linux, como pueda ser Sillicon Graphics y su sistema de ficheros de IRIX. La posibilidad de montar sistemas de ficheros ficticios como capa intermedia entre el dispositivo físico y el VFS permite prácticas como el modo loop, sistemas de ficheros encriptados, ram disks... El nuevo núcleo 2.4 soporta a su vez el manejo de dispositivos de modo bloque en modo caracter, lo que facilita el acceso "crudo" a los datos. El lector puede comprobar leyendo esta serie de artículos, como todas estas operaciones son implementadas de una forma sencilla y altamente eficiente -como no podría ser menos- en nuestro Sistema Operativo favorito.

### Referencias

En el CD-Rom que se acompaña a esta revista se incluye la mayor parte de la documentación disponible sobre el Virtual File System:

- The Kernel hackers Guide ( obsoleto, pero todavía útil )
- The Linux Kernel
- Linux kernel Modules programmer's guide
- The Linux Virtual File System ( transparencias )

Del mismo modo, se incluyen los fuentes de diversos sistemas de ficheros , como un "tar filesystem", para visualización directa de ficheros tar, "intermezzo" un sistema de caché tipo CODA, o AFS, pero que utiliza el sistema de ficheros ext2 en lugar del mapeado en ficheros, y "cachefs", un esqueleto de un sistema de caché, utilizado en el número Anteriór de Linux Actual para describir el VFS y el /proc filesystem

Y por supuesto "Use the source, Luke", el código fuente del núcleo Linux es , como siempre, la mejor referencia.