RT-Thread 中的多线程

简介: RT-Thread 中的多线程

RT-Thread 中的多线程


博主介绍

RT-Thread 线程管理和调度

系统线程

空闲线程

主线程

线程管理

RT-Thread 自动初始化机制

在新线程控制LED

💫点击直接资料领取💫


博主介绍

🌊 作者主页:苏州程序大白


🌊 作者简介:🏆CSDN人工智能域优质创作者🥇,苏州市凯捷智能科技有限公司创始之一,目前合作公司富士康、歌尔等几家新能源公司


💬如果文章对你有帮助,欢迎关注、点赞、收藏(一键三连)和C#、Halcon、python+opencv、VUE、各大公司面试等一些订阅专栏哦


💅 有任何问题欢迎私信,看到会及时回复


RT-Thread 线程管理和调度


RT-Thread 线程管理的主要功能是对线程进行管理和调度,系统中总共存在两类线程,分别是系统线程和用户线程,系统线程是由 RT-Thread 内核创建的线程,用户线程是由应用程序创建的线程,这两类线程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除,如下方图所示,每个线程都有重要的属性,如线程控制块、线程栈、入口函数等。


a20a263eeae14c4a866a7a1bd0e9e77d.png


RT-Thread的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到CPU的使用权。


当调度器调度线程切换时,先将当前线程上下文保存起来,当再切回到这个线程时,线程调度器将该线程的上下文信息恢复。


fc3f17cb0a1140b4b6c45938ead30996.png


线程通过调用函数rt_thread_create/init()进入到初始状态(RT_THREAD_INIT);初始状态的线程通过调用函数 rt_thread_startup() 进入到就绪状态(RT_THREAD_READY);就绪状态的线程被调度器调度后进入运行状态(RT_THREAD_RUNNING);当处于运行状态的线程调用 rt_thread_delay(),rt_sem_take(),rt_mutex_take(),rt_mb_recv() 等函数或者获取不到资源时,将进入到挂起状态(RT_THREAD_SUSPEND);处于挂起状态的线程,如果等待超时依然未能获得资源或由于其他线程释放了资源,那么它将返回到就绪状态。挂起状态的线程,如果调用rt_thread_delete/detach() 函数,将更改为关闭状态(RT_THREAD_CLOSE);而运行状态的线程,如果运行结束,就会在线程的最后部分执行 rt_thread_exit() 函数,将状态更改为关闭状态。


系统线程


系统线程是指由系统创建的线程,用户线程是由用户程序调用线程管理接口创建的线程,在 RT-Thread 内核中的系统线程有空闲线程和主线程。


msh >list_thread
thread pri status sp stack size max used left tick error
-------- --- ------- ---------- ---------- ------ ---------- ---
tshell 20 running 0x00000084 0x00001000 12% 0x00000004 000
tidle0 31 ready 0x00000044 0x00000100 34% 0x0000000a 000


空闲线程


空闲线程是系统创建的最低优先级的线程,线程状态永远为就绪态。当系统中无其他就绪线程存在时,调度器将调度到空闲线程,它通常是一个死循环,且永远不能被挂起。另外,空闲线程在 RT-Thread 也有着它的特殊用途:


若某线程运行完毕,系统将自动删除线程:自动执行 rt_thread_exit() 函数,先将该线程从系统就绪队列中删除,再将该线程的状态更改为关闭状态,不再参与系统调度,然后挂入 rt_thread_defunct僵尸队列(资源未回收、处于关闭状态的线程队列)中,最后空闲线程会回收被删除线程的资源。


空闲线程也提供了接口来运行用户设置的钩子函数,在空闲线程运行时会调用该钩子函数,适合钩入功耗管理、看门狗喂狗等工作。


主线程


在系统启动时,系统会创建 main 线程,它的入口函数为 main_thread_entry(),用户的应用入口函数 main()就是从这里真正开始的,系统调度器启动后,main 线程就开始运行,过程如下图,用户可以在 main() 函数里添加自己的应用程序初始化代码。


int $Sub$$main(void)
{
    rtthread_startup();
    return 0;
}


系统启动后先从汇编代码 startup_stm32l475xx.s 开始运行,然后跳转到 C 代码执行该代码 $Sub$$main.在 rthtread_starup() 中执行了一些启动初始化工作:


int rtthread_startup(void)
{
rt_hw_interrupt_disable();
/* 板级初始化:需在该函数内部进行系统堆的初始化 */
rt_hw_board_init();
/* 打印 RT-Thread 版本信息 */
rt_show_version();
/* 定时器初始化 */
rt_system_timer_init();
/* 调度器初始化 */
rt_system_scheduler_init();
#ifdef RT_USING_SIGNALS
/* 信号初始化 */
rt_system_signal_init();
#endif
/* 由此创建一个用户 main 线程 */
rt_application_init();
/* 定时器线程初始化 */
rt_system_timer_thread_init();
/* 空闲线程初始化 */
rt_thread_idle_init();
/* 启动调度器 */
rt_system_scheduler_start();
/* 不会执行至此 */
return 0;
}


这部分启动代码,大致可以分为四个部分:


1、初始化与系统相关的硬件;


2、初始化系统内核对象,例如定时器、调度器、信号;


3、创建 main 线程,在 main 线程中对各类模块依次进行初始化;


4、初始化定时器线程、空闲线程,并启动调度器。


在 rt_application_init 函数创建了主线程。


/* the system main thread */
void main_thread_entry(void *parameter)
{
extern int main(void);
extern int $Super$$main(void);
#ifdef RT_USING_COMPONENTS_INIT
/* RT-Thread components initialization */
rt_components_init();
#endif
#ifdef RT_USING_SMP
rt_hw_secondary_cpu_up();
#endif
/* invoke system main function */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
$Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
main();
#endif
}
void rt_application_init(void)
{
rt_thread_t tid;
#ifdef RT_USING_HEAP
tid = rt_thread_create("main", main_thread_entry, RT_NULL,
RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(tid != RT_NULL);
#else
rt_err_t result;
tid = &main_thread;
result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(result == RT_EOK);
/* if not define RT_USING_HEAP, using to eliminate the warning */
(void)result;
#endif
rt_thread_startup(tid);
}


9b13b3718c0049d886714368b7265d2a.png


线程管理


下图描述了线程的相关操作(具体函数的使用方法请查阅对应API文档):


11729880980944458c5a65411337a46d.png


RT-Thread 自动初始化机制


自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。


在系统启动流程图中,有两个函数:rt_components_board_init() 与 rt_components_init(),其后的带底色方框内部的函数表示被自动初始化的函数,其中:


1、“board init functions” 为所有通过 INIT_BOARD_EXPORT(fn) 申明的初始化函数。


2、“pre-initialization functions” 为所有通过 INIT_PREV_EXPORT(fn)申明的初始化函数。


3、“device init functions” 为所有通过 INIT_DEVICE_EXPORT(fn) 申明的初始化函数。


4、“components init functions” 为所有通过 INIT_COMPONENT_EXPORT(fn)申明的初始化函数。


5、“enviroment init functions” 为所有通过 INIT_ENV_EXPORT(fn) 申明的初始化函数。


6、“application init functions” 为所有通过 INIT_APP_EXPORT(fn)申明的初始化函数。


用来实现自动初始化功能的宏接口定义详细描述如下表所示:


初始化顺序 宏接口 描述


1 INIT_BOARD_EXPORT(fn) 非常早期的初始化,此时调度器还未启动

2 INIT_PREV_EXPORT(fn) 主要是用于纯软件的初始化、没有太多依赖的函数

3 INIT_DEVICE_EXPORT(fn) 外设驱动初始化相关,比如网卡设备

4 INIT_COMPONENT_EXPORT(fn) 组件初始化,比如文件系统或者 LWIP

5 INIT_ENV_EXPORT(fn) 系统环境初始化,比如挂载文件系统

6 INIT_APP_EXPORT(fn) 应用初始化,比如 GUI 应用

初始化函数主动通过这些宏接口进行申明,如 INIT_BOARD_EXPORT(rt_hw_usart_init),链接器会自动收集所有被申明的初始化函数,放到 RTI 符号段中,该符号段位于内存分布的 RO 段中,该 RTI 符号段中的所有函数在系统初始化时会被自动调用。


例如:


int rt_hw_usart_init(void) /* 串口初始化函数 */
{
... ...
/* 注册串口 1 设备 */
rt_hw_serial_register(&serial1, "uart1",
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
uart);
return 0;
}
INIT_BOARD_EXPORT(rt_hw_usart_init); /* 使用组件自动初始化机制 */


在新线程控制LED


前面我们在潘多拉STM32L4上实现了一个按键控制 LED 和蜂鸣器的例子, 现在我们让该功能独立存在于一个文件中并自动启动独立线程执行。


在 stm32l475-atk-pandora\applications\ 目录创建文件 key_control_led.c 文件,然后修改 SConscript 配置文件:


From building import *
cwd = GetCurrentDir()
src = Split('''
main.c
key_control_led.c
''')
CPPPATH = [str(Dir('#')), cwd]
group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH)
Return('group')


接下来打开 Evn 生成新的 MDK5 工程 scons --target=mdk5:


> scons --target=mdk5
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...


实现的代码如下:


#include
#include
#include
#define THREAD_STACK_SIZE 200 //线程栈大小(字节)
#define THREAD_TIMESLICE 40 //占用的滴答时钟数
#define NULL_LED -1
#define BEEP_TIME 3
#define LED_PIN_RED GET_PIN(E, 7) //红灯管脚
#define LED_PIN_GREEN GET_PIN(E, 8) //绿灯管脚
#define LED_PIN_BLUE GET_PIN(E, 9) //蓝灯管脚
#define KEY_OPEN_RED GET_PIN(D, 10) //点亮红灯按钮
#define KEY_OPEN_GREEN GET_PIN(D, 9) //点亮绿灯按钮
#define KEY_OPEN_BLUE GET_PIN(D, 8) //点亮蓝灯按钮
#define BEEP_PIN GET_PIN(B, 2) //蜂鸣器控制管脚
static rt_thread_t key_led_thread = RT_NULL;
static rt_uint8_t thread_priority = 20;
//根据下标点亮LED
void change_lighting_led(int lightIndex)
{
rt_base_t rgbs[] = {LED_PIN_RED, LED_PIN_GREEN, LED_PIN_BLUE};
for(int i = 0; i < 3; i++)
{
if(i == lightIndex)
{
rt_pin_write(rgbs[i], PIN_LOW);
}
else
{
rt_pin_write(rgbs[i], PIN_HIGH);
}
}
}
//读取按键
void read_key_lighting_led()
{
int beep_time_count = 0;
rt_base_t keys[] = {KEY_OPEN_RED, KEY_OPEN_GREEN, KEY_OPEN_BLUE};
rt_pin_write(BEEP_PIN, PIN_LOW);
change_lighting_led(NULL_LED); //熄灭所有LED
while (1)
{
for(int i = 0; i < 3; i++)
{
if(rt_pin_read(keys[i]) == PIN_LOW)
{
rt_thread_mdelay(50); //去抖动
if(rt_pin_read(keys[i]) == PIN_LOW)
{
change_lighting_led(i); //切换点亮的LED
beep_time_count = 0;
if(beep_time_count < BEEP_TIME)
{
rt_pin_write(BEEP_PIN, PIN_HIGH);
}
else
{
rt_pin_write(BEEP_PIN, PIN_LOW);
}
}
}
}
beep_time_count++;
if(beep_time_count >= BEEP_TIME)
{
rt_pin_write(BEEP_PIN, PIN_LOW);
}
rt_thread_mdelay(50);
}
}
//线程函数
void key_control_led_entry(void *param)
{
//设置管脚的模式
rt_pin_mode(LED_PIN_RED, PIN_MODE_OUTPUT);
rt_pin_mode(LED_PIN_GREEN, PIN_MODE_OUTPUT);
rt_pin_mode(LED_PIN_BLUE, PIN_MODE_OUTPUT);
rt_pin_mode(KEY_OPEN_RED, PIN_MODE_INPUT);
rt_pin_mode(KEY_OPEN_GREEN, PIN_MODE_INPUT);
rt_pin_mode(KEY_OPEN_BLUE, PIN_MODE_INPUT);
rt_pin_mode(BEEP_PIN, PIN_MODE_OUTPUT);
read_key_lighting_led();
}
//创建键盘控制LED线程
int create_key_control_led_thread(void)
{
key_led_thread = rt_thread_create("led_test", key_control_led_entry,
RT_NULL, THREAD_STACK_SIZE, thread_priority, THREAD_TIMESLICE);
if(key_led_thread != RT_NULL)
{
rt_thread_startup(key_led_thread);
}
return 0;
}
INIT_APP_EXPORT(create_key_control_led_thread);


此时的 main 函数是空的,如下:


#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
int main(void)
{
    return RT_EOK;
}


此例中我们使用的是 rt_thread_create 函数进行动态创建,另外还有一个函数


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)


这里需要注意的是线程优先级字段是一个 rt_uint8_t 的类型,所以不可用宏定义去定义优先级。


还有一个参数在使用上可能比较疑惑,那就是 stack_size 参数,设置多大合适呢?一般情况下我们先设置一个值,然后在 FinSH 控制台中使用 list_thread 命令查看使用情况:


b5573bd2f8ff4015990353f3d37cc3c5.png


可以看到 led_test 线程的优先级是 20 状态是 suspend(挂起)状态,栈的起始地址是 0x00000090 栈大小是 0x000000c8 使用率是 72% 线程剩余的运行节拍数是 0x28.


我们设置的这个栈大小就比较合适,一般情况下使用率在 70% 附近比较理想,当然可以根据情况调整,不要浪费资源。



相关文章
|
4月前
|
算法 调度 芯片
RT-Thread快速入门-线程管理
RT-Thread快速入门-线程管理
43 0
RT-Thread快速入门-线程管理
|
4月前
|
数据处理 调度
RT-Thread快速入门-线程管理(下)
RT-Thread快速入门-线程管理(下)
26 0
|
4月前
|
消息中间件 算法 物联网
RT-Thread快速入门-初探RT-Thread
RT-Thread快速入门-初探RT-Thread
38 0
|
4月前
|
算法 调度 容器
RT-Thread快速入门-互斥量
RT-Thread快速入门-互斥量
31 0
RT-Thread快速入门-互斥量
|
8月前
RT-Thread线程创建和删除
RT-Thread线程创建和删除
91 0
|
9月前
|
消息中间件 算法 安全
关于RT-Thread内核的介绍
关于RT-Thread内核的介绍
|
11月前
|
传感器 调度 芯片
【玩转RT-Thread】线程管理(详细原理)(上)
【玩转RT-Thread】线程管理(详细原理)(上)
189 0
|
11月前
|
API 调度
【玩转RT-Thread】线程管理(详细原理)(下)
【玩转RT-Thread】线程管理(详细原理)
181 0
|
11月前
|
编译器
【玩转RT-Thread】线程间同步(一) 信号量
【玩转RT-Thread】线程间同步(一) 信号量
97 0
|
11月前
|
调度
【玩转RT-Thread】 RT-Thread Studio使用(2) 内核实战篇(线程)
【玩转RT-Thread】 RT-Thread Studio使用(2) 内核实战篇(线程)
177 0