RT-Thread快速入门-线程管理(上)

首发,公众号【一起学嵌入式】

在 RT-Thread 中,最基本的调度单位是线程,其他 RTOS 也叫任务。如果学习过或者了解过 RTOS,任务这种叫法是最为熟知的。

本篇文章来学习一下 RT-Thread 线程方面的内容。对于初学者来说,转换一下思维,建立多任务(线程)的编程思想。

引言

对于裸机编程,整个软件系统只有一个线程(任务)在执行,实现方式是通过一个大循环完成的。应用程序是一个无限循环,循环中调用各个功能模块的函数,完成相应的操作。

RTOS 是一个多任务系统,可以把总体功能划分为多个小模块,每个小模块独立运行完成某项功能,即将任务分解。这样使得复杂项目的实现变得简单了。

关于为什么要用 RTOS,或者说 RTOS 的优点,网上资料很多,在此不再赘述。

线程基础

1. 线程调度

对于一款 RTOS 来说,最核心的部分就是线程(任务)调度器。调度器的作用是根据调度算法来决定当前需要执行的线程。

RT-Thread 的线程调度器是抢占式的,基于优先级对线程进行调度。每个线程均具有一个优先级,调度器的主要工作是,从就绪线程列表中查找最高优先级线程,然后将 CPU 的使用权分配给它。

"可抢占"意味着,如果高优先级线程就绪或者满足运行条件,RT-Thread 马上将 CPU 的控制权交给高优先级线程。

2. 线程优先级

RT-Thread 线程的优先级表示线程被调度的优先程度。每个线程都具有优先级,对于重要的线程,应该赋予其高优先级,这样才能保证线程被优先调度。

RT-Thread 最大支持 256个优先级(0~255),数值越小的线程优先级越高。0 为最高优先级。最低优先级默认分配给空闲线程,用户一般不用。

可以根据实际情况配置优先级个数,对于 ARM Cortex-M 系列,普遍采用 32 个优先级(0~31)。

3. 时间片

RT-Thread 允许多个线程具有相同的优先级,相同优先级的线程之间采用时间片轮转的方式进行调度。创建线程的时候,可以配置线程的时间片参数。时间片仅对优先级相同的就绪线程有效。

时间片的作用是约束线程单次运行的时长,其单位是系统时钟节拍(OS Tick)。

4. 线程栈

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

线程栈还用来存放函数中的局部变量。当一个函数调用另外一个函数时,函数中的局部变量会暂时存放在栈中。

线程栈的增长方向由芯片架构决定的:由高地址向低地址增长、由低地址向高地址增长。这两种增长方式 RT-Thread 均支持。对于 ARM Cortex-M 架构,线程栈构造如下图所示:

在这里插入图片描述

5. 线程的入口函数

入口函数是线程实现预期功能的函数。线程的入口函数由用户设计,一般有以下两种形式:

  • 无线循环模式

这种线程会一直被系统循环调度,执行任务,不会删除。

void thread_entry(void *parameter)
{
	while(1)
	{
	  /* 线程处理 */
	}
}
  • 顺序执行或有限次循环模式

这类线程不会循环或者不会永久循环,执行完毕之后,线程将被系统自动删除。

void thread_entry(void *parameter)
{
	/* 处理任务 */
	...
}

线程状态

对于单 CPU 来说,系统运行过程中,同一时刻只有一个线程在处理器运行。在运行过程中,线程有多种不同的运行状态:

  • 初始状态,线程刚创建还未开始运行时处于的状态,此状态下,线程不参与调度。
  • 就绪状态,线程具备运行条件的状态,等待被调度器调度执行。
  • 运行状态,线程正在运行。
  • 挂起状态,也称为阻塞状态。由于资源不可用或线程主动延时一段时间,而进入的状态。线程不能执行。
  • 关闭状态。线程运行结束处于的状态。此时线程不参与调度。

下图是各个状态之间的转换图,通过此图可以对 RT-Thread 线程的运行状态有一个整体的认识。

在这里插入图片描述

图中涉及到的系统调用函数,在后面的学习会进行详细讲解。此处进行简单的说明:

  • rt_thread_create/init() 创建或初始化一个线程,此线程处于初始状态。

  • rt_thread_startup() 函数使得初始化状态的线程进入到就绪状态。

  • rt_thread_delay(),rt_sem_take(), rt_mutex_take() 等函数使得运行状态的线程进入到挂起状态。

  • rt_thread_resume(), rt_sem_release() 等函数使得挂起状态的线程返回到就绪状态。

  • rt_thread_delete/detach() 函数将挂起状态的线程更改为关闭状态。

  • rt_thread_exit(),处于运行状态的线程,运行结束,在线程的最后部分调用此函数,将状态更改为关闭状态。

线程控制块

在 RT-Thread 中,线程控制块由结构体 struct rt_thread 表示。

线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等。

线程控制块也包含线程与线程之间连接用的链表结构,线程等待事件集合等。

详细定义如下,列出了关键结构成员,并加入了注释,了解即可:

struct rt_thread
{
  /* rt 对象 */
  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_ubase_t  init_tick;       /* 线程初始化计数值 */
  rt_ubase_t  remaining_tick;  /* 线程剩余计数值 */

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

  void (*cleanup)(struct rt_thread *tid); /* 线程退出清楚函数指针 */

	...

    rt_uint32_t user_data;  /* 用户数据 */
};
typedef struct rt_thread *rt_thread_t;

创建线程

RT-Thread 提供了先管理相关的系统函数:包含:创建 / 初始化线程、启动线程、运行线程、删除 / 脱离线程 等。

此处只讲解如何创建一个线程。

在 RT-Thread 中,要创建一个线程,并使得它能够被执行,需要两步:

  • 创建线程,此时一个新线程被创建,并处于初始状态。
  • 启动线程,此时线程由初始状态进入就绪状态,可以被调度器执行。

1. 创建线程

创建一个线程的函数 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)

调用这个函数时,系统会从动态内存堆中分配一个线程句柄(线程控制块),并按照参数中指定的栈大小从动态内存堆中分配相应的空间。

函数的各个参数解释如下:

参数描述
name线程的名称;线程名称的最大长度由 rtconfig.h 中的宏 RT_NAME_MAX 指定,多余部分会被自动截掉
entry线程入口函数
parameter线程入口函数参数
stack_size线程栈大小,单位是字节
priority线程的优先级。优先级范围根据系统配置(rtconfig.h 中的RT_THREAD_PRIORITY_MAX 宏定义)
tick线程的时间片大小。单位是操作系统的时钟节拍

线程创建成功,返回线程的控制块指针,也可称为线程句柄。

创建失败,则返回 RT_NULL。

2. 线程启动

线程创建完成后,需要使其进入就绪状态,也就是启动线程。可以通过调用 rt_thread_startup() 函数来完成。其函数原型为:

rt_err_t rt_thread_startup(rt_thread_t thread)

调用此函数成功后,会将线程放到相应优先级队列中等待调度。如果新启动的线程优先级比当前优先级高,将立即切换到这个线程。

参数 thread ,线程句柄,即线程控制块的指针。

线程启动成功,返回 RT_EOK;启动失败,则返回 -RT_ERROR。

线程创建示例

此处用一个简单的例子,来演示一下 RT-Thread 线程创建。程序源码如下:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <rtthread.h>

#define THREAD_PRIORITY    25
#define THREAD_STACK_SIZE  512
#define THREAD_TIMESLICE    5

void thread_entry(void *parameter)
{
	rt_uint32_t count = 0;
	
	while(1)
	{
		/* 线程运行,打印计数 */
		rt_kprintf("thread run: %d\n", count ++);
		rt_thread_mdelay(500);
	}
}

int main(void)
{
	rt_thread_t tid = RT_NULL;
	
    /* 创建线程, 名称是 thread_test, 入口是 thread_entry*/
	tid = rt_thread_create("thread_test",
							thread_entry, RT_NULL,
							THREAD_STACK_SIZE,
							THREAD_PRIORITY, THREAD_TIMESLICE);
							
	/* 线程创建成功,则启动线程 */
	if (tid != RT_NULL)
	{
		rt_thread_startup(tid);
	}

    return 0;
}

编译运行,结果如下,线程创建成功,执行过程中,在打印计数信息:

在这里插入图片描述

OK,今天先到这,下次继续。加油~


公众号【一起学嵌入式】,一起学习、一起成长

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

zsky_01

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

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

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

打赏作者

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

抵扣说明:

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

余额充值