Las interrupciones de Hardware (Hardware Interrupts) es el modo que tienen los dispositivos físicos para establecer comunicación con el sistema operativo. Cuando hablamos de comunicación nos referimos a tareas como que las tarjetas de red informan al sistema de que está procesando un paquete, un disco duro que está leyendo un bloque de datos, etc.
Estas interrupciones se envían de forma directa a la CPU o CPUs. Sin entrar más en definiciones técnicas de como funciona internamente sí que os puedo indicar que en sistemas con múltiples CPUs se pueden producir «descompensaciones». Esto significa que una CPU esté procesando muchas más interrupciones de Hardware que otra.
Ver estadísticas de Hardware Interrupts
El filesystem /proc nos ofrece información extendida sobre las interrupciones de hardware y estadísticas en una tabla en la cual vemos una relación del nº de interrupciones por CPU y dispositivo. Ejemplo:
$ cat /proc/interrupts CPU0 CPU1 CPU2 CPU3 0: 27292 0 0 0 IO-APIC-edge timer 1: 167 1 0 0 IO-APIC-edge i8042 7: 0 0 0 0 IO-APIC-edge parport0 8: 1 0 0 0 IO-APIC-edge rtc0 9: 0 0 0 0 IO-APIC-fasteoi acpi 12: 297 2 0 0 IO-APIC-edge i8042 14: 0 0 0 0 IO-APIC-edge ata_piix 15: 32 23 2 16 IO-APIC-edge ata_piix 18: 3273686896 0 522711 0 IO-APIC-fasteoi eth0 19: 472943614 285201 0 0 IO-APIC-fasteoi eth1
Extraído de la página man de /proc:
/proc/interrupts This is used to record the number of interrupts per CPU per IO device. Since Linux 2.6.24, for the i386 and x86_64 architectures, at least, this also includes interrupts internal to the system (that is, not associated with a device as such), such as NMI (nonmaskable interrupt), LOC (local timer interrupt), and for SMP systems, TLB (TLB flush interrupt), RES (rescheduling interrupt), CAL (remote function call interrupt), and possibly others. Very easy to read formatting, done in ASCII.
Como podéis ver, tenemos las interfaces de red (eth0 y eth1), el ACPI (Advanced Configuration and Power Interface), 8042 (ratón y teclado), etc.
Si os fijáis bien, en la salida anterior del /proc/interrupts se visualiza que las interrupciones del tráfico de red no se distribuyen correctamente entre las CPU. Esto concretamente provocaba que una CPU tuviera una alta utilización de recursos y la otra no:
18: 3273686896 0 522711 0 IO-APIC-fasteoi eth0 19: 472943614 285201 0 0 IO-APIC-fasteoi eth1
Usar SMP affinity para controlar las interrupciones de hardware
Para evitar que un Core o CPU se sature y otras estén sin procesar existe la posibilidad de equilibrarlas usando SMP affinity. Este sistema nos permite especificar que ciertas interrupciones sean dirigidas a ciertos cores.
Como habéis podido ver en la salida del /proc/interrupts, la primera columna especifica un número. Este número es el IRQ Number e identifica a cada dispositivo:
$ ls -ltr /proc/irq/ total 0 -rw------- 1 root root 0 2014-09-30 16:51 default_smp_affinity dr-xr-xr-x 3 root root 0 2014-09-30 16:51 9 dr-xr-xr-x 3 root root 0 2014-09-30 16:51 8 dr-xr-xr-x 2 root root 0 2014-09-30 16:51 7 dr-xr-xr-x 2 root root 0 2014-09-30 16:51 6 dr-xr-xr-x 2 root root 0 2014-09-30 16:51 5 [...]
Dentro de cada uno de esos directorios se encuentra el fichero en el cual se puede especificar el core que manejará sus interrupciones:
$ ls -ltr /proc/irq/2/smp_affinity -rw------- 1 root root 0 2014-09-30 16:53 /proc/irq/19/smp_affinity
# cat /proc/irq/19/smp_affinity f
¿Cómo podríamos distribuir las interrupciones de las interfaces de red del primer ejemplo entre la CPU0 y la CPU1 en lugar de que todas vayan a la CPU0?
Vamos a configurar en la CPU0 las interrupciones de eth0 y en la CPU1 las interrupciones de eth1. Se debe indicar el valor en hexadecimal. En una máquina con 4 CPUs:
Binario Hexadecimal ---------------------------------- CPU 0 0001 1 CPU 1 0010 2 CPU 2 0100 4 CPU 3 1000 8 ----------------------------------
Quedaría del siguiente modo:
# echo "1" > /proc/irq/18/smp_affinity # echo "2" > /proc/irq/19/smp_affinity
Anteriormente estaba con el valor «f», que representa todos los procesadores:
Binario Hexadecimal --------------------------------- CPU 0 0001 1 CPU 1 0010 2 CPU 2 0100 4 CPU 3 1000 8 --------------------------------- ALL CPUs 1111 f
Se podrían generar todas las combinaciones que estimemos oportunas. Si configuraramos «3» las interrupciones deberían distribuirse entre la CPU0 y la CPU1:
Binario Hexadecimal --------------------------------- CPU 0 0001 1 CPU 1 0010 2 --------------------------------- SUMA 0011 3
El cambio en los valores es instantaneo así que deberíamos poder verificar la distribución de interrupciones de hardware inmediatamente tras el cambio con la información de /proc/interrupts:
# cat /proc/interrupts CPU0 CPU1 CPU2 CPU3 0: 7934 0 0 0 IO-APIC-edge timer 1: 219 1 0 0 IO-APIC-edge i8042 7: 0 0 0 0 IO-APIC-edge parport0 8: 1 0 0 0 IO-APIC-edge rtc0 9: 0 0 0 0 IO-APIC-fasteoi acpi 12: 191 2 0 0 IO-APIC-edge i8042 14: 0 0 0 0 IO-APIC-edge ata_piix 15: 0 34 8 31 IO-APIC-edge ata_piix 18: 489947954 26 0 0 IO-APIC-fasteoi eth0 19: 1422 3274725899 0 24 IO-APIC-fasteoi eth1
En este caso, tras el cambio, de estar un core al 90% de utilización se consiguió que pasara al 50% y el otro que se encontraba al 10% pasara a 40% aproximadamente. Hemos conseguido balancear el tráfico de las interfaces de red procesando las interrupciones de hardware cada una en un core distinto. Ahora el servidor puede procesar más tráfico sin que la CPU sea el cuello de botella.
Como siempre, si algún experto en la materia tiene algo que corregir o ampliar sus comentarios son bienvenidos.