[STM32 – RTOS] Các trạng thái của Task và Task API

STM32 RTOS Cac trang thai cua Task va Task API

Ở 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)

word image 11065 1

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

Chap 3: Operations of a Real-Time OS | TRON Forum

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ùng osThreadAttr_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 ý vTaskSuspendAll chỉ 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ư:

  1. osThreadNew: tạo một thread (task) mới.
  2. osThreadTerminate: kết thúc (xóa) một thread (task).
  3. osThreadSuspend: treo một thread (task), không cho thực thi.
  4. osThreadResume: tiếp tục thực thi một thread (task) đã bị treo.
  5. osDelay: đưa thread vào trạng thái chờ trong một khoảng thời gian xác định.
  6. osThreadGetId: lấy ID của thread hiện tại.
  7. osThreadSetPriority: đặt lại độ ưu tiên của một thread.
  8. osThreadGetPriority: lấy độ ưu tiên hiện tại của một thread.
  9. osThreadYield: nhường CPU cho thread khác có cùng độ ưu tiên.
  10. 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ụ đó.

A black screen with white dots Description automatically generated

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.

A screenshot of a computer code Description automatically generated

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:

A screenshot of a computer program Description automatically generated

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.

A screenshot of a computer program Description automatically generated

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 task2
  • osThreadExit(); // Xóa chính nó

Lập trình STM32 RTOS bị Task khác delete

A screenshot of a computer program Description automatically generated

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ó

A screenshot of a computer code Description automatically generated

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.

 

5/5 - (1 bình chọn)

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *