Trong các hệ điều hành thời gian thực (RTOS), việc đồng bộ và quản lý truy cập tài nguyên dùng chung là cực kỳ quan trọng để đảm bảo tính ổn định và an toàn của hệ thống. Hai trong số các công cụ phổ biến được sử dụng để giải quyết vấn đề này là Binary Semaphore và Counting Semaphore.
Dù có tên gọi tương đồng và được dùng trong những tình huống khá giống nhau, hai loại Semaphore này thực chất phục vụ các mục đích khác nhau và có hành vi khác biệt rõ rệt.
1. Khái Niệm Cơ Bản
1.1 Binary Semaphore (Semaphore nhị phân)
Binary Semaphore chỉ có hai trạng thái: 0
và 1
, tương đương với locked (đang giữ) và unlocked (sẵn sàng). Semaphore loại này thường được sử dụng để:
- Đồng bộ hóa giữa task và ISR (Interrupt Service Routine)
- Báo hiệu giữa các task (signal)
- Bảo vệ tài nguyên chỉ có một luồng truy cập tại một thời điểm
1.2 Counting Semaphore (Semaphore đếm)
Counting Semaphore có thể giữ giá trị lớn hơn 1, cho phép nhiều task có thể "take" semaphore nếu còn chỗ trống. Nó thường được dùng để:
- Quản lý số lượng tài nguyên hạn chế (ví dụ: 3 cổng UART)
- Đồng bộ hóa với nhiều nguồn tín hiệu
- Quản lý hàng đợi hoặc buffer vòng
2. Hoạt Động Của Semaphore
2.1 Sơ đồ hoạt động của Binary Semaphore
2.2 Sơ đồ hoạt động của Counting Semaphore
3. So Sánh Chi Tiết Binary vs Counting Semaphore
Tiêu chí | Binary Semaphore | Counting Semaphore |
---|---|---|
Giá trị tối đa | 1 | Tùy ý (do lập trình viên khai báo) |
Mục đích chính | Đồng bộ hóa | Quản lý số lượng tài nguyên |
Dùng để bảo vệ tài nguyên | Có thể, nhưng không tối ưu như mutex | Có thể, nhưng không phải mục tiêu chính |
Có thể bị “give” nhiều lần | Không (chỉ giữ giá trị là 1) | Có thể tăng giá trị liên tục |
Giao tiếp giữa Task và ISR | Rất phù hợp | Không phổ biến, nhưng có thể |
Giải phóng bởi nhiều task | Không khuyến nghị | Có thể |
Hành vi như tín hiệu | Tốt | Tốt, nếu cần đếm số lượng tín hiệu |
Cấu trúc bên trong | Dựa trên queue có 1 phần tử | Dựa trên queue có nhiều phần tử |
4. Ví Dụ Minh Họa
4.1 Ví dụ dùng Binary Semaphore để đồng bộ ISR và Task
SemaphoreHandle_t xBinarySemaphore; void ISR_Handler(void)
{ BaseType_t xHigherPriorityTaskWoken = pdFALSE; xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
} void TaskWaitForISR(void *pvParameters)
{ for (;;) { if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY)) { printf("Interrupt received!\n"); } }
}
Diễn giải:
xBinarySemaphore
là semaphore nhị phân, chỉ mang giá trị0
hoặc1
.- ISR sẽ "give" semaphore để báo cho task biết rằng ngắt đã xảy ra.
- Task đang
xSemaphoreTake
sẽ bị block cho đến khi ISR gọixSemaphoreGiveFromISR
.
4.2 Ví dụ dùng Counting Semaphore để quản lý 3 tài nguyên
SemaphoreHandle_t xCountingSemaphore; void ResourceTask(void *pvParameters)
{ while (1) { if (xSemaphoreTake(xCountingSemaphore, portMAX_DELAY)) { // Sử dụng tài nguyên printf("Task %d dùng tài nguyên\n", (int)pvParameters); vTaskDelay(pdMS_TO_TICKS(1000)); // Trả lại tài nguyên xSemaphoreGive(xCountingSemaphore); } }
} void main()
{ xCountingSemaphore = xSemaphoreCreateCounting(3, 3); // 3 tài nguyên xTaskCreate(ResourceTask, "T1", 1000, (void *)1, 1, NULL); xTaskCreate(ResourceTask, "T2", 1000, (void *)2, 1, NULL); xTaskCreate(ResourceTask, "T3", 1000, (void *)3, 1, NULL); xTaskCreate(ResourceTask, "T4", 1000, (void *)4, 1, NULL);
}
Diễn giải:
- Semaphore khởi tạo với count = 3, nghĩa là có 3 tài nguyên.
- 4 task cạnh tranh sử dụng, chỉ 3 task chạy được cùng lúc, task còn lại phải đợi.
- Sau khi sử dụng xong, task sẽ
give
lại semaphore.
5. Ưu và Nhược Điểm
5.1 Binary Semaphore
Ưu điểm:
- Đơn giản, dễ sử dụng
- Hiệu quả khi đồng bộ ISR với task
- Không cần đếm hay kiểm tra giá trị
Nhược điểm:
- Không dùng được cho nhiều tài nguyên
- Không lưu trữ số lượng tín hiệu nếu nhiều hơn 1 ISR xảy ra trước khi task xử lý
- Dễ bị “mất tín hiệu” nếu task chưa take mà ISR gọi nhiều lần liên tiếp
5.2 Counting Semaphore
Ưu điểm:
- Quản lý nhiều tài nguyên rất tốt
- Có khả năng tích lũy tín hiệu (giữ số lượng)
- Có thể linh hoạt dùng trong nhiều tình huống đồng bộ phức tạp
Nhược điểm:
- Phức tạp hơn binary
- Có thể gây lỗi nếu không kiểm soát chính xác việc
take
vàgive
- Không thích hợp nếu chỉ cần đồng bộ đơn giản hoặc giao tiếp ISR-task
6. Khi Nào Dùng Binary, Khi Nào Dùng Counting?
Trường hợp | Dạng Semaphore phù hợp |
---|---|
ISR báo hiệu cho Task | Binary Semaphore |
Task cần sử dụng tài nguyên độc quyền | Mutex (ưu tiên hơn binary) |
Có nhiều tài nguyên giống nhau (ví dụ 5 UART) | Counting Semaphore |
Có nhiều tín hiệu đến liên tiếp | Counting Semaphore |
Cần “bật đèn xanh” cho task bắt đầu | Binary Semaphore |
7. Kết Luận
Binary Semaphore và Counting Semaphore đều là những công cụ mạnh mẽ cho việc đồng bộ hóa trong RTOS như FreeRTOS. Việc lựa chọn đúng loại semaphore không chỉ giúp hệ thống hoạt động ổn định mà còn tránh được các lỗi tiềm ẩn như race condition, deadlock, hoặc dropped signals.
Tổng kết:
- Binary Semaphore: Sử dụng khi cần đồng bộ một sự kiện duy nhất giữa 2 thành phần (ví dụ ISR và Task).
- Counting Semaphore: Dùng khi cần quản lý số lượng nhiều tài nguyên hoặc nhiều tín hiệu xảy ra liên tiếp.