Skip to content

IRQ affinity và irqbalance

IRQ affinity là tập core được phép xử lý một ngắt phần cứng. Trên hệ thống có core cô lập, đây là phần cấu hình bổ sung cho isolcpus và nohz_full, vì hai tham số đó không ngăn ngắt thiết bị tới core cô lập. Trang này trình bày theo thứ tự: giao diện /proc/irq của kernel, affinity mặc định và tham số irqaffinity=, managed interrupt, daemon irqbalance và cách nó tương tác với core cô lập, cuối cùng là quy trình cấu hình và kiểm tra. Phần cô lập scheduler nằm ở isolcpus và cô lập scheduler domain, phần tắt tick ở nohz_full và adaptive tick.

Nội dung về kernel dựa trên tài liệu và mã nguồn mainline bản 7.4-rc1. Nội dung về irqbalance dựa trên man page và mã nguồn nhánh master của repo irqbalance; phiên bản phát hành mới nhất trên trang releases tại thời điểm viết là v1.9.5.

Giao diện /proc/irq

Mỗi IRQ có một thư mục /proc/irq/<N>/ với các file sau (tạo trong kernel/irq/proc.c):

File Quyền Nội dung
smp_affinity Đọc ghi Bitmask hex các core được phép nhận IRQ
smp_affinity_list Đọc ghi Như trên, dạng danh sách CPU (0-3,8)
effective_affinity Chỉ đọc Bitmask core thực sự được interrupt controller chọn để nhận IRQ
effective_affinity_list Chỉ đọc Như trên, dạng danh sách
affinity_hint Chỉ đọc Gợi ý affinity do driver cung cấp
node Chỉ đọc NUMA node của thiết bị

Hình dưới cho thấy đường đi của một ngắt và các bên có thể ghi smp_affinity. Interrupt controller nhận mask từ kernel và chọn core đích trong mask đó.

Đường đi của ngắt và các bên đặt affinity

smp_affinity là tập core được phép, còn effective_affinity là tập core thực sự nhận ngắt. Với nhiều interrupt controller, kernel chỉ chọn một core trong mask để nhận ngắt, nên effective_affinity thường chỉ có một core nằm trong smp_affinity. Khi kiểm tra ngắt có rơi vào core cô lập hay không, cần đọc effective_affinity_list.

Ví dụ trên máy 8 core, giới hạn IRQ 44 về CPU 0-3 rồi chuyển sang CPU 4-7 (theo ví dụ trong tài liệu kernel):

echo 0f > /proc/irq/44/smp_affinity
echo 4-7 > /proc/irq/44/smp_affinity_list
cat /proc/irq/44/smp_affinity            # 000000f0
cat /proc/irq/44/effective_affinity_list

Dạng danh sách tiện hơn trên máy nhiều core: echo 1024-1031 > smp_affinity_list thay cho một bitmask rất dài. Kernel từ chối mask không chứa core online nào (-EINVAL). Nếu interrupt controller không hỗ trợ đổi affinity, giá trị giữ nguyên mặc định là mọi core.

Affinity mặc định và irqaffinity

File /proc/irq/default_smp_affinity chứa mask mặc định áp dụng cho IRQ chưa được kích hoạt. Khi một IRQ được cấp phát hoặc kích hoạt, affinity của nó được đặt bằng mask này. Mặc định là mọi core.

Tham số boot irqaffinity=<cpu-list> đặt mask mặc định ngay lúc boot. Kernel luôn thêm core boot vào mask này, để tránh trường hợp mask sai làm không core nào nhận ngắt. Nếu danh sách sai cú pháp, kernel in irqaffinity: incorrect CPU range, using default và dùng mask mặc định.

irqaffinity= chỉ thay đổi giá trị khởi đầu. Nó không áp dụng cho managed interrupt (xem mục dưới) và không ngăn user space hoặc irqbalance đổi affinity của từng IRQ sau đó. Các cơ chế cô lập isolcpus=domain và nohz_full= không tự loại core cô lập khỏi mask mặc định này.

Managed interrupt

Thiết bị nhiều hàng đợi (NVMe, NIC đa hàng đợi) thường dùng một vector MSI-X cho mỗi hàng đợi. Khi driver gọi pci_alloc_irq_vectors() với cờ PCI_IRQ_AFFINITY, kernel tự trải các vector lên các core (hàm irq_create_affinity_masks() trong kernel/irq/affinity.c), mỗi vector gắn với một nhóm core. Ngắt kiểu này mang cờ IRQD_AFFINITY_MANAGED và được gọi là managed interrupt.

Affinity của managed interrupt do kernel quản lý hoàn toàn:

  • File smp_affinity và smp_affinity_list của nó chỉ đọc. Hàm irq_can_set_affinity_usr() trả về false với managed interrupt, nên ghi vào file trả về -EPERM.
  • irqbalance và các script đổi affinity không có tác dụng với các IRQ này.
  • Khi tất cả core trong mask của một vector offline, vector bị tắt (IRQD_MANAGED_SHUTDOWN) và được bật lại khi core trở lại.

Cách duy nhất để đẩy managed interrupt ra khỏi core cô lập là cờ managed_irq của isolcpus=. Khi bật, lúc kernel đặt affinity cho một managed interrupt có mask chứa cả core cô lập và core housekeeping, kernel bỏ core cô lập khỏi mask. Nếu mask chỉ gồm core cô lập, kernel giữ nguyên, vì hàng đợi đó chỉ phát sinh ngắt khi task trên core cô lập tự gửi I/O. Hình dưới minh họa với máy 8 core, CPU 0-1 housekeeping, và một thiết bị có ba hàng đợi. Cách kernel chia core cho từng hàng đợi trong hình chỉ là ví dụ, cách chia thực tế phụ thuộc số vector và topology.

Managed interrupt với cờ managed_irq

Đây là cơ chế best effort: với workload không gửi I/O từ core cô lập, các hàng đợi này không phát sinh ngắt.

Daemon irqbalance

irqbalance là daemon user space phân bố ngắt phần cứng lên các core để cân bằng tải xử lý ngắt. Theo README của dự án, nó tìm các nguồn ngắt có lưu lượng cao nhất và tách mỗi nguồn ra một core riêng, đồng thời giữ ngắt gần cache của core liên quan để giảm cache miss. Nó không phải một phần của kernel, mà chỉ ghi vào /proc/irq/<N>/smp_affinity.

Chu kỳ hoạt động

irqbalance lấy mẫu số lần ngắt trong /proc/interrupts theo chu kỳ (mặc định 10 giây, đổi bằng --interval), tính tải và ghi affinity mới cho các IRQ cần di chuyển. Tín hiệu SIGHUP buộc daemon quét lại danh sách IRQ và topology CPU.

Hình dưới tóm tắt một chu kỳ của irqbalance, gồm cả cách xác định core bị cấm (mô tả ở mục Tương tác với core cô lập) và nhánh xử lý khi ghi affinity thất bại.

Vòng lặp của irqbalance

Mỗi IRQ có một mức cân bằng (balance level), quyết định IRQ được gán cho một core, một cache domain hay một package. Mức mặc định suy từ lớp thiết bị PCI sở hữu IRQ (bảng map_class_to_level trong classify.c):

Lớp thiết bị Mức cân bằng mặc định
Khác (other) package
Legacy cache
Lưu trữ (SCSI), video, Ethernet, GbE, 10GbE, sự kiện ảo hóa core

Mức none nghĩa là irqbalance không đụng tới IRQ đó. Khi ghi smp_affinity thất bại với lỗi không tạm thời, như -EPERM của managed interrupt, irqbalance chuyển IRQ đó sang mức none và ghi log IRQ <N> affinity is now unmanaged.

Tùy chọn và biến môi trường

Tùy chọn Tác dụng
-o, --oneshot Cân bằng một lần rồi thoát
-t, --interval=<giây> Chu kỳ lấy mẫu, mặc định 10
-i, --banirq=<irq> Bỏ qua một IRQ, dùng nhiều lần được
-m, --banmod=<module> Bỏ qua mọi IRQ của một kernel module
-c, --deepestcache=<n> Mức cache dùng để chia cache domain, mặc định 2
-p, --powerthresh=<n> Ngưỡng để đưa core vào chế độ tiết kiệm điện, core này không nhận IRQ
-l, --policyscript=<file> Script được gọi cho mỗi IRQ, in ra ban=, balance_level=, numa_node= để ghi đè chính sách
-f, -d, -j Chạy foreground, debug, log định dạng cho systemd journal
Biến môi trường Tác dụng
IRQBALANCE_BANNED_CPULIST Danh sách core không bao giờ nhận IRQ, cú pháp cpulist
IRQBALANCE_BANNED_CPUS Như trên, dạng hexmask. Đã deprecated, ghi chú phát hành v1.8.0 cho biết sẽ bị thay bằng IRQBALANCE_BANNED_CPULIST
IRQBALANCE_ONESHOT, IRQBALANCE_DEBUG Tương đương --oneshot, --debug
IRQBALANCE_ARGS Tham số dòng lệnh thêm cho daemon trong unit systemd

Unit systemd của dự án đọc các biến này từ file irqbalance.env và file irqbalance trong thư mục default của sysconfdir (thường là /etc/default/irqbalance). Bản phân phối có thể đặt file ở chỗ khác, nên cần kiểm tra bằng systemctl cat irqbalance. Unit cũng có điều kiện ConditionVirtualization=!container và ConditionCPUs=>1, nên daemon không chạy trong container hoặc trên máy một core.

Tương tác với core cô lập

Khi không đặt IRQBALANCE_BANNED_CPULIST (và IRQBALANCE_BANNED_CPUS), irqbalance tự cấm các core trong hai file sysfs sau:

File Nội dung Được đặt bởi
/sys/devices/system/cpu/isolated Core cô lập khỏi scheduler domain lúc boot isolcpus= với cờ domain hoặc không cờ
/sys/devices/system/cpu/nohz_full Core adaptive tick nohz_full= hoặc isolcpus=nohz

Từ hành vi này có một số hệ quả cần lưu ý:

  • File isolated lấy từ cpumask HK_TYPE_DOMAIN_BOOT (drivers/base/cpu.c), chỉ phản ánh cô lập lúc boot. Core cô lập lúc chạy bằng cpuset isolated partition không có trong file này, nên irqbalance vẫn có thể gán IRQ vào đó.
  • isolcpus=managed_irq,<list> không kèm domain không đưa core vào file isolated.
  • Đặt IRQBALANCE_BANNED_CPULIST thì danh sách này thay thế hoàn toàn danh sách tự suy ra, không cộng thêm. Giá trị rỗng (IRQBALANCE_BANNED_CPULIST=) cho phép mọi core nhận IRQ.
  • irqbalance không đổi được managed interrupt, nên không thay thế cho cờ managed_irq.

Cấu hình cho core cô lập

Có hai hướng cấu hình, tùy hệ thống có cần cân bằng ngắt giữa các core housekeeping hay không:

Hướng Cách làm Phù hợp khi
Tắt irqbalance Đặt affinity cố định bằng irqaffinity= và script ghi smp_affinity lúc boot, tắt service irqbalance Hệ thống nhỏ, ít core housekeeping, cấu hình cần lặp lại chính xác
Giữ irqbalance Để irqbalance tự cấm core cô lập (boot-time), hoặc đặt IRQBALANCE_BANNED_CPULIST tường minh Nhiều core housekeeping, nhiều thiết bị cần cân bằng ngắt

Các bước chung cho hệ thống có core housekeeping 0-1 và core cô lập 2-7:

  1. Thêm tham số boot irqaffinity=0-1 isolcpus=managed_irq,domain,2-7, kèm nohz_full=2-7 nếu cần tắt tick.
  2. Chọn một trong hai hướng trên. Nếu giữ irqbalance và có dùng cpuset isolated partition, đặt IRQBALANCE_BANNED_CPULIST=2-7.
  3. Với ngắt cần xử lý trên core cô lập (ví dụ hàng đợi NIC dành cho workload trên core đó), ghi smp_affinity_list tường minh và loại IRQ đó khỏi irqbalance bằng --banirq.
  4. Với threaded IRQ (kthread irq/<N>-<tên>), affinity của kthread đi theo affinity của IRQ.
  5. Kiểm tra lại sau khi tải workload.

Ví dụ đẩy mọi IRQ đổi được về core 0-1, bỏ qua managed interrupt:

for d in /proc/irq/[0-9]*; do
    echo 0-1 > "$d/smp_affinity_list" 2>/dev/null || echo "bỏ qua IRQ ${d##*/}"
done

Kiểm tra

cat /proc/irq/default_smp_affinity
grep -H . /proc/irq/*/effective_affinity_list | awk -F: '$2 !~ /^(0|1|0-1)$/'   # IRQ đang nhận ở core khác 0-1
watch -n1 -d 'cat /proc/interrupts'                                              # bộ đếm ngắt theo core
systemctl status irqbalance
journalctl -u irqbalance | grep -i -E "isolated|adaptive|unmanaged"

Khi khởi động, irqbalance ghi log các core bị cấm, với thông điệp Prevent irq assignment to these isolated CPUs và một dòng tương tự cho core adaptive tick. Trong /proc/interrupts, cột của core cô lập chỉ nên tăng ở các dòng ngắt nội bộ của CPU (ví dụ reschedule hoặc TLB shootdown trên x86), không tăng ở dòng của thiết bị.

Liên quan

Tài liệu tham khảo