A la lliçó anterior vam tractar el disc com si fos el dispositiu del sistema. Però meteo-01 té molt més: dos SSD NVMe, un disc mecànic, una targeta de xarxa, un rellotge de temps real, un terminal sèrie, un generador de nombres aleatoris i uns quants controladors USB. I les estacions meteorològiques que li envien dades tenen sensors, rellotges i ràdios. Cap d'aquests dispositius s'assembla als altres: difereixen en velocitat per un factor d'un milió, en unitat de transferència, en si es poden llegir, escriure o totes dues coses, i en si responen en microsegons o en minuts.

El sistema operatiu ha d'oferir una interfície coherent per a tots ells sense conèixer els detalls de cap. Aquesta lliçó tracta de com ho aconsegueix: quina classificació fa servir, per què a UNIX «tot és un fitxer», com apareixen els fitxers a /dev, i quines són les tres maneres que té la CPU de parlar amb un dispositiu. Acabarem seguint el camí d'una lectura d'estació des de la targeta de xarxa fins a la memòria intermèdia de l'ingestor, i deixarem el mecanisme intern de les interrupcions per a la lliçó següent, que el desenvolupa en detall.

Contingut

  1. Per què el sistema operatiu uniformitza els dispositius
  2. L'estructura del subsistema d'E/S
  3. Classificació: bloc, caràcter i xarxa
  4. «Tot és un fitxer» i el directori /dev
  5. Números major i menor
  6. udev i la creació dinàmica de dispositius
  7. Busos i enumeració: lspci, lsusb, lsblk
  8. Les tres maneres de parlar amb un dispositiu
  9. Ports d'E/S i E/S mapejada en memòria
  10. Tècniques del subsistema: buffering, caching, spooling i reserva
  11. Tractament d'errors i reintents
  12. El camí d'una lectura d'estació

Per què el sistema operatiu uniformitza els dispositius

Mira la varietat real a què s'enfronta el nucli de meteo-01:

Dispositiu Velocitat Unitat Operacions Latència
Teclat 100 B/s Caràcter Llegir Humana
Ratolí 500 B/s Caràcter Llegir Humana
Rellotge de temps real Registre Llegir/escriure ns
Terminal sèrie 11 KB/s Caràcter Totes dues ms
Xarxa 1 Gb/s 125 MB/s Paquet Totes dues µs
Disc dur 150 MB/s Bloc Totes dues ms
SSD NVMe 3.500 MB/s Bloc Totes dues µs
GPU 500 GB/s Comanda Totes dues µs

Entre el teclat i la GPU hi ha nou ordres de magnitud de diferència en velocitat. I tanmateix, el programador de l'ingestor escriu:

read(fd, buffer, 1024);

i aquesta mateixa línia funciona si fd és un socket de xarxa, un fitxer a l'SSD, un terminal o el generador d'aleatoris. Aquesta uniformitat no és comoditat: és el que fa possible escriure programari.

Sense ella, cada programa hauria de conèixer el model exacte de cada dispositiu. Un canvi de targeta de xarxa obligaria a recompilar totes les aplicacions. És exactament el problema que el sistema operatiu com a màquina estesa resol, i que vam plantejar a 01-01.

Els objectius concrets del subsistema d'E/S són cinc:

Objectiu Què significa
Independència del dispositiu El programa no sap quin maquinari hi ha a sota
Nomenclatura uniforme Un nom és un nom, sigui quin sigui el dispositiu
Tractament d'errors Els errors es gestionen com més avall millor
Transferència síncrona i asíncrona El programa tria si es bloqueja o no
Compartició i dedicació Un disc es comparteix; una impressora, no

L'estructura del subsistema d'E/S

El subsistema s'organitza en capes, cadascuna afegint abstracció sobre l'anterior:

flowchart TD
    A["Aplicació<br/>ingestor: read(fd, buf, 1024)"] --> B
    B["Biblioteca C<br/>tradueix a la crida al sistema"] --> C
    C["Interfície de crides al sistema<br/>read / write / ioctl"] --> D
    D["Programari d'E/S independent del dispositiu<br/>noms, buffering, memòria cau, permisos, errors"] --> E
    E["Controladors de dispositiu<br/>específics de cada model"] --> F
    F["Gestors d'interrupció"] --> G
    G["Maquinari<br/>controladora del dispositiu + dispositiu"]

El repartiment de responsabilitats:

Capa Què fa Exemple
Aplicació Demana dades amb noms lògics read(fd, buf, 1024)
Independent del dispositiu Tot allò comú a tots Buffering, memòria cau, comprovació de permisos
Controlador Allò específic d'un model Escriure registres de la targeta e1000e
Gestor d'interrupció Reacciona a l'avís del maquinari Marcar la transferència com a completada

El punt clau és a la capa independent del dispositiu: és la que fa tota la feina comuna una sola vegada, perquè cada controlador només hagi d'implementar allò veritablement específic. Un controlador de xarxa no ha de saber res de permisos, ni de noms, ni de buffering: només com parlar amb el seu xip.

I aquí connecta amb la lliçó 01-05: els controladors viuen dins del nucli en un sistema monolític com Linux, carregats com a mòduls. Per això un e1000e defectuós pot tombar la màquina sencera, mentre que en un microkernel viuria a l'espai d'usuari a canvi de més IPC.

Classificació: bloc, caràcter i xarxa

Linux classifica els dispositius en tres grans famílies, i aquesta classificació determina quina interfície ofereix cadascun:

Dispositiu de bloc Dispositiu de caràcter Dispositiu de xarxa
Unitat d'accés Blocs de mida fixa (512 B - 4 KB) Flux de bytes Paquets
Accés aleatori , es pot saltar a qualsevol bloc No, seqüencial No aplica
Emmagatzematge intermedi Memòria cau de pàgines del nucli Directe o mínim Cues de socket
Es pot muntar No No
Interfície Fitxer a /dev Fitxer a /dev Socket, sense fitxer
Exemples /dev/sda, /dev/nvme0n1 /dev/tty0, /dev/random, /dev/null eth0, lo

Dispositius de bloc. La lliçó anterior en va tractar la gestió completa: matriu de blocs numerats per LBA, planificació de peticions, memòria cau. La característica que els defineix és l'accés aleatori: pots llegir el bloc 5.000 sense haver llegit els 4.999 anteriors.

Dispositius de caràcter. Són un flux: els bytes van arribant i no pots retrocedir. Un teclat, un port sèrie, un generador d'aleatoris. No té sentit «buscar la posició 500» en un teclat.

$ ls -l /dev/random /dev/null /dev/zero /dev/tty0
crw-rw-rw- 1 root root  1,   8 ago 31 09:14 /dev/random
crw-rw-rw- 1 root root  1,   3 ago 31 09:14 /dev/null
crw-rw-rw- 1 root root  1,   5 ago 31 09:14 /dev/zero
crw--w---- 1 root tty   4,   0 ago 31 09:14 /dev/tty0

La c inicial indica character device. Fixa't que /dev/null i /dev/zero no corresponen a cap maquinari: són dispositius virtuals implementats enterament en programari. /dev/null descarta tot el que se li escriu; /dev/zero produeix zeros infinits. Que existeixin demostra que l'abstracció de dispositiu és prou general per embolcallar coses que no són dispositius.

Dispositius de xarxa. Són l'excepció interessant: no tenen fitxer a /dev.

$ ls /dev | grep -i eth
(res)

$ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN
2: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP

La raó és de fons: un fitxer implica un flux de bytes ordenat i sense pèrdues, i la xarxa no és això. Els paquets es poden perdre, duplicar o arribar desordenats, i cadascun porta metadades (adreces, ports, protocol) que no encaixen a read()/write(). Per això UNIX va inventar una abstracció diferent —el socket, amb socket(), bind(), sendto(), recvfrom()— per a allò que no cabia en la de fitxer.

És una lliçó de disseny valuosa: una bona abstracció té límits, i forçar-la més enllà produeix interfícies pitjors. Els creadors de BSD ho van reconèixer i van crear una segona abstracció en lloc de deformar la primera.

Tot i així, un cop obert, un socket sí que es maneja amb un descriptor de fitxer i admet read() i write(). L'abstracció de fitxer cobreix l'ús; només la creació i la configuració necessiten interfície pròpia.

«Tot és un fitxer» i el directori /dev

El principi que defineix UNIX: els dispositius es presenten com a fitxers especials dins del sistema de fitxers.

Les conseqüències són enormes i molt pràctiques:

# Els mateixos permisos que un fitxer normal
$ ls -l /dev/nvme0n1
brw-rw---- 1 root disk 259, 0 ago 31 09:14 /dev/nvme0n1

# Les mateixes eines
$ sudo dd if=/dev/nvme0n1 of=/backup/mbr.img bs=512 count=1
$ head -c 32 /dev/urandom | base64

# La mateixa redirecció
$ /opt/meteora/bin/agregador --verbose > /dev/null 2>&1

# Les mateixes crides al sistema
$ strace -e open,read,write cat /dev/zero 2>&1 | head -3

Tres avantatges concrets d'això:

  1. Un sol model de permisos. Que /dev/nvme0n1 pertanyi al grup disk amb permisos rw-rw---- és el que impedeix que l'usuari meteora llegeixi el disc cru saltant-se els permisos del sistema de fitxers. Sense el model de fitxers caldria un sistema de permisos a part per als dispositius.
  2. Reutilització d'eines. cat, dd, cp, grep funcionen sobre dispositius sense haver estat programats per fer-ho.
  3. Composició. Pots redirigir, canalitzar i encadenar dispositius com qualsevol altra cosa.

Un exemple que aprofita les tres alhora:

$ sudo dd if=/dev/nvme0n1p2 bs=4M status=progress | gzip -9 > /backup/meteora.img.gz

Copia un dispositiu de bloc sencer, el comprimeix al vol i el desa. dd no sap res d'NVMe, gzip no sap res de dispositius, i cap dels dos no ho ha hagut de saber.

I l'operador > de la shell aprofita justament el que vam veure a 02-01: la shell fa fork, al fill obre el fitxer com a descriptor 1, i després execve. El programa nou escriu al descriptor 1 sense assabentar-se de res.

Números major i menor

Un fitxer de dispositiu no conté dades: conté una referència a un controlador. Aquesta referència són dos números.

$ ls -l /dev/sda /dev/tty0 /dev/nvme0n1 /dev/null
brw-rw---- 1 root disk    8,   0 ago 31 09:14 /dev/sda
crw--w---- 1 root tty     4,   0 ago 31 09:14 /dev/tty0
brw-rw---- 1 root disk  259,   0 ago 31 09:14 /dev/nvme0n1
crw-rw-rw- 1 root root    1,   3 ago 31 09:14 /dev/null

Analitzem la línia de /dev/sda camp a camp:

b   rw-rw----  1  root  disk   8,   0   ago 31 09:14  /dev/sda
│   └───┬───┘        └─┬─┘    └┬┘  └┬┘
│    permisos      propietari  │    └── número MENOR: quina instància
│                    i grup    └─────── número MAJOR: quin controlador
└── tipus: b = bloc, c = caràcter

On un fitxer normal mostraria la seva mida en bytes, un fitxer de dispositiu mostra dos números separats per una coma. Aquest és el detall que delata que no és un fitxer corrent.

Número Què identifica Qui l'interpreta
Major El controlador que gestiona el dispositiu El nucli, per triar el driver
Menor Quina instància concreta dins d'aquest controlador El mateix controlador

Exemples que aclareixen la mecànica:

$ ls -l /dev/sda /dev/sda1 /dev/sda2 /dev/sdb
brw-rw---- 1 root disk 8,  0 ago 31 09:14 /dev/sda
brw-rw---- 1 root disk 8,  1 ago 31 09:14 /dev/sda1
brw-rw---- 1 root disk 8,  2 ago 31 09:14 /dev/sda2
brw-rw---- 1 root disk 8, 16 ago 31 09:14 /dev/sdb

Tots tenen major 8 (el controlador de discos SCSI/SATA), i el menor els distingeix: 0 és el disc sda sencer, 1 i 2 les seves particions, 16 és el disc següent. La convenció assigna 16 menors per disc, cosa que permet 15 particions a cadascun.

Els majors estan registrats oficialment:

$ cat /proc/devices
Character devices:
  1 mem
  4 tty
  5 /dev/tty
 10 misc
 13 input
189 usb_device

Block devices:
  8 sd
  9 md
 11 sr
252 device-mapper
259 blkext

Quan obres /dev/sda, el nucli llegeix el major (8), busca en aquesta taula quin controlador l'ha registrat, i li passa l'operació juntament amb el menor perquè sàpiga a quin disc es refereix. Aquest és tot el mecanisme de despatx d'E/S.

Pots crear fitxers de dispositiu a mà, encara que avui gairebé mai no calgui:

$ sudo mknod /dev/eldisc b 8 0
$ sudo mknod /dev/laconsola c 4 0

Prova conceptualment reveladora: /dev/eldisc amb major 8 i menor 0 és exactament /dev/sda. El nom no significa res; l'única cosa que importa són els dos números. Els noms són una convenció humana.

udev i la creació dinàmica de dispositius

Antigament /dev era un directori normal amb milers de fitxers creats per endavant per si de cas. Era un desastre: no se sabia quins corresponien a maquinari present, i connectar alguna cosa nova exigia crear el fitxer a mà.

Avui /dev és un sistema de fitxers virtual (devtmpfs) que reflecteix el maquinari realment present, gestionat per udev.

$ mount | grep devtmpfs
udev on /dev type devtmpfs (rw,nosuid,relatime,size=3980212k,nr_inodes=995053,mode=755)

Quan es connecta un dispositiu, la seqüència és:

sequenceDiagram
    participant HW as Maquinari
    participant K as Nucli
    participant U as udevd
    participant FS as /dev
    HW->>K: es connecta un dispositiu USB
    K->>K: detecta i carrega el mòdul del driver
    K->>K: crea el node bàsic a devtmpfs
    K->>U: esdeveniment uevent per netlink
    U->>U: consulta /lib/udev/rules.d i /etc/udev/rules.d
    U->>FS: aplica permisos, propietari i enllaços simbòlics
    U->>U: executa les accions programades

Observar-ho en directe és la millor manera d'entendre-ho:

$ udevadm monitor --property --subsystem-match=usb
KERNEL[1284.221] add   /devices/pci0000:00/0000:00:14.0/usb2/2-1 (usb)
ACTION=add
DEVNAME=/dev/bus/usb/002/007
DEVTYPE=usb_device
ID_VENDOR=FTDI
ID_MODEL=FT232R_USB_UART
ID_SERIAL_SHORT=A50285BI
MAJOR=189
MINOR=134

I consultar tot el que udev sap d'un dispositiu concret:

$ udevadm info --query=all --name=/dev/nvme0n1
P: /devices/pci0000:00/0000:00:1d.0/0000:04:00.0/nvme/nvme0/nvme0n1
N: nvme0n1
S: disk/by-id/nvme-Samsung_SSD_980_PRO_1TB_S5GXNX0T123456
S: disk/by-path/pci-0000:04:00.0-nvme-1
E: DEVTYPE=disk
E: ID_MODEL=Samsung SSD 980 PRO 1TB
E: ID_SERIAL_SHORT=S5GXNX0T123456

Les línies S: són enllaços simbòlics alternatius, i resolen un problema molt real: el nom nvme0n1 depèn de l'ordre de detecció, que pot canviar entre arrencades. Els enllaços per identificador de sèrie no canvien mai.

$ ls -l /dev/disk/by-id/ | head -4
lrwxrwxrwx 1 root root 13 ago 31 09:14 nvme-Samsung_SSD_980_PRO_1TB_S5GXNX0T123456 -> ../../nvme0n1
lrwxrwxrwx 1 root root 15 ago 31 09:14 nvme-Samsung_SSD_980_PRO_1TB_S5GXNX0T123456-part1 -> ../../nvme0n1p1

Per això /etc/fstab ha de fer servir UUID o /dev/disk/by-id/, mai /dev/sda1. Si afegeixes un disc i l'ordre canvia, un fstab amb noms directes pot muntar el volum equivocat. Ho veurem aplicat a Particions, Muntatge i Sistema de Fitxers Virtual.

Regles d'udev

Pots definir les teves pròpies regles. Un cas real de Meteora: una de les estacions es connecta per USB-sèrie al servidor per a diagnòstic, i vols que aparegui sempre amb el mateix nom i accessible per l'usuari meteora.

# /etc/udev/rules.d/70-meteora.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", \
  ATTRS{serial}=="A50285BI", \
  SYMLINK+="meteora/estacio-nord", OWNER="meteora", GROUP="meteora", MODE="0660"

Com funciona cada part:

  • SUBSYSTEM=="tty": només s'aplica a dispositius de terminal sèrie.
  • ATTRS{idVendor}/ATTRS{idProduct}: identifiquen el xip FTDI concret.
  • ATTRS{serial}: distingeix aquesta estació d'una altra amb el mateix xip. Sense això, dos adaptadors idèntics coincidirien amb la regla.
  • SYMLINK+=: crea /dev/meteora/estacio-nord, un nom estable i independent de si el nucli l'ha anomenada ttyUSB0 o ttyUSB3.
  • OWNER/GROUP/MODE: fan el dispositiu accessible a l'usuari meteora sense necessitat de root.

Aquest darrer punt és important des del punt de vista de la seguretat: en lloc d'executar el procés de diagnòstic com a root perquè pugui obrir el port sèrie, se li donen permisos exactes sobre aquest dispositiu concret. És el principi de mínim privilegi, que desenvoluparem a Principis de Protecció i Control d'Accés.

# Recarregar les regles i aplicar-les sense desconnectar el dispositiu
$ sudo udevadm control --reload-rules
$ sudo udevadm trigger --subsystem-match=tty

$ ls -l /dev/meteora/
lrwxrwxrwx 1 root root 10 ago 31 09:22 estacio-nord -> ../ttyUSB0

Una altra regla útil: fixar el planificador d'E/S per tipus de dispositiu, reprenent el de la lliçó anterior.

# /etc/udev/rules.d/60-scheduler.rules
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", \
  ATTR{queue/scheduler}="mq-deadline"
ACTION=="add|change", KERNEL=="nvme[0-9]n[0-9]", \
  ATTR{queue/scheduler}="none"

Així el planificador correcte s'aplica automàticament a cada arrencada i a cada disc que es connecti, segons si és rotacional o no.

Busos i enumeració: lspci, lsusb, lsblk

Els dispositius es connecten a través de busos, i cada bus té el seu propi mecanisme d'enumeració: la manera com el sistema descobreix què hi ha connectat.

Bus Enumeració Eina Connexió en calent
PCI / PCIe Espai de configuració estàndard lspci Limitada
USB Consulta del descriptor en connectar lsusb
SATA/SCSI Consulta a la controladora lsblk, lsscsi
I²C / SPI Declarada a l'arbre de dispositius i2cdetect No
$ lspci
00:00.0 Host bridge: Intel Corporation 8th Gen Core Processor Host Bridge
00:14.0 USB controller: Intel Corporation 200 Series PCH USB 3.0 xHCI Controller
00:17.0 SATA controller: Intel Corporation 200 Series PCH SATA controller [AHCI mode]
00:1d.0 PCI bridge: Intel Corporation 200 Series PCH PCI Express Root Port #9
03:00.0 Ethernet controller: Intel Corporation I210 Gigabit Network Connection
04:00.0 Non-Volatile memory controller: Samsung Electronics NVMe SSD Controller

L'identificador 03:00.0 segueix el format bus:dispositiu.funció, i és l'adreça física de la targeta a la topologia PCI. La versió detallada revela com hi parla el nucli:

$ sudo lspci -v -s 03:00.0
03:00.0 Ethernet controller: Intel Corporation I210 Gigabit Network Connection (rev 03)
        Subsystem: Intel Corporation Device 0000
        Flags: bus master, fast devsel, latency 0, IRQ 128
        Memory at df200000 (32-bit, non-prefetchable) [size=128K]
        I/O ports at e000 [size=32]
        Memory at df280000 (32-bit, non-prefetchable) [size=16K]
        Capabilities: [70] MSI-X: Enable+ Count=5 Masked-
        Kernel driver in use: igb

Cada línia diu alguna cosa que farem servir:

  • bus master: la targeta pot iniciar transferències DMA pel seu compte, sense que la CPU copiï les dades.
  • IRQ 128: la línia d'interrupció assignada. Apareixerà a /proc/interrupts.
  • Memory at df200000 [size=128K]: els seus registres estan mapejats en memòria en aquesta adreça física.
  • I/O ports at e000: a més té ports d'E/S clàssics.
  • MSI-X: Enable+ Count=5: fa servir interrupcions senyalitzades per missatge, amb 5 vectors diferents.
  • Kernel driver in use: igb: quin mòdul la gestiona.

Aquesta darrera línia és la primera comprovació quan un dispositiu no funciona: si no apareix, no hi ha driver carregat i el maquinari és present però inert. És exactament l'escenari que vam veure a 01-05 amb lsmod i modinfo.

$ lsusb
Bus 002 Device 007: ID 0403:6001 Future Technology Devices International FT232 Serial (UART) IC
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub

$ lsusb -t
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/6p, 5000M
    |__ Port 1: Dev 7, If 0, Class=Vendor Specific Class, Driver=ftdi_sio, 12M

lsusb -t mostra l'arbre amb el driver de cada node (ftdi_sio per a l'estació) i la velocitat negociada (12 Mb/s, USB 1.1 completa, suficient per a un port sèrie).

I el mapa complet de dispositius de bloc, que vam veure a la lliçó anterior:

$ lsblk -o NAME,ROTA,SIZE,TYPE,MOUNTPOINT,MODEL
NAME        ROTA   SIZE TYPE  MOUNTPOINT         MODEL
nvme0n1        0 931,5G disk                     Samsung SSD 980 PRO 1TB
└─nvme0n1p2    0   931G part
  └─md0        0   931G raid1 /var/lib/meteora
sda            1   7,3T disk                     ST8000NM0055-1RM112
└─sda1         1   7,3T part  /backup

Les tres maneres de parlar amb un dispositiu

Quan la CPU necessita transferir dades amb un dispositiu, hi ha tres mecanismes possibles. Aquest apartat els contrasta; el mecanisme intern de les interrupcions i del DMA és el tema de la lliçó següent, aquí només interessa quan es fa servir cadascun i quant costa.

E/S programada amb espera activa

La CPU consulta repetidament un registre d'estat del dispositiu fins que indica que està llest, i llavors transfereix les dades ella mateixa, byte a byte o paraula a paraula.

/* Esquema conceptual: NO és codi real de nucli */
while ((llegir_registre(ESTAT) & LLEST) == 0)
    ;                              /* espera activa: cremar CPU */
escriure_registre(DADES, byte);    /* transferir un byte */
  • Avantatge: latència mínima i simplicitat total. Sense interrupcions ni sincronització.
  • Inconvenient: la CPU no fa res més mentre espera.

Amb un dispositiu lent el malbaratament és catastròfic:

Impressora a 100 caràcters/segon
Temps per caràcter: 10 ms
Cicles de CPU malgastats per caràcter (a 3 GHz): 30.000.000

Trenta milions de cicles per lletra. Tot i així, l'espera activa continua sent l'opció correcta en tres casos: quan el dispositiu respon en menys temps del que costaria una interrupció (uns pocs microsegons), durant l'arrencada —quan el sistema d'interrupcions encara no està configurat—, i en un gestor de pànic, quan ja no es pot confiar en res més.

E/S per interrupcions

La CPU inicia l'operació i se n'oblida: adorm el procés i en planifica un altre. Quan el dispositiu acaba, envia una interrupció i el nucli desperta el procés.

1. L'ingestor crida read() sobre el socket
2. No hi ha dades → el nucli el posa en TASK_INTERRUPTIBLE (estat S)
3. El planificador tria un altre procés (02-02)
4. Arriba un paquet → la targeta genera una interrupció
5. El gestor copia les dades a la cua del socket
6. Marca l'ingestor com a executable (estat R)
7. El planificador el torna a executar i read() retorna

Reconeixeràs cada pas: són exactament les transicions del diagrama d'estats de 02-01. Els estats S i D existeixen precisament per això.

  • Avantatge: la CPU s'aprofita durant l'espera.
  • Inconvenient: cada interrupció costa entre 1 i 5 µs, i la CPU continua copiant les dades byte a byte.

I aquí hi ha el problema que queda per resoldre: amb una targeta de xarxa a 1 Gb/s rebent paquets de 1.500 bytes:

Paquets per segon: 125.000.000 / 1.500 = 83.333 paquets/s
Amb una interrupció per paquet: 83.333 interrupcions/s
A 3 µs cadascuna: 0,25 segons de CPU per segon = 25 % d'un nucli

Un quart de nucli només per atendre interrupcions, sense comptar la còpia de les dades.

DMA (accés directe a memòria)

El DMA és una controladora que transfereix dades entre el dispositiu i la memòria sense intervenció de la CPU. La CPU només programa l'operació (adreça de destinació, mida) i rep una única interrupció al final.

Sense DMA, llegir 4 KB del disc:
  4.096 transferències d'1 byte per la CPU
  ~4.096 × 2 instruccions = 8.192 instruccions

Amb DMA:
  1 programació del descriptor (~20 instruccions)
  1 interrupció en completar-se (~3 µs)

Taula comparativa de cost de CPU

Transferir 1 MB des d'un dispositiu:

Tècnica Intervenció de la CPU Interrupcions CPU consumida Quan fer-la servir
Espera activa Copia cada paraula + espera 0 ~100 % Dispositius rapidíssims, arrencada, pànic
Per interrupcions Copia cada paraula Milers 30-60 % Dispositius lents amb poc volum
DMA Programar i recollir 1 < 1 % Tot allò que mogui volum

Amb números concrets per a 1 MB des de l'SSD:

Espera activa:      262.144 paraules de 4 bytes × ~4 cicles = ~1.000.000 cicles
                    + espera de 300 µs bloquejant la CPU

Per interrupcions:  262.144 còpies + ~256 interrupcions × 3 µs = 768 µs de CPU

DMA:                1 descriptor + 1 interrupció = ~5 µs de CPU

Un factor de més de 150 respecte de l'E/S per interrupcions. Per això tot dispositiu que mogui volum fa servir DMA: discos, xarxa, GPU, so. El bus master que vèiem a lspci és precisament la capacitat de fer-ho.

Regla de decisió resumida:

Situació Tècnica
Latència crítica, dispositiu llest en < 5 µs Espera activa
Dispositiu lent, poques dades (teclat, ratolí) Interrupcions
Volum de dades (disc, xarxa, gràfics) DMA
Taxa d'interrupcions molt alta DMA + sondeig adaptatiu (NAPI, a 02-07)

Ports d'E/S i E/S mapejada en memòria

Queda una pregunta: com s'accedeix físicament als registres d'un dispositiu. Hi ha dos enfocaments.

Espai d'E/S separat

x86 té un espai d'adreces a part, de 65.536 ports, amb instruccions dedicades:

in  al, 0x60      ; llegir un byte del port 0x60 (controladora del teclat)
out 0x3F8, al     ; escriure un byte al port 0x3F8 (port sèrie COM1)

Recordaràs in i out de la taula d'instruccions privilegiades de 01-06: només es poden executar en mode nucli. Aquest és el mecanisme que impedeix que un programa d'usuari parli directament amb el maquinari.

$ sudo cat /proc/ioports | head -8
0000-0cf7 : PCI Bus 0000:00
  0000-001f : dma1
  0040-0043 : timer0
  0060-0060 : keyboard
  0064-0064 : keyboard
  0070-0077 : rtc0
  02f8-02ff : serial
  03f8-03ff : serial

Aquí hi ha els ports històrics: 0x60 i 0x64 per al teclat, 0x3F8 per a COM1, 0x70 per al rellotge de temps real. Números que no canvien des de l'IBM PC de 1981.

E/S mapejada en memòria (MMIO)

Els registres del dispositiu s'assignen a adreces de l'espai de memòria física. Llegir-hi o escriure-hi és accedir al dispositiu.

$ sudo cat /proc/iomem | grep -A2 'PCI Bus 0000:03'
df200000-df21ffff : 0000:03:00.0
  df200000-df21ffff : igb
df280000-df283fff : 0000:03:00.0

Els 128 KB a 0xdf200000 són els registres de la targeta de xarxa que vam veure a lspci. El driver igb els ha reservat i hi accedeix amb instruccions normals de memòria.

Comparació dels dos enfocaments:

Ports d'E/S MMIO
Instruccions in/out, privilegiades mov normals
Espai d'adreces Separat, 64 KB Compartit amb la RAM
Mida de transferència Limitada Qualsevol, incloses ràfegues
Protecció Només per mode (tot o res) Per pàgina, via MMU
Es pot posar a la memòria cau No S'ha de deshabilitar (bit PCD)
Arquitectures Gairebé només x86 Universal

MMIO ha guanyat, i per una raó que ara entens perfectament: com que viu a l'espai d'adreces normal, la protecció l'aplica la MMU amb la granularitat de pàgina que vam estudiar a 02-04. Es pot donar a un procés accés als registres d'un dispositiu concret sense donar-li accés a res més. Amb ports d'E/S la protecció és de tot o res.

A més, MMIO permet que un driver accedeixi als registres amb codi C normal, sense assemblador, cosa que fa el codi portable entre arquitectures.

Un detall crític: les pàgines de MMIO s'han de marcar com a no emmagatzemables a la memòria cau (bit PCD de la taula de pàgines, que vam veure a 02-04). Si la CPU desés a la memòria cau un registre d'estat del dispositiu, en llegiria un valor obsolet en lloc de l'estat real del maquinari, i el driver es penjaria esperant una condició que ja s'ha complert. És un exemple perfecte de com dos mecanismes correctes per separat —memòria cau i MMIO— es destrueixen mútuament si no es coordinen.

Tècniques del subsistema: buffering, caching, spooling i reserva

La capa independent del dispositiu aplica quatre tècniques generals. Totes resolen un desajust diferent.

Buffering

Una memòria intermèdia és una àrea de memòria entre el productor i el consumidor. Resol tres problemes diferents:

Problema Exemple Com ho resol la memòria intermèdia
Desajust de velocitat Xarxa a 125 MB/s, disc a 150 MB/s Absorbeix les ràfegues
Desajust de mida Paquets de 1.500 B, blocs de 4 KB Acumula fins a completar
Semàntica de còpia El procés modifica la memòria intermèdia després del write() Es copia abans d'escriure

Ja en vam calcular l'impacte a 01-06: agrupar 170 lectures abans d'escriure reduïa les crides al sistema en un factor de 167.

Per què la doble memòria intermèdia. Amb una sola memòria intermèdia apareix un problema de bloqueig:

Amb UNA memòria intermèdia:
[omplir]     →  [buidar]   →  [omplir]     →  [buidar]
 dispositiu      procés        dispositiu      procés
 ← el dispositiu està PARAT →  ← el procés està PARAT →

Mentre el procés processa la memòria intermèdia, el dispositiu no hi pot escriure i ha d'esperar. I a l'inrevés. Les dues parts se serialitzen.

Amb DUES memòries intermèdies:
Memòria A: [omplir]  [processar] [omplir]  [processar]
Memòria B:           [omplir]    [processar][omplir]
           ← totes dues treballen EN PARAL·LEL →

Mentre el dispositiu omple la memòria A, el procés processa la B. En acabar, s'intercanvien. El rendiment passa d'1/(t_omplir + t_processar) a 1/max(t_omplir, t_processar).

Càlcul per a l'ingestor:

Omplir una memòria intermèdia des de la xarxa: 8 ms
Processar la memòria intermèdia:               5 ms

Una:  1 / (8 + 5) = 76,9 memòries/segon
Dues: 1 / max(8, 5) = 1/8 = 125 memòries/segon
Millora: 62,5 %

La generalització és la memòria intermèdia circular amb N posicions, que és el que fan servir les cues de sockets i les cues de descriptors de les targetes de xarxa modernes.

Caching

Una memòria intermèdia i una memòria cau es confonen sovint, però són coses diferents:

Memòria intermèdia Memòria cau
Què conté L'única còpia de les dades en trànsit Una còpia de dades que existeixen en un altre lloc
Si es perd Es perden les dades Només es perd rendiment
Propòsit Adaptar velocitats i mides Evitar accessos lents

La memòria cau de pàgines de Linux és la memòria cau de disc del sistema, i és la que vèiem com a buff/cache a free -h:

$ free -h
               total        used        free      shared  buff/cache   available
Mem:           7,8Gi       3,1Gi       412Mi       528Mi       4,3Gi       3,9Gi

Aquests 4,3 GB són contingut de fitxers mantingut a la RAM. Quan l'agregador llegeix 2026-08-31.dat per segona vegada, no toca el disc: les dades ja hi són. És la mateixa memòria cau que dona suport a les pàgines de mmap() que vam fer servir a 02-04.

Comprovar-ho és senzill i molt il·lustratiu:

$ sync && echo 3 | sudo tee /proc/sys/vm/drop_caches   # buidar la memòria cau
$ time cat /var/lib/meteora/lectures/2026-08-31.dat > /dev/null
real    0m0,118s

$ time cat /var/lib/meteora/lectures/2026-08-31.dat > /dev/null
real    0m0,006s

20 vegades més ràpid la segona vegada, sense haver tocat el disc. Aquest factor de 20 és el que la memòria cau de pàgines aporta contínuament i de manera invisible.

Spooling

Spool ve de Simultaneous Peripheral Operation On-Line. És la tècnica per a dispositius que no es poden compartir intercalant operacions.

Si dos processos escriuen simultàniament en una impressora sense coordinació, la sortida surt barrejada línia a línia. El spooling ho resol: cada treball s'escriu sencer en un directori de cua, i un dimoni els imprimeix un a un, en ordre.

$ ls -l /var/spool/cups/
$ ls /var/spool/cron/crontabs/
$ ls /var/spool/mail/

L'interessant és que el spooling continua molt viu encara que les impressores ja no importin: cron, el correu i les cues de missatgeria fan servir el mateix patró. La idea general —encuar treballs en emmagatzematge persistent i processar-los seqüencialment amb un únic consumidor— és un dels patrons més reutilitzats de la informàtica.

A Meteora, l'enviament nocturn de la còpia de seguretat al servidor remot funciona així: els fitxers es deixen en un directori de sortida i un procés els transfereix d'un en un, reintentant els que fallen.

Reserva de dispositiu

Alguns dispositius s'han de fer servir en exclusiva. El sistema ofereix mecanismes per reservar-los:

/* Obrir un port sèrie en exclusiva */
int fd = open("/dev/meteora/estacio-nord", O_RDWR | O_NOCTTY);
if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
    fprintf(stderr, "El port ja està en ús per un altre procés\n");
    return 1;
}

flock amb LOCK_EX | LOCK_NB intenta un bloqueig exclusiu sense esperar: si un altre procés ja té el port, falla immediatament en lloc de quedar-se penjat.

La reserva exclusiva introdueix el risc d'interbloqueig: si el procés A té el port sèrie i espera el mòdem, i B té el mòdem i espera el port sèrie, cap dels dos no avança. Aquest problema té lliçó pròpia: Interbloquejos.

Tractament d'errors i reintents

El principi general: els errors es tracten com més a prop del maquinari millor, i només pugen si no es poden resoldre a baix.

Nivell del dispositiu:   reintent automàtic del mateix maquinari
Nivell del controlador:  reintents, reassignació de sectors
Nivell independent:      traducció a un codi d'error estàndard
Nivell d'aplicació:      decideix què fer (reintentar, avisar, avortar)

Un exemple real de la cadena completa, vist des del registre del nucli:

$ sudo dmesg -T | tail -6
[Sun Aug 31 03:22:11 2026] ata3.00: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x0
[Sun Aug 31 03:22:11 2026] ata3.00: irq_stat 0x40000001
[Sun Aug 31 03:22:11 2026] ata3.00: failed command: READ DMA EXT
[Sun Aug 31 03:22:11 2026] ata3.00: status: { DRDY ERR }
[Sun Aug 31 03:22:11 2026] ata3.00: error: { UNC }
[Sun Aug 31 03:22:14 2026] sd 2:0:0:0: [sda] tag#20 Sense Key : Medium Error [current]

Traducció de la traça:

  • UNC (uncorrectable): el disc no ha pogut llegir un sector ni tan sols amb la seva correcció d'errors interna.
  • El controlador va reintentar diverses vegades (d'aquí els 3 segons entre la primera i l'última línia).
  • En esgotar els reintents, va generar un Medium Error.
  • El sistema de fitxers el rep com a EIO, i el procés que ha fet read() obté -1 amb errno = EIO.

Aquest errno és exactament el mecanisme que vam rastrejar fins al seu origen a 01-06: un valor negatiu retornat pel nucli que la libc converteix en -1 més errno.

Categories d'error i què fer amb cadascuna:

Error Significat Cal reintentar?
EIO Error d'E/S físic Potser una vegada; si persisteix, maquinari defectuós
EAGAIN No hi ha dades ara (no bloquejant) , no és un error real
EINTR Interromput per un senyal Sí, sempre
ENOSPC Sense espai No: cal alliberar espai
ENODEV El dispositiu ha desaparegut No: desconnectat
EBUSY En ús per un altre Potser, després d'esperar

EINTR mereix insistència perquè és la font d'errors més traïdora. Ja el vam veure a l'exemple de buffering de 01-06: si arriba un senyal mentre el procés està bloquejat a read() o write(), la crida retorna amb EINTR sense haver fet res. No és un error: cal reintentar. Ignorar-ho produeix pèrdues de dades esporàdiques i irreproduïbles.

ssize_t llegir_robust(int fd, void *buf, size_t n) {
    ssize_t r;
    do {
        r = read(fd, buf, n);
    } while (r == -1 && errno == EINTR);
    return r;
}

Quatre línies que eviten tota una classe de fallades intermitents.

El camí d'una lectura d'estació

Tanquem aplicant-ho tot a un recorregut concret: una estació meteorològica envia una lectura de 24 bytes i l'ingestor la rep. Aquí veiem quines capes hi intervenen i què fa cadascuna; el mecanisme intern de la interrupció i del DMA és el contingut de la lliçó següent.

flowchart TD
    A["Estació meteorològica<br/>envia 24 bytes per UDP"] --> B
    B["Xarxa física<br/>arriba a la targeta I210"] --> C
    C["Targeta de xarxa<br/>valida la trama Ethernet"] --> D
    D["DMA<br/>copia el paquet a una memòria intermèdia de RAM<br/>sense intervenció de la CPU"] --> E
    E["Interrupció<br/>la targeta avisa la CPU: IRQ 128"] --> F
    F["Controlador igb<br/>reconeix la interrupció"] --> G
    G["Pila de xarxa<br/>IP → UDP → busca el socket destinatari"] --> H
    H["Cua del socket<br/>el paquet s'encua"] --> I
    I["El nucli marca l'ingestor<br/>com a executable: S → R"] --> J
    J["Planificador<br/>tria l'ingestor (02-02)"] --> K
    K["read() retorna<br/>els 24 bytes a la memòria intermèdia d'usuari"]

Recorregut amb el paper de cada capa:

Pas Capa Què passa Lliçó
1-3 Maquinari La targeta rep i valida la trama
4 DMA El paquet arriba a la RAM sense CPU 02-07
5 Interrupció La targeta avisa: hi ha feina 02-07
6 Controlador Codi específic de la targeta I210 02-07
7 Independent del dispositiu Pila de xarxa comuna a totes les targetes
8 Buffering La cua del socket absorbeix les ràfegues Aquesta lliçó
9 Gestió de processos L'ingestor passa de S a R 02-01
10 Planificació Competeix per la CPU 02-02
11 Crida al sistema Còpia a l'espai d'usuari 01-06

Fixa't en la divisió de feina. Els passos 1-6 són específics d'aquesta targeta: només el driver igb sap com llegir-ne els registres. A partir del pas 7 el codi és comú a totes les targetes de xarxa del món: la pila IP/UDP no sap ni li importa quin maquinari ha portat el paquet. Aquesta frontera és exactament la que vam dibuixar al diagrama de capes de l'apartat 2, i és la que permet que Linux admeti centenars de targetes diferents amb una sola pila de xarxa.

I observa una cosa més: l'ingestor no ha participat en res d'això. Estava adormit en estat S des del seu read(). Tota la feina l'han feta el maquinari, el DMA, el gestor d'interrupció i la pila de xarxa. El procés només es desperta quan ja hi ha dades esperant-lo.

Un càlcul de cost que motiva la lliçó següent. Amb 800 lectures/segon:

Amb una interrupció per paquet:
  800 interrupcions/s × 3 µs = 2,4 ms/s = 0,24 % d'un nucli
  Manejable amb aquesta càrrega.

Si Meteora creixés fins a 500.000 lectures/segon:
  500.000 × 3 µs = 1,5 segons de CPU per segon
  Més d'un nucli sencer només atenent interrupcions.

Aquest és el problema de la tempesta d'interrupcions, i la seva solució —que el sistema deixi de fer servir interrupcions i passi a consultar activament quan la càrrega és alta— és un dels mecanismes més elegants del nucli de Linux. Es diu NAPI i el veurem en detall tot seguit.

Errors Habituals i Consells

Fer servir /dev/sda1 a /etc/fstab. El nom depèn de l'ordre de detecció i pot canviar en afegir maquinari. Fes servir sempre UUID= o /dev/disk/by-id/. El dia que afegeixis un disc i el sistema no arrenqui, entendràs per què.

Confondre memòria intermèdia i memòria cau. Una memòria intermèdia conté l'única còpia de dades en trànsit; perdre-la vol dir perdre dades. Una memòria cau conté una còpia d'alguna cosa que existeix en un altre lloc; perdre-la només costa rendiment. drop_caches buida memòries cau, mai memòries intermèdies amb dades pendents.

Executar com a root per accedir a un dispositiu. Gairebé sempre la solució correcta és una regla d'udev que doni permisos exactes sobre aquest dispositiu concret a l'usuari que el necessita. Root per llegir un port sèrie és donar mil permisos per fer-ne servir un.

Ignorar EINTR. És la causa més freqüent de fallades intermitents en codi d'E/S. Qualsevol read, write o close sobre un descriptor bloquejant ho ha de contemplar.

Creure que un dispositiu de xarxa hauria de tenir fitxer a /dev. No en té, i és una decisió de disseny conscient: els paquets no són un flux de bytes fiable i ordenat. L'abstracció correcta és el socket.

Interpretar malament lspci quan alguna cosa no funciona. Si el dispositiu apareix però no hi ha línia Kernel driver in use, el maquinari és present i sense driver. Si no apareix en absolut, és un problema físic o de BIOS. Són dos diagnòstics completament diferents.

Consell de diagnòstic: quan un dispositiu no funciona, l'ordre que funciona és: lspci/lsusb (el veu, el sistema?), lspci -v | grep -i driver (té driver?), dmesg -T | tail -50 (què ha dit el nucli en detectar-lo?), ls -l /dev/... (existeix el node i amb quins permisos?), i udevadm info --query=all --name=... (què en sap, udev?). Cinc comprovacions que localitzen el problema a la capa exacta.

Exercicis

Exercici 1: interpretar fitxers de dispositiu

Donada aquesta sortida:

$ ls -l /dev/sda /dev/sda1 /dev/nvme0n1 /dev/tty1 /dev/null /dev/meteora/estacio-nord
brw-rw----  1 root disk    8,   0 ago 31 09:14 /dev/sda
brw-rw----  1 root disk    8,   1 ago 31 09:14 /dev/sda1
brw-rw----  1 root disk  259,   0 ago 31 09:14 /dev/nvme0n1
crw--w----  1 root tty     4,   1 ago 31 09:14 /dev/tty1
crw-rw-rw-  1 root root    1,   3 ago 31 09:14 /dev/null
lrwxrwxrwx  1 root root         7 ago 31 09:22 /dev/meteora/estacio-nord -> ttyUSB0
  1. Classifica cada entrada per tipus i explica com ho saps.
  2. Per què /dev/sda i /dev/sda1 comparteixen el major però no el menor?
  3. Si l'usuari meteora (grup meteora) executa dd if=/dev/sda of=/tmp/copia bs=1M count=10, funciona? I sobre /dev/meteora/estacio-nord?
  4. Què representa el 7 de l'última línia, on les altres tenen dos números?
  5. Escriu una regla d'udev que doni al grup meteora accés de només lectura a /dev/sda, i explica si és bona idea.

Exercici 2: triar la tècnica d'E/S i calcular-ne el cost

Per a cadascun d'aquests dispositius de Meteora, decideix quina tècnica d'E/S és l'adequada (espera activa, interrupcions o DMA) i justifica-ho amb un càlcul:

  • A: Sensor I²C d'una estació, que retorna 2 bytes en 8 µs després de la petició. Es consulta una vegada per minut. El microcontrolador va a 80 MHz.
  • B: Targeta de xarxa de meteo-01 rebent 800 lectures/segon de 24 bytes en paquets UDP de 66 bytes.
  • C: SSD NVMe llegint els 17 MB de 2026-08-31.dat.
  • D: Adaptador USB-sèrie de diagnòstic a 9.600 bauds, que envia 200 caràcters cada vegada que es connecta.

Per a cadascun indica la tècnica, el càlcul del cost de CPU i què passaria si triessis malament.

Exercici 3: diagnosticar un dispositiu que no funciona

Has connectat una segona targeta de xarxa a meteo-01 per separar el trànsit de les estacions del trànsit de l'API, però no apareix com a interfície. Dissenya el procediment de diagnòstic complet:

  1. Escriu la seqüència d'ordres que executaries, en ordre, i què hi busques a cadascuna.
  2. Per a cadascun d'aquests quatre resultats possibles, digues què conclous i què faries:
    • a) lspci no mostra cap targeta nova.
    • b) lspci la mostra però sense línia Kernel driver in use.
    • c) lspci la mostra amb driver, ip link la mostra com a enp5s0 en estat DOWN.
    • d) dmesg mostra igb 0000:05:00.0: Failed to initialize MSI-X interrupts.
  3. Un cop funcionant, escriu una regla d'udev que li doni un nom estable estacions0 i explica per què no n'hi ha prou amb ip link set name.

Solucions

Solució 1

1. Classificació.

Entrada Tipus Com ho sé
/dev/sda Bloc Primer caràcter b; mostra dos números (8, 0) en lloc de mida
/dev/sda1 Bloc Igual, amb menor 1
/dev/nvme0n1 Bloc b, major 259 (blkext)
/dev/tty1 Caràcter Primer caràcter c
/dev/null Caràcter c, major 1 (mem), menor 3
/dev/meteora/estacio-nord Enllaç simbòlic l inicial i fletxa ->

El senyal més fiable és la columna on un fitxer normal posaria la mida: si hi ha dos números separats per una coma, és un fitxer de dispositiu.

2. Mateix major, menor diferent.

El major 8 identifica el controlador (sd, discos SCSI/SATA). Tots dos els gestiona el mateix driver, així que comparteixen major.

El menor identifica quina instància concreta dins d'aquest driver:

menor 0  → el disc sda sencer, des de l'LBA 0 fins al final
menor 1  → la partició sda1, un rang d'LBA dins de sda

Una partició no és un dispositiu diferent: és una finestra sobre un rang de blocs del mateix disc físic. El driver sd rep el menor, consulta la seva taula de particions i aplica el desplaçament corresponent.

La convenció assigna 16 menors per disc: menors 0-15 per a sda i les seves 15 particions, 16-31 per a sdb, i així successivament. Per això /dev/sdb té menor 16.

3. Permisos de l'usuari meteora.

Sobre /dev/sda: NO funciona.

brw-rw---- 1 root disk 8, 0 /dev/sda
  • Propietari root, amb rw. meteora no és root.
  • Grup disk, amb rw. Caldria comprovar si meteora pertany a disk:
$ groups meteora
meteora : meteora

No hi pertany.

  • Altres: ---, sense cap permís.

Resultat: dd: failed to open '/dev/sda': Permís denegat.

I està bé que sigui així. Accés de lectura al disc cru significa poder llegir qualsevol fitxer del sistema saltant-se completament els permisos del sistema de fitxers: /etc/shadow inclòs. Equival a ser root per a lectura.

Sobre /dev/meteora/estacio-nord: SÍ que funciona, si la regla d'udev de la lliçó està activa.

L'enllaç apunta a ttyUSB0, i els permisos que compten són els del destí, no els de l'enllaç (els lrwxrwxrwx d'un enllaç simbòlic són sempre així i no volen dir res). Amb la regla:

crw-rw---- 1 meteora meteora 188, 0 ago 31 09:22 /dev/ttyUSB0

Propietari meteora amb rw: accés concedit. Tot i que dd sobre un port sèrie llegiria el flux entrant, no un contingut amb estructura.

4. El número 7 de l'enllaç simbòlic.

És la mida en bytes de l'enllaç, és a dir, la longitud de la cadena que conté:

"ttyUSB0" = 7 caràcters

Un enllaç simbòlic sí que és un fitxer de veritat: el seu contingut és la ruta de destinació. Per això mostra una mida on els fitxers de dispositiu mostren major i menor. Els dispositius reals no mostren mida perquè no contenen res: només referencien un driver.

Comprovació:

$ readlink /dev/meteora/estacio-nord
ttyUSB0
$ stat -c '%s bytes' /dev/meteora/estacio-nord
7 bytes

5. Regla d'udev per donar lectura de /dev/sda al grup meteora.

# /etc/udev/rules.d/71-meteora-disc.rules
SUBSYSTEM=="block", KERNEL=="sda", GROUP="meteora", MODE="0640"

Resultat: brw-r----- 1 root meteora 8, 0 /dev/sda.

És bona idea? Rotundament no.

El raonament:

  1. Elude completament els permisos del sistema de fitxers. Llegint el dispositiu cru s'accedeix als bytes de tots els fitxers d'aquest disc, inclosos /etc/shadow i les claus privades de TLS. Els permisos de fitxer s'apliquen a través del sistema de fitxers; l'accés al dispositiu se'ls salta.

  2. Viola el principi de mínim privilegi. Si l'agregador necessita llegir dades, necessita llegir fitxers de /var/lib/meteora/lectures/, no el disc sencer. El permís concedit és uns quants ordres de magnitud més gran que la necessitat.

  3. Amplia enormement la superfície d'atac. Si algú compromet un procés que corre com a meteora, obté lectura completa del disc.

  4. No hi ha cap cas legítim a Meteora que ho requereixi. Els usos reals del disc cru —clonatge, anàlisi forense, recuperació— són tasques administratives puntuals, no operacions d'un servei.

Alternativa correcta segons el que es necessiti:

Necessitat real Solució correcta
Llegir les dades de les lectures Permisos sobre /var/lib/meteora/, que ja els té
Consultar l'espai lliure df, que no requereix permisos especials
Veure l'estat SMART del disc sudo amb una entrada concreta per a smartctl
Fer una còpia del disc Tasca administrativa, amb root i puntual
# Si de debò calgués consultar SMART sense ser root:
# /etc/sudoers.d/meteora-smart
meteora ALL=(root) NOPASSWD: /usr/sbin/smartctl -a /dev/sda

Això concedeix exactament una ordre amb exactament uns arguments, en lloc d'accés complet al disc. És la diferència entre una clau d'una porta i una clau mestra de l'edifici.

Solució 2

A: Sensor I²C, 2 bytes en 8 µs, una vegada per minut, microcontrolador a 80 MHz.

Tècnica: espera activa.

Cicles malgastats esperant: 8 µs × 80 MHz = 640 cicles
Freqüència: 1 vegada per minut
Cost de CPU: 640 cicles / (60 s × 80.000.000 cicles/s) = 0,00000013 %

Justificació:

  • 640 cicles és menys del que costaria una interrupció. Configurar el vector, desar el context, executar el gestor i restaurar costa de l'ordre de 200-500 cicles en un microcontrolador, més la complexitat del codi. La interrupció seria més cara que l'espera.
  • És un microcontrolador amb FreeRTOS, com vam establir a 01-03. No hi ha multitasca pesada competint: no hi ha res millor a fer durant aquests 8 µs.
  • La simplicitat té valor propi en sistemes encastats: menys codi, menys estat, menys maneres de fallar.

Si triessis malament (interrupcions): funcionaria, però amb més codi, més complexitat i més consum de CPU que la mateixa espera. Un cas rar on la solució «avançada» és objectivament pitjor.

B: Targeta de xarxa, 800 lectures/s en paquets UDP de 66 bytes.

Tècnica: DMA amb interrupcions (i moderació d'interrupcions).

Paquets per segon: 800
Dades per segon: 800 × 66 B = 52,8 KB/s

Amb espera activa: la CPU no podria fer res més. Descartat.

Amb interrupcions sense DMA:
  Còpia per CPU: 66 bytes = ~17 paraules de 4 bytes per paquet
  800 × (17 còpies × 4 cicles + 3 µs d'interrupció)
  ≈ 800 × 3,02 µs = 2,4 ms/s = 0,24 % d'un nucli

Amb DMA:
  800 interrupcions/s × 3 µs = 2,4 ms/s = 0,24 %
  La còpia la fa el DMA: cost de CPU ≈ 0

Observació honesta: amb només 800 paquets/s, el DMA i les interrupcions pures donen un cost gairebé idèntic, perquè el cost està dominat per la interrupció, no per copiar 66 bytes. El DMA guanya clarament quan els paquets són grans o abundants.

Però hi ha dues raons sòlides per fer servir DMA igualment:

  1. Escalabilitat. Si Meteora creix fins a 500.000 lectures/s, sense DMA la còpia sola consumiria un nucli sencer.
  2. No és opcional. Les targetes modernes només funcionen per DMA: el driver igb programa descriptors i la targeta escriu directament a la RAM. L'E/S programada ja no existeix en aquest maquinari.

Si triessis malament (espera activa): un nucli sencer consultant el registre de la targeta permanentment, per rebre 52,8 KB/s. Absurd.

C: SSD NVMe llegint 17 MB.

Tècnica: DMA, sense cap dubte.

Dades: 17.280.000 bytes = 4.320.000 paraules de 4 bytes

Espera activa o interrupcions amb còpia per CPU:
  4.320.000 còpies × ~4 cicles = 17.280.000 cicles
  A 3 GHz = 5,76 ms de CPU pura només copiant

Amb DMA en peticions d'1 MB:
  17 descriptors + 17 interrupcions × 3 µs = 51 µs de CPU

Factor de millora: 5.760 µs / 51 µs = 113×

I a més hi ha un argument qualitatiu encara més fort:

Temps de lectura de l'SSD: 17 MB / 3.500 MB/s = 4,9 ms

Amb espera activa, la CPU estaria bloquejada 4,9 ms sense fer res. A 3 GHz són gairebé 15 milions de cicles perduts, i equival a més d'un quantum sencer del planificador (4 ms, segons 02-02). Amb DMA, aquests 4,9 ms els aprofita un altre procés.

Si triessis malament: a més del malbaratament, el procés no es podria bloquejar en estat S, i es trencaria tot el model de planificació que vam estudiar a 02-02.

D: USB-sèrie a 9.600 bauds, 200 caràcters en connectar.

Tècnica: interrupcions (que és el que fa el driver ftdi_sio).

9.600 bauds amb 8N1 = 8 bits de dades + 1 d'inici + 1 de parada = 10 bits per caràcter
Caràcters per segon: 9.600 / 10 = 960 c/s
Temps per caràcter: 1,04 ms
Temps total de 200 caràcters: 208 ms

Amb espera activa:
  208 ms de CPU bloquejada a 3 GHz = 624.000.000 cicles malgastats
  I 208 ms són 52 quàntums de 4 ms: 52 torns robats a altres processos

Amb interrupcions (agrupant per lots, com fa USB):
  ~4 interrupcions × 3 µs = 12 µs de CPU

Factor: 208.000 µs / 12 µs = 17.333×

Justificació: 1,04 ms per caràcter és una eternitat per a una CPU. És exactament l'escenari per al qual es van inventar les interrupcions: dispositiu lentíssim, dades escasses, esperes llarguíssimes.

Detall interessant: USB no genera una interrupció per byte. La controladora USB agrupa les transferències en paquets i el xip FTDI té la seva pròpia memòria intermèdia, així que 200 caràcters poden arribar en 3 o 4 transferències. És buffering aplicat al mateix maquinari, exactament per les raons de l'apartat 10.

Si triessis malament (espera activa): 208 ms de CPU bloquejada. A meteo-01, amb l'ingestor rebent 800 lectures/s, aquests 208 ms significarien 166 lectures sense atendre. Un port sèrie de diagnòstic hauria degradat el servei de producció.

Resum:

Cas Tècnica Cost de CPU Criteri decisiu
A: sensor I²C Espera activa 0,0000001 % 640 cicles < cost d'una interrupció
B: xarxa DMA 0,24 % Escalabilitat i maquinari modern
C: SSD DMA 0,001 % Volum de dades: 113× de millora
D: sèrie Interrupcions 0,006 % Dispositiu lentíssim, dades escasses

La regla que n'emergeix: espera activa quan l'espera és més curta que la interrupció; DMA quan hi ha volum; interrupcions en tota la resta.

Solució 3

1. Seqüència de diagnòstic.

# Pas 1: el bus PCI veu la targeta?
$ lspci | grep -i ethernet
# Busco: una segona línia de controladora Ethernet

# Pas 2: té driver associat?
$ lspci -v -s 05:00.0 | grep -E 'Kernel driver|Kernel modules'
# Busco: "Kernel driver in use: igb"

# Pas 3: què ha dit el nucli en detectar-la?
$ sudo dmesg -T | grep -iE 'eth|igb|e1000|link' | tail -30
# Busco: errors d'inicialització, microprogramari, interrupcions

# Pas 4: existeix la interfície de xarxa?
$ ip link show
# Busco: una segona interfície a més de lo i enp3s0

# Pas 5: està carregat el mòdul?
$ lsmod | grep -E 'igb|e1000'
$ modinfo igb | head -5

# Pas 6: què en sap udev, del dispositiu?
$ udevadm info --query=all --path=/sys/class/net/enp5s0 2>/dev/null

# Pas 7: hi ha conflicte d'interrupcions?
$ cat /proc/interrupts | grep -i eth

La lògica de l'ordre és anar de baix a dalt: maquinari → driver → nucli → interfície. No té sentit investigar la configuració de xarxa si el bus PCI ni tan sols veu la targeta.

2. Els quatre escenaris.

a) lspci no mostra cap targeta nova.

Conclusió: el problema és per sota del sistema operatiu. Linux no pot gestionar maquinari que el bus PCI no enumera; no és un problema de drivers ni de configuració.

Causes possibles i què fer:

# Està ben encaixada físicament a la ranura?
# → Apagar, revisar el connector, tornar-la a muntar

# La ranura PCIe està habilitada a la BIOS?
# → Revisar la configuració de la BIOS/UEFI

# La ranura funciona?
# → Provar la targeta en una altra ranura

# La targeta funciona?
# → Provar-la en una altra màquina

# Forçar un reescaneig del bus PCI sense reiniciar:
$ echo 1 | sudo tee /sys/bus/pci/rescan
$ lspci | grep -i ethernet

El reescaneig és útil perquè descarta el cas d'una targeta inserida en calent que no va ser detectada. Si després d'això continua sense aparèixer, el problema és físic o de BIOS.

b) Apareix a lspci però sense Kernel driver in use.

Conclusió: el maquinari és present i correctament enumerat, però no hi ha driver que el gestioni. La targeta existeix i està inerta.

$ sudo lspci -v -s 05:00.0
05:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection
        Kernel modules: igb
        (no apareix "Kernel driver in use")

Si apareix Kernel modules: igb, el nucli sap quin mòdul la gestionaria però no l'ha carregat.

# Carregar-lo manualment
$ sudo modprobe igb
$ dmesg -T | tail -20
$ lspci -v -s 05:00.0 | grep 'Kernel driver'

# Si no existeix el mòdul, identificar el maquinari exacte
$ lspci -nn | grep -i ethernet
05:00.0 Ethernet controller [0200]: Intel Corporation I350 [8086:1521] (rev 01)

# Buscar el driver per l'identificador 8086:1521
$ sudo apt install firmware-linux-nonfree   # si necessita microprogramari

Si el mòdul és a la llista negra, apareixerà a /etc/modprobe.d/:

$ grep -r igb /etc/modprobe.d/

Aquest és l'escenari del driver e1000e defectuós que vam veure a 01-05: maquinari present, mòdul absent o bloquejat.

c) Apareix amb driver, ip link la mostra com a enp5s0 en estat DOWN.

Conclusió: tot el subsistema de dispositius funciona correctament. El driver està carregat, el nucli ha creat la interfície, udev l'ha anomenada. El que falta és configuració de xarxa o enllaç físic.

$ ip link show enp5s0
3: enp5s0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN
# Hi ha cable connectat i enllaç negociat?
$ sudo ethtool enp5s0 | grep -E 'Link detected|Speed'
Link detected: no

# Si "Link detected: no" → problema físic:
#   cable desconnectat o defectuós, o port del commutador apagat

# Si "Link detected: yes" → només falta activar-la:
$ sudo ip link set enp5s0 up
$ sudo ip addr add 10.20.0.5/24 dev enp5s0

Link detected és la comprovació decisiva en aquest escenari: separa un problema de cablatge d'un de configuració, que són dues investigacions completament diferents.

Per fer-ho persistent:

# /etc/systemd/network/20-estacions.network
[Match]
Name=estacions0

[Network]
Address=10.20.0.5/24

d) dmesg mostra Failed to initialize MSI-X interrupts.

Conclusió: el driver s'ha carregat però no ha pogut configurar les interrupcions, que com vam veure a lspci són MSI-X amb 5 vectors. Sense interrupcions funcionant, la targeta no pot avisar que ha rebut paquets.

$ sudo dmesg -T | grep -A5 'Failed to initialize MSI-X'
[Sun Aug 31 10:14:22 2026] igb 0000:05:00.0: Failed to initialize MSI-X interrupts. Falling back to MSI interrupts.

Si diu Falling back to MSI interrupts, el driver ha degradat a MSI i probablement funcioni, encara que amb menys paral·lelisme: en lloc de 5 vectors (un per cua), en tindrà un de sol, cosa que redueix el rendiment amb càrrega alta.

# Comprovar quantes línies fa servir realment
$ cat /proc/interrupts | grep enp5s0
 129:  1204  0  0  0  PCI-MSI 2621440-edge  enp5s0

# Una sola línia: està en MSI, no MSI-X (que en mostraria 5)

Causes i solucions:

# S'han esgotat els vectors d'interrupció del sistema?
$ cat /proc/interrupts | wc -l

# El nucli té MSI deshabilitat per algun paràmetre?
$ cat /proc/cmdline | grep -o 'pci=[^ ]*'
# Si apareix pci=nomsi, és la causa: treure'l del GRUB

# La BIOS té l'IOMMU o VT-d mal configurat?
# → Revisar-ho a la BIOS

# Hi ha actualització de microprogramari o de BIOS pendent?
$ sudo dmidecode -s bios-version

Si funciona degradada a MSI, és acceptable per a Meteora amb 800 lectures/s —ja hem calculat que són 2,4 ms/s de CPU—, però convé resoldre-ho per tenir marge de creixement. El detall d'MSI, MSI-X i per què importen diversos vectors és el contingut de la lliçó següent.

3. Regla d'udev per a un nom estable.

# /etc/udev/rules.d/70-meteora-xarxa.rules
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="igb", \
  ATTR{address}=="a0:36:9f:12:34:56", \
  ATTR{type}=="1", NAME="estacions0"

Explicació de cada condició:

  • SUBSYSTEM=="net": només interfícies de xarxa.
  • ACTION=="add": només en aparèixer el dispositiu.
  • ATTR{address}: l'adreça MAC, que és única i immutable al maquinari. És el que garanteix que la regla identifiqui aquesta targeta i no una altra.
  • ATTR{type}=="1": tipus Ethernet, per no coincidir amb interfícies virtuals.
  • NAME=: el nom definitiu. Compte: per a interfícies de xarxa es fa servir NAME, no SYMLINK, perquè la interfície es reanomena en lloc de crear un àlies.
$ sudo udevadm control --reload-rules
$ sudo udevadm trigger --subsystem-match=net
$ ip link show estacions0

Per què no n'hi ha prou amb ip link set name:

ip link set enp5s0 name estacions0 Regla d'udev
Persisteix després de reiniciar No
S'aplica abans que la configuració de xarxa No
Depèn del nom inicial (si canvia, falla) No, fa servir la MAC
Funciona amb connexió en calent No

El problema de fons és el mateix que amb /dev/sda1 a /etc/fstab: el nom assignat pel nucli depèn de l'enumeració del bus PCI, que pot canviar en afegir o moure maquinari. La targeta que avui és enp5s0 pot ser enp6s0 demà si insereixes una altra targeta en una ranura anterior.

I hi ha un problema d'ordre encara més greu: ip link set name és una ordre que s'executa després que el sistema hagi arrencat la xarxa. Per llavors, systemd-networkd ja haurà intentat configurar enp5s0 amb la configuració d'estacions0, o a l'inrevés, i la interfície podria acabar amb la IP equivocada. Amb udev, el canvi de nom passa en el moment de la detecció, abans que res més toqui la interfície.

A Meteora això és especialment important: si estacions0 (xarxa d'estacions) i la interfície de l'API s'intercanviessin els noms després d'un reinici, l'ingestor escoltaria a la xarxa equivocada i les lectures es perdrien sense cap missatge d'error. Una fallada silenciosa i irreversible, exactament la que cal dissenyar perquè no passi.

Conclusió

El subsistema d'E/S existeix perquè read(fd, buf, 1024) funcioni igual sobre un teclat a 100 B/s i sobre un NVMe a 3.500 MB/s, nou ordres de magnitud de diferència. Ho aconsegueix amb una arquitectura en capes on el programari independent del dispositiu fa tot allò comú —noms, permisos, buffering, memòria cau, errors— i cada controlador només implementa allò veritablement específic del seu model.

La classificació en bloc, caràcter i xarxa determina la interfície: els de bloc admeten accés aleatori i es munten; els de caràcter són fluxos seqüencials; els de xarxa no tenen fitxer a /dev perquè un flux de bytes fiable no descriu bé uns paquets que es poden perdre i desordenar, i per això UNIX va crear una segona abstracció, el socket, en lloc de deformar la primera. El principi «tot és un fitxer» unifica permisos, eines i composició, i descansa en un mecanisme sorprenentment simple: els números major i menor, on el major tria el controlador i el menor la instància. udev omple /dev dinàmicament i les seves regles resolen dos problemes reals: noms estables que no depenen de l'ordre de detecció, i permisos exactes que eviten haver d'executar com a root.

Hi ha tres maneres de parlar amb un dispositiu, i triar bé és qüestió d'aritmètica: espera activa quan l'espera és més curta que una interrupció (o durant l'arrencada i el pànic), interrupcions per a dispositius lents amb poques dades, i DMA per a tot allò que mogui volum, amb una reducció del cost de CPU de més de 150 vegades. Als registres s'hi arriba per ports d'E/S o per MMIO, i MMIO ha guanyat perquè la seva protecció l'aplica la MMU amb granularitat de pàgina, encara que exigeix marcar aquestes pàgines com a no emmagatzemables a la memòria cau. Finalment, les tècniques del subsistema —buffering (amb la doble memòria intermèdia aportant un 62,5 % de millora a l'ingestor), caching (un factor de 20 en la segona lectura del fitxer diari), spooling i reserva— resolen cadascuna un desajust concret, i el tractament d'errors els resol com més avall millor, deixant a dalt només allò que requereix decisió.

Hem seguit el camí d'una lectura d'estació des de la targeta fins a la memòria intermèdia de l'ingestor i hem identificat les capes, però deliberadament hem deixat tres caixes sense obrir: què fa exactament la CPU en rebre una interrupció, com un controlador es registra davant del nucli i quin contracte compleix, i com funciona el DMA per dins. També ha quedat plantejat un problema amb números: a 500.000 lectures per segon, un nucli sencer es dedicaria només a atendre interrupcions. La solució a això, i a tot l'anterior, és a Controladors, Interrupcions i Operacions d'E/S, que tanca el mòdul.

Fonaments de Sistemes Operatius

Mòdul 1: Introducció als Sistemes Operatius

Mòdul 2: Gestió de Recursos

Mòdul 3: Concurrència

Mòdul 4: Estructures de Fitxers

Mòdul 5: Protecció i Seguretat del Sistema

Mòdul 6: Virtualització i Contenidors

Mòdul 7: Administració i Diagnòstic a la Pràctica

© Copyright 2026. Tots els drets reservats