Giới thiệu nhanh
Ở bài trước, khi nói về Queue, mình có nói tới khái niệm ISR Yield. Có nhiều bạn hỏi mình rằng đó là gì và tại sao phải quan tâm tới nó khi thao tác với ngắt trong RTOS. Vì thế, để trả lời cho những câu hỏi đó, trong bài này mình sẽ cùng các bạn tìm hiểu ISR Yield là gì và tại sao các bạn cần để ý tới nó khi thao tác với ngắt trong RTOS nhé!
Nói sơ qua thì trong các hệ điều hành thời gian thực (RTOS), ngắt (Interrupt) là cơ chế thiết yếu để phản ứng tức thời với sự kiện bên ngoài. Tuy nhiên, việc tương tác giữa ngắt và các task trong hệ thống không thể được thực hiện một cách ngẫu nhiên – cần tuân thủ những nguyên tắc chặt chẽ để đảm bảo tính nhất quán và độ trễ tối thiểu. ISR Yield chính là một khái niệm then chốt giúp tối ưu luồng điều khiển giữa ngắt và scheduler trong FreeRTOS.
1. ISR Yield là gì?
ISR Yield là quá trình kích hoạt lại việc lập lịch (context switch) từ trong ngắt (ISR - Interrupt Service Routine) nếu một task có độ ưu tiên cao hơn task hiện tại đã sẵn sàng chạy ngay khi ISR kết thúc.
Trong FreeRTOS, bạn không nên gọi vTaskSwitchContext()
trực tiếp trong ISR. Thay vào đó, sử dụng các hàm "FromISR", như:
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(queueHandle, &data, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
Hàm portYIELD_FROM_ISR()
sẽ chỉ yêu cầu yield nếu xHigherPriorityTaskWoken
là pdTRUE
– nghĩa là một task có độ ưu tiên cao hơn đã sẵn sàng thực thi sau ISR.
2. Tại sao cần ISR Yield?
Giả sử bạn có:
- Task A (ưu tiên thấp hơn)
- Task B (ưu tiên cao hơn)
Nếu Task A đang chạy, và một ISR xuất hiện đưa Task B vào trạng thái sẵn sàng (ready), thì hệ thống phải chuyển sang chạy Task B ngay lập tức sau khi ISR kết thúc. Nếu không, Task A tiếp tục chiếm CPU, gây độ trễ không cần thiết cho Task B – điều tối kỵ trong hệ thống RTOS.
3. Cơ chế hoạt động bên trong
3.1. Tác động của xHigherPriorityTaskWoken
Hầu hết các API "FromISR" của FreeRTOS trả về xHigherPriorityTaskWoken
:
pdTRUE
: Nếu một task cao hơn được đưa vào "ready".pdFALSE
: Không có sự thay đổi về mức ưu tiên.
Điều này cho phép ISR quyết định có cần yield ngay hay không – tránh những lần context switch không cần thiết.
3.2. Vai trò của portYIELD_FROM_ISR
Hàm portYIELD_FROM_ISR()
là macro hoặc hàm inline có trong mỗi port cụ thể (ARM Cortex-M, Xtensa cho ESP32,...). Nó sẽ:
- Đánh dấu cần context switch ngay sau khi ISR hoàn tất.
- Khi thoát khỏi ISR, kernel kiểm tra và nếu cần sẽ thực hiện chuyển task.
Trên Cortex-M:
#define portYIELD_FROM_ISR(x) if(x != pdFALSE) portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT
Trên Xtensa (ESP32):
#define portYIELD_FROM_ISR() portYIELD()
4. Ví dụ thực tế
Mô hình: ISR kích hoạt khi có dữ liệu UART
Giả sử một thiết bị UART gửi dữ liệu, và trong ISR ta gửi dữ liệu đó vào một hàng đợi. Một task khác sẽ tiến hành xử lý chuỗi UART vừa nhận và task này có độ ưu tiên cao hơn task đang chạy.
// logic được đặt trong hàm ngắt
void uart_isr_handler(void *arg) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; char data = UART0.fifo.rw_byte; xQueueSendFromISR(uartQueue, &data, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
Khi xQueueSendFromISR()
thành công và một task đang chờ nhận từ queue, task đó sẽ được đánh dấu ready. Nếu nó có độ ưu tiên cao hơn task hiện tại, portYIELD_FROM_ISR()
sẽ yêu cầu lập lịch lại.
Biểu đồ thời gian
5. Sai lầm phổ biến
- Không gọi
portYIELD_FROM_ISR()
sau khi dùng API FromISR: Điều này dẫn đến task có ưu tiên cao hơn không được chạy ngay lập tức. - Gọi
vTaskDelay()
trong ISR: Không hợp lệ. ISR nên càng ngắn càng tốt – mọi logic phải giao lại cho task. - Không kiểm tra
xHigherPriorityTaskWoken
: Dẫn đến yield không cần thiết hoặc không xảy ra.
6. Tối ưu hóa ISR với ISR Yield
Một số chiến lược chuyên sâu:
- Tách ISR thành hai phần: phần "top-half" (ISR thực thi nhanh, đẩy dữ liệu vào queue), phần "bottom-half" (task xử lý).
- Sử dụng
portYIELD_FROM_ISR()
có điều kiện để tránh context switch không cần thiết (chỉ khi có task ưu tiên cao sẵn sàng). - Kết hợp ISR Yield với Task Notification: nhanh hơn queue trong nhiều trường hợp.
7. Khi nào không cần ISR Yield?
Trong những trường hợp sau:
- ISR chỉ báo hiệu cho task có cùng hoặc thấp hơn mức ưu tiên đang chạy.
- Bạn muốn trì hoãn context switch đến khi ISR khác kết thúc.
- Hệ thống có thiết lập
configMAX_SYSCALL_INTERRUPT_PRIORITY
giới hạn mức ISR có thể gọi API FreeRTOS.
8. Kết luận
ISR Yield là một cơ chế rất quan trọng nhưng thường bị bỏ qua trong thiết kế hệ thống RTOS. Sử dụng đúng sẽ giúp hệ thống:
- Giảm độ trễ xử lý sự kiện.
- Đảm bảo ưu tiên thực thi đúng.
- Tăng tính phản ứng thời gian thực.
Trong những hệ thống nhúng phức tạp như ESP32, STM32, hay bất kỳ hệ thống nào dùng FreeRTOS, hiểu và vận dụng đúng ISR Yield chính là dấu hiệu phân biệt giữa một kỹ sư mới vào nghề và một chuyên gia dày dạn kinh nghiệm.