Ring Buffer là gì trong firmware embedded?
Giải thích Ring Buffer với head, tail, empty, full và wrap-around trong embedded C.
Ghi chú nhanh
Ring Buffer là một hàng đợi dùng mảng cố định. Khi ghi tới cuối mảng, vị trí ghi sẽ quay lại đầu mảng.
Hai biến quan trọng nhất:
head = vị trí ghi dữ liệu mới
tail = vị trí đọc dữ liệu cũ
Trong firmware, Ring Buffer rất được ưa chuộng vì nó không cần cấp phát động, chạy nhanh, và dự đoán được bộ nhớ sử dụng.
Hình dung đơn giản
Hãy tưởng tượng một băng chuyền hình tròn.
Producer đặt hàng vào vị trí head. Consumer lấy hàng ở vị trí tail. Khi đi tới cuối băng chuyền, cả hai quay lại đầu.
[0] [1] [2] [3] [4] [5] [6] [7]
^head ^tail
Sau mỗi lần ghi:
head = (head + 1) % RING_BUF_SIZE;
Đây là dòng code nhỏ nhưng xuất hiện ở rất nhiều nơi: UART RX, UART TX logger, USB stream, audio buffer, event queue…
Empty và Full khác nhau thế nào?
Buffer trống khi:
head == tail
Nhưng nếu ta cho phép ghi đầy 100% mảng, trạng thái full cũng có thể làm head == tail. Vì vậy cách đơn giản thường dùng là chừa lại 1 byte trống.
Buffer đầy khi:
next_head == tail
Ví dụ:
uint16_t next = (head + 1) % RING_BUF_SIZE;
if (next == tail) {
// buffer full
}
Mất 1 byte dung lượng, nhưng đổi lại logic rõ ràng và ít bug hơn. Trong embedded, ít bug hơn thường đáng giá hơn 1 byte RAM.
Vì sao hữu ích trong firmware?
Ring Buffer giúp tách hai tốc độ khác nhau:
- CPU ghi log rất nhanh vào RAM;
- UART/DMA truyền log chậm hơn ở background.
Nếu không có buffer, producer phải chờ consumer. Với debug log, điều này có thể làm thay đổi timing của firmware.
Bẫy dễ gặp
1. Quên xử lý wrap-around
DMA không thể luôn truyền một vùng dữ liệu liên tục từ tail đến head, vì dữ liệu có thể bị chia làm hai đoạn khi vòng qua cuối mảng.
[old data .... end][begin .... new data]
Thường phải truyền đoạn từ tail đến cuối buffer trước, sau đó callback truyền tiếp đoạn từ đầu buffer.
2. Cập nhật tail sai trong DMA callback
Không nên làm kiểu:
tail = head; // nguy hiểm
Vì trong lúc DMA đang truyền, producer có thể đã ghi thêm dữ liệu mới làm head thay đổi. Callback chỉ nên tăng tail đúng bằng số byte DMA vừa truyền xong.
Cách nhớ
Ring Buffer = hàng đợi vòng tròn trong RAM.
Producer ghi ở head.
Consumer đọc ở tail.
Full thì phải có policy rõ ràng.
Bài liên quan nên đọc tiếp
- Producer-Consumer pattern trong hệ thống logging embedded
- DMA Normal mode và Circular mode khác nhau thế nào?
- Thiết kế hệ thống UART logging non-blocking trên STM32
Thấy nội dung này hữu ích?
Lưu lại hoặc chia sẻ cho người cũng đang học firmware, BIOS/UEFI và embedded systems.
Nội dung liên quan
Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.
FreeRTOS trên STM32: Task, Queue, Signal và cách tổ chức firmware cho cả team
Triển khai dự án FreeRTOS trên STM32.
UART non-blocking logger trên STM32 với DMA + Ring Buffer
Thiết kế debug UART non-blocking cho STM32: tránh printf, xử lý mất log với DMA, dùng ring buffer và DMA callback.
STM32 Non-Blocking UART Logger
Case study thiết kế module logging non-blocking cho STM32, hỗ trợ UART DMA, Ring Buffer, bare-metal, FreeRTOS và production build mode.
Biến note thành bài viết hoàn chỉnh
Notes là nơi ghi nhanh khái niệm.