Load balancing в OpenWrt
Вступ
Цей посібник пояснює, як вручну реалізувати балансування навантаження (Load balancing) в OpenWrt, закріплюючи переривання (IRQ) за певними Ethernet-портами та призначаючи один або кілька ядер CPU для обробки мережевих черг.
SMP IRQ Affinity та встановлення бітової маски
З джерела: https://www.kernel.org/doc/html/latest/core-api/irq/irq-affinity.html
`/proc/irq/IRQ#/smp_affinity` і `/proc/irq/IRQ#/smp_affinity_list` визначають, які CPU дозволені для певного джерела переривань (IRQ). Це бітова маска (`smp_affinity`) або список CPU (`smp_affinity_list`). Заборонено відключати всі CPU одразу. Якщо контролер IRQ не підтримує afініті (affinity), значення не змінюється і залишається за замовчуванням — усі CPU.
`/proc/irq/default_smp_affinity` визначає маску спорідненості (affinity) за замовчуванням, яка застосовується до всіх неактивних IRQ. Після активації IRQ його бітова маска спорідненості буде встановлена в значення за замовчуванням. Її потім можна змінити, як описано вище. Маска за замовчуванням — `0xffffffff`.
Щоб призначити IRQ конкретному CPU або групі CPU, потрібно використовувати бітову маску. Для цього вмикаємо потрібні CPU у двійковому представленні та перетворюємо це число у шістнадцяткове. Це дозволяє обмежити обробку певних IRQ певними ядрами CPU — для досягнення балансування або в гетерогенних SoC, наприклад, при розподілі між продуктивними та енергоефективними ядрами.
Бітові маски для CPU:
| Бінарне | Hex | CPU-ядра |
|---|---|---|
| 00000001 | 1 | 0 |
| 00000010 | 2 | 1 |
| 00000011 | 3 | 0,1 |
| 00000100 | 4 | 2 |
| 00000101 | 5 | 0,2 |
| 00000110 | 6 | 1,2 |
| 00000111 | 7 | 0,1,2 |
| 00001000 | 8 | 3 |
| 00001001 | 9 | 0,3 |
| 00001010 | A | 1,3 |
| 00001011 | B | 0,1,3 |
| 00001100 | C | 2,3 |
| 00001101 | D | 0,2,3 |
| 00001110 | E | 1,2,3 |
| 00001111 | F | 0,1,2,3 |
| … | … | … |
| 00110000 | 30 | 4,5 |
Стандартні налаштування в OpenWrt
У OpenWrt за замовчуванням використовується підтримка багатоядерної обробки (SMP).
Наступні скрипти відповідають за ці налаштування:
cat /etc/hotplug.d/net/20-smp-packet-steering cat /etc/hotplug.d/net/40-net-smp-affinity
Більш автоматизоване рішення — використання irqbalance:
irqbalance — це демон для Linux, що розподіляє апаратні переривання між логічними ядрами процесора. Його мета — покращення загальної продуктивності, що дозволяє рівномірніше розподіляти навантаження та оптимізувати енергоспоживання.
Однак `irqbalance` не завжди забезпечує передбачуваний розподіл навантаження. Тому краще використовувати ручне налаштування для точнішого контролю над балансуванням.
Переривання
Насамперед потрібно знайти та ідентифікувати активні переривання.
Щоб переглянути поточні налаштування або зміни:
cat /proc/interrupts
Нижче наведено приклад з NanoPi R4S, який має 4 ядра A53 (CPU 0–3) і 2 ядра A72 (CPU 4 і 5):
root@OpenWrt:~# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5
23: 27142318 12185540 5391618 2352924 137831569 145154023 GICv3 30 Level arch_timer
25: 67873664 61308794 11619382 2662637 16876546 43550490 GICv3 113 Level rk_timer
26: 0 0 0 0 0 0 GICv3-23 0 Level arm-pmu
27: 0 0 0 0 0 0 GICv3-23 1 Level arm-pmu
28: 0 0 0 0 0 0 GICv3 37 Level ff6d0000.dma-controller
29: 0 0 0 0 0 0 GICv3 38 Level ff6d0000.dma-controller
30: 0 0 0 0 0 0 GICv3 39 Level ff6e0000.dma-controller
31: 0 0 0 0 0 0 GICv3 40 Level ff6e0000.dma-controller
32: 1 0 0 0 0 0 GICv3 81 Level pcie-sys
34: 0 0 0 0 0 0 GICv3 83 Level pcie-client
35: 0 0 0 0 165575364 0 GICv3 44 Level eth0
36: 20438175 0 0 0 0 0 GICv3 97 Level dw-mci
37: 0 0 0 0 0 0 GICv3 58 Level ehci_hcd:usb1
38: 0 0 0 0 0 0 GICv3 60 Level ohci_hcd:usb3
39: 0 0 0 0 0 0 GICv3 62 Level ehci_hcd:usb2
40: 0 0 0 0 0 0 GICv3 64 Level ohci_hcd:usb4
42: 0 0 0 0 0 0 GICv3 91 Level ff110000.i2c
43: 6 0 0 0 0 0 GICv3 67 Level ff120000.i2c
44: 0 0 0 0 0 0 GICv3 68 Level ff160000.i2c
45: 6 0 0 0 0 0 GICv3 132 Level ttyS2
46: 0 0 0 0 0 0 GICv3 129 Level rockchip_thermal
47: 6393498 0 0 0 0 0 GICv3 89 Level ff3c0000.i2c
50: 0 0 0 0 0 0 GICv3 147 Level ff650800.iommu
52: 0 0 0 0 0 0 GICv3 149 Level ff660480.iommu
56: 0 0 0 0 0 0 GICv3 151 Level ff8f3f00.iommu
57: 0 0 0 0 0 0 GICv3 150 Level ff903f00.iommu
58: 0 0 0 0 0 0 GICv3 75 Level ff914000.iommu
59: 0 0 0 0 0 0 GICv3 76 Level ff924000.iommu
69: 0 0 0 0 0 0 GICv3 59 Level rockchip_usb2phy
70: 0 0 0 0 0 0 GICv3 137 Level xhci-hcd:usb5
71: 0 0 0 0 0 0 GICv3 142 Level xhci-hcd:usb7
72: 0 0 0 0 0 0 rockchip_gpio_irq 21 Level rk808
78: 0 0 0 0 0 0 rk808 5 Edge RTC alarm
82: 0 0 0 0 0 0 rockchip_gpio_irq 7 Edge fe320000.mmc cd
84: 0 0 0 0 0 0 ITS-MSI 0 Edge PCIe PME, aerdrv
85: 10 0 0 0 0 0 rockchip_gpio_irq 10 Level stmmac-0:01
86: 0 0 0 0 0 0 rockchip_gpio_irq 22 Edge gpio-keys
87: 0 0 0 0 0 1156859750 ITS-MSI 524288 Edge eth1
IPI0: 7085496 10371429 7027071 6124604 310818 114897 Rescheduling interrupts
IPI1: 2817025 2457651 882759 515246 2752519 543745 Function call interrupts
IPI2: 0 0 0 0 0 0 CPU stop interrupts
IPI3: 0 0 0 0 0 0 CPU stop (for crash dump) interrupts
IPI4: 5558568 4633615 2762056 1122565 763629 3435183 Timer broadcast interrupts
IPI5: 413711 300799 161541 117511 109020 76881 IRQ work interrupts
IPI6: 0 0 0 0 0 0 CPU wake-up interrupts
Err: 0
Щоб знайти IRQ для ваших Ethernet-інтерфейсів, використовуйте:
grep eth /proc/interrupts
Приклад:
root@OpenWrt:~# grep eth /proc/interrupts 35: 0 0 0 0 165661665 0 GICv3 44 Level eth0 87: 0 0 0 0 0 1157284700 ITS-MSI 524288 Edge eth1
Отже, тут `eth0` — це IRQ 35, а `eth1` — IRQ 87.
Одне переривання (IRQ) можна прив’язати лише до одного ядра CPU.
Прив’язка переривань до ядер CPU
Призначити переривання від `eth0` ядру CPU 0:
echo 1 > /proc/irq/35/smp_affinity
Призначити переривання від `eth1` ядру CPU 1:
echo 2 > /proc/irq/87/smp_affinity
Автоматичне визначення IRQ для інтерфейсу:
# IRQ для eth0: echo f > /proc/irq/$(grep eth0 /proc/interrupts | awk -F ':' '{print $1}' | xargs)/smp_affinity # IRQ для eth1: echo f > /proc/irq/$(grep eth1 /proc/interrupts | awk -F ':' '{print $1}' | xargs)/smp_affinity
Команда `grep` знаходить рядок із `/proc/interrupts`, `awk` витягує перший стовпчик (номер IRQ), а `xargs` прибирає зайві пробіли.
У ядрах Linux версії 5.15 і новіших потрібно використовувати:
echo -n #HEX# > /proc/irq/#НОМЕР_IRQ#/smp_affinity
Якщо ви перезапустите Smart Queue Management (SQM) або зміните його налаштування, це скине прив’язку переривань до CPU. Вам потрібно повторно застосувати налаштування вручну.
Мережеві черги
Джерело: Receive Packet Steering (RPS)
Receive Packet Steering (RPS) подібний до Receive Side Scaling (RSS) тим, що дозволяє скеровувати пакети на певні ядра CPU для обробки. Але RPS реалізований програмно, що дозволяє уникнути перенавантаження апаратної черги одного мережевого інтерфейсу, яка могла б стати “вузьким місцем” при великому трафіку.
Мережеві черги можуть бути розподілені між усіма ядрами CPU або ж прив’язані до одного ядра.
Призначити чергу eth0 ядру 3:
echo 4 > /sys/class/net/eth0/queues/rx-0/rps_cpus
Призначити чергу eth1 ядру 4:
echo 8 > /sys/class/net/eth1/queues/rx-0/rps_cpus
Призначити черги eth0 та eth1 всім 6 ядрам:
echo 3f > /sys/class/net/eth0/queues/rx-0/rps_cpus echo 3f > /sys/class/net/eth1/queues/rx-0/rps_cpus
Перманентне збереження налаштувань
Ви можете або відредагувати існуючий скрипт:
cat /etc/hotplug.d/net/40-net-smp-affinity
Або створити власний скрипт із новими значеннями, і помістити його в каталог:
/etc/hotplug.d/net/
Приклад:
# /etc/hotplug.d/net/50-mysettings-for-net-smp-affinity # Призначення переривань # eth0 → ядро 0 echo 1 > /proc/irq/35/smp_affinity # eth1 → ядро 2 echo 2 > /proc/irq/87/smp_affinity # RPS: використовувати всі ядра (0–5) echo 3f > /sys/class/net/eth0/queues/rx-0/rps_cpus echo 3f > /sys/class/net/eth1/queues/rx-0/rps_cpus
Тепер перезавантажте пристрій і перевірте, чи збереглися налаштування.
Примітки
Подяка за обговорення та внесок:
- mercygroundabyss
- moeller0
- walmartshopper
- xShARkx
Джерела/тематики обговорень: