Using the QEMU simulator to outline the ThreadX task scheduling process.0. Build ExampleCreate Task 0 and Task 1
#include "tx_api.h"
#include "tx_thread.h"
// Task 0 entry
void thread_0_entry(ULONG thread_input){
TX_THREAD *current_thread;
thread_0_counter++;
TX_THREAD_GET_CURRENT(current_thread);
tx_thread_terminate(current_thread);
}
// Task 1 entry
void thread_1_entry(ULONG thread_input){
while(1)
{
/* Increment the thread counter. */
thread_1_counter++;
while(1);
}}
// Create tasks
void tx_application_define(void *first_unused_memory){
CHAR *pointer = TX_NULL;
/* Create a byte memory pool from which to allocate the thread stacks. */
tx_byte_pool_create(&byte_pool_0, "byte pool 0", memory_area, DEMO_BYTE_POOL_SIZE);
/* Put system definition stuff in here, e.g. thread creates and other assorted create information. */
/* Allocate the stack for thread 0. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
/* Create the main thread. */
tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, pointer, DEMO_STACK_SIZE, 1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Allocate the stack for thread 1. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
/* Create threads 1 and 2. These threads pass information through a ThreadX message queue. It is also interesting to note that these threads have a time slice. */
tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1, pointer, DEMO_STACK_SIZE, 16, 16, 4, TX_AUTO_START);
}
1. From the termination of Task 0’s thread_0_entry to Task 1’s while(1)
Code Walkthrough:
//thead0 context Task 0 state from TX_READY -> TX_TERMINATED
_tx_thread_terminate thread_ptr -> tx_thread_state = TX_TERMINATED;
/* Call actual thread suspension routine. */
_tx_thread_system_suspend(thread_ptr);//(Step 1)
/* Check for preemption. */
_tx_thread_system_preempt_check();
/* Pickup thread pointer. */
TX_THREAD_GET_CURRENT(current_thread)
/* Pickup the next execute pointer. */
thread_ptr = _tx_thread_execute_ptr;
if (current_thread != thread_ptr)
{
_tx_thread_system_return();//(Step 2)cpu porting layer impls(M3 implementation)
}
Step 1, Preparing for Task SwitchingThe current task state is set to terminated (TX_TERMINATED), and it searches for other tasks of the same priority in the current priority linked list. If there are no other tasks to execute in the current priority queue, it first clears the corresponding bit in _tx_thread_priority_maps for that priority, and then quickly finds the corresponding priority queue using a bitmap, saving it to the global pointer _tx_thread_execute_ptr
/* Calculate the lowest bit set in the priority map. */
TX_LOWEST_SET_BIT_CALCULATE(priority_map, priority_bit)
/* Setup the next highest priority variable. */
_tx_thread_highest_priority = base_priority + ((UINT) priority_bit);
_tx_thread_execute_ptr = _tx_thread_priority_list[_tx_thread_highest_priority];
Step 2, Completing the Task Switching ActionThe task switching action is completed through the _tx_thread_system_return interface, which assigns the following global pointer: _tx_thread_current_ptr = _tx_thread_execute_ptr, and restores the context from the target task stack.Different processors implement the _tx_thread_system_return interface according to their characteristics, triggering context switching, as shown in the M3 interface implementation
// M3 implementation
__attribute__( ( always_inline ) ) static inline void _tx_thread_system_return_inline(void){
unsigned int interrupt_save;
/* Set PendSV to invoke ThreadX scheduler. */
*((volatile ULONG *) 0xE000ED04) = ((ULONG) 0x10000000);
if (__get_ipsr_value() == 0)
{
interrupt_save = __get_interrupt_posture();
#ifdef TX_PORT_USE_BASEPRI
__set_basepri_value(0);
#else
__enable_interrupts();
#endif
__restore_interrupt(interrupt_save);
}}
Then it enters the PENDSV exception entry, returning from PENDSV to the target task Thread 1
Note: The above task entry refers to the entry of the task business-related code. In the ThreadX system definition, when a task starts executing, it always enters from a unified entry (_tx_thread_shell_entry) and then enters the user-defined entry (thread_entry) in a callback manner.