目录
前言
接下来我们将学习内核中的线程管理,实现创建一个动态线程和初始化一个静态线程,动态线程实现红灯亮,静态线程实现绿灯亮。
一、线程的工作机制
1.1 线程控制块
在 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; /* 用户数据 */ };
其中 init_priority 是线程创建时指定的线程优先级,在线程运行过程当中是不会被改变的(除非用户执行线程控制函数进行手动调整线程优先级)。cleanup 会在线程退出时,被空闲线程回调一次以执行用户设置的清理现场等工作。最后的一个成员 user_data 可由用户挂接一些数据信息到线程控制块中,以提供一种类似线程私有数据的实现方式。
1.2 线程状态
线程运行的过程中,同一时间内只允许一个线程在处理器中运行,从运行的过程上划分,线程有多种不同的运行状态,如初始状态、挂起状态、就绪状态等。在 RT-Thread 中,线程包含五种状态,操作系统会自动根据它运行的情况来动态调整它的状态。 RT-Thread 中线程的五种状态,如下表所示:
状态 | 描述 |
---|---|
初始状态 | 当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_INIT |
就绪状态 | 在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。此状态在 RT-Thread 中的宏定义为 RT_THREAD_READY |
运行状态 | 线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。此状态在 RT-Thread 中的宏定义为 RT_THREAD_RUNNING |
挂起状态 | 也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_SUSPEND |
关闭状态 | 当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_CLOSE |
1.3 线程栈
T-Thread 线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。
线程栈还用来存放函数中的局部变量:函数中的局部变量从线程栈空间中申请;函数中局部变量初始时从寄存器中分配(ARM 架构),当这个函数再调用另一个函数时,这些局部变量将放入栈中。
1.4 线程优先级
RT-Thread 线程的优先级是表示线程被调度的优先程度。每个线程都具有优先级,线程越重要,赋予的优先级就应越高,线程被调度的可能才会越大。RT-Thread 最大支持 256 个线程优先级 (0~255),数值越小的优先级越高,0 为最高优先级。对于 ARM Cortex-M 系列,普遍采用 32 个优先级。最低优先级默认分配给空闲线程使用,用户一般不使用。在系统中,当有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程抢占处理器运行。
1.5 时间片
每个线程都有时间片这个参数,但时间片仅对优先级相同的就绪态线程有效。系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度时,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick),假设有 2 个优先级相同的就绪态线程 A 与 B,A 线程的时间片设置为 10,B 线程的时间片设置为 5,那么当系统中不存在比 A 优先级高的就绪态线程时,系统会在 A、B 线程间来回切换执行,并且每次对 A 线程执行 10 个节拍的时长,对 B 线程执行 5 个节拍的时长,如下图。
二、线程相关API
下图描述了线程的相关操作,包含:创建 / 初始化线程、启动线程、运行线程、删除 / 脱离线程。可以使用 rt_thread_create() 创建一个动态线程,使用 rt_thread_init() 初始化一个静态线程,动态线程与静态线程的区别是:动态线程是系统自动从动态内存堆上分配栈空间与线程句柄(初始化 heap 之后才能使用 create 创建动态线程),静态线程是由用户分配栈空间与线程句柄。
2.1 创建和删除动态线程
rt_thread_t rt_thread_create(const char *name, void(*)(void *parameter)entry,
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 线程的优先级
tick 线程的时间片大小。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度
返回
thread 线程创建成功,返回线程句柄
RT_NULL 线程创建失败
rt_err_t rt_thread_delete(rt_thread_t thread)
删除线程
该函数将删除一个线程。 线程对象将从线程队列中删除,并从空闲线程中的系统对象管理中删除。
参数
thread 要删除的线程句柄
返回
成功 RT_EOK,
失败 RT_ERROR
2.2 初始化和脱离静态线程
rt_err_t rt_thread_init (struct rt_thread *thread, const char *name,
void(*)(void *parameter)entry, void *parameter, void *stack_start, rt_uint32_t stack_size,
rt_uint8_t priority, rt_uint32_t tick)
初始化线程
此函数将初始化一个线程,通常用于初始化一个静态线程对象。
参数
thread 线程句柄。线程句柄由用户提供出来,并指向对应的线程控制块内存地址。
name 线程的名称;线程名称的最大长度由 rtconfig.h 中定义的 RT_NAME_MAX 宏指定,多余部分会被自动截掉。
entry 线程的入口函数
parameter 入口函数的传入参数
stack_start 线程堆栈的起始地址
stack_size 线程栈大小,单位是字节。在大多数系统中需要做栈空间地址对齐(例如 ARM 体系结构中需要向 4 字节地址对齐)。
priority 线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0 ~ 255,数值越小优先级越高,0 代表最高优先级。
tick 线程的时间片大小。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。
返回
成功 RT_EOK
失败 RT_ERROR
rt_err_t rt_thread_detach(rt_thread_t thread)
脱离线程
该函数将脱离一个线程。 线程对象将从线程队列中删除,并从系统对象管理中脱离/删除。
参数
thread 线程句柄,它应该是由 rt_thread_init 进行初始化的线程句柄。
返回
成功 RT_EOK
失败 RT_ERROR
2.3 启动线程
rt_err_t rt_thread_startup(rt_thread_t thread)
启动线程
此函数将启动一个线程并将其放入系统就绪队列
参数
thread 被要被启动的线程句柄
返回
成功 RT_EOK
失败 RT_ERROR
2.4 线程睡眠
rt_err_t rt_thread_sleep(rt_tick_t tick)
线程睡眠
该函数将使当前线程睡眠几个系统始终节拍的时间
参数
tick 线程睡眠的时间
返回
RT_EOK
rt_err_t rt_thread_delay(rt_tick_t tick)
线程延时
该函数将使当前线程延时几个系统始终节拍的时间。
参数
tick 延时的系统节拍数
返回
RT_EOK
rt_err_t rt_thread_mdelay(rt_int32_t ms)
线程毫秒延时
此函数将使当前线程延迟几毫秒。
参数
ms 延时时间
返回
RT_EOK
2.5 挂起和恢复线程
rt_err_t rt_thread_suspend(rt_thread_t thread)
挂起线程
该函数将挂起指定的线程。
参数
thread 要被挂起的线程
返回
成功 RT_EOK,
失败 RT_ERROR
rt_err_t rt_thread_resume(rt_thread_t thread)
使线程恢复运行
线程恢复就是让挂起的线程重新进入就绪状态,并将线程放入系统的就绪队列中; 如果被恢复线程在所有就绪态线程中,位于最高优先级链表的第一位,那么系统 将进行线程上下文的切换。
参数
thread 将要被恢复的线程
2.6 线程入口函数
2.6.1 无限循环模式
void thread_entry(void* paramenter)
{
while (1)
{
/* 等待事件的发生 */
/* 对事件进行服务、进行处理 */
}
}
2.6.2 顺序执行或有限次循环模式
static void thread_entry(void* parameter)
{
/* 处理事务 #1 */
…
/* 处理事务 #2 */
…
/* 处理事务 #3 */
}
2.7 线程错误码
#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 /* 非法参数 */
三、 代码实现
#include <rtthread.h>
#include <drv_common.h>
#include <rtdevice.h>
#include <rtdbg.h>
#include <stdio.h>
#define RED_LED GET_PIN(B,5)
#define GREEN_LED GET_PIN(E,5)
#define KEY_0 GET_PIN(E,4)
#define KEY_1 GET_PIN(E,3)
#define STACK_SIZE 512
#define PRIORITY 5
#define TICK 10
static rt_thread_t red_tid = RT_NULL;
static struct rt_thread green_tid;
static rt_thread_t key_0_tid = RT_NULL;
static rt_thread_t key_1_tid = RT_NULL;
static void RED_thread_entry(void *paramenter)
{
rt_pin_mode(RED_LED, PIN_MODE_OUTPUT);
rt_pin_write(RED_LED, PIN_HIGH);
rt_thread_mdelay(100);
rt_pin_write(RED_LED, PIN_LOW);
}
static void GREEN_thread_entry(void *paramenter)
{
rt_pin_mode(GREEN_LED, PIN_MODE_OUTPUT);
rt_pin_write(GREEN_LED, PIN_HIGH);
rt_thread_mdelay(100);
rt_pin_write(GREEN_LED, PIN_LOW);
}
int main(void)
{
char stack[512] = {0};
red_tid = rt_thread_create("Red_thread", RED_thread_entry, RT_NULL, STACK_SIZE, PRIORITY, TICK);
rt_thread_init(&green_tid, "Green_thread", GREEN_thread_entry, RT_NULL, &stack[0], sizeof(stack), PRIORITY, TICK);
rt_thread_startup(red_tid);
rt_thread_startup(&green_tid);
while (1)
{
rt_thread_mdelay(500);
}
//rt_pin_write(RED_LED, PIN_LOW);
//rt_pin_write(GREEN_LED, PIN_LOW);
return RT_EOK;
}