[Tự Học FreeRTOS] Chapter 1: Task Management (Phần 3)

1.8 Idle Task và Idle Task Hook

Những task trong Ví dụ 1.4 dành phần lớn thời gian của chúng ở trạng thái Blocked. Trong khi ở trạng thái này, chúng không thể chạy và cũng không được scheduler chọn.

FreeRTOS phải luôn luôn có ít nhất một task ở trạng thái Running. Để đảm bảo được trường hợp này, scheduler tự động tạo Idle task khi vTaskStartScheduler() được gọi. Idle task không làm gì khác ngoài việc ở trong một vòng lặp (loop), do đó cũng như các task ở những ví dụ đầu tiên, nó luôn có thể chạy. Điều này vẫn đúng ngay cả khi sử dụng các tính năng tiết kiệm năng lượng đặc biệt của FreeRTOS, trong trường hợp đó, bộ vi điều khiển chạy FreeRTOS sẽ được đặt vào chế độ tiết kiệm năng lượng nếu không có task nào của ứng dụng tạo ra thực thi được.

Idle task thì có độ ưu tiên thấp nhất (độ ưu tiên bằng không) để đảm bảo nó không bao giờ ngăn cản một task của ứng dụng (application task) có độ ưu tiên cao hơn đi vô trạng thái Running. Tuy nhiên, không ai cấm nhà thiết kế tạo ra các task có cùng độ ưu tiên với Idle task, và do đó chia sẻ độ ưu tiên này nếu muốn. Macro configIDLE_SHOULD_YIELD trong FreeRTOSConfig.h được xài để ngăn Idle task tiêu tốn thời gian xử lý. Thời gian xử lý này nếu cấp cho các task của ứng dụng mà có cùng độ ưu tiên bằng 0 thì sẽ hiệu quả hơn. Phần 1.12 Scheduling Algorithms, mô tả configIDLE_SHOULD_YIELD.

Idle task chạy ở độ ưu tiên thấp nhất để đảm bảo nó bị đẩy ra khỏi trạng thái Running ngay khi có task có độ ưu tiên cao hơn đi vô trạng thái Ready. Điều này có thể thấy ở thời điểm tn trong Hình 1.9, khi Idle task bị hoán đổi để nhường cho Task 2 thực thi ngay tại nơi instant Task 2 rời khỏi trạng thái Blocked. Task 2 được gọi là đã chiếm quyền (pre-empt) của Idle task. Sự chiếm quyền (pre-emtion) xảy ra tự động và không cần thông báo cho task bị chiếm quyền.

Chú ý: Nếu một task xài hàm API vTaskDelete() để xóa (delete) chính nó thì cần đảm bảo rằng Idle task không bị giành thời gian xử lý. Bởi vì Idle task chịu trách nhiệm cho việc dọn dẹp tài nguyên của kernel (kernel resources) mà task bị xóa đã xài.

1.8.1 Các Hàm Idle Task Hook

Chúng ta có thể thêm chức năng đặc trưng cho ứng dụng một cách trực tiếp vô Idle task thông qua hàm Idle Hook (hay gọi là Idle Callback). Hàm này được gọi tự động bởi Idle task, trong vòng lặp của Idle task, mỗi vòng lặp là một lần gọi.

Công dụng của Idle task hook thông thường bao gồm:

  • Thực thi độ ưu tiên thấp, background, hay chức năng xử lý liên tục mà không tốn RAM để tạo nhiều task ứng dụng cho mỗi mục đích.
  • Đo lường dung lượng xử lý dự phòng (spare processing capacity). Idle task sẽ chạy chỉ khi tất cả task có độ ưu tiên cao hơn không làm việc, do đó việc đo lường dung lượng xử lý dự phòng đặt ở Idle task giúp việc chỉ thị thời gian xử lý dự phòng (spare processing time) được rõ ràng.
  • Đưa bộ xử lý vô chế độ công xuất thấp, giúp thực hiện giải pháp tiết kiệm công suất dễ dàng và tự động mỗi khi không có xử lý nào của ứng dụng cần thực thi, mặc dù cách tiết kiệm khả dĩ thường đạt được bằng chế độ chờ ít tick (tick-less idle mode).

1.8.2 Những Giới Hạn Khi Thực Hiện Các Hàm Idle Task Hook

Các hàm Idle Task Hook phải tuân theo những quy tắc sau.

Một hàm Idle Task Hook phải không bao giờ được có ý định block hoặc suspend chính nó.

Chú ý: Việc block Idle task sẽ gây ra trường hợp là không có sẵn task nào để vô trạng thái Running.

Nếu một task của ứng dụng xài hàm API vTaskDelete() để xóa chính nó, thì Idle Task Hook phải luôn luôn trả về (return) cho chính task đã gọi nó trong một khoảng thời gian hợp lý. Là bởi vì Idle task có trách nhiệm dọn dẹp những tài nguyên của kernel mà đã cấp cho task này trước khi nó bị xóa.

Các hàm Idle Task Hook phải có tên và prototype như trong Code Block 1.18.

void vApplicationIdleHook( void );

Code Block 1.18. Tên và prototype của một hàm Idle Task Hook

Ví dụ 1.7 Định nghĩa một hàm Idle Task Hook

Ví dụ 1.4 chúng ta đã thực hiện block bằng cách gọi hàm API vTaskDelay() tạo thời gian rảnh (idle time). Thời gian khi mà Idle task thực thi thì tất cả task của ứng dụng đang ở trạng thái Blocked. Ví dụ 1.7 tận dụng khoảng thời gian rảnh này thông qua một hàm bổ sung là Idle Hook, code của nó như trong Code Block 1.19.

/* Declare a variable that will be incremented by the hook function. */
volatile unsigned long ulIdleCycleCount = 0UL;

/*
* Idle hook functions MUST be called vApplicationIdleHook(), take no
* parameters, and return void.
*/
void vApplicationIdleHook( void )
{
    /* This hook function does nothing but increment a counter. */
    ulIdleCycleCount++;
}

Code Block 1.19. Một hàm Idle Task Hook đơn giản

configUSE_IDLE_HOOK phải được set thành 1 trong FreeRTOSConfig.h để cho hàm Idle Hook được gọi. Chỉnh sửa hàm thực hiện task (đã tạo ở ví dụ trước) một chút để in ra giá trị ulIdleCycleCount, như Code Block 1.20 dưới đây.

void vTaskFunction( void * pvParameters )
{
    char * pcTaskName;
    const TickType_t xDelay250ms = pdMS_TO_TICKS( 250 );

    /*
    * The string to print out is passed in via the parameter. Cast this to
    * a character pointer.
    */
    pcTaskName = ( char * ) pvParameters;

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ;; )
    {
        /*
        * Print out the name of this task AND the number of times
        * ulIdleCycleCount has been incremented.
        */
        vPrintLineAndNumber( pcTaskName, ulIdleCycleCount );
        /* Delay for a period of 250 milliseconds. */
        vTaskDelay( xDelay250ms );
    }
}

Code Block 1.20. Hàm thực hiện task ví dụ sửa lại để in giá trị ulIdleCycleCount

Hình 1.13 là output của Ví dụ 1.7 tạo ra. Có thể thấy rằng hàm Idle Task Hook thực thi gần 4 triệu lần giữa mỗi lần lặp của các task ứng dụng (số lần lặp phụ thuộc theo tốc độ phần cứng).

C:\Temp>rtosdemo
Task 2 is running
ulIdleCycleCount = 0
Task 1 is running
ulIdleCycleCount = 0
Task 2 is running
ulIdleCycleCount = 3869504
Task 1 is running
ulIdleCycleCount = 3869504
Task 2 is running
ulIdleCycleCount = 8564623
Task 1 is running
ulIdleCycleCount = 8564623
Task 2 is running
ulIdleCycleCount = 13181489
Task 1 is running
ulIdleCycleCount = 13181489
Task 2 is running
ulIdleCycleCount = 17838406
Task 1 is running
ulIdleCycleCount = 17838406
Task 2 is running

Hình 1.13. Output của Ví dụ 1.7

1.9 Thay Đổi Độ Ưu Tiên Của Task

1.9.1 Hàm API vTaskPrioritySet()

Hàm API vTaskPrioritySet() thay đổi độ ưu tiên của task sau khi scheduler đã khởi động. Hàm này chỉ có hiệu lực khi INCLUDE_vTaskPrioritySet đã set thành 1 trong FreeRTOSConfig.h.

void vTaskPrioritySet( TaskHandle_t xTask,
                       UBaseType_t uxNewPriority );

Code Block 1.21. Prototype của hàm API vTaskPrioritySet()

Tham số của vTaskPrioritySet():

  • pxTask

Handle của task mà độ ưu tiên của nó sẽ được thay đổi (task chủ thể). Xem tham số pxCreatedTask của hàm API xTaskCreate() để có thông tin về cách lấy handle của task.

Một task có thể đổi độ ưu tiên của chính nó bằng cách truyền NULL thay cho một task handle hợp lệ.

  • uxNewPriority

Độ ưu tiên mới của task. Giá trị này bị giới hạn bởi độ ưu tiên lớn nhất khả dĩ là (configMAX_PRIORITIES – 1), với configMAX_PRIORITIES là một hằng số trong header file FreeRTOSConfig.h.

1.9.2 Hàm API uxTaskPriorityGet()

Hàm API uxTaskPriorityGet() trả về độ ưu tiên của task. Hàm này chỉ có hiệu lực khi INCLUDE_uxTaskPriorityGet đã set thành 1 trong FreeRTOSConfig.h.

UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask );

Code Block 1.22. Prototype của hàm API vTaskPriorityGet()

Tham số và giá trị trả về của vTaskPriorityGet():

  • pxTask

Handle của task mà độ ưu tiên của nó sẽ được truy vấn (task chủ thể). Xem tham số pxCreatedTask của hàm API xTaskCreate() để có thông tin về cách lấy handle của task.

Một task có thể truy vấn (query) độ ưu tiên của chính nó bằng cách truyền NULL thay cho một task handle hợp lệ.

  • Giá trị trả về

Độ ưu tiên hiện tại được gán cho task được truy vấn.

Ví dụ 1.8 Đổi độ ưu tiên của task

(đang viết tiếp)