RT-Thread Quick Start - Thread Management (Part 2)

 

This article focuses on the interface functions related to thread management provided by RT-thread, as well as practical demonstrations.

First: thread creation

In RT-Thread, there are two ways to create a thread:

  • In the dynamic creation mode, the thread stack and thread control block are allocated by the system from the dynamic memory heap.

  • In the static creation method, the thread stack and thread control block are allocated by user definition.

1. Dynamically create threads

Dynamically create threads, users do not need to consider the issue of thread stack and thread control block space allocation, all allocation is done automatically by the system. Users only need to care about other key thread attributes.

The interface function of RT-Thread to dynamically create a thread is  rt_thread_create(), and its function prototype is:

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)

The detailed parameters of this function have been explained in detail in the previous article, so I won't repeat them here.

The key parameters are:

  • The thread entry function pointer  entryrequires the user to define a function. When creating a thread, put the function name in this parameter position.

  • Thread stack size  stack_size, in bytes. Set this parameter according to the actual situation, and how to determine this value will be analyzed later.

  • Thread priority  priority. The priority value is determined according to the importance of the task that the thread needs to complete. The smaller the value, the higher the priority.

  • Time slice tick, the unit is the system clock beat, this parameter will only be used if there are threads with the same priority.

Example of dynamically creating threads:

/* 线程入口函数 */
void thread_entry(void *parameter)
{
    ...
}

/* 定义线程控制块指针 */
rt_thread_t tid = RT_NULL;
/* 创建线程 */
tid = rt_thread_create("thread_test", thread_entry, 
                                                RT_NULL, 512, 10, 5);

First define a thread control block pointer (thread handle), and then call  rt_thread_create() the function to create a thread.

The name of this thread is “thread_test”; the thread entry function  thread_entry; the parameter of the entry function is  RT_NULL, no entry parameter; the size of the thread stack is 512 bytes; the thread priority is 10; the thread time slice is 5.

2. Create threads statically

Creating a thread in a static way requires the user to consider a little more things: thread control block definition, thread stack space application, thread stack start address, etc.

Statically create a thread in two steps:

  • User-defined thread control block structure variables, apply for thread stack memory space.

  • Initialize the thread control block, that is, initialize the thread.

Thread control block (thread handle) definition can be done by defining  struct rt_thread structure variables:

struct rt_thread  thread_static;

The thread stack can be allocated by defining an array, or by dynamic memory allocation:

/* 数组方式确定线程栈,应该定义成全局数组 */
char thread_stack[1024];

/* 动态内存申请方式,确定线程栈 */
char *thread_stack = (char *)rt_malloc(1024);

The  rt_malloc() functions will be explained in detail in the memory management article later.

After the thread control block and thread stack are defined, they need to be initialized. RT-Thread provides the thread initialization function interface rt_thread_init(), whose function prototype is defined as:

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)

The parameters of this function are explained as follows:

parameter describe
thread Thread handle, provided by the user, points to the memory address of the thread control block
name thread name
entry thread entry function
parameter Parameters of the thread entry function
stack_start Thread stack starting address
stack_size Thread stack size, in bytes.
priority The priority of the thread.
tick The time slice size of the thread.

If the function executes successfully, it returns RT_EOK; if it fails, it returns -RT_EOK.

It should be noted that the stack head address provided by the user needs to be aligned by the system, for example, the CPU of the ARM architecture needs to be aligned by 4 bytes.

Example of creating threads statically:

/* 线程栈起始地址做内存对齐 */
ALIGN(RT_ALIGN_SIZE)
char thread_stack[1024];
/* 定义线程控制块 */
struct rt_thread thread;

/* 线程入口函数 */
void thread_entry(void *parameter)
{
    ...
}

/* 初始化线程控制块 */
rt_thread_init(&thread, "thread_test", thread_entry,
                RT_NULL, &thread_stack[0], sizeof(thread_stack),
                10, 5);

First define the thread stack and the thread control block, and then initialize the thread control block.

thread The thread handle is the address of  the thread control block &thread; the thread name is  "thread_test"; the thread entry function is  thread_entry; the parameter of the entry function is RT_NULL; the starting address of the thread stack is the starting address of the defined array; the thread stack size is the number of bytes of the array; priority The level is 10; the time slice is 5.

Second: Determine the key parameters of the thread

Creating a thread has several key parameters that need to be determined by the user:

  • thread stack size

  • thread priority

  • thread time slice

For beginners, the determination of these parameters is not easy to grasp, or in other words, it is not known how appropriate the settings are.

In fact, there is no unified standard for the determination of these parameters, and decisions need to be made based on actual applications and specific analysis.

1. Determination of thread stack size

In RTOS-based programming, each thread (task) needs its own stack space, and the stack size required by each thread will vary depending on the application.

The content that needs to use the stack space is as follows:

  • The items that need to use stack space for a function call are: function local variables, function parameters, function return address, and function internal state.

  • Context for thread switching. The registers needed for thread switching need to be stacked.

  • During task execution, an interruption occurs. Registers need to be pushed onto the stack.

Adding these together in practical applications can roughly get the minimum requirements of the stack, but the calculation is very troublesome. When actually allocating the stack size, you can roughly calculate a value and double it, which is safer.

2. Thread priority assignment

In RT-Thread, the smaller the thread priority value, the higher its priority. Idle tasks have the lowest priority.

There is no specific standard for the allocation of thread priority. Generally, it is configured according to specific application conditions.

In order to enable an event to be processed in a timely manner, the thread processing the event can be set to a higher priority. For example, key detection, touch detection, serial port data processing and so on.

For those threads whose real-time processing is not very high, you can configure a lower priority. For example, LED blinking, interface display and so on.

3. Thread time slice allocation

For thread scheduling with the same priority, if the thread time slice is allocated longer, the execution time of the thread will be longer.

According to the actual application situation, if a certain thread takes a long time to complete a certain transaction, a larger time slice can be allocated to it. Threads that take less time are assigned smaller time slices.

If there are no threads of the same priority in the application, this parameter has no effect.

Third: thread sleep

In RTOS, if you need to wait for a while, you must not use ordinary delay waiting (CPU idling), you should call the delay waiting function provided by RTOS. If ordinary delay is used, then RTOS loses its real-time performance and wastes CPU resources.

RT-Thread provides a system function to delay the current thread for a period of time, and rerun the thread after the specified time. Thread sleep can use the following three functions:

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); /* 单位为 毫秒 */

These three functions have the same function. Calling them can make the current thread enter the suspended state and last for a specified period of time. After this time is reached, the thread will wake up and enter the ready state again.

rt_thread_sleep/delay() parameter  tick, the unit is 1 system clock beat (OS tick).

rt_thread_mdelay() parameter  ms, the unit is 1ms.

The return value of the function is RT_EOK.

Making a thread sleep, that is, calling one of these three functions, is also a way to give up CPU authority, allowing low-priority threads to be executed.

If the high-priority thread does not yield CPU operations, then the low-priority thread will never get CPU execution permission, causing problems.

Therefore, the high-priority thread either waits for a certain system resource to be unavailable and enters the suspended state, or calls these three sleep functions to enter the suspended state, thereby giving the low-priority thread a chance to execute.

Fourth: Example of thread creation

This is used to demonstrate how to use the thread creation function described above:

#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;
    while (1)
    {
        /* 线程1采用低优先级运行,一直打印计数值 */
        rt_kprintf("thread1 count: %d\n", count ++);
        /* 延时 500ms */
        rt_thread_mdelay(500);
    }
}

ALIGN(RT_ALIGN_SIZE)
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 main()
{
    /* 创建线程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);
}

This example creates threads in two ways: statically and dynamically. One thread is automatically deleted by the system after running, and the other thread keeps printing the count.

Compile and run, the result is as follows

picture

 

Fifth: System thread

A system thread refers to a thread created by the system, while a user thread is a thread created by a user program calling a thread creation function. There are two system threads in the RT-Thread kernel:

  • idle thread

  • main thread

1. Idle thread

An idle thread is the lowest priority thread that is always in a ready state. When there are no other ready threads in the system, the scheduler allocates the CPU

Permissions are given to idle threads. An idle thread can never be suspended.

The idle thread of RT-Thread has special purpose:

  • The idle thread reclaims the resources of the deleted thread.

  • An idle thread hook function can be set and called in an idle thread.

2. Main thread

When the system starts, it will automatically create the main thread, and its entry function is  main_thread_entry(), and the user's application entry function  main() starts from here.

RT-Thread system startup process, you can refer to the article: RT-Thread Quick Start - Understand the kernel startup process

After the system scheduler starts, the main thread starts to run, and the function calling process is shown in the following figure:

picture

 

Users can  main() add their own application code in the function.

Sixth: Other thread management functions

Here is a list of other thread management function interfaces provided by RT-Thread, beginners can take it as an understanding. If you want to learn in detail, you can check the official programming manual.

1. Delete thread

When the thread created by rt_thread_create() is not needed, you can use the following function interface to delete it completely:

rt_err_t rt_thread_delete(rt_thread_t thread); 

The parameter of the function  thread is the thread control block pointer.

The function of this function is to delete the thread object from the thread queue, release the heap space occupied by the thread, and change the corresponding thread state to  RT_THREAD_CLOSE state.

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

rt_err_t rt_thread_detach (rt_thread_t thread);

The thread itself will not call these two functions, it should be called by other threads to delete a certain thread.

2. Get the current running thread

RT-Thread provides a function interface for querying the currently executing thread handle:

rt_thread_t rt_thread_self(void);

The return value of this function is the thread control block pointer (thread handle).

If the call fails, RT_NULL is returned, indicating that the system scheduler has not been started.

3. The thread yields the processor

A thread in the running state can voluntarily give up the CPU usage permission by calling the following function:

rt_err_t rt_thread_yield(void);

After calling this function, the current thread first deletes itself from the ready priority thread queue where it is located, then hangs itself to the end of the priority queue list, and then activates the scheduler for thread context switching (if the current priority only has this A thread, the thread continues to execute without context switching).

4. Suspend and resume threads

The function interface of thread suspension is as follows:

rt_err_t rt_thread_suspend (rt_thread_t thread);

The parameter  thread is the thread handle (thread control block pointer).

If the thread suspends successfully, it returns RT_OK; if the suspend fails, it returns -RT_ERROR.

Note that normally this function should not be used to suspend the thread itself.

To resume a suspended thread is to let it re-enter the ready state and put the thread in the system's ready queue. The function interface for thread recovery is:

rt_err_t rt_thread_resume (rt_thread_t thread);

5. Thread of control

When you need to heap a thread for other control, you can call the following function interface:

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

The parameter  thread is a thread handle; the parameter  cmd is a control instruction; and the parameter arg is a control instruction parameter.

Returns  RT_EOK, indicating that the execution is successful. Returned  -RT_ERROR, indicating that the execution failed.

Indication control commands  cmd currently support the following commands:

  • RT_THREAD_CTRL_CHANGE_PRIORITY, dynamically change the thread priority.

  • RT_THREAD_CTRL_STARTUP, starts running a thread.

  • RT_THREAD_CTRL_CLOSE, to close a thread.

  • RT_THREAD_CTRL_BIND_CPU, bind the thread to a certain CPU.

6. Set and delete the idle hook function

RT-Thread provides a function interface to set the idle hook function:

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

The hook function runs automatically in an idle thread, and you can do some other things in the hook function, such as blinking the system indicator light.

An idle thread must always be ready, so a hook function cannot call a function that suspends the thread.

Seventh: Summary

So far, the content related to RT-Thread thread management has been learned. These two articles explain the theoretical knowledge related to RT-Thread threads and the system function interfaces provided.

And combined with experiments to demonstrate the use of thread creation and thread delay. Other thread management is briefly introduced.

For getting started, after understanding the basics of threads, you can use the thread creation function.

For in-depth study, you can refer to the official programming manual to learn more about thread management and other function interfaces.

Guess you like

Origin blog.csdn.net/weixin_41114301/article/details/131876567