- vừa được xem lúc

Quản lý ưu tiên và lập lịch tác vụ hiệu quả trong FreeRTOS

0 0 4

Người đăng: delinux

Theo Viblo Asia

Giới thiệu

Như đã giới thiệu từ những bài trước, hệ điều hành thời gian thực (RTOS) như FreeRTOS cho phép bạn thiết kế các ứng dụng nhúng có khả năng thực hiện nhiều nhiệm vụ (task) đồng thời. Một trong những yếu tố then chốt quyết định đến hiệu suất và độ ổn định của hệ thống là cách quản lý ưu tiên (priority) và lập lịch (scheduling) các tác vụ. Nếu các bạn cứ thao thao bất tuyệt tạo các Task, đặt mức ưu tiên của các task bằng cách random hoặc đặt theo cảm tính thì rất có thể chương trình của bạn sẽ gặp các vấn đề như: chạy không đúng như luồng các bạn mong muốn ban đầu, có task chạy liên tục nhưng có những task bị lãng quên, .... Vì thế, trong bài viết này, chúng ta sẽ cùng tìm hiểu sâu về:

  • Cách sử dụng và thiết lập mức ưu tiên tác vụ hợp lý
  • Những lỗi phổ biến khi sử dụng ưu tiên sai cách và cách khắc phục
  • Thực nghiệm lập lịch bằng code ví dụ có lỗi và giải pháp tương ứng
  • Sơ đồ minh họa cách lập lịch giữa các task

1. Tổng quan về lập lịch trong FreeRTOS

FreeRTOS sử dụng bộ lập lịch ưu tiên với preemptive (có thể ngắt để nhường CPU) hoặc cooperative (tác vụ tự nhường CPU). Mỗi tác vụ có một mức ưu tiên (số nguyên không âm), càng cao thì càng được ưu tiên thực thi.

1.1. Các trạng thái của tác vụ

  • Running: Đang được thực thi
  • Ready: Sẵn sàng thực thi nếu CPU rảnh
  • Blocked: Đang chờ một điều kiện (queue, semaphore...)
  • Suspended: Bị tạm ngưng bởi vTaskSuspend()

image.png

1.2. Cấu hình Scheduler trong FreeRTOS

FreeRTOS hỗ trợ:

  • Preemptive scheduling (cấu hình configUSE_PREEMPTION = 1)
  • Time slicing (chia sẻ thời gian giữa các task cùng mức ưu tiên)
  • Manual yield (dùng taskYIELD() để tự nhường)

2. Thiết kế mức ưu tiên tác vụ hiệu quả

Việc gán mức ưu tiên cho tác vụ không chỉ là "cao hay thấp", mà cần được đánh giá dựa trên:

  • Tính chất thời gian thực: Task có yêu cầu thời gian phản hồi nhanh?
  • Thời lượng xử lý: Có tiêu tốn CPU nhiều không?
  • Tần suất hoạt động: Có chạy liên tục hay thỉnh thoảng mới chạy?
  • Quan hệ phụ thuộc: Có phụ thuộc task khác không?

Dưới đây là một bảng gợi ý phương pháp đặt mức ưu tiên cho task mà mình hay sử dụng:

Loại tác vụ Ưu tiên gợi ý
Xử lý ngắt ISR Rất cao
Giao tiếp cảm biến định kỳ Trung bình
Gửi UART định kỳ Thấp
Nhận UART từ ISR
Thu thập, xử lý dữ liệu cảm biến khẩn cấp
Cao
Tác vụ nền (nền tảng hệ thống) Rất thấp (có thể là idle)

3. Tình huống thực tế & lỗi thường gặp

OK! Lý thuyết vậy là đủ rồi, dưới đây mình sẽ thực hành luôn cho các bạn thấy những tình huống có thể xảy ra và khắc phục nó.

Ở đây mình sử dụng esp32 và esp-idf frame-work nhé!

3.1. Tác vụ ưu tiên thấp chặn tác vụ ưu tiên cao

Tình huống khi lỗi: Ở đây mình sẽ tạo 2 task. Bài toán này có thể là bài toán một task thu thập dữ liệu cảm biến, một task xử lý dữ liệu. Mình cho 2 task có độ ưu tiên bằng nhau và đều là độ ưu tiên cao nha. Cùng xem điều gì sẽ xảy ra nhé:


void task1 (void * ctx)
{
#define Max_count 10
#define Min_count 0 for (;;) { for (int i=Min_count; i <= Max_count; i++) { if (i == Min_count) { printf ("\t====================>task1: %d\n", i); } if ( i == Max_count) { printf ("\t====================>task1: %d\n", i); } } vTaskDelay(pdMS_TO_TICKS(50)); }
} void task2 (void * ctx)
{
#define Max_count 1000
#define Min_count 0 for (;;) { for (int i=Min_count; i<Max_count; i++) { if (i == 0) { printf ("task2: %d\n", i); } if ( i == 999) { printf ("task2: %d\n", i); } } vTaskDelay(pdMS_TO_TICKS(50)); }
} void app_main()
{ xTaskCreate(task1, "task1", 2048, NULL, 3, NULL); xTaskCreate(task2, "task2", 2048*2, NULL, 3, NULL);
}

Kết quả khi chạy:

task2: 0
task2: 999 ====================>task1: 0 ====================>task1: 10
task2: 0
task2: 999 ====================>task1: 0 ====================>task1: 10

Như các bạn có thể thấy, Task 2 xử lý xong 1000 lần đếm rồi task 1 mới thực hiện nhiệm vụ của nó. Như vậy trong thực tế sẽ rất nguy hiểm nếu task 1 là task cần xử lý nhanh (giả sử như hệ thống tự lái của ô tô, khi thu dữ liệu từ sensor phát hiện có vật cản phía trước nhưng task xử lý dữ liệu sensor đó không được đặt mức ưu tiên đúng mức thì khả năng là "xin vĩnh biệt cụ" luôn 😂

Giải pháp: Các bạn chỉ cần thay đổi mức ưu tiên của task 2 thấp hơn task 1 hoặc cho mức ưu tiên của task 1 cao hơn mức hiện tại và nhớ cần tuân theo quy tắc đã nói ở phía bên trên 😃


3.2. Priority Inversion (Đảo ngược ưu tiên)

Tình huống lỗi:

  • Task A (ưu tiên thấp) giữ tài nguyên mutex
  • Task B (ưu tiên cao) cần mutex đó -> bị block
  • Task C (ưu tiên trung bình) chiếm CPU -> Task A không được thực thi -> Task B bị "treo"

Kết quả: Hệ thống bị đóng băng logic do tác vụ ưu tiên cao không thể thực hiện, gây mất thời gian thực.

Giải pháp:

  • Dùng Mutex với Priority Inheritance (tự động nâng ưu tiên task đang giữ mutex)
xSemaphore = xSemaphoreCreateMutex();

3.3. Starvation (đói tài nguyên)

Khi một task ưu tiên thấp không bao giờ có cơ hội chạy vì các task ưu tiên cao luôn chiếm CPU.

Giải pháp:

  • Tránh dùng ưu tiên "cứng" mà không delay
  • Sử dụng cơ chế time slicing hoặc cooperative yield để chia CPU

Kết luận

Lập lịch tác vụ hiệu quả và quản lý ưu tiên là kỹ năng thiết yếu khi làm việc với FreeRTOS. Việc hiểu rõ các nguy cơ như priority inversion, starvation và khả năng tối ưu tài nguyên CPU theo yêu cầu thời gian thực sẽ giúp hệ thống hoạt động ổn định và hiệu quả.

Hãy luôn kiểm tra hành vi thực tế thông qua đo đạc và theo dõi log, thay vì chỉ dựa trên dự đoán trong lý thuyết.


Bạn có thể mở rộng bài học này với việc sử dụng task notification, semaphore và event group để phối hợp giữa nhiều tác vụ trong hệ thống phức tạp hơn.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giải mã AUTOSAR: Kiến trúc tiêu chuẩn ngành Automotive

Giới thiệu. AUTOSAR có vẻ khá xa lạ đối với người làm về công nghệ thông tin, nhưng đối với những bạn làm về Embedded System, đặc biệt là trong lĩnh vực Automotive, thì cũng.

0 0 24

- vừa được xem lúc

Từ khoá volatile trong lập trình C

Trong lập trình nhúng chắc hẵn bạn đã từng gặp phải tình huống khi chương trình C của bạn cho ra kết quả không đúng, mặc dù mã code có vẻ đúng? Một nguyên nhân có thể gây ra vấn đề này là việc tối ưu

0 0 24

- vừa được xem lúc

Giải mã AUTOSAR: Kiến trúc để đời ngành Automotive - Phần 2

Updating. Trong bài viết này, ta cùng tìm hiểu kỹ hơn về lớp trên cùng của kiến trúc, chính là lớp Application Layer (ASW).

0 0 20

- vừa được xem lúc

Con Trỏ Hàm - C/C++

Hàm Con Trỏ (Function Pointer) là gì. . Con trỏ tới hàm (A pointer to a function): Đây là biến lưu trữ địa chỉ hàm mà sau này có thể gọi bằng con trỏ này. .

0 0 18

- vừa được xem lúc

Cơ bản về WatchDog Timer trong hệ thống nhúng (Embedded System)

Khái Niệm. Trong hệ thống nhúng, a WatchDog Timer (Bộ đếm thời gian giám sát) là một thành phần hoặc tính năng của phần cứng được thiết kế để giám sát hoạt động của hệ thống và thực hiện hành động khắ

0 0 14

- vừa được xem lúc

Tối ưu hóa thời gian khởi động Nvidia Xavier

1. Vai trò.

0 0 11