1. El problema de la variedad
El tema 8 terminó con dos observaciones: los drivers son más de la mitad del código de un kernel, y cada modelo de controlador necesita el suyo. Hay miles. Si esa variedad llegara a los programas, cada aplicación tendría que saber con qué disco, qué teclado y qué tarjeta de red trabaja.
La tarea del sistema operativo en este terreno es impedirlo. Tiene que lograr dos cosas que tiran en direcciones opuestas:
| Hacia arriba, a los programas | Hacia abajo, al hardware |
|---|---|
| Presentar pocas interfaces, simples y estables: todos los discos se usan igual. | Hablar con cada controlador en su idioma particular. |
| Ocultar la lentitud: dormir al proceso que espera y despertarlo después. | Atender las interrupciones cuando llegan, sin importar qué se estaba haciendo. |
| Proteger: decidir qué proceso puede usar cada dispositivo. | Ser el único que toca el hardware. |
| Aceptar dispositivos que se conectan y desconectan en cualquier momento. | Detectarlos, identificarlos y asignarles un driver. |
En Unix, la respuesta a la primera columna la conocemos del tema 28: un dispositivo se presenta como un archivo. Se abre con open, se lee con read, se escribe con write. Este tema muestra qué hay debajo de esa idea. Mantenemos la convención del tema 8: controlador es el circuito, y driver, el programa del kernel que lo maneja.
2. Las capas de la entrada y salida
Entre la llamada read de un programa y el dispositivo hay varias capas de software. Solo la última conoce el hardware:
| Capa | Qué hace | Conoce el hardware |
|---|---|---|
| Sistema de archivos virtual | Recibe la llamada y la dirige a quien corresponda, según el tipo de archivo. | No |
| Subsistemas | La parte común a toda una clase de dispositivos: la caché y las colas para los discos; la edición de líneas para las terminales. | No |
| Drivers | Traducen las operaciones genéricas a las órdenes concretas de un controlador, y atienden sus interrupciones. | Sí |
La capa intermedia es la que evita que cada driver repita el trabajo de los demás. Un driver de disco no implementa una caché ni decide en qué orden atender los pedidos: eso está escrito una vez, para todos. Solo sabe leer y escribir bloques en su controlador.
3. El directorio /dev
Los dispositivos tienen nombre en el árbol de directorios, y todos están en /dev:
$ ls -l /dev/nvme0n1* /dev/null /dev/tty /dev/pts/0
brw-rw---- 1 root disk 259, 0 oct 12 09:14 /dev/nvme0n1
brw-rw---- 1 root disk 259, 1 oct 12 09:14 /dev/nvme0n1p1
brw-rw---- 1 root disk 259, 2 oct 12 09:14 /dev/nvme0n1p2
crw-rw-rw- 1 root root 1, 3 oct 12 09:14 /dev/null
crw--w---- 1 diego tty 136, 0 oct 12 13:18 /dev/pts/0
crw-rw-rw- 1 root tty 5, 0 oct 12 13:11 /dev/tty
Son los archivos de tipo b y c de la tabla del tema 28. Tienen dueño, grupo, permisos y fechas, como cualquier archivo, y donde debería figurar el tamaño aparecen dos números separados por una coma, que explicamos en la sección siguiente. No contienen datos: son puntos de acceso.
El directorio mismo tampoco está en el disco. Es un sistema de archivos en memoria, el devtmpfs que vimos en la salida de df, y se reconstruye en cada arranque. Sus entradas las crea el kernel a medida que los drivers detectan dispositivos; después, el servicio systemd-udevd del tema 22 ajusta sus permisos y añade nombres alternativos.
Los nombres siguen convenciones que permiten reconocer de qué se trata:
| Nombre | Dispositivo |
|---|---|
nvme0n1, nvme0n1p2 | Un disco NVMe, y su segunda partición. |
sda, sdb1 | Discos SATA y memorias USB: el primero, y la primera partición del segundo. |
loop0 | Un archivo común tratado como si fuera un disco. |
tty1 | Una consola de texto del equipo. |
pts/0 | Una terminal de una ventana o de una conexión remota. |
ttyS0, ttyUSB0 | Puertos serie. |
input/event3 | Un dispositivo de entrada: teclado, ratón, panel táctil. |
null, zero, urandom | Dispositivos sin hardware, de la sección 6. |
4. Número mayor y número menor
Lo que vincula un archivo de /dev con un dispositivo no es su nombre. Son los dos números que ls -l muestra en el lugar del tamaño:
| Número mayor | Número menor | |
|---|---|---|
| Identifica | El driver | Uno de los dispositivos que maneja ese driver |
| Lo interpreta | El kernel, para saber a quién pasar el pedido | El driver, a su manera |
En /dev/nvme0n1p2 | 259 | 2: la segunda partición |
En /dev/null | 1: el driver de memoria | 3 |
Las tres entradas del disco comparten el número mayor, porque las atiende el mismo driver, y se distinguen por el menor. El kernel publica qué driver registró cada número mayor:
$ cat /proc/devices
Character devices:
1 mem
4 tty
5 /dev/tty
13 input
136 pts
…
Block devices:
7 loop
8 sd
259 blkext
Hay dos listas, porque los dispositivos de caracteres y los de bloques se numeran por separado. Que el nombre es irrelevante se demuestra creando un archivo de dispositivo propio, con el comando mknod. Le damos un nombre cualquiera y los números de /dev/null:
$ sudo mknod agujero c 1 3 # de caracteres, mayor 1, menor 3
$ sudo chmod 666 agujero
$ ls -l agujero
crw-rw-rw- 1 root root 1, 3 oct 12 13:20 agujero
$ echo "esto desaparece" > agujero
$ cat agujero
$ rm -f agujero
El archivo agujero, creado en nuestro directorio, se comporta exactamente como /dev/null: acepta lo que se le escribe y no devuelve nada. Es el mismo dispositivo con otro nombre. Cuando un programa lo abre, el kernel lee en su inodo el tipo y los dos números, y con ellos llega al driver; la ruta y el nombre no intervienen.
Crear archivos de dispositivo requiere ser administrador, y se entiende por qué: quien pudiera crear un archivo con los números del disco y permisos amplios tendría acceso directo a su contenido.
5. Bloques y caracteres
En el tema 8 presentamos las dos clases según cómo se accede a los datos. Vistas desde el kernel, las diferencias son más amplias:
| Dispositivo de bloques | Dispositivo de caracteres | |
|---|---|---|
| Los datos son | Bloques numerados, de tamaño fijo | Una corriente de bytes |
| Acceso | A cualquier bloque, en cualquier orden | En secuencia, a medida que llegan o salen |
Cambiar de posición con lseek | Tiene sentido | Por lo general, no |
| Pasa por la caché de páginas | Sí | No |
| El kernel reordena y agrupa los pedidos | Sí | No |
| Uso habitual | Contener un sistema de archivos | Hablar directamente con un programa |
| Ejemplos | Discos, particiones, memorias USB | Terminales, teclado, ratón, sonido, puertos serie |
La penúltima fila marca la diferencia de uso. Los programas casi nunca abren un dispositivo de bloques: abren archivos, y es el sistema de archivos quien lee y escribe los bloques por ellos. El acceso directo queda para las herramientas que trabajan por debajo de los archivos, como las de particionado y formato. Es lo que hizo mbr.c en el tema 20, al leer los primeros 512 bytes de /dev/nvme0n1.
Queda una tercera clase que no aparece en /dev: las interfaces de red. Una tarjeta de red no se abre ni se lee como un archivo, porque no entrega bytes sino paquetes dirigidos a distintos programas. Tiene nombre —enp3s0, wlan0—, se administra con el comando ip y se usa mediante sockets. Es la excepción más importante al lema de que todo es un archivo.
6. Dispositivos sin hardware
Varios de los archivos más usados de /dev no corresponden a ningún circuito. Son drivers que no manejan nada: su comportamiento está definido por completo en el código del kernel.
| Archivo | Al leer | Al escribir | Se usa para |
|---|---|---|---|
/dev/null | No hay nada: fin de archivo inmediato | Lo acepta y lo descarta | Desechar una salida que no interesa |
/dev/zero | Bytes en cero, sin fin | Lo descarta | Crear archivos de un tamaño dado |
/dev/full | Bytes en cero | Falla con «disco lleno» | Probar cómo reacciona un programa ante ese error |
/dev/urandom | Bytes aleatorios, sin fin | — | Generar claves e identificadores |
/dev/tty | La terminal del proceso que lo abre, sea cual sea | Hablar con el usuario aunque la salida esté redirigida | |
$ head -c 16 /dev/urandom | xxd
00000000: 4e1c 9a03 d762 b580 2f6a e911 0c5d 73a8 N....b../j...]s.
$ head -c 1M /dev/zero | wc -c
1048576
$ wc -c < /dev/null
0
Los usamos varias veces: /dev/null en casi todos los temas, /dev/zero para crear archivos de prueba en los temas 14 y 20, y /dev/full en el tema 14, para provocar un error de escritura. El kernel usa además su generador de números aleatorios para el ASLR del tema 27.
Su interés aquí es que muestran, sin hardware que estorbe, qué es un driver. Este es el de /dev/null, tomado del código fuente de Linux con mínimas simplificaciones:
static ssize_t read_null(struct file *archivo, char *bufer, size_t cantidad, loff_t *posicion)
{
return 0; /* cero bytes leídos: fin de archivo */
}
static ssize_t write_null(struct file *archivo, const char *bufer, size_t cantidad, loff_t *posicion)
{
return cantidad; /* «escribí todo», sin hacer nada */
}
static const struct file_operations null_fops = {
.read = read_null,
.write = write_null,
};
Eso es todo el dispositivo. Dos funciones, y una estructura que las reúne. Cuando un programa hace write(fd, datos, 100) sobre /dev/null, el recorrido de la llamada al sistema termina en write_null, que devuelve 100. El programa recibe ese 100 como resultado y concluye que escribió cien bytes. Las funciones tienen la misma forma que las llamadas read y write del tema 14, y devuelven lo mismo que ellas.
7. Un driver, por dentro
La estructura file_operations del ejemplo es la pieza central. Es una tabla de punteros a funciones, una por cada operación que se puede hacer sobre un archivo:
| Campo | El kernel lo llama cuando un programa… |
|---|---|
.open | abre el archivo de dispositivo. |
.read | llama a read. |
.write | llama a write. |
.llseek | llama a lseek. |
.unlocked_ioctl | llama a ioctl. |
.mmap | pide mapear el dispositivo en su memoria. |
.release | cierra el último descriptor que se refería a él. |
Un driver de caracteres consiste, en esencia, en escribir esas funciones para un controlador concreto y registrar la tabla ante el kernel con un número mayor. El camino de una llamada queda así:
- El programa llama a
open("/dev/algo"). El kernel recorre la ruta y encuentra un inodo de tipo dispositivo, con sus números mayor y menor. sistema de archivos virtual - Busca qué driver registró ese número mayor y anota su tabla de operaciones en la entrada de archivo abierto. sistema de archivos virtual
- Llama a la función
.opendel driver, que prepara el dispositivo. driver - El programa llama a
readcon el descriptor. El kernel toma la tabla anotada y llama a su función.read. driver - La función del driver consulta el controlador. Si no hay datos, pone a dormir al proceso. driver
- Llega la interrupción. El manejador del driver recoge el dato y despierta al proceso, como en el tema 17. driver
.readcopia el dato al programa y devuelve la cantidad. vuelta a modo usuario
Un driver tiene, entonces, dos mitades que se ejecutan en momentos distintos. Una responde a los pedidos de los programas, a través de la tabla de operaciones. La otra responde al dispositivo, a través del manejador de interrupción. Entre ambas hay una cola de datos y un mecanismo para dormir y despertar procesos. Es la estructura que vimos en el recorrido de la tecla del tema 17, ahora con nombre.
Es también la tercera vez que encontramos el mismo recurso. La tabla de llamadas al sistema, la tabla de vectores de interrupción y esta tabla de operaciones resuelven el mismo problema: que una parte del sistema pueda invocar a otra sin conocerla, a través de un índice o de un conjunto de funciones acordado de antemano. Gracias a eso se puede añadir un driver, con los módulos del tema 24, sin tocar el resto del kernel.
8. La terminal
La terminal es el dispositivo de caracteres que más usamos, y su comportamiento explica varias cosas que observamos sin detenernos. El comando tty informa a qué archivo de dispositivo está conectada la terminal actual:
$ tty
/dev/pts/0
$ echo "hola, terminal" > /dev/pts/0
hola, terminal
Escribir en ese archivo es escribir en la pantalla. Es lo que hace cualquier programa cuando usa el descriptor 1, según vimos en la tabla de descriptores de la shell. Con dos ventanas abiertas, un echo dirigido desde una a /dev/pts/1 hace aparecer el texto en la otra.
Los nombres tienen historia. tty viene de teletype, las máquinas de escribir eléctricas que servían de terminal en los años sesenta. Las terminales actuales son ventanas, y el kernel las atiende con un par de dispositivos falsos llamado pseudoterminal: un extremo, el de /dev/pts, se comporta como una terminal de verdad ante los programas; el otro lo maneja el programa que dibuja la ventana, o el servicio de acceso remoto.
Entre el teclado y el programa, el kernel interpone una capa que procesa lo que se escribe, la disciplina de línea. Su configuración se consulta con stty:
$ stty -a
speed 38400 baud; rows 24; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; start = ^Q; stop = ^S; susp = ^Z; …
… icanon … echo …
| Ajuste | Efecto | Lo observamos en |
|---|---|---|
icanon | Modo por líneas: el kernel retiene lo escrito, permite corregirlo con la tecla de borrar, y lo entrega al programa al pulsar Enter. | Tema 14: read devolvía la línea completa. |
echo | El kernel muestra en pantalla cada carácter que se escribe. | — |
intr = ^C | Al recibir Ctrl+C, el kernel envía SIGINT al proceso en primer plano. | Tema 26 |
susp = ^Z | Al recibir Ctrl+Z, envía SIGTSTP. | Tema 26 |
eof = ^D | Al recibir Ctrl+D al comienzo de una línea, hace que read devuelva 0. | Tema 14 |
La tabla resuelve varios misterios menores. El eco de lo que se escribe no lo hace el programa, sino el kernel. Ctrl+C no es una tecla que la shell detecte: es un carácter que la disciplina de línea convierte en señal. Y un programa que lee de la terminal no recibe nada hasta que se pulsa Enter, porque hasta entonces los caracteres están en un búfer del kernel, donde aún pueden corregirse.
Estos ajustes pueden cambiarse. Los programas que piden una contraseña desactivan el eco; los editores de texto desactivan el modo por líneas, para recibir cada tecla en el momento.
$ stty -echo; read secreto; stty echo # lo que se escribe no se ve
$ echo "escribiste: $secreto"
escribiste: abracadabra
9. ioctl: lo que no cabe en leer y escribir
Cambiar el eco de una terminal no es leer ni escribir bytes. Tampoco lo es expulsar un disco, ajustar el volumen o consultar el tamaño de una ventana. Para todo lo que no encaja en las dos operaciones básicas existe una llamada al sistema de propósito general:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long operacion, ...);
Recibe un descriptor, un número que indica la operación y un argumento cuyo significado depende de ella. El kernel no interpreta nada: pasa los tres valores a la función .unlocked_ioctl del driver, y es el driver quien sabe qué hacer. Cada driver define sus propios números de operación. El siguiente programa pregunta al driver de la terminal cuántas filas y columnas tiene la ventana:
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
int main(void)
{
struct winsize ventana;
/* TIOCGWINSZ: «terminal, dame el tamaño de tu ventana» */
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ventana) == -1) {
perror("ioctl");
return 1;
}
printf("La terminal tiene %d filas y %d columnas\n", ventana.ws_row, ventana.ws_col);
return 0;
}
$ gcc -Wall -Wextra -g ventana.c -o ventana
$ ./ventana
La terminal tiene 24 filas y 80 columnas
$ ./ventana > salida.txt
ioctl: Inappropriate ioctl for device
En la primera ejecución, el descriptor 1 es la terminal, y su driver responde. Así averiguan ls y top el ancho disponible para acomodar su salida. En la segunda, el descriptor 1 es un archivo regular: la operación no tiene sentido para él, y el kernel responde con el error ENOTTY.
Ese error tiene un uso inesperado. Es la forma en que un programa averigua si su salida es una terminal: intenta una operación propia de terminales y observa si falla. Es lo que hace la biblioteca de C, según vimos en el tema 15, para elegir entre el búfer por líneas y el búfer completo; y es la razón por la que ls usa colores y columnas en la pantalla y una lista simple cuando su salida va a una tubería.
ioctl es poco elegante: una sola llamada con miles de significados, sin comprobación de tipos. Es el precio de mantener pequeña la interfaz principal. Las alternativas modernas consisten en exponer los ajustes como archivos, que es lo que vemos a continuación.
10. /sys: el inventario
/dev contiene los puntos de acceso a los dispositivos: sirve para usarlos. Para saber qué dispositivos hay, cómo están conectados y cómo están configurados existe otro directorio, /sys, donde el kernel publica su inventario. Lo usamos en el tema 8 para leer los identificadores de un dispositivo PCI.
| Directorio | Organiza los dispositivos según |
|---|---|
/sys/devices | Su conexión física: qué está enchufado a qué. Es el árbol real. |
/sys/bus | El bus al que pertenecen: pci, usb. |
/sys/class | Su función: net, input, sound, tty. |
/sys/block | Los dispositivos de bloques. |
Los tres últimos son vistas del primero, armadas con enlaces simbólicos. Cada dispositivo es un directorio, y cada uno de sus atributos, un archivo de una sola línea:
$ ls /sys/block
loop0 loop1 nvme0n1
$ cat /sys/block/nvme0n1/size # tamaño, en sectores de 512 bytes
1000215216
$ cat /sys/block/nvme0n1/dev # números mayor y menor
259:0
$ cat /sys/block/nvme0n1/queue/rotational # ¿es un disco giratorio?
0
$ cat /sys/class/net/enp3s0/address # la dirección física de la tarjeta de red
a8:5e:45:1c:9b:02
El tamaño coincide con el que informó fdisk en el tema 20. El archivo dev es el eslabón entre los dos directorios: contiene los números que llevará la entrada correspondiente de /dev. Y la última línea muestra que la tarjeta de red, que no tiene archivo en /dev, sí figura en el inventario.
Comandos como lsblk, lspci y lsusb obtienen de aquí lo que muestran. Algunos de estos archivos admiten escritura, y permiten cambiar un ajuste sin recurrir a ioctl. Recorremos /sys con más detalle en el tema 31.
11. Conectar un dispositivo en marcha
Casi todas las piezas de este tema intervienen cuando se enchufa una memoria USB. La secuencia completa une además varios temas anteriores:
- Se conecta la memoria. El controlador USB detecta el cambio eléctrico en el puerto y genera una interrupción. hardware
- El driver del controlador USB interroga al dispositivo y lee sus identificadores de fabricante, de producto y de clase. Es la enumeración del tema 8. kernel
- El kernel crea su directorio en
/sysy emite un aviso con los datos del dispositivo. kernel systemd-udevdrecibe el aviso y busca un módulo cuyo alias coincida con esos identificadores. Lo carga, como vimos en el tema 24. espacio de usuario- El driver de almacenamiento USB toma el dispositivo. Lo registra como un disco, lee su tabla de particiones y crea los dispositivos de bloques correspondientes. kernel
- Aparecen
/dev/sdby/dev/sdb1. udev les asigna grupo y permisos, y crea enlaces con nombres estables. espacio de usuario - El entorno de escritorio monta el sistema de archivos de la partición en un directorio, y muestra su icono. espacio de usuario
Los pasos del kernel quedan en su registro. Son las líneas que mostramos en el tema 11, sin poder explicarlas del todo:
$ sudo dmesg | tail -3
[ 8123.401222] usb 1-2: new high-speed USB device number 7 using xhci_hcd
[ 8123.553871] usb-storage 1-2:1.0: USB Mass Storage device detected
[ 8124.580113] sd 2:0:0:0: [sdb] Attached SCSI removable disk
Los «nombres estables» del paso 6 resuelven un problema práctico. El nombre sdb depende del orden de conexión: la misma memoria puede ser sdc mañana. Por eso udev mantiene directorios con enlaces que identifican a cada dispositivo por algo propio:
$ ls -l /dev/disk/by-uuid
lrwxrwxrwx 1 root root 15 oct 12 09:14 3f2a8c1e-7b4d-4e9a-b6c0-5d1f9e2a7c38 -> ../../nvme0n1p2
lrwxrwxrwx 1 root root 15 oct 12 09:14 A1B2-C3D4 -> ../../nvme0n1p1
El primer identificador es el de la partición raíz que aparecía en grub.cfg y en /proc/cmdline, en el tema 21. Este directorio es el lugar donde ese identificador se convierte en un dispositivo concreto.
La secuencia muestra además un reparto de tareas que ya es familiar. El kernel detecta, identifica y ofrece el mecanismo; la política —qué módulo cargar, qué permisos poner, dónde montar— la deciden programas de usuario. El comando udevadm monitor permite ver los avisos del paso 3 mientras se conecta y se desconecta un dispositivo.
12. La capa de bloques
En la figura de la sección 2, entre los sistemas de archivos y los drivers de disco hay una capa que merece un comentario propio. Los discos son, con mucha diferencia, los dispositivos más lentos de los que depende el rendimiento del sistema, y el kernel dedica un subsistema entero a usarlos lo menos y lo mejor posible. Hace tres cosas:
| Técnica | En qué consiste | La vimos en |
|---|---|---|
| Caché | Los bloques leídos quedan en memoria; una segunda lectura no llega al disco. | Tema 7 |
| Escritura diferida | Lo escrito queda en memoria y se envía al disco más tarde, agrupado. | Temas 7 y 28 |
| Planificación de pedidos | Los pedidos pendientes se ordenan y se combinan antes de entregarlos al driver. | — |
La tercera es una forma de planificación, emparentada con la de la CPU: cuando varios procesos piden bloques a la vez, alguien decide en qué orden atenderlos. En los discos giratorios, el orden importaba muchísimo, porque mover el cabezal de un extremo a otro costaba milisegundos; con los discos de estado sólido importa menos, y a menudo el kernel no reordena nada.
$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline
El valor entre corchetes es el planificador en uso. En este disco NVMe no hay ninguno: los pedidos pasan directamente al driver. La actividad se observa con vmstat, que usamos en el tema 19: sus columnas bi y bo son los bloques leídos y escritos por segundo, y wa, el porcentaje de tiempo en que los procesadores estuvieron ociosos esperando al disco.
Los dispositivos de caracteres no tienen nada parecido. Una tecla o un byte de un puerto serie se entregan en el orden y en el momento en que llegan.
13. Quién puede usar un dispositivo
Como los dispositivos son archivos, la protección no necesita un mecanismo aparte. Se aplican los permisos de siempre, y la primera salida de este tema ya los mostraba:
| Archivo | Permisos | Dueño y grupo | Resultado |
|---|---|---|---|
/dev/null | rw-rw-rw- | root, root | Cualquiera puede leerlo y escribirlo. |
/dev/pts/0 | rw--w---- | diego, tty | Solo el usuario de esa sesión lee lo que se escribe en su terminal. |
/dev/nvme0n1 | rw-rw---- | root, disk | Solo el administrador y el grupo disk. |
El último caso es el más importante. Un usuario común no puede abrir el disco, y tiene que ser así: quien lee el dispositivo de bloques directamente obtiene los bytes de todos los archivos, sin que intervengan los permisos de ninguno. La protección de los archivos individuales descansa sobre la protección del dispositivo que los contiene. Por eso mbr.c necesitó sudo en el tema 20.
Para los dispositivos que un usuario sí debe poder usar, la solución habitual es el grupo. El sonido pertenece al grupo audio; los puertos serie, a dialout; las cámaras, a video. Añadir un usuario a uno de esos grupos le concede el acceso sin hacerlo administrador. En un equipo de escritorio, además, el servicio de sesiones concede esos permisos automáticamente a quien está sentado frente a la máquina.
Usuarios, grupos y permisos son el mecanismo de protección que recorre todo el sistema, y el contenido del tema 30.
14. Resumen
- El kernel oculta la variedad del hardware tras unas pocas interfaces. Solo los drivers conocen los detalles de cada controlador.
- La entrada y salida se organiza en capas: el sistema de archivos virtual, los subsistemas comunes a cada clase de dispositivo y los drivers.
/devcontiene los archivos de dispositivo. Es un sistema de archivos en memoria, que el kernel y udev llenan en cada arranque.- Un archivo de dispositivo se vincula con su driver por el número mayor, y con el dispositivo concreto por el menor. El nombre no interviene.
- Los dispositivos de bloques ofrecen acceso a bloques en cualquier orden, pasan por la caché y contienen sistemas de archivos. Los de caracteres entregan una corriente de bytes.
- Las interfaces de red no tienen archivo en
/dev. /dev/null,/dev/zeroy/dev/urandomson drivers sin hardware, definidos por completo en el código del kernel.- Un driver de caracteres es una tabla de funciones —
open,read,write,ioctl— que el kernel invoca, más un manejador de interrupción. - La terminal es un dispositivo de caracteres. El kernel edita la línea, hace el eco y convierte Ctrl+C y Ctrl+Z en señales.
ioctlsirve para las operaciones que no son leer ni escribir. Su fallo conENOTTYes la forma de saber si un descriptor es una terminal./syses el inventario de dispositivos: cada uno es un directorio, y cada atributo, un archivo.- Al conectar un dispositivo, el kernel lo detecta e identifica, y udev carga el módulo, crea los nombres y aplica los permisos.
- El acceso a un dispositivo se controla con los permisos de su archivo.
15. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué capa de la entrada y salida conoce los detalles del hardware?
Solo el driver. El sistema de archivos virtual y los subsistemas comunes, como la caché o la gestión de terminales, son iguales para cualquier hardware.
2. ¿Está /dev guardado en el disco?
No. Es un sistema de archivos en memoria. El kernel crea sus entradas a medida que los drivers detectan dispositivos, y se reconstruye en cada arranque.
3. ¿Qué identifican el número mayor y el número menor?
El mayor identifica el driver que atiende el dispositivo. El menor distingue, para ese driver, a cuál de sus dispositivos se refiere: por ejemplo, qué partición de un disco.
4. Se crea con mknod un archivo llamado pepe, de caracteres, con números 1 y 3. ¿Cómo se comporta?
Como /dev/null, que tiene esos mismos números. El kernel llega al driver por el tipo y los números guardados en el inodo; el nombre y la ubicación del archivo no cuentan.
5. Menciona tres diferencias entre un dispositivo de bloques y uno de caracteres.
El de bloques permite acceder a cualquier bloque en cualquier orden, y el de caracteres entrega los bytes en secuencia. El de bloques pasa por la caché de páginas y sus pedidos pueden reordenarse. Y el de bloques suele contener un sistema de archivos, mientras que el de caracteres se usa directamente.
6. ¿Por qué una tarjeta de red no aparece en /dev?
Porque no entrega una corriente de bytes que un programa pueda leer, sino paquetes destinados a distintos programas. Se usa mediante sockets y se administra con otras herramientas.
7. Un programa escribe 100 bytes en /dev/null. ¿Qué devuelve write, y por qué?
Devuelve 100. La función de escritura de ese driver no hace nada con los datos y devuelve la cantidad que se le pidió escribir.
8. ¿Qué es la estructura file_operations?
Una tabla de punteros a las funciones que implementan las operaciones sobre un archivo: abrir, leer, escribir, ioctl, cerrar. Cada driver registra la suya, y el kernel la usa para dirigir cada llamada al sistema a la función correspondiente.
9. ¿Cuáles son las dos mitades de un driver?
Las funciones de la tabla de operaciones, que se ejecutan cuando un programa hace una llamada al sistema, y el manejador de interrupción, que se ejecuta cuando el dispositivo avisa. Se comunican mediante una cola de datos y el mecanismo de dormir y despertar procesos.
10. ¿Quién muestra en pantalla los caracteres que se escriben en una terminal?
El kernel, en la capa llamada disciplina de línea, cuando el ajuste echo está activado. El programa que lee todavía no recibió nada.
11. ¿Cómo se convierte Ctrl+C en la terminación de un programa?
La disciplina de línea reconoce ese carácter como el de interrupción y, en lugar de entregarlo al programa, hace que el kernel envíe SIGINT al proceso que está en primer plano en esa terminal.
12. ¿Para qué existe ioctl?
Para las operaciones sobre un dispositivo que no consisten en leer ni escribir datos: consultar o cambiar su configuración, darle órdenes. El kernel pasa el pedido al driver, que define el significado de cada número de operación.
13. ¿Por qué ./ventana > salida.txt falla con «Inappropriate ioctl for device»?
Porque el descriptor 1 ya no es una terminal sino un archivo regular, que no admite la operación de consultar el tamaño de la ventana. El kernel devuelve ENOTTY.
14. ¿En qué se diferencian /dev y /sys?
/dev contiene los puntos de acceso para usar los dispositivos. /sys contiene información sobre ellos: cuáles hay, cómo están conectados y cómo están configurados.
15. ¿Por qué un usuario común no puede leer /dev/nvme0n1?
Porque leyendo el disco directamente obtendría el contenido de todos los archivos, salteando los permisos de cada uno. El archivo de dispositivo solo es accesible para el administrador y para un grupo reservado.
Para practicar
- Lista
/devconls -ly cuenta cuántos archivos hay de cada tipo, conls -l /dev | cut -c1 | sort | uniq -c. - Averigua con
/proc/devicesqué drivers atienden a/dev/tty, a tu terminal y a tu disco. - Crea con
mknodun archivo propio que se comporte como/dev/zero, y otro como/dev/urandom. Consulta antes sus números conls -l. - Abre dos terminales, averigua el nombre de cada una con
tty, y escribe en una desde la otra. Mira los permisos del archivo y deduce quién más podría hacerlo. - Ejecuta
./leer, del tema 14, después destty -icanon. ¿Cuántos bytes devuelvereadahora, y cuándo? Restablece el modo constty icanon. - Ejecuta
./ventana, cambia el tamaño de la ventana y vuelve a ejecutarlo. Prueba también./ventana | cat. - Modifica
ventana.cpara que, siioctlfalla conENOTTY, escriba «la salida no es una terminal» en lugar del error. - Recorre
/sys/class/nety, para cada interfaz, muestra su dirección y su estado, que está en el archivooperstate. - Ejecuta
udevadm monitory conecta una memoria USB. Luego busca en/dev/disk/by-idel nombre estable que recibió, y las líneas correspondientes endmesg. - Observa
vmstat 1mientras ejecutascatsobre un archivo grande, dos veces seguidas. ¿Qué ocurre con la columnabien la segunda lectura?