Ở bài trước chúng ta đã tìm hiểu cách tạo Task và lập lịch cho Task, ở bài này chúng ta sẽ tìm hiểu các Task API và các trạng thái của Task. Nắm vững được điều này giúp chúng ta làm việc với Task hiệu quả hơn.
Bài 2 về RTOS trong Serie Học lập trình STM32 từ A tới Z
Tìm hiểu về các trạng thái của Task (Task Status)
![[STM32 - RTOS] Các trạng thái của Task và Task API 2 word image 11065 1](https://khuenguyencreator.com/wp-content/uploads/2024/08/word-image-11065-1.jpeg)
Bạn có thể hình dung các trạng thái của Task giống như các bệnh nhân ở phòng khám chỉ có 1 bác sĩ (CPU): người đang được khám là Running, người đang ngồi chờ đến lượt là Ready, người được gửi đi xét nghiệm chờ kết quả là Blocked, người xin về nhà nghỉ chưa khám tiếp là Suspended, còn người đã khám xong ra về là Deleted.
Ready (Sẵn sàng)
Trạng thái Ready là khi một task đã sẵn sàng để được CPU xử lý, nhưng hiện tại chưa được thực thi (vì đang có task khác ưu tiên cao hơn hoặc bằng chạy). Task ở trạng thái này có thể chuyển sang trạng thái Running khi được bộ lập lịch của FreeRTOS chọn.
Running (Đang chạy)
Trạng thái Running là khi task đang được CPU xử lý. Với CPU 1 nhân như STM32, chỉ có một task duy nhất ở trạng thái này tại một thời điểm. Khi task tự chờ hoặc bị task khác chiếm quyền, nó sẽ chuyển sang trạng thái khác như Blocked hoặc Ready.
Blocked (Bị chặn)
Task chuyển sang trạng thái Blocked khi nó đang chờ một sự kiện hoặc tài nguyên, chẳng hạn như chờ tín hiệu từ một task khác hoặc chờ hết một khoảng thời gian. Khi điều kiện chờ được đáp ứng, task sẽ chuyển về trạng thái Ready.
Suspended (Bị tạm dừng)
Trạng thái Suspended là khi task bị tạm dừng và không được lập lịch để thực thi cho đến khi nó được tiếp tục (resume). Đây là trạng thái hữu ích khi cần tạm dừng các task chưa cần thiết để tiết kiệm tài nguyên CPU.
Deleted (Đã xóa)
Trạng thái Deleted là khi task đã bị xóa và không còn tồn tại trong hệ thống. Khi một task hoàn thành nhiệm vụ của mình hoặc không còn cần thiết, nó có thể được xóa để giải phóng tài nguyên.
Task Switching hay Contex Switching – chuyển đổi Task
![[STM32 - RTOS] Các trạng thái của Task và Task API 3 Chap 3: Operations of a Real-Time OS | TRON Forum](https://khuenguyencreator.com/wp-content/uploads/2024/08/chap-3-operations-of-a-real-time-os-or-tron-forum.png)
Context switching là quá trình lưu trữ trạng thái của task hiện tại và khôi phục trạng thái của task tiếp theo được chọn để thực thi. Trạng thái này bao gồm các thanh ghi CPU, con trỏ ngăn xếp và các thông tin cần thiết khác, để đảm bảo task có thể tiếp tục thực thi từ đúng điểm mà nó bị dừng lại.
- Lưu trữ ngữ cảnh: khi một task bị dừng lại, trạng thái hiện tại của CPU (các thanh ghi) được lưu vào ngăn xếp riêng của task đó.
- Khôi phục ngữ cảnh: trạng thái của task tiếp theo được khôi phục từ ngăn xếp của nó, và CPU tiếp tục thực thi task đó từ điểm mà nó bị dừng lại.
Kernel sẽ đảm nhiệm việc chuyển task: nó lưu lại context (trạng thái) hiện tại của task bị dừng và khôi phục lại context của task được chạy tiếp. Kernel sẽ thực hiện công việc này trong các trường hợp:
- Hết thời gian thực thi đã định trước (time slice), được tính bằng ngắt tick hệ thống (SysTick), hoặc
- Khi có sự kiện làm một task có độ ưu tiên cao hơn thoát khỏi trạng thái chờ, như signal, queue, semaphore,…
- Khi task gọi hàm
osThreadYield()để báo kernel chuyển sang task khác mà không đợi tới hết time slice
Các Task API thường dùng
Ta có bảng các hàm API cơ bản của task trong CMSIS RTOS (phiên bản CMSIS-RTOS v2, đúng với interface CMSIS_V2 mà chúng ta chọn trong CubeMX) và FreeRTOS:
| Feature | CMSIS RTOS API | FreeRTOS API |
|---|---|---|
| Define task attribute | osThreadAttr_t | tham số của xTaskCreate |
| Create task | osThreadNew | xTaskCreate |
| Terminate task | osThreadTerminate | vTaskDelete |
| Yield task | osThreadYield | taskYIELD |
| Delay task | osDelay | vTaskDelay |
| Get task ID | osThreadGetId | xTaskGetCurrentTaskHandle |
| Set task priority | osThreadSetPriority | vTaskPrioritySet |
| Get task priority | osThreadGetPriority | uxTaskPriorityGet |
| Suspend task | osThreadSuspend | vTaskSuspend |
| Suspend scheduler (khóa chuyển task) | osKernelLock | vTaskSuspendAll |
| Resume task | osThreadResume | vTaskResume |
| Resume scheduler | osKernelUnlock | xTaskResumeAll |
| Get state of task | osThreadGetState | eTaskGetState |
| List current tasks info | osThreadEnumerate | vTaskList |
| Delay task until specified time | osDelayUntil | vTaskDelayUntil |
Ghi chú:
- CMSIS RTOS API: là một giao diện chuẩn hóa cho các hệ điều hành thời gian thực (RTOS) trên vi điều khiển ARM Cortex-M, giúp tạo ra mã nguồn độc lập với RTOS bên dưới. Phiên bản cũ CMSIS-RTOS v1 dùng các tên như
osThreadDef,osThreadCreate, bản v2 dùngosThreadAttr_t,osThreadNew. - FreeRTOS API: là giao diện API gốc của FreeRTOS, một RTOS phổ biến cho các hệ thống nhúng. Lưu ý
vTaskSuspendAllchỉ tạm dừng việc chuyển task, chứ không chuyển các task sang trạng thái Suspended.
Một số API thường dùng như:
- osThreadNew: tạo một thread (task) mới.
- osThreadTerminate: kết thúc (xóa) một thread (task).
- osThreadSuspend: treo một thread (task), không cho thực thi.
- osThreadResume: tiếp tục thực thi một thread (task) đã bị treo.
- osDelay: đưa thread vào trạng thái chờ trong một khoảng thời gian xác định.
- osThreadGetId: lấy ID của thread hiện tại.
- osThreadSetPriority: đặt lại độ ưu tiên của một thread.
- osThreadGetPriority: lấy độ ưu tiên hiện tại của một thread.
- osThreadYield: nhường CPU cho thread khác có cùng độ ưu tiên.
- osKernelStart: bắt đầu bộ lập lịch của RTOS.
Trạng thái Ready, Running và Blocked
Khi Nào Task Ở Trạng Thái Ready
Khi Task Được Tạo: khi một task mới được tạo bằng cách gọi osThreadNew trong CMSIS RTOS hoặc xTaskCreate trong FreeRTOS, task sẽ ở trạng thái “Ready” và chờ để được bộ lập lịch chọn để chạy.
Khi Task Chuyển Từ Blocked Sang Ready: sau khi một task bị blocked bởi osDelay, osSemaphoreAcquire, osMutexAcquire, osMessageQueueGet, hoặc osEventFlagsWait, và điều kiện chờ đã được thỏa mãn (thời gian delay kết thúc, semaphore hoặc mutex có sẵn, có dữ liệu trong queue, hoặc sự kiện xảy ra), task sẽ chuyển từ trạng thái “Blocked” sang “Ready”.
Khi Task Chuyển Từ Suspended Sang Ready: khi một task bị treo (suspended) bởi osThreadSuspend trong CMSIS RTOS hoặc vTaskSuspend trong FreeRTOS, và sau đó được tiếp tục (resume) bởi osThreadResume hoặc vTaskResume, task sẽ chuyển từ trạng thái “Suspended” sang “Ready”.
Khi Task Được Giải Phóng từ Waiting List: khi một task đang chờ trong danh sách chờ của một hàng đợi (queue) hoặc semaphore và điều kiện chờ đã được thỏa mãn, nó sẽ chuyển sang trạng thái “Ready”.
Khi nào Task ở trạng thái Blocked
Chờ Delay: khi một task gọi hàm osDelay trong CMSIS RTOS hoặc vTaskDelay trong FreeRTOS, task sẽ ở trạng thái Blocked cho đến khi thời gian delay kết thúc.
Chờ Semaphore: khi một task chờ semaphore bằng cách gọi osSemaphoreAcquire trong CMSIS RTOS hoặc xSemaphoreTake trong FreeRTOS, nếu semaphore không có sẵn, task sẽ bị Blocked cho đến khi semaphore được giải phóng (hoặc hết timeout).
Chờ Mutex: khi một task chờ mutex bằng cách gọi osMutexAcquire trong CMSIS RTOS hoặc xSemaphoreTake (với mutex) trong FreeRTOS, nếu mutex không có sẵn, task sẽ bị Blocked cho đến khi mutex được giải phóng.
Chờ Message/Queue: khi một task chờ nhận dữ liệu từ queue bằng cách gọi osMessageQueueGet trong CMSIS RTOS hoặc xQueueReceive trong FreeRTOS, nếu queue rỗng, task sẽ bị Blocked cho đến khi có dữ liệu.
Chờ Event/Signal: khi một task chờ một sự kiện hoặc tín hiệu bằng cách gọi osEventFlagsWait trong CMSIS RTOS, nếu sự kiện hoặc tín hiệu chưa được thiết lập, task sẽ bị Blocked cho đến khi sự kiện đó xảy ra.
Lập trình STM32 RTOS trạng thái Blocked
Trong bài trước: STM32 Schedule chúng ta đã biết cách tạo Task và chạy Task. Chúng ta sẽ xét chi tiết các trạng thái của Task trong ví dụ đó.
![[STM32 - RTOS] Các trạng thái của Task và Task API 4 A black screen with white dots Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/08/a-black-screen-with-white-dots-description-automa.png)
Với Task 3 có độ ưu tiên là High, defaultTask là Normal và Task 2 có độ ưu tiên Low.
Khi khởi tạo Task bằng lệnh: defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
Task ở trạng thái Ready: sẵn sàng để Running.
Khi osKernelStart(); được gọi, dựa vào độ ưu tiên của các Task, Kernel sẽ chạy Task 3 đầu tiên.
Lúc này Task 3 sẽ ở trạng thái Running: chúng ta in chuỗi ra qua cổng UART1, do đó ta thấy chuỗi Task 3… được in ra.
Sau đó gọi hàm osDelay(1000);
Khi gọi Delay, Task 3 sẽ chuyển sang trạng thái Blocked. Trong trường hợp này Task 3 sẽ ở trạng thái Blocked trong 1000ms.
![[STM32 - RTOS] Các trạng thái của Task và Task API 5 A screenshot of a computer code Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/08/a-screenshot-of-a-computer-code-description-autom.png)
Trong thời gian này, Kernel chuyển sang Task có độ ưu tiên thấp hơn là defaultTask, tiếp tục như vậy tới Task 2.
Khi hết thời gian osDelay, Task sẽ chuyển lại về trạng thái Ready, sau đó được Kernel tiếp tục cho chạy theo độ ưu tiên.
Khi xem Log UART các bạn sẽ thấy cả 3 task được in gần như ngay lập tức, nối tiếp nhau (khoảng cách giữa các dòng chỉ bằng thời gian truyền chuỗi qua UART, vì mỗi task vừa Blocked là task tiếp theo được chạy luôn), sau đó dừng khoảng 1s rồi lại in cả 3.
Đó là do cả 3 task đều có thời gian bị Blocked là 1000ms.
Trạng thái Suspended
Khi Nào Task Ở Trạng Thái Suspended
Khi Task Bị Treo Một Cách Chủ Động: một task chuyển sang trạng thái “Suspended” khi bị gọi các hàm osThreadSuspend trong CMSIS RTOS hoặc vTaskSuspend trong FreeRTOS.
Treo Chính Nó: một task có thể tự treo chính nó bằng cách gọi osThreadSuspend hoặc vTaskSuspend với handle của chính nó.
Lập trình STM32 RTOS sử dụng task Suspend một task khác
Thực hiện tạo 2 Task: defaultTask với độ ưu tiên Normal, Task 2 có độ ưu tiên Low.
Gen code và lập trình trên 2 Task như sau:
![[STM32 - RTOS] Các trạng thái của Task và Task API 6 A screenshot of a computer program Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/08/a-screenshot-of-a-computer-program-description-au.png)
defaultTask có độ ưu tiên cao hơn sẽ chạy trước, sau đó Suspend Task 2 sau 1s và Resume Task 2 sau 3s.
Trong khi defaultTask bị delay lần đầu tiên (delay 1s),
Task 2 sẽ được chạy và in log mỗi 200ms.
Kết quả
Giải thích kết quả:
- 0ms: defaultTask bắt đầu, log “defaultTask (normal) is Running\r\n”
- 1ms: task2 log “task2 (low) is Running\r\n”
- 201ms: task2 log “task2 (low) is Running\r\n”
- 401ms: task2 log “task2 (low) is Running\r\n”
- 601ms: task2 log “task2 (low) is Running\r\n”
- 801ms: task2 log “task2 (low) is Running\r\n”
- 1000ms: defaultTask log “Suspending task2 (low)\r\n”, task2 bị treo
- 3000ms: defaultTask log “Resuming task2 (low)\r\n”, task2 được tiếp tục
- Khoảng 3000ms: task2 log “task2 (low) is Running\r\n” ngay sau khi được resume
- Tiếp tục vòng lặp như vậy
Lưu ý: Nếu task bị Suspend trong lúc đang Blocked (ví dụ đang nằm trong osDelay), thì khi được Resume nó sẽ chuyển thẳng sang Ready và chạy tiếp ngay, phần thời gian delay còn lại bị bỏ qua. Vì vậy đừng dùng Suspend/Resume để “kéo dài” một khoảng delay, kết quả thời gian sẽ không như bạn tính.
Lập trình STM32 RTOS task tự suspend chính nó
Chúng ta sẽ dùng defaultTask để resume cho Task 2.
Task 2 sẽ tự Suspend chính nó mỗi 1s.
![[STM32 - RTOS] Các trạng thái của Task và Task API 7 A screenshot of a computer program Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/08/a-screenshot-of-a-computer-program-description-au-1.png)
Log in ra sẽ như sau:
0ms: defaultTask bắt đầu, log “defaultTask (normal) is Running\r\n” và vào trạng thái Blocked trong 2s
1ms: task2 log “task2 (low) is Running\r\n” sau đó vào trạng thái Blocked trong 1s
1000ms: task2 log “task2 (low) is Suspending itself\r\n”, task2 tự treo chính nó
2000ms: defaultTask log “Resuming task2 (low)\r\n”, task2 chuyển trạng thái sang Ready
2001ms: Kernel chuyển task, sau đó task2 log “task2 (low) is Running\r\n” và tiếp tục bị Blocked trong 1s
3000ms: task2 log “task2 (low) is Suspending itself\r\n”, task2 tự treo
4000ms: defaultTask log “defaultTask (normal) is Running\r\n”
6000ms: defaultTask log “Resuming task2 (low)\r\n”, task2 được tiếp tục
6001ms: task2 log “task2 (low) is Running\r\n”
7000ms: task2 log “task2 (low) is Suspending itself\r\n”, task2 tự treo
Trạng thái Deleted
Khi nào Task ở trạng thái Deleted
Task chuyển sang trạng thái Deleted (bị xóa) khi nó tự kết thúc hoặc bị xóa bởi một task khác. Trạng thái Deleted có nghĩa là task đã bị loại khỏi danh sách các task của hệ thống, tài nguyên của nó (stack, TCB) sẽ được giải phóng và sử dụng lại.
Ví dụ:
osThreadTerminate(task2Handle);// Xóa task2osThreadExit();// Xóa chính nó
Lập trình STM32 RTOS bị Task khác delete
![[STM32 - RTOS] Các trạng thái của Task và Task API 8 A screenshot of a computer program Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/08/a-screenshot-of-a-computer-program-description-au-2.png)
defaultTask sẽ được chạy và in Log, sau đó vào trạng thái Blocked trong 5s.
Trong thời gian đó, Task 2 sẽ được chạy và in Log mỗi 1s.
Khi hết thời gian Blocked, defaultTask sẽ xóa Task 2, sau đó rơi vào vòng for để in log mỗi 1s.
Task 2 sau khi bị xóa sẽ được giải phóng bộ nhớ và không còn được lập lịch để chạy nữa.
Kết quả:
- 0ms: defaultTask bắt đầu, log “defaultTask (normal) is Running\r\n”
- 1ms: task2 bắt đầu, log “task2 (low) is Running\r\n”
- 1000ms: task2 log “task2 (low) is Running\r\n”
- 2000ms: task2 log “task2 (low) is Running\r\n”
- 3000ms: task2 log “task2 (low) is Running\r\n”
- 4000ms: task2 log “task2 (low) is Running\r\n”
- 5000ms: defaultTask log “Deleting task2 (low)\r\n”, task2 bị xóa
- 5001ms: defaultTask tiếp tục log “defaultTask (normal) is Running\r\n”
- 6000ms: defaultTask log “defaultTask (normal) is Running\r\n”
- Sau đó chỉ có defaultTask chạy
Lập trình STM32 RTOS tự delete chính nó
![[STM32 - RTOS] Các trạng thái của Task và Task API 9 A screenshot of a computer code Description automatically generated](https://khuenguyencreator.com/wp-content/uploads/2024/08/a-screenshot-of-a-computer-code-description-autom-1.png)
Khi mới bắt đầu, defaultTask sẽ được chạy trước, in Log rồi vào trạng thái Blocked trong 1s.
Ngay sau đó Task 2 có độ ưu tiên thấp hơn sẽ được chạy, in log và vào trạng thái Blocked trong 1s.
Sau 1s, defaultTask được chạy, in log và lại vào Blocked.
Ngay sau đó Task 2 hết Blocked và chạy lệnh osThreadExit() (tự xóa chính mình). Sau lệnh này Task 2 đã bị xóa.
Hệ thống chỉ còn lại defaultTask chạy và in log mỗi 1s.
Kết quả:
- 0ms: defaultTask bắt đầu, log “defaultTask (normal) is Running\r\n”
- 1ms: task2 bắt đầu, log “task2 (low) is Running and will delete itself\r\n”
- 1000ms: defaultTask log “defaultTask (normal) is Running\r\n”
- 1001ms: task2 log “task2 (low) is now deleting itself\r\n” và sau đó task2 tự xóa
- 2000ms: defaultTask log “defaultTask (normal) is Running\r\n”
- 3000ms: defaultTask log “defaultTask (normal) is Running\r\n”
Lưu ý: Khi một task tự xóa chính nó, FreeRTOS không giải phóng bộ nhớ ngay mà để Idle Task dọn dẹp sau. Nếu các task khác chạy liên tục không bao giờ Blocked (ví dụ vòng lặp không có osDelay), Idle Task sẽ không có cơ hội chạy, bộ nhớ của task đã xóa không được thu hồi và heap sẽ cạn dần.
Kết
Nắm vững các trạng thái của Task giúp cho việc điều khiển Task mượt mà hơn. Các task đôi khi cần dừng lại (suspend) để chờ tài nguyên, hoặc bị xóa đi để giải phóng tài nguyên cho các tác vụ khác.
Hi vọng bài viết này có ích với bạn, hẹn gặp lại trong bài tiếp theo.

![[STM32 - RTOS] Các trạng thái của Task và Task API 1 STM32 RTOS Cac trang thai cua Task va Task API](https://khuenguyencreator.com/wp-content/uploads/2024/08/STM32-RTOS-Cac-trang-thai-cua-Task-va-Task-API.jpg)