RT-Thread thread management (study notes)

This article refers to [Wildfire EmbedFire] "RT-Thread Kernel Implementation and Application Development - Based on STM32", which is only used as a personal study note. For more detailed content and steps, please check the original text (you can download it from the Wildfire Data Download Center)

thread basic concepts

An RT-Thread thread can be thought of as a collection of independent threads. Each thread runs in its own environment. At any time, only one thread is running, and the RT-Thread scheduler decides which thread to run. The scheduler will continuously start and stop each thread, and it seems that all threads are executing at the same time. As a thread, you do not need to know anything about the activities of the scheduler. It is the main responsibility of the scheduler to save the context (register values, stack contents) when the thread is switched in and out. To achieve this, each RT-Thread thread needs to have its own stack. When a thread is cut out, its execution environment will be saved in the thread's stack, so that when the thread runs again, the last running environment can be correctly restored from the stack.

The thread module of RT-Thread can provide users with multiple threads, realize switching and communication between threads, and help users manage business program processes. In this way, users can devote more energy to the realization of business functions.

--original

Basic Concepts of Thread Scheduler

The thread scheduler provided in RT-Thread is a priority-based full preemptive scheduling: in the system, except for the interrupt handling function, the code of the lock part of the scheduler and the code to disable interrupts, other parts of the system cannot be preempted. All are preemptible, including the thread scheduler itself. The system supports a total of 256 priorities from 0 to 255. The smaller the value, the higher the priority. 0 is the highest priority, and 255 is allocated to idle threads, which are generally not used by users. In some systems with limited resources, the system configuration that only supports 8 or 32 priorities can be selected according to the actual situation). 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.

——RT-Thread Official Chinese Manual

The concept of thread state

During the running process of a thread, only one thread is allowed to run in the processor at a time. Divided from the running process, a thread has a variety of different running states, such as running state, non-running state, etc. In the RT-Thread real-time operating system, the thread contains five states, and the operating system will automatically adjust its state dynamically according to its running situation. The five thread states in RT-Thread are as follows:

——RT-Thread Official Chinese Manual

condition describe
RT_THREAD_INIT Thread initial state. It is in this state when the thread has just been created and has not started running; in this state, the thread does not participate in scheduling.
RT_THREAD_SUSPEND Suspended and blocked. The thread is suspended at this time: it may be suspended waiting because the resource is unavailable; or the thread actively delays for a period of time and is suspended. In this state, the thread does not participate in scheduling.
RT_THREAD_READY ready state. The thread is running; or after the current thread has finished running and yields the processor, the operating system looks for the highest-priority ready thread to run.
RT_THREAD_RUNNING running state. The thread is currently running. On a single-core system, only the thread returned by the rt_thread_self() function is in this state; on a multi-core system, it is not subject to this restriction.
RT_THREAD_CLOSE Thread end state. It will be in this state when the thread finishes running. Threads in this state do not participate in thread scheduling.

thread state migration

RT-Thread provides a series of operating system call interfaces, so that threads can switch back and forth between five running states. The following figure is a schematic diagram of switching between several states:

Image source: RT-Thread official Chinese manual

insert image description here

The thread rt_thread_create/initenters the initial state ( RT_THREAD_INIT ) by calling the function; then rt_thread_startupenters the ready state ( RT_THREAD_READY ) by calling the function; when the thread in the ready state calls rt_thread_delay, rt_sem_take, rt_mb_recvand other functions or because it cannot obtain resources, it will enter the suspended state ( RT_THREAD_SUSPEND ); a thread in a suspended state will return to the ready state if it fails to obtain resources after waiting for a timeout or because other threads release resources. If the thread in the suspended state is called, it rt_thread_delete/detachwill change to the closed state ( RT_THREAD_CLOSE ); while the thread in the running state will change to the closed state ( RT_THREAD_CLOSErt_thread_exit ) by executing the function in the last part of the thread if the running ends .

——RT-Thread Official Chinese Manual

Common thread functions

The following are some commonly used thread manipulation functions.
Thread suspension function rt_thread_suspend()

When a thread calls rt_thread_delay, the calling thread will suspend actively. When calling functions such as rt_sem_take and rt_mb_recv, the unusable resources will also cause the calling thread to suspend. A thread in a suspended state, if the resources it waits for time out (exceeds its set waiting time), then the thread will no longer wait for these resources and return to the ready state; or, when other threads release the thread waiting for resource, the thread also returns to the ready state.

——RT-Thread Official Chinese Manual

Note: Usually this function should not be used to suspend the thread itself. If you really need to use the rt_thread_suspend function to suspend the current thread, you need to call the rt_schedule() function immediately after calling the rt_thread_suspend() function to perform manual thread context switching.

--original

Example of use (original code list)

rt_kprintf("挂起 LED1 线程!\n");
uwRet = rt_thread_suspend(led1_thread);/* 挂起 LED1 线程 */
if (RT_EOK == uwRet)
{
    
    
	rt_kprintf("挂起 LED1 线程成功!\n");
}else
{
    
    
	rt_kprintf("挂起 LED1 线程失败!失败代码:0x%lx\n",uwRet);
}

Thread resume function rt_thread_resume()

Thread recovery is to make the suspended thread re-enter the ready state. If the resumed thread is at the first position in the highest priority list among all ready state threads, the system will switch the thread context.

——RT-Thread Official Chinese Manual

Example of use (original code list)

rt_kprintf("恢复 LED1 线程!\n");
uwRet = rt_thread_resume(led1_thread);/* 恢复 LED1 线程! */
if (RT_EOK == uwRet)
{
    
    
	rt_kprintf("恢复 LED1 线程成功!\n");
}else
{
    
    
	rt_kprintf("恢复 LED1 线程失败!失败代码:0x%lx\n",uwRet)
}

Overview of thread related interfaces

Excerpted from "RT-Thread Official Chinese Manual"

function thread safety interrupt routine Function Details
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); Safety not callable When calling this function, the system will allocate a thread handle (ie TCB, thread control block) from the dynamic heap memory and allocate 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.
rt_err_t rt_thread_delete(rt_thread_t thread); Safety callable 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 just changes the corresponding thread state to RT_THREAD_CLOSE state, and then puts it into the rt_thread_defunct queue; and the real deletion action (release thread control block and release thread stack) needs to be executed next time When the idle thread is idle, the last thread deletion action is completed by the idle thread. Static threads initialized with rt_thread_init cannot be deleted using this interface.
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); Safety callable The rt_thread_init function is used to initialize the static thread object. The thread handle (or thread control block pointer) and thread stack are provided by the user. Static thread means that the thread control block and thread running stack are generally set as global variables, which are determined and allocated at compile time, 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 system aligned (for example, 4-byte alignment is required on ARM).
rt_err_t rt_thread_detach(rt_thread_t thread); Safety callable 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(), and the object operated by the rt_thread_detach() function is the thread control block initialized by the rt_thread_init() function. Likewise, the thread itself should not call this interface to detach from the thread itself.
rt_err_t rt_thread_startup(rt_thread_t thread); Safety callable When this function is called, the state of the thread will be changed to the ready state and placed in the corresponding priority queue for scheduling. If the newly started thread has a higher priority than the current thread, it will switch to this thread immediately.
rt_thread_t rt_thread_self(void); Safety callable During the running of the program, the same piece 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. Note: Please do not call this function in the interrupt service routine, because it cannot accurately obtain the current execution thread. When the scheduler is not started, this interface returns RT_NULL.
rt_err_t rt_thread_yield(void); Safety callable 调用该函数后,当前线程首先把自己从它所在的就绪优先级线程队列中删除,然后把自己挂到这个优先级队列链表的尾部,然后激活调度器进行线程上下文切换(如果当前优先级只有这一个线程,则这个线程继续执行,不进行上下文切换动作)。
rt_err_t rt_thread_sleep(rt_tick_t tick); rt_err_t rt_thread_delay(rt_tick_t tick); 安全 不可调用 这两个函数接口的作用相同,调用它们可以使当前线程挂起一段指定的时间,当这个时间过后,线程会被唤醒并再次进入就绪状态。这个函数接受一个参数,该参数指定了线程的休眠时间(单位是OS Tick时钟节)。
rt_err_t rt_thread_suspend(rt_thread_t thread); 安全 可调用 当线程调用rt_thread_delay,调用线程将主动挂起,当调用rt_sem_take,rt_mb_recv等函数时,资源不可使用也将导致调用线程挂起。处于挂起状态的线程,如果其等待的资源超时(超过其设定的等待时间),那么该线程将不再等待这些资源,并返回到就绪状态;或者,当其它线程释放掉该线程所等待的资源时,该线程也会返回到就绪状态。• 注:通常不应该使用这个函数来挂起线程本身,如果确实需要采用rt_thread_suspend函数挂起当前任务,需要在调用rt_thread_suspend()函数后立刻调用rt_schedule()函数进行手动的线程上下文切换。
rt_err_t rt_thread_resume(rt_thread_t thread); 安全 可调用 线程恢复就是让挂起的线程重新进入就绪状态,如果被恢复线程在所有就绪态线程,位于最高优先级链表的第一位,那么系统将进行线程上下文的切换。
rt_err_t rt_thread_control(rt_thread_t thread, rt_uint8_t cmd, void* arg); 安全 可调用 指示控制命令cmd当前支持的命令包括 RT_THREAD_CTRL_CHANGE_PRIORITY - 动态更改线程的优先级;RT_THREAD_CTRL_STARTUP - 开始运行一个线程,等同于rt_thread_startup()函数调用; RT_THREAD_CTRL_CLOSE - 关闭一个线程,等同于rt_thread_delete()函数调用。
void rt_thread_idle_init(void); 不安全 不可调用 系统运行过程中必须存在一个最终可运行的线程,可以调用该函数初始化空闲线程
void rt_thread_idle_sethook(void (*hook)(void)); 不安全 不可调用 当空闲线程运行时会自动执行设置的钩子函数,由于空闲线程具有系统的最低优先级,所以只有在空闲的时候才会执行此钩子函数。空闲线程是一个线程状态永远为就绪态的线程,因此设置的钩子函数必须保证空闲线程在任何时刻都不会处于挂起状态,例如rt_thread_delay() , rt_sem_take() 等可能会导致线程挂起的函数都不能使用。

线程设计

程序上下文
线程的5种状态构成了程序运行的上下文状态,RT-Thread中程序运行的上下文包括:

  • 中断服例程:它运行在非线程的执行环境下,中断服务程序最好保持精简短小,因为中断服务是一种高于任何线程的存在;
  • 普通线程:不应该使用死循环;
  • 空闲线程:通常这个空闲线程钩子能够完成
    一些额外的特殊功能,例如系统运行状态的指示,系统省电模式等。

真正的系统资源回收工作在idle线程(空闲线程)完成,所以。对于空闲线程钩子上挂接的程序,它应该:

  • 不会挂起的idle线程;
  • 不应该陷入死循环,需要留出部分时间用于系统处理僵尸线程的系统资源回收。

线程设计要点
在线程设计时,我们需要考虑到:

  • 上下文环境
    对于工作内容,首先需要考虑运行环境,多个工作内容是否有重叠,能否一起处理。例如键盘事件:正常

  • 线程的状态跃迁
    状态跃迁指的是线程运行状态的变化,在进行线程设计时,应该保证线程在不活跃的时候,必须让出处理器,即让它进入阻塞状态。

  • 线程运行时间长度

线程运行时间长度被定义为,在线程所关心的一种事件或多种事件触发状态下,线程由阻塞态跃迁为就绪态执行设定的工作,再从就绪态跃迁为阻塞态所需要的时间(一般还应加上这段时间内,这个线程不会被其它线程所抢占的先决条件)。线程运行时间长度将和线程的优先级设计密切相关,同时也决定着设计的系统是否能够满足预计的实时响应的指标。

例如,对于事件A对应的服务线程Ta,系统要求的实时响应指标是1ms,而Ta的最大运行时间是500us。此时,系统中还存在着以50ms为周期的另一线程Tb,它每次运行的最大时间长度是100us。在这种情况下,即使把线程Tb的优先级抬到比Ta更高的位置,对系统的实时性指标也没什么影响(因为即使在Ta的运行过程中,Tb抢占了Ta的资源,但在规定的时间内(1ms),Ta也能够完成对事件A的响应)。

——RT-Thread官方中文手册

线程管理实验

此实验参考原文对应实验代码,只包含man()函数,按键驱动及其他外设初始化相关代码下面未给出。

要想按键线程可以控制LED线程,前者的线程优先级必须高于后者。

#include "board.h"
#include "rtthread.h"


// 定义线程控制块指针
static rt_thread_t led0_thread = RT_NULL;
static rt_thread_t key_thread = RT_NULL;

/******************************************************************************
* @ 函数名  : led0_thread_entry
* @ 功  能  : LED0线程入口函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void led0_thread_entry(void *parameter)
{
    
    
	while(1)
	{
    
    
		LED0(ON);
		rt_thread_delay(500); // 500个tick(500ms)
		rt_kprintf("led0_thread 正在运行, LED0_ON\r\n");
		LED0(OFF);
		rt_thread_delay(500);
		rt_kprintf("led0_thread 正在运行, LED0_OFF\r\n");
	}
}

/******************************************************************************
* @ 函数名  : key_thread_entry
* @ 功  能  : 按键线程入口函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void key_thread_entry(void *parameter)
{
    
    
	rt_err_t uwRet = RT_EOK;
	while(1)
	{
    
    
		// KEY0 被按下
		if(Key_Scan(KEY0_GPIO_PORT, KEY0_GPIO_PIN) == KEY_ON)
		{
    
    
			rt_kprintf("挂起 led0_thread。\n");
			uwRet = rt_thread_suspend(led0_thread); // 挂起 led0_thread
			if(uwRet == RT_EOK)
			{
    
    
				rt_kprintf("挂起线程成功!\n");
			}
			else
			{
    
    
				rt_kprintf("挂起线程失败!\n");
			}
		}
		
		// WK_UP 被按下
		if(Key_Scan(WK_UP_GPIO_PORT, WK_UP_GPIO_PIN) == KEY_ON)
		{
    
    
			rt_kprintf("恢复 led0_thread。\n");
			uwRet = rt_thread_resume(led0_thread); // 恢复 led0_thread
			if(uwRet == RT_EOK)
			{
    
    
				rt_kprintf("恢复线程成功!\n");
			}
			else
			{
    
    
				rt_kprintf("恢复线程失败!\n");
			}
		}
		rt_thread_delay(20);
	}
}

int main(void)
{
    
    
	// 硬件初始化和RTT的初始化已经在component.c中的rtthread_startup()完成

	// 创建一个动态线程
	led0_thread =                                 // 线程控制块指针
	rt_thread_create("led0",                      // 线程名字
	                led0_thread_entry,            // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    5,                            // 线程优先级
					10);                          // 线程时间片
	
	
	// 开启线程调度
	if(led0_thread != RT_NULL)
		rt_thread_startup(led0_thread);
	else
		return -1;
							
	// 创建一个按键线程
	key_thread =                                 // 线程控制块指针
	rt_thread_create("key",                      // 线程名字
	                key_thread_entry,            // 线程入口函数
	                RT_NULL,                     // 入口函数参数
	                255,                         // 线程栈大小
				    4,                           // 线程优先级
					10);                         // 线程时间片
	// 开启线程调度
	if(key_thread != RT_NULL)
		rt_thread_startup(key_thread);
	else
		return -1;
}

遇到个小坑,野火的按键初始化中GPIO模式设置为浮空输入,这应该是因为野火开发板有硬件下拉,但正点原子没有硬件下拉,所以要设置为下拉输入;另外,野火的按键扫描函数没加消抖。

实验现象

When the key KEY0is pressed, the led0_thread thread is suspended, and when the key WK_UPis pressed, the led0_thread thread continues to run. (But I don't know why sometimes short press the button will suspend or fail to resume)

insert image description here

Guess you like

Origin blog.csdn.net/weixin_43772810/article/details/123663748