🌱 RTOS 10. Hiểu về Race Condition và Cách Phòng Tránh Hiệu Quả
Một vấn đề mà developer thường xuyên phải đối mặt trong RTOS hay cả multi-thread đó là Race Condition, dịch nghĩa ra thì là "điều kiện cuộc đua", trong bài viết này mình sẽ sử dụng đúng thuật ngữ tiếng anh. Race Condition xảy ra khi các task đồng thời truy cập vào một tài nguyên, và các câu lệnh đan xen lẫn nhau, khiến cho chương trình không diễn ra đúng như quy trình đã được thiết kế.
Các tài nguyên có thể sử dụng chung giữa các task, có thể là biến global, có thể là một cổng usb, hay đơn giản hơn là các thanh ghi.
Các nội dung chính
- Ví dụ tình huống xảy ra Race Condition
- Race Condition xảy ra như thế nào?
- Hậu quả gây ra của Race Condition
- Cách khắc phục Race Condition
Ví dụ tình huống xảy ra Race Condition
👉 Để hiểu về Race Condition, chúng ta sẽ đi vào bài toán sau, giả sử chương trình MCU sử dụng RTOS với các task sau:
- Task main: Thực hiện nháy LED PB3.
- Ngắt Systick_Handler: Thực hiện nháy LED PB2.
- Cả hai cùng thao tác trên thanh ghi GPIOB_ODR (Output Data Register của GPIOB).
Quy trình thao tác trên thanh ghi (Load-Modify-Store): Khi CPU thực hiện một thao tác thay đổi bit của thanh ghi GPIOB_ODR:
- Load: Nạp giá trị hiện tại của GPIOB_ODR vào một thanh ghi tạm (ví dụ: R2).
- Modify: Thay đổi giá trị trong thanh ghi tạm (R2) để điều khiển bit LED.
- Store: Ghi lại giá trị từ thanh ghi tạm (R2) vào GPIOB_ODR.
👉👉👉 Xét về mặt ngôn ngữ ASM, các bạn có thể hiểu đơn giản như sau, thực hiện thao tác trên một thanh ghi (GPIOB_ODR), CPU sẽ không thao tác trực tiếp trên thanh ghi đó, mà thực hiện một quy trình "Load>>Modify>>Store". Tức là CPU sẽ load giá trị của thanh ghi đó, sau đó chỉnh sửa (Modify), và cuối cùng cùng là Store giá trị trở lại các thanh ghi đó.
Như chương trình C, chúng ta sẽ không thấy sự khác biệt khi ngắt Systick xảy ra, chúng ta đang hiểu theo hướng, LED PB3 đảo sau đó nó mới nhảy đến chương trình ngắt để đảo LED PB2.
Race Condition xảy ra như thế nào?
- Tình huống khi ngắt Systick xảy ra giữa quá trình Load-Modify-Store của task main, "một câu lệnh C bằng 3 câu lệnh ASM", nên việc ngắt systick xảy ra khi mà quá trình thực hiện lệnh Toggle LED PB3 là hoàn toàn có thể xảy ra (xảy ra giữa 3 lệnh ASM trên).
- Task main đang trong quá trình Load giá trị thanh ghi GPIOB_ODR và chuẩn bị Modify.
- Ngắt Systick_Handler xảy ra và thực hiện thao tác Toggle LED PB2 ➜ Thay đổi bit số 2 (PB2) trong GPIOB_ODR.
- Khi quay lại task main, CPU tiếp tục thực hiện Modify và Store với giá trị ban đầu được Load, bỏ qua thay đổi của Systick_Handler.
⟹ Kết quả: LED PB3 thay đổi nhưng LED PB2 bị đặt lại giá trị không đúng.
➤ Hậu quả gây ra của Race Condition
- Chương trình không hoạt động đúng: LED PB2 bị mất trạng thái mà Systick_Handler vừa thiết lập.
👉 Trên đây chính là ví dụ tiêu biểu nhất của Race Condition, đối với việc chỉ có task main và một chương trình ngắt, việc này cũng có thể xảy ra. Như vậy, trong RTOS, với nhiều task và nhiều ngắt systick liên tiếp, thì Race Condition xảy ra là việc không thể tránh khỏi. Điều này gây "hỗn loạn" chương trình, và có thể gây sai lệch chương trình khi mà các task sử dụng chung các tài nguyên.
Cách khắc phục Race Condition
➤ Sử dụng Mutex (Mutual Exclusion)
Mutex đảm bảo rằng chỉ có một task/ngắt được phép truy cập tài nguyên tại một thời điểm. Trong RTOS, có thể dùng API Mutex như osMutexAcquire() và osMutexRelease().
➤ Sử dụng Critical Section
Critical Section là đoạn mã mà CPU không thể bị gián đoạn khi thực hiện. Bằng cách tắt ngắt (disable interrupt) trong các đoạn mã quan trọng:
taskENTER_CRITICAL();
// Đoạn mã truy cập tài nguyên chung
taskEXIT_CRITICAL();
Điều này ngăn chặn ngắt xảy ra trong khi task đang thực hiện thao tác trên tài nguyên.
➤ Cơ chế bảo vệ khác
- Binary Semaphore hoặc Counting Semaphore sử dụng để đồng bộ truy cập tài nguyên.
- Event Flags: Được dùng để kiểm soát trình tự thực hiện giữa các task.
Race Condition là một trong những lỗi nghiêm trọng khi lập trình RTOS hoặc multi-thread. Việc hiểu rõ cách CPU thực hiện thao tác trên tài nguyên và sử dụng các cơ chế bảo vệ như Mutex hoặc Critical Section là rất cần thiết để tránh lỗi này. Các cách trên sẽ được giới thiệu ở những bài viết sau.