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

Hiểu rõ về Mutex trong FreeRTOS!!!

0 0 2

Người đăng: delinux

Theo Viblo Asia

Giới thiệu nhanh

Hello các bạn! Ở bài trước về Semaphore, mình đã nhắc đến rằng có một cơ chế khác dùng để đồng bộ và bảo vệ tài nguyên dùng chung, đó chính là Mutex. Mình cũng nói rằng Mutex có sự khác biệt nhất định so với Semaphore, nhất là khi nói đến việc bảo vệ tài nguyên – thứ mà ta gặp rất nhiều khi làm việc với FreeRTOS hay bất kỳ hệ điều hành thời gian thực nào.

Vậy, hôm nay tụi mình sẽ cùng đi sâu vào Mutex là gì, sự khác biệt với Semaphore ở đâu, khi nào nên dùng Mutex thay vì Semaphore, và cách sử dụng Mutex sao cho đúng.


1. Mutex là gì?

Mutex là viết tắt của Mutual Exclusion – tức là “loại trừ lẫn nhau”. Hiểu đơn giản, đây là một kỹ thuật đảm bảo rằng chỉ một task duy nhất được quyền truy cập một tài nguyên tại một thời điểm.

Tài nguyên ở đây có thể là:

  • Một biến dùng chung giữa các task
  • Một thiết bị ngoại vi (ví dụ UART)
  • Một hàm xử lý không reentrant (không an toàn khi gọi đồng thời)

2. Mutex hoạt động như thế nào?

Về cơ bản, Mutex hoạt động gần giống như Binary Semaphore. Một task có thể take Mutex, và sau khi dùng xong tài nguyên, task đó phải give lại Mutex để task khác có thể truy cập.

Tuy nhiên, Mutex có hai điểm khác biệt quan trọng:

a. Mutex có khái niệm về “chủ sở hữu”

Chỉ task nào take Mutex thành công mới có quyền give nó. Đây là điểm khác biệt lớn nhất với Semaphore, nơi bất kỳ task nào cũng có thể give.

b. Mutex hỗ trợ Priority Inheritance (thừa kế ưu tiên)

Giả sử task A (ưu tiên thấp) giữ Mutex, và task B (ưu tiên cao) đang đợi Mutex đó. Trong trường hợp này, để tránh priority inversion (đảo ngược ưu tiên), FreeRTOS sẽ tạm thời nâng ưu tiên của task A lên ngang bằng task B để Mutex được trả nhanh hơn.


3. Minh họa cơ bản về Mutex

Cùng xem sơ đồ sau để hiểu cách Mutex hoạt động giữa các task:

+-----------+ +-----------+ +-----------+
| Task Low | | Task Med | | Task High |
| (prio 1) | | (prio 2) | | (prio 3) |
+-----------+ +-----------+ +-----------+ | | | |--- take Mutex ----> | |<-- Mutex acquired -- | | ... (đang dùng tài nguyên) | | | | | |--- ready -------->| | | | | Task High muốn take Mutex | |<----- blocked trên Mutex -------------| | Priority Inheritance kicks in | | Task Low tạm nâng lên prio 3 | | Task Low kết thúc sử dụng | |---- give Mutex ---------------------> | | Task High tiếp tục chạy |

✅ Kết luận từ sơ đồ:

  • Nếu dùng Semaphore: Task High có thể bị chờ rất lâu nếu Task Low không trả Semaphore đúng lúc.
  • Với Mutex: FreeRTOS hỗ trợ priority inheritance để giảm thời gian chờ đợi không cần thiết.

4. Cách dùng Mutex trong FreeRTOS

FreeRTOS cung cấp API cho Mutex như sau:

Tạo Mutex:

SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();

Task sử dụng Mutex:

void Task1(void *pvParameters)
{ for(;;) { if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) { // Dùng tài nguyên // ... xSemaphoreGive(xMutex); } }
}

Mutex cũng có phiên bản gọi là Recursive Mutex nếu bạn cần cho phép một task take mutex nhiều lần liên tiếp (dạng đệ quy):

xSemaphoreCreateRecursiveMutex();

5. Mutex vs Semaphore – Phân biệt cụ thể

Tiêu chí Mutex Binary Semaphore
Mục đích chính Bảo vệ tài nguyên dùng chung Đồng bộ giữa các task/ISR
Ai được give Chỉ task đã take mới give được Bất kỳ task/ISR nào
Hỗ trợ priority inheritance Không
Gọi từ ISR Không Có thể
Được sử dụng như Recursive Có thể (Recursive Mutex) Không
Trọng lượng bộ nhớ Nặng hơn Semaphore một chút Nhẹ hơn

👉 Kết luận nhanh:

  • Dùng Mutex để bảo vệ tài nguyên dùng chung (UART, I2C, biến toàn cục…).
  • Dùng Binary Semaphore để đồng bộ giữa các task hoặc giữa ISR với task.

6. Một ví dụ thực tế: bảo vệ UART khi ghi từ nhiều task

Giả sử bạn có 3 task ghi log qua UART. Nếu không dùng Mutex, dữ liệu log sẽ bị trộn lẫn, vì các task ghi đồng thời.

void vLogTask(void *pvParam)
{ for(;;) { if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) { printf("Task %d: Hello\n", (int)pvParam); xSemaphoreGive(xMutex); } vTaskDelay(pdMS_TO_TICKS(500)); }
}

Trong main():

xMutex = xSemaphoreCreateMutex();
xTaskCreate(vLogTask, "Log1", 1024, (void*)1, 1, NULL);
xTaskCreate(vLogTask, "Log2", 1024, (void*)2, 1, NULL);
xTaskCreate(vLogTask, "Log3", 1024, (void*)3, 1, NULL);

7. Lưu ý khi dùng Mutex

✅ Không dùng trong ISR

Mutex không được thiết kế để dùng từ ISR vì nó không an toàn về mặt ngữ nghĩa (không có context để “nhớ” task nào đang sở hữu mutex). Nếu bạn cần đồng bộ từ ISR → Task, hãy dùng Binary Semaphore hoặc Direct To Task Notification.

✅ Nhớ give sau khi take

Nếu bạn take mà quên give, mutex sẽ bị “giữ chặt” mãi mãi – gây deadlock cho các task khác.

✅ Tránh lạm dụng

Mutex chỉ nên dùng khi thực sự cần thiết. Nếu tài nguyên không dùng chung hoặc đã an toàn nhờ cách thiết kế khác, không cần thêm Mutex để tránh tốn CPU và RAM không cần thiết.


8. Tình huống nguy hiểm: Deadlock

Deadlock xảy ra khi hai task giữ Mutex mà mỗi task lại đợi Mutex của task kia. Ví dụ:

// Task A
xSemaphoreTake(Mutex1, portMAX_DELAY);
xSemaphoreTake(Mutex2, portMAX_DELAY); // bị kẹt nếu Mutex2 đang bị Task B giữ // Task B
xSemaphoreTake(Mutex2, portMAX_DELAY);
xSemaphoreTake(Mutex1, portMAX_DELAY); // bị kẹt nếu Mutex1 đang bị Task A giữ

Giải pháp:

  • Quy định thứ tự take Mutex giống nhau ở mọi task.
  • Dùng timeout hợp lý.
  • Dùng watchdog để phát hiện deadlock.

9. Khi nào nên dùng Recursive Mutex?

Nếu bạn viết một hàm có sử dụng Mutex và hàm đó lại được gọi từ một đoạn code khác đã take mutex rồi, bạn sẽ cần một Recursive Mutex. Trường hợp này tương đối hiếm, nhưng rất quan trọng nếu xảy ra.

Ví dụ:

void foo() { xSemaphoreTakeRecursive(xMutex, portMAX_DELAY); // ... xSemaphoreGiveRecursive(xMutex);
} void bar() { xSemaphoreTakeRecursive(xMutex, portMAX_DELAY); foo(); // Không bị deadlock nếu dùng Recursive Mutex xSemaphoreGiveRecursive(xMutex);
}

10. Tổng kết

Đến đây, bạn đã hiểu rõ hơn về Mutex rồi đúng không? Mình tóm gọn lại vài ý:

Mutex = Mutual Exclusion, dùng để bảo vệ tài nguyên dùng chung. ✅ Có hỗ trợ priority inheritance – khác với Semaphore. ✅ Chỉ task sở hữu mới được trả Mutex.Không dùng Mutex trong ISR.Có phiên bản Recursive Mutex cho các hàm gọi lồng nhau.

Mutex không chỉ là một phần quan trọng trong FreeRTOS, mà còn là một khái niệm nền tảng trong mọi hệ điều hành. Dùng đúng Mutex sẽ giúp hệ thống của bạn ổn định, tránh deadlock, và tối ưu hiệu suất khi nhiều task cùng truy cập một tài nguyê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 25

- 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 25

- 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 21

- 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 15

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

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

1. Vai trò.

0 0 14