Học FreeRTOS, p3

Bài này tiếp tục series học FreeRTOS, bàn về hàng đợi (queue), tiếp tục Phần 1Phần 2.

Trong FreeRTOS, mỗi task như là một chương trình riêng biệt, và để liên lạc giữa các task này, kernel cung cấp một số phương tiện có thể kể đến như queue, binary semaphores, counting semaphores, multex, và task notification. Sử dụng semaphores được đề cập ở những phần tiếp theo.

Đây là API đầy đủ của queue, một số hàm cơ bản về create, read, write, và delete tương ứng như sau cho các ví dụ ở learn_freertos/chapter2.

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
                            UBaseType_t uxItemSize );

BaseType_t xQueueSend( QueueHandle_t xQueue,
                       const void * pvItemToQueue,
                       TickType_t xTicksToWait );

BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void *pvBuffer,
                          TickType_t xTicksToWait );
void vQueueDelete( QueueHandle_t xQueue );

Đặt điểm queue trong FreeRTOS:

  • Một queue chứa một số hữu hạn các phần tử được khai báo khi khởi tạo. Quá trình khởi tạo chỉ không thành công khi vùng heap không còn đủ chổ trống.
  • Queue không được sở hữu bởi một task cụ thể nào cả, và được truy cập từ nhiều task. Thông thường một queue được ghi từ nhiều task, và được đọc ở một task nào đấy.
  • Queue hoạt động theo cơ chế FIFO (first in first out). Việc đọc thực hiện ở cuối queue, và việc ghi có thể ở đầu (xQueueSendToFront) hoặc đuôi (xQueueSendToBack). Một item khi được ghi vào queue thì giá trị của nó được sao chép lại, nghĩa là ta có thể free sau đó, hoặc sử dụng biến cục bộ cho việc ghi.
  • Block khi đọc queue. Khi một task ra lệnh đọc một queue, nếu queue đang trống nó sẽ đi vào chế độ Block và chờ. Task sẽ thoát ra khỏi chế độ Block khi một task khác hoặc interrupt service routine nào đó ghi vào queue này. Task cũng sẽ đi đến Ready nếu thời gian chờ kết thúc. Trong trường hợp nhiều task đang đọc, chỉ có một task được unblock, task đó có priority cao nhất. Nếu chúng cùng priority, task nào chờ lâu hơn thì sẽ được ưu tiên trước.
  • Block khi ghi queue. Khi một task ra lệnh ghi vào queue, nó sẽ đi vào chế độ Block nếu queue đang đầy. Task sẽ thoát ra khỏi Block khi queue có chổ trống trở lại, hoặc kết thúc thời gian chờ. Trường hợp nhiều task đang chờ để ghi thì task nào có priority cao hơn sẽ được unblock trước. Nếu cùng priority, task nào chờ lâu hơn sẽ được ưu tiên trước.

 

Ví dụ

blocking ở receiving

Nằm ở learn_freertos/chapter2/block_receiving.c. Chương trình làm việc như sau: 2 task ghi vào một queue kích thước 5 số long, một task khác đọc từ queue và in ra số long này.

Receiver của vReceiverTask có ưu tiên cao hơn, nên được thực thi trước, khi truy xuất đến queue thì nó nhảy vào trạng thái Block với timeout là 0 (chờ mãi mãi). Lúc này, vSenderTask với instance Task 1 được thực thi, và gởi số 100 vào queue. Ngay lập tức Receiver nhảy đến trạng thái Running, đọc từ queue, in ra terminal, rồi quay lại vòng lặp và việc đọc queue tiếp theo đặt task này vào trạng thái block lần nữa. Task 1 được resume, chạy tiếp đến taskYIED.

taskYIED báo với kernel rằng nó không chiếm đoạt CPU nữa, và kernel đặt Task 2 vào trạng thái Running. Quá trình tương tự như Task 1.

Kết quả ở terminal chạy file thực thi như sau:

Running as PID: 26812
Timer Resolution for Run TimeStats is 100 ticks per second.
Receiver: 100
Receiver: 200
Receiver: 100
Receiver: 200
Receiver: 100
Receiver: 200
Receiver: 100

Mô hình hoạt động các task này như hình bên dưới.

block_receiving

 

Để hiểu thêm, một số thông số sau có thể được thay đổi.

  • thay đổi `mainTASK_RECEIVER_PRIORITY` thành nhỏ hơn hoặc bằng `mainTASK_SENDER_PRIORITY`.
  • đổi `mainQUEUE_SIZE` thành 1, hoặc 10.
  • xóa lệnh `taskYIELD` trong `vSenderTask`.

blocking ở sender

Nằm ở learn_freertos/chapter2/block_sending.c. Chương trình này cũng giống chương trình ở trên, khác biệt là receive task có priority nhỏ hơn sending task.

Queue được khởi tạo để chứa mainQUEUE_SIZE = 3 struct xData. Quá trình chạy được diễn tả ngắn gọn như sau:

  • sender 1 đẩy vào queue, queue length = 1. `taskYield` cho phép sender 2 chạy.
  • sender 2 đẩy vào queue, queue length = 2. `taskYield` cho phép sender 1 chạy.
  • sender 1 đẩy vào queue, queue length = 3, queue đầy và không có khả năng nhận thêm dữ liệu nữa. `taskYield` cho phép sender 2 chạy.
  • sender 2 đẩy dữ liệu vào queue, nhưng không được. sender 2 chuyển sang trạng thái Blocking, với timeout là `xTicksToWait = 100ms`. sender 1 được kernel gọi lên thực thi.
  • sender 1 đẩy dữ liệu vào queue, nhưng không được. sender 1 chuyển sang trạng thái blocking. Kernel lúc này cho phép receiver task thực thi.
  • receiver task đọc 1 item từ queue, làm cho queue không còn đầy nữa. Sự kiện này trigger sender 2 task sống dậy (tại sao không phải là sender 1?)
  • sender 2 ghi tiếp một item vào queue, làm queue đầy trở lại. Sau đó nó gọi `taskYield` nhưng sender 1 đang ở trạng thái blocking nên chính sender 2 lại tiếp tục thực hiện, ghi item tiếp theo vào queue; việc queue đã đầy khiến sender 2 được đặt vào trạng thái blocking. receving task được kích hoạt.
  • receiving task đọc 1 item từ queue, ngay lập tức trigger sender 1 task nhảy sang trạng thái running. Quá trình giống sender 2, và được lặp lại vô tận.

Nếu đoạn trên dài quá thì bạn có thể xem hình sau.

block_receiving.PNG

Để hiểu thêm, bạn có thể thêm vào một task sender (sender 3 chẳng hạn).

 

Xác nhận: Các hình ở trên được lấy từ sách Using The FreeRTOS Realtime Kernel của Richard Barry.

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s