nohz_full và adaptive tick¶
Tham số boot nohz_full= tắt ngắt timer định kỳ (scheduling-clock tick) trên các core chỉ chạy một task, để workload chạy liên tục trong user space mà không bị kernel chen ngang mỗi tick. Trang này trình bày theo thứ tự: vai trò của tick, ba chế độ quản lý tick trong Kconfig, điều kiện để một core dừng tick, quan hệ với RCU và rcu_nocbs, chi phí đánh đổi, cách kiểm chứng và các giới hạn đã biết. Phần cô lập scheduler nằm ở isolcpus và cô lập scheduler domain, phần định tuyến ngắt ở IRQ affinity và irqbalance.
Nội dung dựa trên tài liệu và mã nguồn kernel mainline bản 7.4-rc1.
Vai trò của tick¶
Tick là ngắt timer định kỳ mà kernel lập trình trên mỗi core, với tần số CONFIG_HZ (thường 100, 250, 300 hoặc 1000 Hz). Mỗi tick, kernel làm các việc sau trên core đó:
- Cập nhật thống kê thời gian CPU (CPU time accounting) của task đang chạy.
- Kiểm tra xem task hiện tại đã hết lượt chạy chưa để preempt khi có task khác chờ.
- Cập nhật tải của runqueue, phục vụ cân bằng tải.
- Thông báo cho RCU rằng core đã đi qua trạng thái quiescent và xử lý RCU callback.
- Chạy timer đã hết hạn và, trên core giữ vai trò timekeeping, cập nhật đồng hồ hệ thống (jiffies, thời gian thực).
Với một core chỉ chạy một task tính toán trong user space, phần lớn các việc trên là thừa: không có task nào khác để chuyển sang. Mỗi tick vẫn tạo một lần vào kernel, chiếm core trong thời gian xử lý ngắt và làm thay đổi trạng thái cache, TLB của workload. Theo tài liệu kernel, bỏ tick giúp cải thiện thời gian đáp ứng xấu nhất đúng bằng thời gian xử lý tối đa của một tick.
Ba chế độ quản lý tick¶
| Kconfig | Hành vi | Trường hợp phù hợp |
|---|---|---|
CONFIG_HZ_PERIODIC=y |
Không bao giờ bỏ tick | Tải nặng với nhiều task chạy từng đợt ngắn, thời gian rảnh rất ngắn. Một số hệ real-time chọn chế độ này để tránh độ trễ khi thoát khỏi idle |
CONFIG_NO_HZ_IDLE=y |
Bỏ tick khi core rảnh (dyntick-idle) | Mặc định cho đa số hệ thống, giảm điện năng tiêu thụ |
CONFIG_NO_HZ_FULL=y |
Bỏ tick khi core rảnh hoặc chỉ có một task runnable (adaptive tick) | Ứng dụng real-time và HPC có vòng lặp ngắn |
Hình dưới minh họa vị trí ngắt tick của từng chế độ trên cùng một chuỗi hoạt động của core: hai task chạy luân phiên, một khoảng idle, rồi một task duy nhất. Với NO_HZ_FULL, đoạn cuối chỉ có tick khi core thuộc danh sách nohz_full=.
Các kernel cũ dùng CONFIG_NO_HZ=n và CONFIG_NO_HZ=y thay cho hai chế độ đầu. CONFIG_NO_HZ_FULL kéo theo CONFIG_NO_HZ_COMMON, nên mọi chi phí của dyntick-idle cũng có mặt trong adaptive tick.
Với kernel dyntick-idle, tham số nohz=off tắt chế độ bỏ tick lúc rảnh. Mặc định là nohz=on.
Kernel build với CONFIG_NO_HZ_FULL=y không tự bật adaptive tick cho core nào. Danh sách core phải được chỉ định bằng nohz_full=. Vì thế một kernel phân phối có thể bật CONFIG_NO_HZ_FULL mà các core vẫn hoạt động như dyntick-idle khi không có tham số này.
Cú pháp và ràng buộc¶
Ví dụ nohz_full=1,6-8 biến CPU 1, 6, 7, 8 thành adaptive-ticks CPU. Tham số isolcpus=nohz,<cpu-list> có tác dụng tương đương.
Các ràng buộc của tham số:
- Không được đánh dấu toàn bộ core. Phải còn ít nhất một core không thuộc
nohz_fullđể lo timekeeping, nhằm giữ chogettimeofday()trên các core nohz_full trả về giá trị chính xác. Do đó hệ thống cần ít nhất hai core thì tham số mới có tác dụng. - Kernel ép core boot nằm ngoài danh sách để giữ timekeeping.
- Mọi core trong
nohz_full=được offload RCU callback như thể cũng có trongrcu_nocbs=. - Nếu dùng cùng
isolcpus=nohz, danh sách phải giống nhau (xem Xử lý tham số trong kernel). - Danh sách core housekeeping tương ứng đọc được ở
/sys/devices/system/cpu/housekeepingtừ kernel 6.19.
Điều kiện dừng tick¶
Một core trong nohz_full chỉ dừng tick khi môi trường cho phép. Các điều kiện chính, theo tài liệu kernel:
| Điều kiện | Lý do |
|---|---|
| Chỉ có một task runnable trên core | Nhiều task cần tick để preempt luân phiên |
| Không có POSIX CPU timer trên core | Timer này đếm thời gian CPU, cần tick để kiểm tra hết hạn |
| Không có RCU callback xếp hàng trên core | Đã được bảo đảm nhờ offload RCU callback |
| Số perf event không vượt số bộ đếm phần cứng | Khi vượt, perf cần tick để xoay vòng các event |
Một core vẫn ở chế độ adaptive tick khi task chuyển vào kernel qua system call, và chỉ thoát chế độ này khi cần, ví dụ khi core đó xếp một RCU callback. Tuy vậy, mỗi lần vào kernel đều có thể kích hoạt việc cần tick trở lại, nên workload phù hợp là workload dành gần hết thời gian ở user space.
Tài liệu kernel cũng ghi nhận một trường hợp bỏ sót: core có một task SCHED_FIFO ưu tiên cao cùng một số task SCHED_OTHER vẫn nhận tick, dù task FIFO sẽ chạy tới khi tự block. Cài đặt hiện tại chỉ xét số task runnable.
Tick dư 1 Hz¶
Một số việc của scheduler vẫn cần tick định kỳ: tính tải CPU, cập nhật trung bình của scheduler, vruntime của thực thể CFS, avenrun và cân bằng tải. Kernel giữ các việc này bằng một tick dư khoảng 1 Hz.
Tick dư này không chạy trên core nohz_full. Nó được offload thành công việc trong workqueue và chạy trên core housekeeping thay cho core nohz_full. Workqueue toàn cục mặc định chạy trên mọi core, nên để chắc chắn tick dư không quay lại core cô lập, cần giới hạn cpumask của unbound workqueue về core housekeeping:
Có thể đạt cùng kết quả bằng cờ domain của isolcpus= hoặc cpuset isolated partition, vì core cô lập theo cách này bị loại khỏi unbound workqueue.
RCU và rcu_nocbs¶
RCU (Read-Copy-Update) là cơ chế đồng bộ cho phép reader đọc không khóa, còn việc giải phóng dữ liệu cũ được hoãn qua callback cho tới khi mọi reader đã xong. Mặc định, RCU callback của một core được chạy trên chính core đó trong ngữ cảnh softirq. Core có callback đang chờ không được vào dyntick-idle hay adaptive tick.
Tham số rcu_nocbs=<cpu-list> (cần CONFIG_RCU_NOCB_CPU=y) bật chế độ no-callback cho các core trong danh sách. Callback của các core này được chạy bởi các kthread riêng:
| Kthread | Vai trò |
|---|---|
rcuop/N |
Chạy callback của RCU-preempt cho core N |
rcuos/N |
Chạy callback của RCU-sched cho core N |
rcuog/N |
Điều phối grace period cho một nhóm kthread rcuo |
Hình dưới tóm tắt phần việc được đẩy sang core housekeeping khi một core chạy nohz_full: RCU callback do kthread rcuog và rcuop xử lý, và tick dư 1 Hz chạy trong unbound workqueue (xem mục trên). Hình giả định các kthread này đã được giữ trên core housekeeping.
Kernel không tự gắn các kthread rcuo vào core housekeeping. Nếu không gắn affinity, scheduler tự chọn nơi chạy chúng, có thể là core cô lập. Khi dùng cùng isolcpus=domain hoặc cpuset isolated, kthread unbound được giữ ngoài core cô lập. Nếu không, cần gắn bằng taskset hoặc cgroup.
Các tham số liên quan:
rcu_nocbskhông kèm danh sách (không có dấu=): không core nào ở chế độ no-callback lúc boot, nhưng có thể bật lúc chạy qua cpuset.rcu_nocb_poll: kthreadrcuotự poll định kỳ thay vì chờ core offload đánh thức. Core cô lập không phải gửi lệnh đánh thức nữa, đổi lại tốn điện hơn vì kthread thức dậy theo chu kỳ.nohz_full=vàrcu_nocbs=có độ ưu tiên cao hơn KconfigCONFIG_RCU_NOCB_CPU_DEFAULT_ALL.
Trên kernel bật CONFIG_RCU_BOOST=y, mỗi core còn có kthread rcuc/N chạy callback. Offload bằng rcu_nocbs= khiến rcuc/N không còn việc để làm.
Chi phí đánh đổi¶
Tài liệu CPU isolation của kernel xếp nohz_full vào loại cô lập tốn kém nhất. Các chi phí:
- Vào và ra kernel (system call, exception, ngắt) chậm hơn, do kernel phải dùng các thao tác read-modify-write có thứ tự đầy đủ để báo cho RCU rằng core đang ở trạng thái quiescent mở rộng khi chạy user space. Thời gian CPU cũng được tính ở mỗi lần qua ranh giới kernel thay vì tính định kỳ theo tick.
- Xử lý RCU callback qua kthread có khóa tốn hơn xử lý tại chỗ.
- Core housekeeping phải chạy tick dư 1 Hz thay cho mọi core nohz_full.
- Thống kê scheduler của core nohz_full được tính hơi khác, có thể ảnh hưởng tới cân bằng tải của task real-time.
- Đổi danh sách nohz_full hoặc rcu_nocbs đòi hỏi reboot.
Vì vậy nohz_full phù hợp với workload dành gần hết thời gian ở user space và chỉ gọi kernel trong giai đoạn chuẩn bị, ví dụ ứng dụng DPDK poll-mode. Với workload gọi system call liên tục, chi phí vào/ra kernel có thể lớn hơn lợi ích bỏ tick, cần đo để quyết định.
Yêu cầu khi dùng nohz_full¶
Theo tài liệu CPU isolation của kernel, workload chạy trên core nohz_full cần thỏa:
- Mỗi core chạy đúng một task. Nhiều task trên một core làm tick quay lại.
- Không gọi vào kernel từ core cô lập trong giai đoạn xử lý chính, vì mỗi lần gọi có thể gây nhiễu.
- Không dùng POSIX CPU timer trên core cô lập. Ứng dụng real-time cần hành động theo thời gian CPU phải dùng cách khác.
- Kiến trúc phải có clocksource ổn định. TSC không tin cậy, cần clocksource watchdog giám sát, không tương thích với nohz_full.
Kiểm chứng trên hệ thống¶
Kiểm tra cấu hình:
grep -E "CONFIG_NO_HZ_FULL|CONFIG_RCU_NOCB_CPU" /boot/config-$(uname -r)
cat /sys/devices/system/cpu/nohz_full # danh sách core nohz_full
cat /sys/devices/system/cpu/housekeeping # core housekeeping (6.19 trở lên)
dmesg | grep -i -E "nohz|housekeeping|rcu.*offload"
ps -eo pid,psr,comm | grep rcuo # kthread rcuo đang chạy ở core nào
Đếm ngắt timer trên từng core trong /proc/interrupts là cách nhanh để thấy tick đã dừng hay chưa. Trên x86, dòng LOC (Local timer interrupts) gần như không tăng trên core nohz_full khi workload đang chạy:
Trên Arm, ngắt timer xuất hiện dưới tên arch_timer. Cần đối chiếu tên dòng tương ứng của kiến trúc đang dùng.
Để tìm nhiễu còn lại, tài liệu kernel đề xuất chạy một vòng lặp user space trên core cô lập và bật ftrace:
cd /sys/kernel/tracing
echo 0 > tracing_on; echo > trace
echo 1 > events/sched/sched_switch/enable # nhiễu do task khác chiếm core
echo 1 > events/irq_vectors/enable # nhiễu do ngắt (x86)
echo 1 > events/timer/tick_stop/enable # lý do tick không dừng được
echo 1 > tracing_on
taskset -c 7 ./user_loop & sleep 10; kill $!
echo 0 > tracing_on
cat per_cpu/cpu7/trace
Event tick_stop cho biết lý do khi tick không dừng được (nhiều task, POSIX timer, perf event, RCU...). Ở mức cao hơn, rtla osnoise đo và tổng hợp nhiễu của hệ thống bằng tracer trong kernel. Công cụ cpunoise (git.kernel.org) làm việc tương tự từ user space. Cách dùng ftrace chi tiết ở FTrace.
Các giới hạn đã biết¶
- Adaptive tick chỉ có tác dụng khi core có đúng một task runnable, dù còn nhiều trường hợp khác không cần tick.
- Ít nhất một core luôn giữ tick để phục vụ timekeeping. Khi có core nohz_full, core housekeeping giữ tick kể cả khi mọi core đều rảnh.
- Dyntick-idle làm chậm việc vào và ra idle. Workload nhạy với độ trễ thoát idle có thể dùng PM QoS từ user space (cách được ưu tiên), hoặc trên x86 dùng
intel_idle.max_cstate=để giới hạn C-state,idle=mwaithoặcidle=poll.idle=pollcó thể làm CPU quá nhiệt dẫn tới throttling, và vô hiệu hóa Turbo Mode trên CPU Intel. - Một số nguồn nhiễu chỉ loại bỏ được bằng cách ràng buộc workload. TLB shootdown toàn cục chỉ tránh được bằng cách không thực hiện thao tác unmap gây ra nó, ví dụ unload kernel module. Page fault và TLB miss giảm được bằng huge page, prefault vùng nhớ làm việc và
mlock()/mlockall(). - nohz_full chỉ xử lý tick. Ngắt thiết bị, kernel thread và tiến trình hệ thống cần cấu hình riêng bằng IRQ affinity và cô lập scheduler.
Liên quan¶
- isolcpus và cô lập scheduler domain: khái niệm housekeeping, cô lập scheduler và cpuset.
- IRQ affinity và irqbalance: đẩy ngắt thiết bị ra khỏi core nohz_full.
- Real-time KVM tuning (Virtualization): nohz_full trên host và trong guest của VM real-time.
- DPDK (Embedded): workload poll-mode thường chạy trên core nohz_full.
- FTrace: công cụ trace dùng để kiểm chứng.
Tài liệu tham khảo¶
- Kernel docs: NO_HZ: Reducing Scheduling-Clock Ticks (
Documentation/timers/no_hz.rst) - Kernel docs: CPU Isolation (
Documentation/admin-guide/cpu-isolation.rst) - Kernel docs: kernel-parameters, mục
nohz=,nohz_full=,rcu_nocbs=,rcu_nocb_poll - Kernel docs: Reducing OS jitter due to per-cpu kthreads
- Kernel docs: RTLA osnoise
- cpunoise (git.kernel.org)
- Mã nguồn
kernel/sched/isolation.c