Thread Management

In daily life, when we want to complete a big task, we usually break it down into multiple simple and easy-to-solve small problems. When the small problems are solved one by one, the big problem will be solved. In a multi-threaded operating system, developers are also required to break down a complex application into multiple small, schedulable, and serialized program units. When the tasks are divided reasonably and executed correctly, this design can enable the system to meet the performance and time requirements of the real-time system. For example, let the embedded system perform such a task. The system collects data through sensors and displays the data on the display. In a multi-threaded real-time system, this task can be broken down into two subtasks, as shown in the figure below. One subtask continuously reads sensor data and writes the data to shared memory. The other subtask periodically reads data from shared memory and outputs the sensor data to the display.

Switching execution between sensor data receiving task and display task

In RT-Thread, the program entity corresponding to the above subtask is the thread. The thread is the carrier for implementing tasks. It is the most basic scheduling unit in RT-Thread. It describes the operating environment for the execution of a task and also describes the priority level of the task. Important tasks can be set with relatively high priorities, while unimportant tasks can be set with lower priorities. Different tasks can also be set with the same priority and run in turns.

When a thread is running, it thinks that it is running in an exclusive CPU mode. The running environment of the thread is called context, which refers to various variables and data, including all register variables, stacks, memory information, etc.

This chapter will be divided into 5 sections to introduce RT-Thread thread management. After reading this chapter, readers will have a deeper understanding of RT-Thread's thread management mechanism, such as: what states does a thread have, how to create a thread, why there are idle threads, and other questions, and they will have a clear answer in their minds.

The main function of RT-Thread thread management is to manage and schedule threads. There are two types of threads in the system, namely system threads and user threads. System threads are threads created by the RT-Thread kernel, and user threads are threads created by applications. Both types of threads will allocate thread objects from the kernel object container. When the thread is deleted, it will also be deleted from the object container, as shown in the figure below. Each thread has important properties, such as thread control block, thread stack, entry function, etc.

Object containers and thread objects

RT-Thread's thread scheduler is preemptive. Its main job is to find the highest priority thread from the list of ready threads to ensure that the highest priority thread can be run. Once the highest priority task is ready, it can always get the right to use the CPU.

When a running thread makes a thread with a higher priority than it meet the running conditions, the current thread's right to use the CPU is deprived, or given up, and the high-priority thread immediately obtains the right to use the CPU.

If the interrupt service routine makes a high-priority thread meet the running conditions, when the interrupt is completed, the interrupted thread is suspended and the high-priority thread starts running.

When the scheduler schedules a thread switch, it first saves the current thread context. When switching back to this thread, the thread scheduler restores the context information of the thread.

In RT-Thread, the thread control block is represented by the structure struct rt_thread. The thread control block is a data structure used by the operating system to manage threads. It stores some thread information, such as priority, thread name, thread status, etc. It also contains a linked list structure for connecting threads, a thread waiting event set, etc. The detailed definition is as follows:

/* 线程控制块 */
struct rt_thread
{
    /* rt 对象 */
    char        name[RT_NAME_MAX];     /* 线程名称 */
    rt_uint8_t  type;                   /* 对象类型 */
    rt_uint8_t  flags;                  /* 标志位 */

    rt_list_t   list;                   /* 对象列表 */
    rt_list_t   tlist;                  /* 线程列表 */

    /* 栈指针与入口指针 */
    void       *sp;                      /* 栈指针 */
    void       *entry;                   /* 入口函数指针 */
    void       *parameter;              /* 参数 */
    void       *stack_addr;             /* 栈地址指针 */
    rt_uint32_t stack_size;            /* 栈大小 */

    /* 错误代码 */
    rt_err_t    error;                  /* 线程错误代码 */
    rt_uint8_t  stat;                   /* 线程状态 */

    /* 优先级 */
    rt_uint8_t  current_priority;    /* 当前优先级 */
    rt_uint8_t  init_priority;        /* 初始优先级 */
    rt_uint32_t number_mask;

    ......

    rt_ubase_t  init_tick;               /* 线程初始化计数值 */
    rt_ubase_t  remaining_tick;         /* 线程剩余计数值 */

    struct rt_timer thread_timer;      /* 内置线程定时器 */

    void (*cleanup)(struct rt_thread *tid);  /* 线程退出清除函数 */
    rt_uint32_t user_data;                      /* 用户数据 */
};copymistakeCopy Success

Among them, init_priority is the thread priority specified when the thread is created, and it will not be changed during the thread running process (unless the user executes the thread control function to manually adjust the thread priority). When the thread exits, cleanup will be called back by the idle thread once to perform the user-set cleanup and other tasks. The last member user_data can be used by the user to attach some data information to the thread control block to provide a similar implementation method for thread private data.

Thread stack

RT-Thread threads have independent stacks. When switching threads, the context of the current thread will be stored in the stack. When the thread is to be resumed, the context information is read from the stack for restoration.

The thread stack is also used to store local variables in functions: local variables in functions are requested from the thread stack space; local variables in functions are initially allocated from registers (ARM architecture), and when this function calls another function, these local variables will be placed on the stack.

For the first time the thread runs, you can manually construct this context to set up some initial environments: entry function (PC register), entry parameters (R0 register), return location (LR register), and current machine running status (CPSR register).

The growth direction of the thread stack is closely related to the chip architecture. Versions before RT-Thread 3.1.0 only support the stack growing from high addresses to low addresses. For the ARM Cortex-M architecture, the thread stack can be constructed as shown in the following figure.

Thread stack (ARM)

The thread stack size can be set in this way. For MCUs with relatively large resources, a larger thread stack can be appropriately designed. You can also set a larger stack initially, for example, specify a size of 1K or 2K bytes, and then use the list_thread command in FinSH to view the size of the stack used by the thread during thread execution. Through this command, you can see the maximum stack depth used by the thread from the time the thread starts running to the current time point, and then add an appropriate margin to form the final thread stack size, and finally modify the stack space size.

Thread Status

During the running process of a thread, only one thread is allowed to run in the processor at the same time. From the perspective of the running process, threads have different running states, such as initial state, suspended state, ready state, etc. In RT-Thread, a thread contains five states, and the operating system will automatically adjust its state dynamically according to its running situation. The five states of threads in RT-Thread are shown in the following table:

state

describe

Initial state

When a thread is just created and has not yet started running, it is in the initial state; in the initial state, the thread does not participate in scheduling. This state is defined in the RT-Thread macro as RT_THREAD_INIT

Ready state

In the ready state, threads are queued according to their priority, waiting to be executed; once the current thread has finished running and gives up the processor, the operating system will immediately look for the highest priority ready thread to run. This state is defined in the RT-Thread macro as RT_THREAD_READY

Running status

The thread is currently running. In a single-core system, only the thread returned by the rt_thread_self() function is in the running state; in a multi-core system, there may be more than one thread in the running state. This state is defined in the RT-Thread macro as RT_THREAD_RUNNING

Suspended state

Also called blocking state. It may be suspended waiting due to unavailable resources, or the thread may be suspended due to active delay for a period of time. In the suspended state, the thread does not participate in scheduling. The macro definition of this state in RT-Thread is RT_THREAD_SUSPEND

Closed state

When the thread finishes running, it will be in the closed state. The closed thread does not participate in the thread scheduling. This state is defined in the macro of RT-Thread as RT_THREAD_CLOSE

Thread Priority

The priority of an RT-Thread thread indicates the priority of a thread being scheduled. Each thread has a priority. The more important a thread is, the higher the priority it should be given, and the greater the possibility that the thread will be scheduled.

RT-Thread supports up to 256 thread priorities (0~255). The smaller the value, the higher the priority, and 0 is the highest priority. In some systems with tight resources, you can choose to support only 8 or 32 priorities according to the actual situation; for the ARM Cortex-M series, 32 priorities are generally used. The lowest priority is assigned to the idle thread by default and is generally not used by users. In the system, when a thread with a higher priority than the current thread is ready, the current thread will be swapped out immediately, and the high-priority thread will preempt the processor to run.

Time Slice

Each thread has a time slice parameter, but the time slice is only valid for ready threads with the same priority. When the system schedules ready threads with the same priority using a time slice round-robin scheduling method, the time slice plays a role in constraining the single running time of the thread. Its unit is one system tick (OS Tick). For details, see the "Clock Management" section. Assume that there are two ready threads A and B with the same priority. The time slice of thread A is set to 10, and the time slice of thread B is set to 5. Then, when there is no ready thread with a higher priority than A in the system, the system will switch back and forth between threads A and B, and execute thread A for 10 ticks each time and thread B for 5 ticks each time, as shown in the following figure.

Same priority time slice rotation

Thread entry function

The entry in the thread control block is the entry function of the thread, which is the function that the thread implements the expected function. The entry function of the thread is designed and implemented by the user, and generally has the following two code forms:

- Infinite loop mode:

In real-time systems, threads are usually passive: this is determined by the characteristics of real-time systems. Real-time systems usually always wait for external events to occur and then provide corresponding services:

void thread_entry(void* paramenter)
{
    while (1)
    {
    /* 等待事件的发生 */

    /* 对事件进行服务、进行处理 */
    }
}copymistakeCopy Success

Threads seem to have no factors that limit program execution, and it seems that all operations can be executed. However, as a real-time system, a real-time system with clear priorities, if a program in a thread falls into an infinite loop operation, then threads with lower priorities than it will not be able to be executed. Therefore, one thing that must be noted in a real-time operating system is that the thread cannot fall into an infinite loop operation, and there must be an action to give up the right to use the CPU, such as calling a delay function in the loop or actively suspending. The purpose of designing such an infinite loop thread by the user is to allow this thread to be scheduled and run by the system loop all the time and never deleted.

- Sequential execution or limited loop mode:

For example, simple sequential statements, do while() or for() loops, etc. These threads will not loop or will not loop forever. They are "one-shot" threads and will be executed to completion. After execution, the thread will be automatically deleted by the system.

static void thread_entry(void* parameter)
{
    /* 处理事务 #1 */

    /* 处理事务 #2 */

    /* 处理事务 #3 */
}copymistakeCopy Success

Thread error code

A thread is an execution scenario. The error code is closely related to the execution environment, so each thread is equipped with a variable to save the error code. The error codes of threads are as follows:

#define RT_EOK           0 /* 无错误     */
#define RT_ERROR         1 /* 普通错误     */
#define RT_ETIMEOUT      2 /* 超时错误     */
#define RT_EFULL         3 /* 资源已满     */
#define RT_EEMPTY        4 /* 无资源     */
#define RT_ENOMEM        5 /* 无内存     */
#define RT_ENOSYS        6 /* 系统不支持     */
#define RT_EBUSY         7 /* 系统忙     */
#define RT_EIO           8 /* IO 错误       */
#define RT_EINTR         9 /* 中断系统调用   */
#define RT_EINVAL       10 /* 非法参数      */copymistakeCopy Success

RT-Thread provides a series of operating system call interfaces to switch the thread state back and forth between these five states. The conversion relationship between several states is shown in the following figure:

Thread state transition diagram

The thread enters the initial state (RT_THREAD_INIT) by calling the function rt_thread_create/init(); the thread in the initial state enters the ready state (RT_THREAD_READY) by calling the function rt_thread_startup(); the thread in the ready state enters the running state (RT_THREAD_RUNNING) after being scheduled by the scheduler; when the thread in the running state calls rt_thread_delay(), rt_sem_take(), rt_mutex_take(), rt_mb_recv() and other functions or fails to obtain resources, it will enter the suspended state (RT_THREAD_SUSPEND); if the thread in the suspended state still fails to obtain resources after waiting for timeout or because other threads release resources, it will return to the ready state. If the thread in the suspended state calls the rt_thread_delete/detach() function, it will change to the closed state (RT_THREAD_CLOSE); and if the thread in the running state ends, the rt_thread_exit() function will be executed at the end of the thread to change the state to the closed state.

As mentioned above, system threads are threads created by the system, and user threads are threads created by user programs calling thread management interfaces. System threads in the RT-Thread kernel include idle threads and main threads.

Idle Thread

The idle thread is the lowest priority thread created by the system, and the thread state is always in the ready state. When there are no other ready threads in the system, the scheduler will schedule to the idle thread, which is usually an infinite loop and can never be suspended. In addition, the idle thread also has its special purpose in RT-Thread:

If a thread has finished running, the system will automatically delete the thread: the rt_thread_exit() function will be automatically executed, first the thread will be deleted from the system ready queue, then the thread's state will be changed to the closed state, and it will no longer participate in system scheduling, and then it will be hung in the rt_thread_defunct zombie queue (a thread queue whose resources have not been recycled and is in the closed state). Finally, the idle thread will recycle the resources of the deleted thread.

The idle thread also provides an interface to run the user-set hook function, which will be called when the idle thread is running. It is suitable for processing power management, watchdog feeding, etc. The idle thread must have a chance to be executed, that is, other threads are not allowed to be stuck in while(1) all the time, and must call a blocking function; otherwise, operations such as thread deletion and recycling will not be executed correctly.

Main Thread

When the system starts, the system will create the main thread, whose entry function is main_thread_entry(). The user's application entry function main() actually starts from here. After the system scheduler is started, the main thread starts running. The process is shown in the figure below. Users can add their own application initialization code in the main() function.

Main thread calling process

The previous two sections of this chapter have explained the concept of the functions and working mechanisms of threads. I believe that everyone is no longer unfamiliar with threads. This section will go deep into the various interfaces of RT-Thread threads and provide some source code to help readers understand threads at the code level.

The following figure describes the related operations of threads, including: creating/initializing threads, starting threads, running threads, and deleting/detaching threads. You can use rt_thread_create() to create a dynamic thread and use rt_thread_init() to initialize a static thread. The difference between dynamic threads and static threads is that for dynamic threads, the system automatically allocates stack space and thread handles from the dynamic memory heap (you can use create to create dynamic threads only after initializing the heap), while for static threads, the user allocates stack space and thread handles.

For a thread to become an executable object, the kernel of the operating system must create a thread for it. A dynamic thread can be created through the following interface:

rt_thread_t rt_thread_create(const char* name,
                            void (*entry)(void* parameter),
                            void* parameter,
                            rt_uint32_t stack_size,
                            rt_uint8_t priority,
                            rt_uint32_t tick);copymistakeCopy Success

When this function is called, the system allocates a thread handle from the dynamic heap memory and allocates the corresponding space from the dynamic heap memory according to the stack size specified in the parameter. The allocated stack space is aligned according to the RT_ALIGN_SIZE method configured in rtconfig.h. The parameters and return values ​​of thread creation rt_thread_create() are shown in the following table:

parameter

describe

name

The name of the thread; the maximum length of the thread name is specified by the macro RT_NAME_MAX in rtconfig.h, and the excess part will be automatically truncated

entry

Thread entry function

parameter

Thread entry function parameters

stack_size

Thread stack size in bytes

priority

The priority of the thread. The priority range depends on the system configuration (RT_THREAD_PRIORITY_MAX macro definition in rtconfig.h). If 256 priority levels are supported, the range is from 0 to 255. The smaller the value, the higher the priority. 0 represents the highest priority.

tick

The time slice size of the thread. The unit of the time slice (tick) is the clock beat of the operating system. When there are threads of the same priority in the system, this parameter specifies the maximum length of time that a thread can run in one scheduling. When this time slice runs out, the scheduler automatically selects the next ready thread of the same priority to run

return

——

thread

The thread is created successfully and the thread handle is returned

RT_NULL

Thread creation failed

For some threads created using rt_thread_create(), when they are no longer needed or when an error occurs during operation, we can use the following function interface to completely delete the thread from the system:

rt_err_t rt_thread_delete(rt_thread_t thread);copymistakeCopy Success

After calling this function, the thread object will be removed from the thread queue and deleted from the kernel object manager. The stack space occupied by the thread will also be released, and the reclaimed space will be reused for other memory allocations. In fact, using the rt_thread_delete() function to delete the thread interface only changes the corresponding thread state to the RT_THREAD_CLOSE state and then puts it into the rt_thread_defunct queue; the actual deletion action (releasing the thread control block and releasing the thread stack) needs to be completed by the idle thread the next time the idle thread is executed. The parameters and return values ​​of the thread deletion rt_thread_delete() interface are shown in the following table:

parameter

describe

thread

The thread handle to delete

return

——

RT_EOK

Thread deleted successfully

-RT_ERROR

Failed to delete thread

Note

Note: The rt_thread_create() and rt_thread_delete() functions are only valid when the system dynamic heap is enabled (that is, the RT_USING_HEAP macro is defined).

Thread initialization can be completed using the following function interface to initialize the static thread object:

rt_err_t rt_thread_init(struct rt_thread* thread,
                        const char* name,
                        void (*entry)(void* parameter), void* parameter,
                        void* stack_start, rt_uint32_t stack_size,
                        rt_uint8_t priority, rt_uint32_t tick);copymistakeCopy Success

The thread handle (or thread control block pointer) and thread stack of static threads are provided by the user. Static threads refer to thread control blocks and thread running stacks that are generally set as global variables, which are determined and allocated during compilation, and the kernel is not responsible for dynamically allocating memory space. It should be noted that the stack head address provided by the user needs to be aligned to the system (for example, 4-byte alignment is required on ARM). The parameters and return values ​​of the thread initialization interface rt_thread_init() are shown in the following table:

parameter

describe

thread

Thread handle. The thread handle is provided by the user and points to the corresponding thread control block memory address.

name

The name of the thread; the maximum length of the thread name is specified by the RT_NAME_MAX macro defined in rtconfig.h, and the excess part will be automatically truncated

entry

Thread entry function

parameter

Thread entry function parameters

stack_start

Thread stack start address

stack_size

The thread stack size in bytes. In most systems, stack space address alignment is required (for example, ARM architecture requires alignment to 4-byte addresses)

priority

The priority of the thread. The priority range depends on the system configuration (RT_THREAD_PRIORITY_MAX macro definition in rtconfig.h). If 256 priority levels are supported, the range is from 0 to 255. The smaller the value, the higher the priority. 0 represents the highest priority.

tick

The time slice size of the thread. The unit of the time slice (tick) is the clock beat of the operating system. When there are threads of the same priority in the system, this parameter specifies the maximum length of time that a thread can run in one scheduling. When this time slice runs out, the scheduler automatically selects the next ready thread of the same priority to run

return

——

RT_EOK

Thread creation successful

-RT_ERROR

Thread creation failed

For threads initialized with rt_thread_init(), using rt_thread_detach() will detach the thread object from the thread queue and kernel object manager. The thread detach function is as follows:

rt_err_t rt_thread_detach (rt_thread_t thread);copymistakeCopy Success

The parameters and return values ​​of the thread detachment interface rt_thread_detach() are shown in the following table:

parameter

describe

thread

The thread handle, which should be the thread handle initialized by rt_thread_init.

return

——

RT_EOK

Thread detached successfully

-RT_ERROR

Thread detachment failed

This function interface corresponds to the rt_thread_delete() function. The object operated by the rt_thread_delete() function is the handle created by rt_thread_create(), while the object operated by the rt_thread_detach() function is the thread control block initialized by the rt_thread_init() function. Similarly, the thread itself should not call this interface to detach from the thread itself.

The created (initialized) thread state is in the initial state and has not entered the scheduling queue of the ready thread. We can call the following function interface to put the thread into the ready state after the thread is initialized/created successfully:

rt_err_t rt_thread_startup(rt_thread_t thread);copymistakeCopy Success

When this function is called, the thread state will be changed to the ready state and placed in the corresponding priority queue to wait for scheduling. If the priority of the newly started thread is higher than that of the current thread, it will be switched to this thread immediately. The parameters and return values ​​of the thread startup interface rt_thread_startup() are shown in the following table:

parameter

describe

thread

Thread handle

return

——

RT_EOK

Thread started successfully

-RT_ERROR

Thread start failed

During the running of the program, the same section of code may be executed by multiple threads. During execution, the handle of the currently executing thread can be obtained through the following function interface:

rt_thread_t rt_thread_self(void);copymistakeCopy Success

The return value of this interface is shown in the following table:

return

describe

thread

The handle of the currently running thread

RT_NULL

Failed, the scheduler has not started yet

When the current thread's time slice is used up or the thread actively requests to give up the processor resources, it will no longer occupy the processor, and the scheduler will select the next thread of the same priority to execute. After the thread calls this interface, the thread is still in the ready queue. The thread gives up the processor using the following function interface:

rt_err_t rt_thread_yield(void);copymistakeCopy Success

After calling this function, the current thread first deletes itself from the ready priority thread queue, then hangs itself at the end of the priority queue linked list, and then activates the scheduler to perform thread context switching (if there is only one thread with the current priority, this thread continues to execute without context switching).

The rt_thread_yield() function is similar to the rt_schedule() function, but the system behaves completely differently when there are other ready threads of the same priority. After executing the rt_thread_yield() function, the current thread is swapped out, and the next ready thread of the same priority will be executed. After executing the rt_schedule() function, the current thread is not necessarily swapped out. Even if it is swapped out, it will not be placed at the end of the ready thread list. Instead, the highest priority thread in the system will be selected for execution (if there is no thread with a higher priority than the current thread in the system, then after executing the rt_schedule() function, the system will continue to execute the current thread).

In actual applications, we sometimes need to delay the current thread for a period of time and restart it after the specified time arrives. This is called "thread sleep". Thread sleep can use the following three function interfaces:

rt_err_t rt_thread_sleep(rt_tick_t tick);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_mdelay(rt_int32_t ms);copymistakeCopy Success

These three function interfaces have the same function. Calling them can make the current thread suspend for a specified period of time. After this period of time, the thread will be awakened and enter the ready state again. This function accepts a parameter that specifies the sleep time of the thread. The parameters and return values ​​of the thread sleep interface rt_thread_sleep/delay/mdelay() are shown in the following table:

parameter

describe

tick/ms

The thread sleep time: The parameter tick passed to sleep/delay is in units of 1 OS Tick; the parameter ms passed to mdelay is in units of 1ms;

return

——

RT_EOK

Operation successful

When a thread calls rt_thread_delay(), the thread will be suspended actively; when calling functions such as rt_sem_take() and rt_mb_recv(), the thread will be suspended if resources are unavailable. If the resource that the thread is waiting for times out (exceeds the set waiting time), the thread will no longer wait for these resources and return to the ready state; or, when other threads release the resources that the thread is waiting for, the thread will also return to the ready state.

Thread suspension uses the following function interface:

rt_err_t rt_thread_suspend (rt_thread_t thread);copymistakeCopy Success

The parameters and return values ​​of the thread suspension interface rt_thread_suspend() are shown in the following table:

parameter

describe

thread

Thread handle

return

——

RT_EOK

Thread suspended successfully

-RT_ERROR

The thread suspend failed because the thread is not in the ready state.

Note

Note: It is a very dangerous behavior for a thread to try to suspend another thread, so RT-Thread has strict usage restrictions on this function: this function can only be used to suspend the current thread (that is, suspend itself), and cannot be used to suspend thread B in thread A. And after suspending the thread itself, you need to call rt_schedule()the function immediately to manually switch the thread context. This is because when thread A tries to suspend thread B, thread A does not know what program thread B is running. Once thread B is using kernel objects such as mutexes and semaphores that affect and block other threads (such as thread C), if other threads are also waiting for this kernel object at this time, then thread A's attempt to suspend thread B will cause starvation of other threads (such as thread C), seriously endangering the real-time performance of the system.

Resuming a thread means making the suspended thread re-enter the ready state and putting the thread into the system's ready queue; if the resumed thread is at the first place in the highest priority list among all ready threads, the system will switch the thread context. Thread resumption uses the following function interface:

rt_err_t rt_thread_resume (rt_thread_t thread);copymistakeCopy Success

The parameters and return values ​​of the thread recovery interface rt_thread_resume() are shown in the following table:

parameter

describe

thread

Thread handle

return

——

RT_EOK

Thread resumed successfully

-RT_ERROR

Thread recovery failed because the thread state is not RT_THREAD_SUSPEND

When you need to perform some other control on the thread, such as dynamically changing the thread priority, you can call the following function interface:

rt_err_t rt_thread_control(rt_thread_t thread, rt_uint8_t cmd, void* arg);copymistakeCopy Success

The parameters and return values ​​of the thread control interface rt_thread_control() are shown in the following table:

Function parameters

describe

thread

Thread handle

cmd

Instruction control command

arg

Control parameters

return

——

RT_EOK

Control execution is correct

-RT_ERROR

fail

Indicator control command cmd currently supports the following commands:

  • RT_THREAD_CTRL_CHANGE_PRIORITY: dynamically change the thread priority;

  • RT_THREAD_CTRL_STARTUP: Start running a thread, equivalent to the rt_thread_startup() function call;

  • RT_THREAD_CTRL_CLOSE: Close a thread, equivalent to the rt_thread_delete() or rt_thread_detach() function call.

The idle hook function is the hook function of the idle thread. If the idle hook function is set, the idle hook function can be automatically executed to do other things when the system executes the idle thread, such as the system indicator light. The interface for setting/deleting the idle hook is as follows:

rt_err_t rt_thread_idle_sethook(void (*hook)(void));
rt_err_t rt_thread_idle_delhook(void (*hook)(void));copymistakeCopy Success

The input parameters and return values ​​of the idle hook function rt_thread_idle_sethook() are shown in the following table:

Function parameters

describe

hook

Set the hook function

return

——

RT_EOK

Set up for success

-RT_EFULL

Setup failed

The input parameters and return values ​​of the idle hook delete function rt_thread_idle_delhook() are shown in the following table:

Function parameters

describe

hook

Deleted hook function

return

——

RT_EOK

Deleted successfully

-RT_ENOSYS

Deletion failed

Note

Note: The idle thread is a thread that is always in the ready state, so the hook function must ensure that the idle thread will not be in the suspended state at any time. For example, functions that may cause the thread to suspend, such as rt_thread_delay() and rt_sem_take(), cannot be used. In addition, since malloc, free and other memory-related functions use semaphores as critical section protection, such functions are not allowed to be called in the hook function!

During the operation of the entire system, the system is in the process of thread running, interrupt triggering - responding to interrupts, switching to other threads, or even switching between threads, or the system context switching is the most common event in the system. Sometimes users may want to know what kind of thread switching occurred at a certain moment. You can set a corresponding hook function by calling the following function interface. This hook function will be called when the system thread switches:

void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to));copymistakeCopy Success

The input parameters for setting the scheduler hook function are shown in the following table:

Function parameters

describe

hook

Indicates a user-defined hook function pointer

The declaration of the hook function hook() is as follows:

void hook(struct rt_thread* from, struct rt_thread* to);copymistakeCopy Success

The input parameters of the scheduler hook function hook() are shown in the following table:

Function parameters

describe

from

Indicates the thread control block pointer that the system wants to switch out

to

Indicates the thread control block pointer to which the system is switching

Note

Note: Please write your hook function carefully. Any mistake may cause the whole system to malfunction (in this hook function, calling system API is basically not allowed, and the currently running context should not be suspended).

The following is an application example in the Keil simulator environment.

This example creates a dynamic thread and a static thread. After the static thread completes its task and is automatically recycled by the system, the dynamic thread with a lower priority can start running and printing information.

Note: RT-Thread 5.0 and later versions have ALIGNchanged the keyword to rt_align, so be careful when using it.

#include <rtthread.h>

#define THREAD_PRIORITY         25
#define THREAD_STACK_SIZE       512
#define THREAD_TIMESLICE        5

static rt_thread_t tid1 = RT_NULL;

/* 线程 1 的入口函数 */
static void thread1_entry(void *parameter)
{
    rt_uint32_t count = 0;

    for (count = 0; count < 10 ; count++)
    {
        /* 线程 1 采用低优先级运行 */
        rt_kprintf("thread1 count: %d\n", count);
        rt_thread_mdelay(500);
    }
    rt_kprintf("thread1 exit\n");
    /* 线程 1 运行结束后也将自动被系统脱离 */
}

#if defined(RT_VERSION_CHECK) && (RTTHREAD_VERSION >= RT_VERSION_CHECK(5, 0, 1))
    rt_align(RT_ALIGN_SIZE)
#else
    ALIGN(RT_ALIGN_SIZE)
#endif
static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程 2 入口 */
static void thread2_entry(void *param)
{
    rt_uint32_t count = 0;

    /* 线程 2 拥有较高的优先级,以抢占线程 1 而获得执行 */
    for (count = 0; count < 10 ; count++)
    {
        /* 线程 2 打印计数值 */
        rt_kprintf("thread2 count: %d\n", count);
    }
    rt_kprintf("thread2 exit\n");
    /* 线程 2 运行结束后也将自动被系统脱离 */
}

/* 线程示例 */
int thread_sample(void)
{
    /* 创建线程 1,名称是 thread1,入口是 thread1_entry*/
    tid1 = rt_thread_create("thread1",
                            thread1_entry, RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);

    /* 如果获得线程控制块,启动这个线程 */
    if (tid1 != RT_NULL)
        rt_thread_startup(tid1);

    /* 初始化线程 2,名称是 thread2,入口是 thread2_entry */
    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(thread_sample, thread sample);copymistakeCopy Success

The simulation results are as follows:

 \ | /
- RT -     Thread Operating System
 / | \     3.1.0 build Aug 24 2018
 2006 - 2018 Copyright by rt-thread team
msh >thread_sample
msh >thread2 count: 0
thread2 count: 1
thread2 count: 2
thread2 count: 3
thread2 count: 4
thread2 count: 5
thread2 count: 6
thread2 count: 7
thread2 count: 8
thread2 count: 9
thread2 exit
thread1 count: 0
thread1 count: 1
thread1 count: 2
thread1 count: 3
…copymistakeCopy Success

When thread 2 counts to a certain value, it will be completed, thread 2 will be automatically deleted by the system, and the counting will stop. Only then will thread 1 print the count.

Note

Note: Regarding thread deletion: Most threads are executed in a loop and do not need to be deleted; for threads that can be completed, RT-Thread will automatically delete the thread after the thread is completed, and the deletion action is completed in rt_thread_exit(). Users only need to understand the function of this interface, and it is not recommended to use this interface (this interface can be called by other threads or in the timer timeout function to delete a thread, but this is rarely used).

This example creates two threads and prints the count during execution, as shown in the following code:

#include <rtthread.h>

#define THREAD_STACK_SIZE   1024
#define THREAD_PRIORITY     20
#define THREAD_TIMESLICE    10

/* 线程入口 */
static void thread_entry(void* parameter)
{
    rt_uint32_t value;
    rt_uint32_t count = 0;

    value = (rt_uint32_t)parameter;
    while (1)
    {
        if(0 == (count % 5))
        {
            rt_kprintf("thread %d is running ,thread %d count = %d\n", value , value , count);
            if(count> 200)
                return;
        }
         count++;
     }
}

int timeslice_sample(void)
{
    rt_thread_t tid = RT_NULL;
    /* 创建线程 1 */
    tid = rt_thread_create("thread1",
                            thread_entry, (void*)1,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid != RT_NULL)
        rt_thread_startup(tid);


    /* 创建线程 2 */
    tid = rt_thread_create("thread2",
                            thread_entry, (void*)2,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE-5);
    if (tid != RT_NULL)
        rt_thread_startup(tid);
    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(timeslice_sample, timeslice sample);copymistakeCopy Success

The simulation results are as follows:

 \ | /
- RT -     Thread Operating System
 / | \     3.1.0 build Aug 27 2018
 2006 - 2018 Copyright by rt-thread team
msh >timeslice_sample
msh >thread 1 is running ,thread 1 count = 0
thread 1 is running ,thread 1 count = 5
thread 1 is running ,thread 1 count = 10
thread 1 is running ,thread 1 count = 15

thread 1 is running ,thread 1 count = 125
thread 1 is rthread 2 is running ,thread 2 count = 0
thread 2 is running ,thread 2 count = 5
thread 2 is running ,thread 2 count = 10
thread 2 is running ,thread 2 count = 15
thread 2 is running ,thread 2 count = 20
thread 2 is running ,thread 2 count = 25
thread 2 is running ,thread 2 count = 30
thread 2 is running ,thread 2 count = 35
thread 2 is running ,thread 2 count = 40
thread 2 is running ,thread 2 count = 45
thread 2 is running ,thread 2 count = 50
thread 2 is running ,thread 2 count = 55
thread 2 is running ,thread 2 count = 60
thread 2 is running ,thread 2 cunning ,thread 2 count = 65
thread 1 is running ,thread 1 count = 135

thread 2 is running ,thread 2 count = 205copymistakeCopy Success

From the running count results, we can see that the running time of thread 2 is half of that of thread 1.

When the thread performs scheduling switching, scheduling will be executed. We can set a scheduler hook so that we can do some extra things when the thread switches. This example prints the switching information between threads in the scheduler hook function, as shown in the following code:

Note: RT-Thread5.0 and higher versions struct rt_threadmove the name member of the structure to parent. When using it, the code needs to be thread->namechanged from thread->parent.name, otherwise the compilation will report an error!

#include <rtthread.h>

#define THREAD_STACK_SIZE   1024
#define THREAD_PRIORITY     20
#define THREAD_TIMESLICE    10

/* 针对每个线程的计数器 */
volatile rt_uint32_t count[2];

/* 线程 1、2 共用一个入口,但入口参数不同 */
static void thread_entry(void* parameter)
{
    rt_uint32_t value;

    value = (rt_uint32_t)parameter;
    for (int count = 0; count < 10 ; count++)
    {
        rt_kprintf("thread %d is running\n", value);
        rt_thread_mdelay(1000); // 延时一段时间
    }
}

static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;

static void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to)
{
#if defined(RT_VERSION_CHECK) && (RTTHREAD_VERSION >= RT_VERSION_CHECK(5, 0, 1))
    rt_kprintf("from: %s -->  to: %s \n", from->parent.name ,to->parent.name);
#else
    rt_kprintf("from: %s -->  to: %s \n", from->name , to->name);
#endif
}

int scheduler_hook(void)
{
    /* 设置调度器钩子 */
    rt_scheduler_sethook(hook_of_scheduler);

    /* 创建线程 1 */
    tid1 = rt_thread_create("thread1",
                            thread_entry, (void*)1,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid1 != RT_NULL)
        rt_thread_startup(tid1);

    /* 创建线程 2 */
    tid2 = rt_thread_create("thread2",
                            thread_entry, (void*)2,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY,THREAD_TIMESLICE - 5);
    if (tid2 != RT_NULL)
        rt_thread_startup(tid2);
    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(scheduler_hook, scheduler_hook sample);

int scheduler_del(void)
{
    rt_scheduler_sethook(RT_NULL);
    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(scheduler_del, scheduler_del sample);copymistakeCopy Success

The simulation results are as follows:

 \ | /
- RT -     Thread Operating System
 / | \     3.1.0 build Aug 27 2018
 2006 - 2018 Copyright by rt-thread team
msh > scheduler_hook
msh >from: tshell -->  to: thread1
thread 1 is running
from: thread1 -->  to: thread2
thread 2 is running
from: thread2 -->  to: tidle
from: tidle -->  to: thread1
thread 1 is running
from: thread1 -->  to: tidle
from: tidle -->  to: thread2
thread 2 is running
from: thread2 -->  to: tidle
…copymistakeCopy Success

From the simulation results, we can see that when switching threads, the scheduler hook function is working normally and has been printing thread switching information, including switching to idle threads. You can use scheduler_delthe scheduler hook function to cancel.

Last updated

Assoc. Prof. Wiroon Sriborrirux, Founder of Advance Innovation Center (AIC) and Bangsaen Design House (BDH), Electrical Engineering Department, Faculty of Engineering, Burapha University