Linux está diseñado para soportar distintos típos de dispositivos físicos. Incluso para un tipo específico, como un disco duro, existen diferentes interfaces de manejo entre un fabricante de hardware y otro. Además de los dispositivos físicos, Linux posee soporte para sistemas de archivos lógicos que lo hace interoperante entre distintos sistemas operativos, con los siguientes propósitos:
- Multiples dispositivos físicos de hardware
- Multiples sistemas de archivos lógicos
- Múltiples tipos de archivos ejecutables
- Homogeneidad, es decir, una interfaz común entre los sistemas de archivos lógicos y el hardware
- Rendimiento
- Seguridad de datos, perder o corromper datos
- Seguridad de acceso, restricción a los archivos, quotas, permisos, etc.
También se usan dos interfaces para el manejo de filesystems. Una interfaz para llamadas de usuarios y otra para los subsistemas del kernel. La interfaz para los usuarios maneja archivos y directorios. Las operaciones en archivos incluyen open/read/close/write/seek, que están definidas en el estandar POSIX y en los directorios readdir/creat/unlink/chmod/stat tambien definidas en POSIX.
Pero la interface de los subsistemas del Kernel es bastante mas interesante. Esta posee estructuras de datos y funciones para manipulación directa para otros subsistemas. De hecho, existen dos interfaces para el resto del kernel: inodos y archivos.
La interfaz de inodos posee soporte para create(), lookup(), link()/symlink(), mkdir(), mknod(),etc.
La interfaz de archivos posee soporte para open()/release(), read()/write(), select(), mmap(), fsync(), etc.
Volviendo con los controladores de dispositivos o drivers, Linux posee tres tipos de drivers: char, block y network. Los dos más relevantes son char y block. Los dispositivos char son aquellos que su lectura es secuencial (de a un char a la vez), como un mouse. Los dispositivos block pueden ser accedidos de cualquier manera, pero solo puede leerse o escribirse de a bloques.
Todos los drivers soportan las operaciones mencionadas recien. Además, cada dispositivo puede ser accesado como si fuera un archivo. Ya que el Kernel se entiende con los dispositivos de esa manera, no es complicado agregar un nuevo dispositivo, ya que solo es necesario implementar el código específico del hardware para el soporte de la abstracción de archivos.
Además, el kernel posee un buffer de caché para mejorar el rendimiento cuando usa dispositivos block. Todo acceso a un dispositivo block pasa a través de un subsistema de buffer. Este buffer aumenta considerablemente el rendimiento minimizando las lecturas y escrituras hacia y desde los dispositivos.
Cada dispositivo posee una cola de peticion (request queue). Cuando el buffer no puede responder una petición, se agrega una petición a esta cola y hace que la petición duerma (sleep) hasta que pueda responder. Este buffer usa un espacio separado del kernel como un thread unico, manejado por kflushd.
Existen distintos mecanismos para mover datos entre el computador hacia los dispositivos
- Polling (votación)
- DMA (acceso directo a memoria)
- Interrupciones
En el caso de polling, el driver verifica periódicamente el CSR (Control & Status Register) para ver si la petición ha sido completada. Si es así, el dispositivo inicia la siguiente petición y continua. Polling es altamente efectivo en dispositivos de transferencia lenta, como disketteras y modems.
En el caso de DMA, el driver inicia una transferencia directa entre la memoria del computador y el periférico. Esta transferencia es concurrente con la CPU y permite que la CPU siga haciendo otras tareas mientras se transfieren datos. Cuando la operación termina, la CPU recibe una interrupción.
Cuando un dispositivo desea cambiar de estado (por ejemplo, presionar el botón del mouse) o reporta el final de una operación, envía una interrupción al procesador. Si las interrupciones estan habilitadas, el procesador detiene su ejecución actual y comienza a ejecutar el codigo de manejo de interrupciones del kernel. El kernel encuentra el manejador correcto para invocar. Cuando una interrupción es manejada, la CPU se ejecuta en un contexto especial, es decir, todas las interrupciones quedan pendientes hasta que la interrupción actual haya terminado. Debido a esto, los manejadores de interrupciones deben ser altamente eficientes. Si ocurre el caso que un manejador de interrupciones no puede terminar el trabajo, reasigna el trabajo pendiente en un manejador de tipo "bottom-half" , es decir, codigo que se ejecutará la proxima vez que la llamada sea completada para evitar latencias y promover la concurrencia.
Volviendo ahora a los sistemas de archivos lógicos, es más fácil acceder a un dispositivo si es manejado como un archivo que directamente en el hardware. Un filesystem lógico puede ser montado en un punto de montaje dentro del filesystem virtual. Esto significa que un bloque asociado a un dispositivo contiene archivos y una información de estructura que permite al filesystem lógico acceder al dispositivo. Un dispositivo sólo puede tener un soporte lógico de archivo. Sin embargo, el dispositivo puede ser "adiestrado" para poseer soporte para un soporte lógico en otro tipo de filesystem.
En pocas palabras y con una explicación más clara, los dispositivos por lo general se encuentran en /dev y cada dispositivo se representa como un archivo. Es perfectamente posible reescribir el manejador del dispositivo para que soporte otro tipo de filesystem lógico.
Para el soporte de filesystems virtuales, Linux usa Inodos para representar un archivo en un dispositivo block. El inodo es virtual en el sentido que contiene operaciones que estan implementadas de distintas maneras, dependiendo del sistema lógico y físico donde el archivo reside. El inodo es usado como un lugar de almacenamiento para toda la información relacionada con el manejo de, por ejemplo, abrir un archivo del disco. Guarda buffers, el largo del archivo , etc.
Mucha de la funcionabilidad de los filesystems virtuales radica en los módulos. Esta configuración permite a los usuarios compilar un Kernel tan pequeño como sea posible, cargando solamente los modulos que sean necesarios.
[ N. del E.: de repente sale la incógnita, el soporte para , lo compilo como módulo o integrado al kernel? aquí esta la respuesta].
Ideal es el caso en el cual el dispositivo pudiera ocuparse y liberarse en tiempo de ejecución. Si el módulo fuera agreagado activamente al kernel, se deben pasar sus parámetros en momento de booteo, sin considerar tampoco el gasto de memoria en mantener el uso de un dispositivo que no esté conectado, como por ejemplo, una impresora.