【RT-Thread基础教程】线程的基本使用
@TOC
前言
在嵌入式系统开发中,RTOS(Real-Time Operating System)扮演着至关重要的角色,而RT-Thread作为一款开源的实时操作系统,在嵌入式领域中备受欢迎。线程是RTOS中的基本执行单元,其良好的多任务处理能力使得嵌入式系统能够更有效地响应各种事件和任务。
本文将着重介绍在RT-Thread中线程的基本使用方法,包括线程的创建、调度、挂起和删除等方面。通过深入理解线程的基本概念和操作,读者将能更好地利用RT-Thread构建稳定而高效的嵌入式系统。
一、线程介绍
1.1 线程是什么
当你在玩游戏时,你可能需要同时处理很多事情,比如控制角色移动、检测碰撞、播放音效等等。RT-Thread的线程就像是你在玩游戏时,可以让你同时做多件事情的小助手。
想象一下,你是一位游戏玩家,游戏中有很多任务需要完成,比如探险、打怪兽、收集宝藏等等。你可以把每一个任务想象成一个线程。当你在探险的同时,你的小伙伴可以在打怪兽,而另一个小伙伴则在寻找宝藏。每个人都在做不同的事情,但是又能够和你一起玩游戏。
就像这样,RT-Thread的线程就是系统中可以独立运行的小任务单位。它们可以同时执行不同的操作,比如读取传感器数据、控制电机、处理网络通信等等。这样,系统就可以更高效地完成各种任务,就像你在玩游戏时,可以同时进行多种操作一样。
1.2 线程与任务的关系是什么
RT-Thread的线程和FreeRTOS的任务在概念上非常相似,都是实时操作系统中用于执行代码的基本执行单元。它们之间有一些相似之处,但也存在一些细微的区别。
相似之处:
基本概念: 在RT-Thread和FreeRTOS中,线程和任务都是用于执行代码的基本单元,它们使得系统能够同时执行多个任务或线程,实现多任务并发。
创建和调度: 无论是线程还是任务,它们都可以被创建、调度和管理。这包括设置优先级、分配堆栈空间、启动和暂停等基本操作。
实时性: RT-Thread和FreeRTOS都是实时操作系统,因此它们都注重任务或线程的实时性,可以在有限的时间内响应和处理事件。
二、线程的创建与删除
基础知识
rt_thread结构体
在 RT-Thread 中,线程是 RT-Thread 中最基本的调度单位,使用 rt_thread 结构体表示线程。
这个结构体在rtthread.h
中定义。
rt_thread 描述了一个线程执行的运行环境,也描述了这个线程所处的优先等级。
系统中总共存在两类线程,分别是系统线程和用户线程
系统线程由 RT-Thread 内核创建
用户线程由用户应用程序创建
这两类线程都会从内核对象容器中分配线程对象,如下图所示。
每个线程由三部分组成:线程控制块(rt_thread 结构体)、线程栈和入口函数。
线程栈
想象一下你在搭积木,每一块积木都代表着你要做的事情。而RTOS的线程栈就像是你用来记录完成这些事情所需要的工具箱。在这个工具箱里,你可以放入各种各样的工具,比如需要用到的指令、数据、函数等等。
现在,想象一下你正在搭一个特别高的积木塔。当你往上搭的时候,你需要不停地在塔上方添加新的积木,同时你手里拿的积木只能放那么多。这时,如果你的手里的积木不够用了,你就需要从工具箱里拿新的积木来继续搭建。而RTOS的线程栈就像是你手里的积木空间,它是用来存放当前线程执行过程中所需要的信息的地方。当线程执行的深度超过了栈的容量时,就需要从工具箱里拿新的空间来继续执行。
简而言之,RTOS的线程栈就像是一个工具箱,用来存放线程执行所需的信息,包括指令、数据等等。当线程执行过程中需要更多的空间时,系统会自动从线程栈中申请新的空间,以确保线程可以正确
可以使用两种方法提供线程栈:静态分配、动态分配。栈的大小通常由用户定义,如
下使用全局数组提供了一个静态栈,大小为 512 字节:
rt_uint32_t test_stack[512];
对于资源比较大的 MCU,可以适当设置较大的线程栈。
也可以在初始化时设置为较大的栈,比如 1K 或 2K,在进入系统后,通过终端的
list_thread 命令查看当前线程栈的最大使用率。如果使用率超过 70%,将线程栈再设置大
一点;如果远低于 70%,将线程栈设置小一点。
入口函数
入口函数是线程要运行函数,由用户自行设计。
可分为无限循环模式和顺序执行模式。
无限循环及时在函数中为while(1)
式的
顺序执行模式即是我们普通C语言的方式,不使用死循环
使用无限循环这种模式时,需要注意,一个实时系统,不应该让一个线程一直处于最高优先级
占用 CPU,让其它线程得不到执行。因此,在这种模式中,需要调用延时函数或者主动挂起。
这种无限循环模式设计的目的是让这个线程一直循环调度运行,而不结束。
使用顺序执行模式时,线程不会一直循环,最后一定会执行完毕。执行完毕后,线程将被系统自动删除。
2.1 创建线程
创建动态线程
我们可以使用下面这个函数进行动态的创建线程:
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)
参数1为线程的名称,不用管。
参数2为线程的入口函数,有一个void *
的参数,无返回值
参数3为传入入口函数的参数
参数4为栈的大小
参数5为优先级
参数6为线程时间片大小
返回值的意义:
成功返回thread句柄
失败返回:RT_NULL
线程时间片是什么?
想象一下你在玩一个很棒的电子游戏。游戏里有很多任务要完成,比如探险、打怪兽、制造物品等。现在,每个任务都有一个特定的时间,比如10分钟,你需要在规定的时间内完成这个任务。
RT-Thread的线程时间片大小就好比是这个任务的时间。假设你有3个任务,每个任务都有10分钟的时间片。这意味着你可以在第一个任务上玩10分钟,然后切换到第二个任务,再玩10分钟,接着切换到第三个任务,也是10分钟。之后,你再回到第一个任务,继续循环。
线程时间片的大小就是每个任务能够连续执行的时间。在RT-Thread中,操作系统给每个线程分配一个小时间段,让它执行一段时间,然后切换到下一个线程。这种轮流执行的方式可以让系统中的不同线程都有机会执行,实现多任务并发。
简而言之,RT-Thread的线程时间片大小就是每个线程被允许连续执行的时间,就像你在游戏中每个任务被允许玩的时间一样。这样可以确保系统中的各个线程都有公平的机会运行。
创建静态线程
我们可以使用下面这个函数进行创建静态的线程
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)
和动态创建线程只有参数5有区别,他为栈的开头地址,我们可以创建一个buf,然后传入他的首地址去即可
2.2 启动线程
创建了线程,这仅仅是创建了,你没让跑起来,所以我们需要下面这个函数让他开始跑起来:
rt_err_t rt_thread_startup(rt_thread_t thread);
参数为线程的handle
返回值的取值有下面这些:
/* RT-Thread error code definitions */
#define RT_EOK 0 /**< There is no error */
#define RT_ERROR 1 /**< A generic error happens */
#define RT_ETIMEOUT 2 /**< Timed out */
#define RT_EFULL 3 /**< The resource is full */
#define RT_EEMPTY 4 /**< The resource is empty */
#define RT_ENOMEM 5 /**< No memory */
#define RT_ENOSYS 6 /**< No system */
#define RT_EBUSY 7 /**< Busy */
#define RT_EIO 8 /**< IO error */
#define RT_EINTR 9 /**< Interrupted system call */
#define RT_EINVAL 10 /**< Invalid argument */
RT_EOK
为成功
RT_ERROR
为错误
RT_ETIMEOUT
为超时
2.3 创建线程示例
/*
* Copyright (c) 2006-2024, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-02-22 RT-Thread first version
*/
#include <rtthread.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
rt_uint32_t test_stack[512];
void thread1t(void *param)
{
while(1)
{
rt_kprintf("Hello World\n");
rt_thread_mdelay(1000);
}
}
int main(void)
{
rt_thread_t thread1 = rt_thread_create("thread", thread1t, NULL, 1000, 1, 10);
if (thread1 != RT_NULL)
{
if (rt_thread_startup(thread1) == RT_EOK)
{
rt_kprintf("Thread1 started successfully.\n");
}
else
{
rt_kprintf("Failed to start Thread1.\n");
}
}
else
{
rt_kprintf("Failed to create Thread1.\n");
}
return 0;
}
2.4 删除线程
rt_thread_detach函数
如果你使用的是rt_thread_init
创建的线程,你需要使用rt_thread_detach
进行删除他
他的函数原型如下:
rt_err_t rt_thread_detach(rt_thread_t thread);
参数为线程handle
rt_thread_delete函数
如果你使用的是rt_thread_create
创建的线程,你需要使用rt_thread_delete
进行删除他
他的函数原型如下:
rt_err_t rt_thread_delete(rt_thread_t thread);
参数为线程handle
总结
通过本文的学习,我们详细了解了在RT-Thread中线程的基本使用方法。首先,我们学习了如何创建线程,包括指定线程入口函数、传递参数以及设置线程优先级等。其次,我们深入探讨了线程的调度机制,了解了RTOS如何按照优先级和时间片轮转的方式进行线程调度。同时,我们介绍了线程的挂起和删除操作,以及如何通过互斥锁等机制实现线程间的同步和通信。
总体而言,熟练掌握RT-Thread中线程的基本使用方法对于嵌入式系统的开发至关重要。通过合理的线程设计和管理,我们能够提高系统的响应速度、实现多任务协同工作,从而更好地满足不同应用场景下的需求。希望本文对读者在使用RT-Thread进行嵌入式开发时有所帮助。感谢阅读。
- 点赞
- 收藏
- 关注作者
评论(0)