Skip to content

Real-time Linux (PREEMPT_RT)

PREEMPT_RT là cấu hình biến Linux thành kernel real-time: hầu hết mã kernel có thể bị ngắt (preempt) để nhường CPU cho task ưu tiên cao hơn, nhờ đó độ trễ từ lúc một task real-time sẵn sàng tới lúc nó thực sự chạy được giới hạn ở mức thấp và ổn định. Trang này trình bày theo thứ tự: real-time nghĩa là gì với Linux, các mô hình preemption của kernel, ba cơ chế chính của PREEMPT_RT, những điểm khác biệt mà người viết driver và ứng dụng cần biết, chính sách lập lịch real-time, cấu hình kernel và phần cứng, cách đo độ trễ, rồi quan hệ với CPU isolation. Cô lập core được trình bày ở isolcpus, nohz_full và IRQ affinity.

Nội dung dựa trên tài liệu "Real-time preemption" trong cây mã nguồn kernel mainline (Documentation/core-api/real-time/), bản trên nhánh master tại thời điểm viết.

Real-time nghĩa là gì

Real-time không có nghĩa là nhanh nhất, mà là có thể dự đoán được: thời gian đáp ứng tệ nhất (worst-case latency) phải nằm dưới một giới hạn biết trước. Một hệ thống có độ trễ trung bình 5 µs nhưng thỉnh thoảng lên 2 ms không phải là hệ real-time nếu deadline là 100 µs. Vì vậy mọi đánh giá real-time đều nhìn vào giá trị lớn nhất và đuôi phân bố, không nhìn vào giá trị trung bình.

Với kernel Linux thông thường, độ trễ tệ nhất bị kéo dài bởi các đoạn mã chạy với preemption hoặc ngắt bị tắt: spinlock, xử lý ngắt, softirq, timer. Trong các đoạn đó, scheduler không thể chuyển sang task ưu tiên cao hơn dù task đó đã sẵn sàng. PREEMPT_RT thu nhỏ các đoạn này tới mức tối thiểu.

Các mô hình preemption

Kernel cung cấp nhiều mô hình preemption, chọn khi build trong kernel/Kconfig.preempt:

Mô hình Tùy chọn Đặc điểm
Không ép preempt (server) CONFIG_PREEMPT_NONE Mô hình truyền thống, ưu tiên thông lượng; độ trễ thường tốt nhưng không có bảo đảm
Preempt tự nguyện (desktop) CONFIG_PREEMPT_VOLUNTARY Thêm các điểm preempt tường minh trong mã kernel
Preempt đầy đủ CONFIG_PREEMPT Mã kernel có thể bị preempt ở mọi nơi trừ các đoạn giữ spinlock hoặc tắt ngắt
Preempt do scheduler điều khiển CONFIG_PREEMPT_LAZY Gần với preempt đầy đủ nhưng ít preempt task thường hơn, để lấy lại một phần thông lượng
Real-time CONFIG_PREEMPT_RT Preempt được cả trong phần lớn các đoạn giữ spinlock và xử lý ngắt

Các mô hình có sẵn phụ thuộc kiến trúc và phiên bản kernel. PREEMPT_RT được đưa vào mainline ở Linux 6.12 (2024) cho x86, ARM64 và RISC-V; trước đó nó tồn tại dưới dạng bộ patch ngoài cây mã nguồn trong nhiều năm. Tùy chọn này yêu cầu bật CONFIG_EXPERT.

Ba cơ chế chính

Theo tài liệu kernel, PREEMPT_RT đưa phần lớn ngữ cảnh thực thi của kernel về dưới quyền điều khiển của scheduler bằng ba cơ chế. Chỉ còn một số đường mã rất thấp như entry code, scheduler và phần xử lý ngắt mức thấp là không preempt được.

Spinlock có thể ngủ (sleeping spinlock). Ở kernel thường, lấy một spinlock_t nghĩa là tắt preemption rồi quay vòng chờ khóa, nên trong suốt thời gian giữ khóa không task nào khác chạy được trên core đó. Ở PREEMPT_RT, spinlock_t được hiện thực bằng rtmutex: task chờ khóa không quay vòng mà tắt việc di chuyển sang core khác (migration), nhường quyền ưu tiên cho task đang giữ khóa, rồi ngủ cho tới khi khóa được trả. Vì không tắt preemption, một task ưu tiên cao hơn vẫn chạy được trong lúc khóa đang bị giữ. Các đoạn thật sự không được preempt (scheduler, timer, xử lý ngắt mức thấp) dùng raw_spinlock_t, giữ nguyên ngữ nghĩa cũ.

Kế thừa ưu tiên (priority inheritance). Khi task A có ưu tiên real-time phải chờ khóa đang bị task B ưu tiên thấp giữ, B tạm thời nhận chính sách và mức ưu tiên của A để chạy nhanh xong đoạn găng và trả khóa. Không có cơ chế này, B có thể bị một task trung bình chiếm CPU, khiến A chờ không giới hạn (đảo ngược ưu tiên, priority inversion).

Ngắt chạy trong thread (threaded interrupt). Mọi ngắt bị ép chạy trong thread, trừ các ngắt đăng ký với cờ IRQF_NO_THREAD, IRQF_PERCPU hoặc IRQF_ONESHOT. Phần xử lý trong ngữ cảnh ngắt chỉ đánh thức thread tương ứng; phần việc chính chạy trong thread do scheduler quản lý, mặc định với chính sách SCHED_FIFO ưu tiên 50. Trong lúc thread chưa xử lý xong, nguồn ngắt được che ở bộ điều khiển ngắt. Nhờ đó một task real-time có ưu tiên cao hơn 50 có thể chạy trước cả phần xử lý ngắt của thiết bị khác.

Điểm khác biệt khi viết mã cho kernel real-time

Các cơ chế trên làm thay đổi một số giả định quen thuộc của người viết driver:

Chủ đề Kernel thường PREEMPT_RT
spinlock_t Tắt preemption; phiên bản _irqsave tắt ngắt Có thể ngủ, không tắt preemption; không cần tắt ngắt vì ngắt đã chạy trong thread
Softirq Chạy với preemption tắt Chạy trong thread, có thể bị preempt; local_bh_disable() không còn bảo vệ được dữ liệu per-CPU
Dữ liệu per-CPU Thường được bảo vệ bằng preempt_disable() Nên dùng local_lock_t
hrtimer Mặc định chạy trong ngữ cảnh ngắt cứng Mặc định chạy trong softirq (thread ktimers); cần HRTIMER_MODE_HARD nếu bắt buộc chạy trong ngắt cứng
Cấp phát bộ nhớ GFP_ATOMIC dùng được trong vùng tắt preemption Bộ cấp phát dùng khóa có thể ngủ, nên không được cấp phát hoặc giải phóng trong vùng tắt preemption hay tắt ngắt
irq_work Chạy trong ngữ cảnh ngắt Chạy trong thread per-CPU, trừ mục đánh dấu IRQ_WORK_HARD_IRQ
RCU callback Chạy trong softirq Chạy trong ngữ cảnh tiến trình (rcutree.use_softirq=0 được ép bật)

Mẫu "quay vòng tới khi sẵn sàng" (spin until ready) cũng phải được xem lại: ở PREEMPT_RT, callback của timer chạy trong thread có thể bị chính thread đang chờ nó preempt, gây livelock. Kernel giải quyết bằng các cơ chế bắt tay có kế thừa ưu tiên, ví dụ các kiểu sequence lock kết hợp khóa như seqcount_spinlock_t. Một driver dùng đúng API chuẩn của kernel thường không phải sửa gì; driver tự quay vòng chờ thanh ghi phần cứng trong vùng tắt preemption là nguồn gây độ trễ phổ biến.

Chính sách lập lịch real-time

PREEMPT_RT chỉ phát huy tác dụng khi ứng dụng chạy với chính sách lập lịch real-time. Mặc định, task dùng SCHED_OTHER (còn gọi SCHED_NORMAL): scheduler chia CPU công bằng và không bảo đảm task mới sẵn sàng được chạy ngay.

Chính sách Cách chọn task Dùng khi
SCHED_FIFO Ưu tiên cố định 1 tới 99; task ưu tiên cao hơn chạy ngay khi sẵn sàng và chạy cho tới khi tự nhường CPU Thread real-time thông dụng nhất
SCHED_RR Như SCHED_FIFO nhưng các task cùng mức ưu tiên luân phiên theo lát thời gian Nhiều task cùng mức ưu tiên cần chia CPU
SCHED_DEADLINE Thuật toán Earliest Deadline First (EDF) kèm cơ chế Constant Bandwidth Server (CBS); task khai báo thời gian chạy, deadline và chu kỳ Task định kỳ cần bảo đảm băng thông CPU và cách ly giữa các task

Chính sách được đặt bằng sched_setattr() hoặc sched_setscheduler() trong mã, hoặc bằng lệnh chrt khi khởi chạy, ví dụ chrt -f 80 ./app. Ứng dụng real-time thường khóa toàn bộ bộ nhớ vào RAM bằng mlockall() để tránh page fault khi đang chạy, và cấp phát trước mọi bộ đệm trong giai đoạn khởi tạo.

Mức ưu tiên của các thread real-time phải được sắp xếp cùng với các thread ngắt (mặc định 50) và các thread nội bộ của kernel như ktimers: đặt thread ứng dụng cao hơn thread ngắt của thiết bị nó phụ thuộc có thể làm chính nó bị chặn.

Cấu hình kernel và phần cứng

Tài liệu "Real-Time Kernel configuration" của kernel liệt kê các tùy chọn ảnh hưởng tới độ trễ tệ nhất. Các điểm chính:

Tùy chọn Khuyến nghị Lý do
CONFIG_PREEMPT_RT Bật (bắt buộc) Không có thì kernel không preempt đầy đủ
Bộ điều tốc tần số CPU Dùng performance hoặc giữ tần số cố định; không dùng ondemand Tần số thay đổi theo tải làm mất tính lặp lại của độ trễ
CONFIG_CPU_IDLE Giữ bật, nhưng giới hạn trạng thái nghỉ sâu (ví dụ processor.max_cstate=1 trên nền ACPI) Thoát khỏi trạng thái nghỉ sâu tốn thời gian
CONFIG_LOCKUP_DETECTOR Tắt, dùng watchdog phần cứng thay thế Tạo timer trong ngắt cứng định kỳ, gây gai độ trễ
CONFIG_EFI_DISABLE_RUNTIME Bật Gọi vào firmware EFI có thể chặn ngắt và chuyển ngữ cảnh
CONFIG_NO_HZ_FULL Tùy loại tải Có lợi cho tải tính toán chạy liên tục ở user space trên core cô lập; với vòng điều khiển định kỳ ngắn, tick đều lại tốt hơn
CONFIG_PROVE_LOCKING, CONFIG_IRQSOFF_TRACER, CONFIG_PREEMPT_TRACER Tắt khi vận hành Tốn chi phí ngay cả khi không dùng

Tài liệu cũng nhấn mạnh phần cứng: cache và băng thông bộ nhớ dùng chung giữa các core và với DMA của thiết bị, các bus có độ trễ biến thiên (USB), quản lý năng lượng của link PCIe (ASPM), và GPU tích hợp dùng chung cache với CPU đều ảnh hưởng tới tính xác định. Trên x86, Intel Cache Allocation Technology và AMD Platform QoS, trên ARM64 là MPAM, cho phép chia cache qua giao diện resctrl. Kernel real-time không bù được cho phần cứng hoặc firmware gây gián đoạn, ví dụ ngắt quản lý hệ thống (SMI) trên x86.

Đo độ trễ

Công cụ Đo gì
cyclictest (gói rt-tests) Thread real-time ngủ theo chu kỳ, đo khoảng lệch giữa thời điểm lẽ ra thức dậy và thời điểm thực sự chạy; công cụ chuẩn để so sánh cấu hình, xem ví dụ ở Linux Embedded Overview
Tracer timerlat Tương tự cyclictest nhưng chạy trong hạ tầng trace của kernel, tách độ trễ phần ngắt và phần thread, giúp tìm nguyên nhân
Tracer osnoise Đo nhiễu của hệ điều hành lên một vòng lặp chạy liên tục: tổng nhiễu, nhiễu lớn nhất, và số lần bị NMI, ngắt, softirq, thread chen vào; có thêm hwlat để phát hiện nhiễu từ phần cứng
rtla Công cụ dòng lệnh dựng trên timerlat và osnoise (rtla timerlat top, rtla timerlat hist, rtla osnoise top), cho bản tóm tắt và histogram

Phép đo chỉ có ý nghĩa khi chạy đủ lâu (hàng giờ) và có tải: tải CPU, tải bộ nhớ, tải I/O và lưu lượng mạng giống điều kiện vận hành, vì các gai độ trễ hiếm thường chỉ xuất hiện khi hệ thống bận. Trong môi trường máy ảo, cách đo trong guest được trình bày ở Real-time KVM tuning.

Quan hệ với CPU isolation

PREEMPT_RT và CPU isolation giải quyết hai phần khác nhau của cùng một bài toán:

PREEMPT_RT CPU isolation
Mục tiêu Giới hạn thời gian từ lúc task real-time sẵn sàng tới lúc nó chạy Loại bỏ công việc khác chen vào core dành riêng
Cách làm Biến các đoạn không preempt được thành preempt được Đẩy task, ngắt, timer, RCU callback sang core housekeeping
Phù hợp nhất với Task real-time được đánh thức theo sự kiện hoặc theo chu kỳ, dùng chung core với việc khác Task chạy liên tục trên một core (poll mode), không nhường CPU

Hệ thống có yêu cầu khắt khe, như DU của trạm gốc 5G chạy L1 và L2 trên CPU với deadline theo slot, thường dùng cả hai: kernel PREEMPT_RT, các core cô lập cho thread xử lý theo slot và thread poll-mode của DPDK, ngắt và tác vụ hệ thống dồn về core housekeeping. Xem Kiến trúc DU và CU và L1 & Acceleration.

Liên quan

Tài liệu tham khảo