RTT学习

RT-Thread内核配置示例

RT-Thread的一个重要特性是高度可裁剪性,支持对内核进行精细调整,对组件进行灵活拆卸。

配置主要是通过修改工程目录下的rtconfig.h文件来进行,用户可以通过打开/关闭该文件中的宏定义来对代码进行条件编译,最终达到系统配置和裁剪的目的。

线程管理

在日常生活中,我们要完成一个大任务,一般会将它分解成多个简单、容易解决的小问题,小问题逐个被解决,大问题也就随之解决了。
在多线程操作系统中,也同样需要开发人员把一个复杂的应用分解成多个小的、可调度的、序列化的程序单元,当合理地划分任务并正确地执行时,这种设计能够让系统满足实时系统的性能及时间的要求。

例如嵌入式系统执行这样的任务,系统传感器采集数据,并通过显示屏将数据显示出来,在多线程实时系统中,可以将这个任务分解成两个子任务。
在这里插入图片描述
一个子任务不间断地读取传感器数据,并将数据写到共享内存中,另外一个子任务周期性地从共享内存中读取数据,并将传感器数据输出到显示屏上。

在RT-Thread中,与上述子任务对应的程序实体就是线程,线程是实现任务的载体,是RTT中最基本的调度单位,它描述了一个任务执行的运行环境,也描述了这个任务所处的优先等级,重要的任务可设置相对较高的优先级,非重要的任务可以设置较低的优先级,不同的任务还可以设置相同的优先级,轮流运行。

当线程运行时,它会认为自己是以独占CPU的方式在运行,线程执行时的运行环境称为上下文,具体来说就是各个变量和数据,包括所有的寄存器变量、堆栈、内存信息等。

线程管理

RTT线程管理主要是对线程进行管理和调度,系统中总共存在两类线程,分别是系统线程和用户线程,系统线程是由RTT内核创建的线程,用户线程是由应用程序创建的线程,这两类线程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除。

在这里插入图片描述
线程调度器是抢占式的,主要工作是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到CPU的使用权。

当一个运行着的线程使一个比它优先级高的线程满足运行条件,当前线程的CPU使用权就被剥夺了,或者说被让出了,高优先级的线程立刻得到了 CPU 的使用权。

如果是终端服务程序使一个高优先级的线程满足运行条件,中断完成时,被中断的线程挂起,优先级高的线程开始运行。

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

线程控制块

线程控制块由结构体struct rt_thread表示

struct rt_thread{
	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_atomic_t init_tick; /* 线程初始化计数值 */
    rt_atominc_t remaining_tick; /*线程剩余计数值*/

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

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

其中init_priority是线程创建时指定的线程优先级,在线程运行过程当中是不会被改变的(除非用户执行线程控制函数进行手动调整线程优先级)。
cleanup会在线程退出时,被空闲线程回调一次以执行用户设置的清理现场等工作。
最后的一个成员user_data可由用户挂接一些数据信息到线程控制块中,以提供一种类似线程私有数据的实现方式。

线程栈

RT-Thread线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。

线程栈还用来存放函数中的局部变量:函数中的局部变量从线程栈空间中申请,函数中局部变量初始时从寄存器中分配,当这个函数再调用另一个函数时,这些局部变量将放入栈中。

对于线程第一次运行,可以以手工的方式构造这个上下文来设置一些初始的环境:入口函数(PC寄存器),入口参数(R0寄存器),返回位置(LR寄存器)、当前机器运行状态(CPSR寄存器)。

在这里插入图片描述
线程栈大小可以这样设定,对于资源相对较大的MCU,可以适当设计较大的线程栈;也可以在初始时设置较大的栈,例如指定大小为1K或2K字节,然后在FinSH中用list_thread命令查看线程运行过程中线程所使用的栈的大小,通过此命令,能够看到从线程启动运行时,到当前时刻点,线程使用的最大深度,而后加上适当的余量形成最终的线程栈大小,最后对栈空间大小加以修改。

线程状态

线程运行的过程中,同一时间内只允许一个线程在处理器中运行。

  1. 初始状态:当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。此状态在RT-Thread中的宏定义为RT_THREAD_INIT。
  2. 就绪状态:在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。 RT_THREAD_READY
  3. 运行状态:线程当前正在运行。在单核系统中,只有rt_thread_self()函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。此状态在 RT-Thread 中的宏定义为 RT_THREAD_RUNNING
  4. 挂起状态:也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度,此状态在RT-Thread中的宏定义为RT_THREAD_SUSPEND
  5. 关闭状态:当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。此状态在 RT-Thread 中的宏定义为RT_THREAD_CLOSE。

线程优先级

RT-Thread 线程的优先级是表示线程被调度的优先程度。每个线程都具有优先级,线程越重要,赋予的优先级就应越高,线程被调度的可能才会越大。

RT-Thread 最大支持 256 个线程优先级 (0~255),数值越小的优先级越高,0 为最高优先级。在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置。

对于ARM Cortex-M系列,普遍采用32个优先级。最低优先级默认分配给空闲线程使用,用户一般不使用。

当有比当前线程优先级更高的线程就绪时,当前线程立刻被换出,高优先级线程抢占处理器运行。

时间片

每个线程都有时间片这个参数,但时间片仅对优先级相同的就绪态线程有效。
系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度时,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick)。

假设有两个优先级相同的就绪态线程A与B,A线程的时间片设置为10,B线程的时间片设置为5,,那么当系统中不存在比 A 优先级高的就绪态线程时,系统会在 A、B 线程间来回切换执行,并且每次对 A 线程执行 10 个节拍的时长,对 B 线程执行 5 个节拍的时长,如下图。

在这里插入图片描述

线程的入口函数

线程控制块中的entry是线程的入口函数,它是线程实现预期功能的函数。线程的入口函数由用户设计实现,一般有以下两种代码形式:

无限循环模式
在实时系统中,线程通常是被动式的:这个是由实时系统的特性所决定的,实时系统通常总是等待外界事件的发生,而后进行相应的服务:

void thread_entry(void* parameter){
	while(1){
		/*等待事件的发生*/
		/*对事件进行服务、进行处理*/
	}
}

线程中不能陷入死循环操作,必须要有让出去CPU使用权的动作,如循环中调用延时函数或主动挂起。用户设计这种无限循环的线程的目的,就是为了让这个线程一直被系统循环调度运行,永不删除。

顺序执行或有限次循环模式
如简单的顺序语句,do while()或for()循环等,此类线程不会循环或不会永久循环,可谓是 “一次性” 线程,一定会被执行完毕。
在执行完毕后,线程将被系统自动删除。

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

线程错误码

一个线程就是一个执行场景,错误码是执行环境密切相关的,所以每个线程配备了一个变量用于保存错误码

#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 /* 非法参数      */

线程状态切换

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

系统线程

空闲线程
空闲线程(idle)是系统创建的最低优先级的线程,线程状态永远为就绪态
当系统中无其他就绪线程存在时,调度器将调度到空闲线程,它通常是一个死循环,且永远不能被挂起。

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

空闲线程也提供了接口来运行用户设置的钩子函数,在空闲线程运行时会调用该钩子函数,适合处理功耗管理、看门狗喂狗等工作。
空闲线程必须有得到执行的机会,即其它线程不允许一直死循环,必须调用具有阻塞性质的函数;否则例如线程删除、回收等操作将无法得到正确执行。

主线程

  • 13
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饼干饼干圆又圆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值