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¶
- isolcpus, nohz_full, IRQ affinity: cô lập core, bổ sung cho PREEMPT_RT.
- Lập trình ứng dụng real-time trên Linux (Programming): phía ứng dụng, gồm
mlockall, chạm trước stack,clock_nanosleepvà mutex kế thừa ưu tiên. - Linux Kernel Overview: mốc PREEMPT_RT vào mainline.
- Common Clock Framework: ảnh hưởng của PREEMPT_RT tới locking trong một framework cụ thể.
- Linux Embedded Overview (Embedded): real-time trong hệ nhúng, ví dụ đo bằng
cyclictest. - Real-time KVM tuning (Virtualization): PREEMPT_RT cho host và guest khi chạy máy ảo real-time.
- Kiến trúc DU và CU và L1 & Acceleration (Wireless): DU, một ứng dụng điển hình của kernel real-time.
Tài liệu tham khảo¶
- Kernel docs: Real-time preemption (
Documentation/core-api/real-time/): theory of operation, differences, kernel configuration, considering hardware. - Kernel docs: Lock types and their rules (
Documentation/locking/locktypes.rst) - Mã nguồn
kernel/Kconfig.preempt: các mô hình preemption. - Kernel docs: Deadline Task Scheduling
- Kernel docs: Timerlat tracer và OSNOISE tracer
- Kernel docs: rtla-timerlat
- man7: sched(7): các chính sách lập lịch.
- Phoronix: Real-Time "PREEMPT_RT" Support Merged For Linux 6.12 (09/2024).
- Real-Time Linux wiki: cyclictest