目录
[1. 线程结构](#1. 线程结构)
[2. 线程创建](#2. 线程创建)
[2.1 静态线程创建](#2.1 静态线程创建)
[2.2 动态线程创建](#2.2 动态线程创建)
[2.3 源码分析](#2.3 源码分析)
[2.4 线程内存结构](#2.4 线程内存结构)
[3. 线程状态](#3. 线程状态)
[3.1 线程状态分类](#3.1 线程状态分类)
[3.2 就绪状态和运行态](#3.2 就绪状态和运行态)
[3.3 阻塞/挂起状态](#3.3 阻塞/挂起状态)
[3.3.1 阻塞工况](#3.3.1 阻塞工况)
[3.4 关闭状态](#3.4 关闭状态)
[3.4.1 线程关闭接口](#3.4.1 线程关闭接口)
[3.4.2 静态线程关闭](#3.4.2 静态线程关闭)
[3.4.3 动态线程关闭](#3.4.3 动态线程关闭)
[3.6 状态切换](#3.6 状态切换)
[4.1 调度器相关变量](#4.1 调度器相关变量)
[4.2 调度器初始化](#4.2 调度器初始化)
[4.3 调度器启动](#4.3 调度器启动)
[4.4 调度切换](#4.4 调度切换)
[4.5 同优先级线程轮询](#4.5 同优先级线程轮询)
[4.6 调度器逻辑图](#4.6 调度器逻辑图)
[5. 线程接口](#5. 线程接口)
[5.1 设置钩子接口](#5.1 设置钩子接口)
[5.2 线程新建](#5.2 线程新建)
[5.3 线程启动](#5.3 线程启动)
[5.4 线程阻塞接口](#5.4 线程阻塞接口)
[5.4.1 线程固定周期阻塞](#5.4.1 线程固定周期阻塞)
[5.4.2 线程固定延时阻塞](#5.4.2 线程固定延时阻塞)
[5.4.3 线程永久阻塞(挂起)](#5.4.3 线程永久阻塞(挂起))
[5.5 线程强制恢复接口](#5.5 线程强制恢复接口)
[5.6 线程定时器恢复](#5.6 线程定时器恢复)
[5.7 线程同优先级轮询](#5.7 线程同优先级轮询)
[5.8 线程命令接口](#5.8 线程命令接口)
[5.9 线程关闭接口](#5.9 线程关闭接口)
[5.9.1 静态线程关闭接口](#5.9.1 静态线程关闭接口)
[5.9.2 动态线程关闭接口](#5.9.2 动态线程关闭接口)
[5.10 线程退出接口](#5.10 线程退出接口)
[5.11 线程清除接口](#5.11 线程清除接口)
[5.12 线程查询](#5.12 线程查询)
[6. 系统线程](#6. 系统线程)
[6.1 空闲线程](#6.1 空闲线程)
[6.1.1 空闲线程作用](#6.1.1 空闲线程作用)
[6.1.2 空闲线程创建和启动](#6.1.2 空闲线程创建和启动)
[6.1.3 空闲线程入口函数](#6.1.3 空闲线程入口函数)
[6.2 软件定时器线程](#6.2 软件定时器线程)
[6.2.1 定时器线程作用](#6.2.1 定时器线程作用)
[6.2.2 定时器线程创建和启动](#6.2.2 定时器线程创建和启动)
[6.2.3 定时器入口函数](#6.2.3 定时器入口函数)
本章讲解RT_Thread Nano实时操作系统的核心部分------线程。单核CPU裸核运行时,一般是多中断+单线程,即前后台系统;如实现多线程并发,须搭载操作系统。有的实时操作系统,使用的是任务并发,其实线程和任务在实时操作系统中本质是一样的。
本章基于RT_Thread Nano V3.1.5版本分析
1. 线程结构
线程通过线程控制块(rt_thread)维护,rt_thread可分为几个部分:内核对象、线程链表节点、线程栈部分、线程状态部分、线程回调函数部分、线程计数器和定时器部分、线程事件部分、线程优先级部分等。声明如下:
cpp
struct rt_thread
{
/*内核对象部分-STR*/
char name[RT_NAME_MAX]; /**<线程内核对象名称 */
rt_uint8_t type; /**<线程内核对象类型 */
rt_uint8_t flags; /**<线程内核对象标志 */
rt_list_t list; /**<线程内核对象链表节点 */
/*线程部分-STR*/
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; /**< 线程初始化优先级 */
/**< 线程优先级位域*/
#if RT_THREAD_PRIORITY_MAX > 32 // 优先级个数大于32个时的位域
rt_uint8_t number;
rt_uint8_t high_mask;
#endif
rt_uint32_t number_mask;
/**< 线程事件*/
#if defined(RT_USING_EVENT)
rt_uint32_t event_set;
rt_uint8_t event_info;
#endif
/**< 线程执行周期*/
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内核源码分析(二)------链表和对象管理》。
线程栈:*sp、*stack_addr、stack_size三个元素用于管理线程栈,每个线程都会分配一块内存,用于存取线程的局部变量和线程切换时的运行环境,各个线程栈相互独立。*sp指向栈当前使用位置(栈顶);*stack_addr指向栈首地址,stack_size表示线程栈空间大小。
线程栈有2种访问方式:低地址->高地址、高地址->低地址;具体使用哪一种需要与CPU内核设置保持一致。操作系统通过宏定义ARCH_CPU_STACK_GROWS_UPWARD进行设置。
线程回调函数:*entry指向线程回调函数,*parameter为回调函数参数指针。
优先级部分:init_priority表示线程新建时分配的优先级,数值越小表示优先级越高。current_priority表示实际运行时的优先级,大部分情况current_priority等于init_priority,当出现优先级反转现象时,优先级继承机制会运行,current_priority会被改写。比如:高优先级线程H等待低优先级线程L释放资源而被阻塞,线程H出现优先级翻转现象,优先级继承机制将线程L优先级current_priority修改为与线程H一样,当线程H阻塞解除时,线程L优先级current_priority再修改为init_priority。
线程调度为了快速检测优先级,通过标志字rt_thread_ready_priority_group的bit位来判断就绪线程优先级的分布,如下所示:
将就绪线程优先级的位域合并到一起,即可得出总的优先级分布矩阵,当总优先级个数小于32时,仅需要32位无符号整数即可表示所有优先级状态,即线程参数number_mask,比如:优先级为10,则number_mask=10000000000B。
当总优先级个数大于32时(最大不超过256),使用number、high_mask表示,number表示优先级在数组rt_thread_ready_table中的位置,high_mask表示优先级掩码。比如:优先级为60,则number=60/8=7;因60%8=4,则high_mask=10000B;如果线程切换为就绪状态,则将该线程优先级合并到系统总优先级位域,即rt_thread_ready_table[number]|=high_mask。
同级线程轮询参数:
如果最高优先级线程有多个,这几个相同优先级的线程会轮询执行,线程轮询切换在 SysTick中断中进行,为防止频繁切换,每个线程通过参数init_tick设置轮询保持时间,通过参数remaining_tick监视执行时间;比如:线程A(pThreadA)、线程B(pThreadB)均为最高优先级线程,当线程A执行时间超过pThreadA->init_tick个时间片后,才会由切换为线程B运行。
具体代码分析参照章节5.7。
线程定时器部分:
RT_Thread操作系统是通过软件定时器thread_timer进行线程延时阻塞和恢复,定时器回调函数固定为rt_thread_timeout;其功能为恢复线程(阻塞态->就绪态),具体代码分析参照章节5.6。
2. 线程创建
线程创建有两种接口:动态线程创建、静态线程创建;
rt_thread_init:静态线程新建,线程栈和线程控制块均为全局变量,内存无法回收利用,但是方便维护与监视。
rt_thread_create:动态线程新建,线程栈和线程控制块均从系统栈区动态申请,内存可以回收利用。
注:内存申请和释放涉及的内存管理模块,本章暂不详细说明,后文的分析均依据动态任务创建的方式分析!
2.1 静态线程创建
通过静态接口创建线程,需先定义好线程控制器和线程栈,示例如下:
cpp
/*****新建静态线程接口******/
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) // 线程同优先级轮询保持时间
/*****新建静态线程举例*****/
rt_thread g_ThreadA;
unsigned char cStack[0x400];
int iThreadApara;
void vThreadA(*int para)
{
while(1);
}
void main()
{
// 新建静态线程threadA,回调函数vThreadA,线程栈大小为16K,优先级为10,同优先级线程轮询保持时间为10个时间片
rt_thread_init(&g_ThreadA,"threadA",vThreadA,&iThreadApara,cStack,0x4000,10,10);
}
2.2 动态线程创建
动态创建线程,无需定义线程控制器和线程栈,二者均通过申请动态内存存储。
cpp
/*新建动态线程接口*/
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) // 线程同优先级轮询保持时间
/*****新建动态线程举例*****/
int iThreadApara;
void vThreadA(*int para)
{
while(1);
}
void main()
{
// 新建动态线程threadA,回调函数vThreadA,线程栈大小为16K,优先级为10,同优先级线程轮询保持时间为10个时间片
rt_thread_create("threadA",vThreadA,&iThreadApara,0x4000,10,10);
}
2.3 源码分析
静态线程创建,先对线程的系统对象进行初始化,并挂接到线程对象容器,然后进行线程数据初始化,不进行线程链表挂接,所以新建线程状态为初始化(RT_THREAD_INIT)。
cpp
/*新建静态线程*/
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) // 线程同优先级轮询保持时间
{
/* 断言*/
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(stack_start != RT_NULL);
/*内核对象初始化--初始化线程对象,并挂接到线程对象容器 */
rt_object_init((rt_object_t)thread, RT_Object_Class_Thread, name);
/*线程数据初始化 */
return _rt_thread_init(thread,name,entry,parameter,stack_start,stack_size,
priority,tick);
}
动态线程创建,先申请线程控制器内存,然后初始化系统对象,并挂接到线程对象容器,再进行线程栈内存申请,最后进行线程数据初始化,不进行线程链表挂接,所以新建线程状态为初始化(RT_THREAD_INIT)。
cpp
/*新建动态线程*/
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) // 线程同优先级轮询保持时间
{
struct rt_thread *thread;
void *stack_start;
/*动态分配线程控制器内存,并初始化线程对象,挂接至线程容器链表*/
thread = (struct rt_thread *)rt_object_allocate(RT_Object_Class_Thread, name);
if (thread == RT_NULL) return RT_NULL;
/*动态分配线程栈内存*/
stack_start = (void *)RT_KERNEL_MALLOC(stack_size);
if (stack_start == RT_NULL)
{
/* 分配栈失败,释放线程对象*/
rt_object_delete((rt_object_t)thread);
return RT_NULL;
}
}
静态创建和动态创建线程均通过调用函数_rt_thread_init进行线程数据初始化,代码分析如下:
cpp
/* 线程数据初始化*/
static 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) // 线程同优先级轮询保持时间
{
/*1. 线程链表节点初始化*/
rt_list_init(&(thread->tlist));
/*2. 线程回调函数和参数设置*/
thread->entry = (void *)entry;
thread->parameter = parameter;
/*3. 线程栈初始化*/
thread->stack_addr = stack_start;// 首地址
thread->stack_size = stack_size; // 栈大小
/*3.1 栈内容初始化为#号 */
rt_memset(thread->stack_addr, '#', thread->stack_size);
/*3.2 初始化栈指针SP*/
#ifdef ARCH_CPU_STACK_GROWS_UPWARD
/*3.3 栈访问方式:低地址->高地址,低地址为栈底*/
thread->sp=(void*)rt_hw_stack_init(thread->entry, thread->parameter,
(void*)((char*)thread->stack_addr),(void*)rt_thread_exit);
#else
/*3.4 栈访问方式:高地址->底地址,高地址为栈底*/
thread->sp=(void*)rt_hw_stack_init(thread->entry, thread->parameter,
(rt_uint8_t*)((char*)thread->stack_addr+thread->stack_size -
sizeof(rt_ubase_t)),(void*)rt_thread_exit);
#endif
/*4. 优先级初始化*/
RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);
thread->init_priority = priority;
thread->current_priority = priority;
/*4.1 优先级位域初始化*/
thread->number_mask = 0;
#if RT_THREAD_PRIORITY_MAX > 32
thread->number = 0;
thread->high_mask = 0;
#endif
/*5. 同优先级线程轮询计数初始化*/
thread->init_tick = tick;
thread->remaining_tick = tick;
/*6. 线程状态初始化 */
thread->error = RT_EOK;
thread->stat = RT_THREAD_INIT;
/*7. 初始化清除函数和用户数据*/
thread->cleanup = 0;
thread->user_data = 0;
/*8. 线程定时器初始化*/
rt_timer_init(&(thread->thread_timer), // 定时器控制器
thread->name, // 定时器名称,取线程名称
rt_thread_timeout, // 定时器回调函数,固定为rt_thread_timeout
thread, // 定时器回调函数参数,取线程指针
0, // 定时时间
RT_TIMER_FLAG_ONE_SHOT); // 定时器类型:单次定时器
/*9.新建线程钩子调用*/
RT_OBJECT_HOOK_CALL(rt_thread_inited_hook, (thread));
return RT_EOK;
}
2.4 线程内存结构
以动态线程为例,线程动态创建后,内存结构如下所示:
3. 线程状态
本节重点讲解线程状态及各个状态之间的切换。
3.1 线程状态分类
RT_Thread中线程挂起状态与阻塞状态宏定义一致,挂起状态实质为永久阻塞状态。
cpp
/*线程状态宏定义*/
#define RT_THREAD_INIT 0x00 /**< 初始化状态*/
#define RT_THREAD_READY 0x01 /**< 就绪状态*/
#define RT_THREAD_SUSPEND 0x02 /**< 阻塞状态 */
#define RT_THREAD_RUNNING 0x03 /**< 运行状态 */
#define RT_THREAD_BLOCK RT_THREAD_SUSPEND /**< 挂起状态(与阻塞状态相同)*/
#define RT_THREAD_CLOSE 0x04 /**< 关闭状态*/
#define RT_THREAD_STAT_MASK 0x0f /**< 状态掩码 */
3.2 就绪状态和运行态
线程创建后,为初始化状态(RT_THREAD_INIT),如果实现运行,必须切至就绪状态。调用线程启动函数rt_thread_startup,线程将被挂接到就绪链表,由初始化状态切换为就绪态,如果线程为最高优先级,且被调度选中执行,该线程由就绪态切换为运行态。
线程如果为阻塞/挂起态,调用线程恢复函数rt_thread_resume,线程重新挂接到就绪链表,由阻塞/挂起态切换为就绪态,如果线程为最高优先级,且被调度选中执行,该线程为由就绪态切换为运行态。
线程就绪链表实质为一链表数组,优先级作为数组下标,即每个优先级可以挂接多个线程。
cpp
extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; // 线程就绪链表数组
线程就绪态切换接口如下所示,具体代码分析参照章节5。
cpp
/*线程thread启动(初始化->就绪状态)*/
rt_err_t rt_thread_startup(rt_thread_t thread)
/*阻塞线程thread恢复(阻塞/挂起态->就绪态)*/
rt_err_t rt_thread_resume(rt_thread_t thread)
3.3 阻塞/挂起状态
当线程进行延时出让,或者等待消息、信号时,会由运行态切换为阻塞/挂起状态,线程节点将从就绪链表移除,并将线程状态修改为阻塞/挂起态。
线程如果从阻塞/挂起态恢复为就绪状态,有定时器自动恢复和强制恢复2种方式,本质上是将线程移回就绪链表,并修改为就绪状态。
(1)延时阻塞恢复:通过线程定时器rt_thread->thread_timer延时恢复就绪。
(2)强制恢复:通过调用rt_thread_resume接口强制恢复就绪,比如挂起状态要想恢复就绪,就必须通过调用rt_thread_resume实现。
3.3.1 阻塞工况
线程在如下情况下会出现阻塞/挂起:
(1)线程主动延时出让或挂起,执行如下接口可以实现,具体代码分析参考章节5。
cpp
/*线程延时(阻塞),调用周期固定inc_tick,tick记录再次进入该接口时间,阻塞时间不固定,单位为时间片*/
rt_err_t rt_thread_delay_until(rt_tick_t *tick, rt_tick_t inc_tick)
/*线程延时(阻塞)一定时间tick,单位时间片*/
rt_err_t rt_thread_delay(rt_tick_t tick)
/*线程延时(阻塞)一定时间ms,单位毫秒(ms)*/
rt_err_t rt_thread_mdelay(rt_int32_t ms)
/*线程thread永久阻塞,即切换为挂起态*/
rt_err_t rt_thread_suspend(rt_thread_t thread)
注1:rt_thread_delay_until表示两次调用函数的周期为inc_tick,阻塞时间不固定,甚至无阻塞。
注2:如果调用rt_thread_suspend挂起当前运行态线程,挂起后必须立刻启动调度rt_schedule。
(2)内存申请阻塞线程:
内存申请涉及临界数据防护,可能会阻塞线程。
cpp
void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time)
(3)IPC通信阻塞:
cpp
/*获取信号,当信号被占用时,线程阻塞,最大阻塞时间为time*/
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)
/*获取互斥信号,当信号被占用时,线程阻塞,最大阻塞时间为time*/
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
/*接收事件,当没有事件时,线程阻塞,最大阻塞时间为timeout*/
rt_err_t rt_event_recv(rt_event_t event,rt_uint32_t set,rt_uint8_t option,
rt_int32_t timeout,rt_uint32_t *recved)
/*发送邮件,当邮箱满时,线程阻塞,最大阻塞时间为timeout*/
rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_ubase_t value,rt_int32_t timeout)
/*接收邮件,当邮箱空时,线程阻塞,最大阻塞时间为timeout*/
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)
/*发送队列成员,当队列满时,线程阻塞,最大阻塞时间为timeout*/
rt_err_t rt_mq_send_wait(rt_mq_t mq,const void *buffer,rt_size_t size,rt_int32_t timeout)
/*接收队列成员,当队列空时,线程阻塞,最大阻塞时间为timeout*/
rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer,rt_size_t size,rt_int32_t timeout)
/*等待信号,当没有信号时,线程阻塞,最大阻塞时间为timeout*/
int rt_signal_wait(const rt_sigset_t *set, rt_siginfo_t *si, rt_int32_t timeout)
3.4 关闭状态
有些线程是临时建立的,完成使命后不再使用,可以切换至关闭状态,如果是动态线程,状态切换后,还需通过空闲线程(tidle)进行内存释放。
3.4.1 线程关闭接口
线程关闭接口如下所示,具体代码分析参照章节5.9。
cpp
/*删除静态线程,即切换至关闭状态*/
rt_err_t rt_thread_detach(rt_thread_t thread)
/*删除动态线程,即切换至关闭状态,线程插入到无效线程链表rt_thread_defunct */
rt_err_t rt_thread_delete(rt_thread_t thread)
3.4.2 静态线程关闭
(1)将线程从就绪链表移除。
(2) 如果线程设置了线程清理回调,则调用线程清理。
(3)将线程定时器从定时器链表移除,将线程定时器基类从定时器容器移除。
(4)将线程基类从线程容器移除。
(5)线程状态修改为关闭状态。
3.4.3 动态线程关闭
(1) 将线程从就绪链表移除。
(2)如果线程设置了线程清理回调,则调用线程清理。
(3)将线程定时器从定时器链表移除,将线程定时器基类从定时器容器移除。
(4)线程状态修改为关闭状态。
(5)将线程插入到无效线程链表rt_thread_defunct。
(6)由系统空闲线程统一释放无效线程内存,并从线程容器移除线程。
3.6 状态切换
1.创建线程→初始化态:线程创建后,为初始化状态。
2.初始化态→就绪态:线程启动后,根据优先级将线程连接至就绪链表,等待调度器进行调度。
3.就绪态←→运行态:系统调度器启动后,指针rt_current_thread指向优先级最高的线程控制块,取该线程执行,该线程状态为运行态,同时rt_current_thread指向的原任务切换为就绪态。
4.就绪/运行态←→阻塞/挂起态:正在运行的线程发生阻塞(主动延时出让、读信号量等待)时,会从就绪列表中删除,线程由运行态变成阻塞/挂起态,然后触发线程调度,切换其他线程执行;当该线程阻塞时间到或者调用强制恢复时,线程会重新插入就绪列表,然后触发线程调度,如果该线程为最高优先级,则切为运行态。
5.关闭态:当线程完成使命后,调用线程关闭函数,线程从所关联的链表中移除;动态线程还需要释放内存。
4.线程调度
RT_Thread操作系统支持抢占式线程调度 和相同优先级线程轮询执行。
4.1 调度器相关变量
系统调度器涉及的变量如下:
cpp
/*1.就绪线程链表(每个优先级一张链表),用于维护就绪线程*/
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];
/*2.无效线程链表,用于连接关闭的动态线程,在空闲线程中统一释放内存*/
rt_list_t rt_thread_defunct;
/*3.就绪线程优先级矩阵,用于快速定位有效优先级*/
rt_uint32_t rt_thread_ready_priority_group;// 32个优先级
#if RT_THREAD_PRIORITY_MAX > 32
/* Maximum priority level, 256 */
rt_uint8_t rt_thread_ready_table[32]; // 256个优先级
#endif
/*4.中断挂起标志*/
extern volatile rt_uint8_t rt_interrupt_nest;
/*5.调度器挂起标志*/
static rt_int16_t rt_scheduler_lock_nest;
/*6.运行态线程*/
struct rt_thread *rt_current_thread = RT_NULL;
/*7.当前运行态线程优先级*/
rt_uint8_t rt_current_priority;
4.2 调度器初始化
调度器初始化主要对调度器相关变量初始化,接口分析如下:
cpp
/*初始化调度器*/
void rt_system_scheduler_init(void)
{
register rt_base_t offset;
rt_scheduler_lock_nest = 0;
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,("start scheduler: max priority 0x%02x\n",
RT_THREAD_PRIORITY_MAX));
/*1.就绪链表数组初始化*/
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
{
rt_list_init(&rt_thread_priority_table[offset]);
}
/*2.最高优先级变量初始化*/
rt_current_priority = RT_THREAD_PRIORITY_MAX - 1;
/*3.运行态线程初始化*/
rt_current_thread = RT_NULL;
/*4.优先级位域初始化 */
rt_thread_ready_priority_group = 0;// 最大32个优先级
#if RT_THREAD_PRIORITY_MAX > 32
rt_memset(rt_thread_ready_table, 0, sizeof(rt_thread_ready_table));// 最大256个优先级
#endif
/*5.无效线程链表初始化 */
rt_list_init(&rt_thread_defunct);
}
4.3 调度器启动
调度器启动之前是不会执行任何线程的,系统线程最好在调度启动之前完成创建和启用。调度器选出当前优先级最高的线程,通过触发PendSv中断进行上下文切换,退出PendSv中断后,将CPU切换至用户线程模式运行该线程。
CPU切换至用户线程模式后,将在用户线程模式和handle中断模式交替切换,如无故障,将不会再返回handle线程模式;所以调度启动之后的代码不会被执行到。
cpp
/**调度器启动 */
void rt_system_scheduler_start(void)
{
register struct rt_thread *to_thread;
register rt_ubase_t highest_ready_priority;
#if RT_THREAD_PRIORITY_MAX > 32
register rt_ubase_t number;
number = __rt_ffs(rt_thread_ready_priority_group) - 1;
highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
#else
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#endif
/*获取最高优先级的第一个线程*/
to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
rt_current_thread = to_thread;
/* 切换最高优先级线程执行 */
rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);
}
注:RT_Thread启动分析可以参考《RT_Thread内核源码分析(一)------CM3内核和上下文切换》
4.4 调度切换
调度切换函数rt_schedule()可以在线程、时间片中断、其他中断中调用,当运行态线程被阻塞或有新线程切换为就绪状态时,就会主动调用rt_schedule()进行线程切换。线程切换最终在PendSv中断中进行,rt_schedule()主要为PendSv中断进行参数传递,并挂起PendSv中断。具体上下文切换汇编代码分析分析参考《RT_Thread内核源码分析(一)------CM3内核和上下文切换》。
调度器切换场景:
(1)运行态线程被阻塞,线程主动调用rt_schedule()进行调度切换;
(2)挂起/阻塞态线程切换至就绪态,主动调用rt_schedule()进行调度切换;
(3)有新线程新建并启动,线程模式主动调用rt_schedule()进行调度切换;
(4)时间片中断检测到高优先级线程,调用rt_schedule()进行调度切换;
(5)时间片中断检测到同等优先级线程,调用rt_schedule()进行线程轮询执行;
调度器切换源码分析:
cpp
void rt_schedule(void)
{
rt_base_t level;
struct rt_thread *to_thread;
struct rt_thread *from_thread;
/* 进入临界区:关中断*/
level = rt_hw_interrupt_disable();
/1.调度器非闭锁状态/
if (rt_scheduler_lock_nest == 0)
{
/* 1.1 获取线程最高优先级*/
register rt_ubase_t highest_ready_priority;
#if RT_THREAD_PRIORITY_MAX <= 32
highest_ready_priority= __rt_ffs(rt_thread_ready_priority_group)-1;
#else
register rt_ubase_t number;
number = __rt_ffs(rt_thread_ready_priority_group) - 1;
highest_ready_priority=(number<<3)+__rt_ffs(rt_thread_ready_table[number])- 1;
#endif
/* 1.2 获取最高优先级线程指针 */
to_thread=rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread, tlist);
/* 1.3 最高优先级线程非当前线程,需要进行上线文切换 */
if (to_thread != rt_current_thread)
{
/*1.3.1 更新运行态线程优先级*/
rt_current_priority = (rt_uint8_t)highest_ready_priority;
/*1.3.2 更新原运行态线程*/
from_thread = rt_current_thread;
/*1.3.3 更新即将切换为运行态的线程*/
rt_current_thread = to_thread;
/*1.3.4 执行调度钩子*/
RT_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread));
/* 日志打印 */
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
("[%d]switch to priority#%d "
"thread:%.*s(sp:0x%p), "
"from thread:%.*s(sp: 0x%p)\n",
rt_interrupt_nest, highest_ready_priority,
RT_NAME_MAX, to_thread->name, to_thread->sp,
RT_NAME_MAX, from_thread->name, from_thread->sp));
/* 1.3.5 检测线程栈是否存在溢出现象*/
#ifdef RT_USING_OVERFLOW_CHECK
_rt_scheduler_stack_check(to_thread);
#endif
/* 1.3.6 线程中进行切换(判断中断非屏蔽标志)*/
if (rt_interrupt_nest == 0)
{
/* 设置上下文切换标志,传递切换参数,挂起PendSv中断*/
rt_hw_context_switch((rt_ubase_t)&from_thread->sp,
(rt_ubase_t)&to_thread->sp);
/* 使能中断*/
rt_hw_interrupt_enable(level);
/* PendSv中断在中断使能后执行,进行上下文切换*/
return ;
}
/* 1.3.7 中断中进行切换(判断中断非屏蔽标志)*/
else
{
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));
/* 设置上下文切换标志,传递切换参数,挂起PendSv中断*/
rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp,
(rt_ubase_t)&to_thread->sp);
/* PendSv中断优先级低,将在其他中断退出后执行,进行上下文切换*/
}
}
}
/* 退出临界区:开中断*/
rt_hw_interrupt_enable(level);
}
4.5 同优先级线程轮询
SysTick中断为操作系统提供时间片,并进行轮询和抢占式调度。功能如下:
(1)时间片计数,时间片是操作系统的心跳,即最小时间单位。
(2)线程切换:如果就绪链表有高优先级线程,切换至高优先级线程,如果当前线程优先级最高并且同优先级线程有多个,轮询切换至同级优先级线程;为防止频繁同级切换,线程参数thread->remaining_tick用作切换周期,即当前任务执行时间超过thread->remaining_tick个时间片后才会进行同级切换。
(3)遍历硬件定时器,并对时间到的定时器进行回调,对时间到的循环定时器进行重置。
cpp
/*1、SysTick中断入口*/
void SysTick_Handler(void)
{
/*进入临界*/
rt_interrupt_enter();
/*时间片执行*/
rt_tick_increase();
/*退出临界 */
rt_interrupt_leave();
}
/*2、时间片执行*/
void rt_tick_increase(void)
{
struct rt_thread *thread;
/*全局时间片计数器*/
++ rt_tick;
/*检查时间片 */
thread = rt_thread_self();
/*执行时间是否到,未到不能进行线程切换*/
-- thread->remaining_tick;
if (thread->remaining_tick == 0)
{
/*更换当前线程初始化时间片*/
thread->remaining_tick = thread->init_tick;
/*同级线程切换*/
rt_thread_yield();
}
/*检查硬件定时器*/
rt_timer_check();
}
4.6 调度器逻辑图
5. 线程接口
本节主要进行线程相关接口代码分析:
5.1 设置钩子接口
用户可根据需求设置相关钩子。
cpp
static void (*rt_thread_suspend_hook)(rt_thread_t thread); // 线程阻塞钩子
static void (*rt_thread_resume_hook) (rt_thread_t thread); // 线程恢复钩子
static void (*rt_thread_inited_hook) (rt_thread_t thread); // 线程新建钩子
/*线程阻塞钩子设置,钩子函数不得有阻塞*/
void rt_thread_suspend_sethook(void (*hook)(rt_thread_t thread))
{
rt_thread_suspend_hook = hook;
}
/*线程恢复钩子设置,钩子函数不得有阻塞*/
void rt_thread_resume_sethook(void (*hook)(rt_thread_t thread))
{
rt_thread_resume_hook = hook;
}
/*线程新建钩子设置,钩子函数不得有阻塞*/
void rt_thread_inited_sethook(void (*hook)(rt_thread_t thread))
{
rt_thread_inited_hook = hook;
}
5.2 线程新建
线程新建相关接口_rt_thread_init、rt_thread_init、rt_thread_create在章节2中进行了代码分析,本章不再赘述。
5.3 线程启动
线程启动前必须已经新建,调用启动后,将被挂接到就绪链表,由初始化状态切换为就绪态。
cpp
/*启动线程thread*/
rt_err_t rt_thread_startup(rt_thread_t thread)
{
/* 断言 */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
/* 1. 优先级设置*/
thread->current_priority = thread->init_priority;
/* 1.1 优先级位域设置*/
#if RT_THREAD_PRIORITY_MAX > 32
/* 优先级总个数大于32,number表示位域数组下标,high_mask对应字节的位域*/
thread->number = thread->current_priority >> 3; /* 5bit */
thread->number_mask = 1L << thread->number;
thread->high_mask = 1L << (thread->current_priority & 0x07); /* 3bit */
#else
/* 优先级总个数不大于32,number_mask 表示优先级位域*/
thread->number_mask = 1L << thread->current_priority;
#endif
RT_DEBUG_LOG(RT_DEBUG_THREAD, ("startup a thread:%s with priority:%d\n",
thread->name, thread->init_priority));
/* 2.线程状态设置为阻塞状态*/
thread->stat = RT_THREAD_SUSPEND;
/* 3.恢复线程,线程挂接到就绪链表,阻塞态->就绪态 */
rt_thread_resume(thread);
/* 4.当前线程存在,启动线程调度,选取最高优先级线程为运行态线程*/
if (rt_thread_self() != RT_NULL)
{
rt_schedule();
}
return RT_EOK;
}
5.4 线程阻塞接口
5.4.1 线程固定周期阻塞
该接口用于实现线程周期执行,比如:线程循环每隔10ms进行一次,由于线程代码执行时间不固定,阻塞时间也就不固定,甚至阻塞时间为0。所以使用该接口时,需要先测试线程执行时间,设置参数inc_tick需要留出足够的时间裕度。
该接口使用到了线程软件定时器,定时器回调函数固定为rt_thread_timeout,代码分析参照5.6.
cpp
/*线程延时(阻塞),调用周期为inc_tick,tick记录在次进入时间,阻塞时间不固定*/
rt_err_t rt_thread_delay_until(rt_tick_t *tick, rt_tick_t inc_tick)
{
register rt_base_t level;
struct rt_thread *thread;
RT_ASSERT(tick != RT_NULL);
/* 当前线程断言 set to current thread */
thread = rt_thread_self();
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
/* 进入临界区 disable interrupt */
level = rt_hw_interrupt_disable();
/*1. 与上次执行该接口的时间差未到设定的周期时间,阻塞线程,阻塞时间为:设置周期-(当前时间片-上次执行接口的时间片)*/
if(rt_tick_get()-*tick < inc_tick)
{
/* 计算定时时刻*/
*tick = *tick + inc_tick - rt_tick_get();
/* 阻塞线程*/
rt_thread_suspend(thread);
/* 设置定时器*/
rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, tick);
/* 启动定时器 */
rt_timer_start(&(thread->thread_timer));
/* 退出临界区 */
rt_hw_interrupt_enable(level);
/* 执行线程调度,切换至其他线程*/
rt_schedule();
/* 定时时间到,阻塞解除后,容错错误码*/
if (thread->error == -RT_ETIMEOUT)
{
thread->error = RT_EOK;
}
}
/*2. 与上次执行该接口的时间差已超设定的周期时间,不阻塞*/
else
{
rt_hw_interrupt_enable(level);
}
/*3. 更新接口执行完成时间*/
*tick = rt_tick_get();
return RT_EOK;
}
5.4.2 线程固定延时阻塞
rt_thread_delay与rt_thread_mdelay接口功能相同,均为线程固定延时阻塞,二者形参单位不同,rt_thread_delay接口形参tick单位为时间片。rt_thread_mdelay形参单位为毫秒。
rt_thread_mdelay更方便跨平台使用。
cpp
/*线程延时(阻塞)一定时间,单位时间片*/
rt_err_t rt_thread_delay(rt_tick_t tick)
{
return rt_thread_sleep(tick);
}
/*线程延时(阻塞)一定时间,单位ms*/
rt_err_t rt_thread_mdelay(rt_int32_t ms)
{
rt_tick_t tick;
// 毫秒数转化为时间片数
tick = rt_tick_from_millisecond(ms);
return rt_thread_sleep(tick);
}
/*线程延时(阻塞)一定时间,单位时间片*/
rt_err_t rt_thread_sleep(rt_tick_t tick)
{
register rt_base_t temp;
struct rt_thread *thread;
/* 进入临界区 */
temp = rt_hw_interrupt_disable();
/* 当前线程断言 */
thread = rt_current_thread;
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
/*线程阻塞,从就绪链表移除*/
rt_thread_suspend(thread);
/*启动线程软件计时器,定时延时为tick */
rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &tick);
rt_timer_start(&(thread->thread_timer));
/* 退出临界区*/
rt_hw_interrupt_enable(temp);
/* 启动调度 */
rt_schedule();
/* 定时时间到,容错误码*/
if (thread->error == -RT_ETIMEOUT)
thread->error = RT_EOK;
return RT_EOK;
}
5.4.3 线程永久阻塞(挂起)
如果挂起的是当前线程,挂起后,必须执行rt_schedule()进行调度切换。
线程挂起后,如无定时器恢复,只能通过线程恢复接口(rt_thread_resume)切回就绪态。
cpp
/*挂起就绪线程(挂起态),如果挂起当前线程,挂起后,必须执行rt_schedule()进行调度切换*/
rt_err_t rt_thread_suspend(rt_thread_t thread)
{
register rt_base_t temp;
/* 断言*/
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
RT_DEBUG_LOG(RT_DEBUG_THREAD, ("thread suspend: %s\n", thread->name));
if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_READY)
{
RT_DEBUG_LOG(RT_DEBUG_THREAD, ("thread suspend: thread disorder, 0x%2x\n",
thread->stat));
return -RT_ERROR;
}
/* 进入临界区*/
temp = rt_hw_interrupt_disable();
/* 从就绪链表移除线程*/
rt_schedule_remove_thread(thread);
/* 更新线程状态为阻塞态*/
thread->stat = RT_THREAD_SUSPEND | (thread->stat & ~RT_THREAD_STAT_MASK);
/* 停止定时器*/
rt_timer_stop(&(thread->thread_timer));
/* 退出临界区*/
rt_hw_interrupt_enable(temp);
/*线程阻塞调用钩子*/
RT_OBJECT_HOOK_CALL(rt_thread_suspend_hook, (thread));
return RT_EOK;
}
5.5 线程强制恢复接口
将线程从阻塞链表切换到就绪链表(阻塞态->就绪态) ,与线程定时器回调恢复接口(rt_thread_timeout)不同,该函数为手动强制切换,对于挂起的线程只能通过该接口恢复,该函数执行成功后一般需要执行调度切换。
cpp
rt_err_t rt_thread_resume(rt_thread_t thread)
{
register rt_base_t temp;
/* 断言thread check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
RT_DEBUG_LOG(RT_DEBUG_THREAD, ("thread resume: %s\n", thread->name));
/*非阻塞状态*/
if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_SUSPEND)
{
RT_DEBUG_LOG(RT_DEBUG_THREAD, ("thread resume: thread disorder, %d\n",thread->stat));
return -RT_ERROR;
}
/* 进入临界区*/
temp = rt_hw_interrupt_disable();
/* 从阻塞链表移除线程*/
rt_list_remove(&(thread->tlist));
/* 停止定时器*/
rt_timer_stop(&thread->thread_timer);
/* 退出临界区*/
rt_hw_interrupt_enable(temp);
/* 将线程插入到就绪链表*/
rt_schedule_insert_thread(thread);
/* 线程恢复钩子函数执行*/
RT_OBJECT_HOOK_CALL(rt_thread_resume_hook, (thread));
return RT_EOK;
}
5.6 线程定时器恢复
从章节5.4中看出,线程延时阻塞会启动线程定时器,回调函数为 rt_thread_timeout(void *parameter),在定时器线程中,定时时间到后自动调用,将线程切换到就绪链表,线程状态修改为就绪态(阻塞态->就绪态)。
cpp
/*阻塞时间到,线程恢复*/
void rt_thread_timeout(void *parameter)
{
struct rt_thread *thread;
thread = (struct rt_thread *)parameter;
/*断言*/
RT_ASSERT(thread != RT_NULL);
RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
/* 超时标志*/
thread->error = -RT_ETIMEOUT;
/* 从阻塞链表移除线程*/
rt_list_remove(&(thread->tlist));
/* 插入到就绪链表*/
rt_schedule_insert_thread(thread);
/* 启动调度*/
rt_schedule();
}
5.7 线程同优先级轮询
章节4.4 中提到,如果最高优先级线程有多个,时间片中断会对这几个线程轮流执行。
cpp
/*同优先级级切换*/
rt_err_t rt_thread_yield(void)
{
register rt_base_t level;
struct rt_thread *thread;
/* 进入临界区*/
level = rt_hw_interrupt_disable();
/* 当前线程*/
thread = rt_current_thread;
/* 线程状态就绪,并且有同优先级的其他线程*/
if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_READY &&thread->tlist.next !=
thread->tlist.prev)
{
/* 将当前线程从就绪链表移除从头部移至尾部*/
rt_list_remove(&(thread->tlist));
rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),&
(thread->tlist));
/* 退出临界区*/
rt_hw_interrupt_enable(level);
/*切换线程*/
rt_schedule();
return RT_EOK;
}
/* 退出临界区*/
rt_hw_interrupt_enable(level);
return RT_EOK;
}
5.8 线程命令接口
该接口通过下发命令方式进行线程操作,具体命令如下:
(1)启动优先级继承命令,用于互斥信号使用过程出现优先级翻转的情况。
(2)启动线程,等同于章节5.3。
(3)关闭线程,等同于章节5.9。
(4)线程绑定内核,用于多核CPU ,Nano版本中没有该功能。
cpp
/*线程控制,控制命令cmd:
* RT_THREAD_CTRL_CHANGE_PRIORITY 优先级继承
* RT_THREAD_CTRL_STARTUP 启动线程;
* RT_THREAD_CTRL_CLOSE 关闭线程
* RT_THREAD_CTRL_BIND_CPU 线程绑定内核.
*/
rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg)
{
register rt_base_t temp;
/* 线程断言*/
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
switch (cmd)
{
/*优先级继承功能*/
case RT_THREAD_CTRL_CHANGE_PRIORITY:
/* 进入临界区*/
temp = rt_hw_interrupt_disable();
/* 就绪任务*/
if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_READY)
{
/* 移除就绪线程*/
rt_schedule_remove_thread(thread);
/* 修改为继承后的优先级*/
thread->current_priority = *(rt_uint8_t *)arg;
/* 更新优先级位域*/
#if RT_THREAD_PRIORITY_MAX > 32
thread->number = thread->current_priority >> 3; /* 5bit */
thread->number_mask = 1 << thread->number;
thread->high_mask = 1 << (thread->current_priority & 0x07); /* 3bit */
#else
thread->number_mask = 1 << thread->current_priority;
#endif
/* 线程插入至新优先级就绪链表*/
rt_schedule_insert_thread(thread);
}
/*非就绪任务,只更新优先级和优先级位域*/
else
{
thread->current_priority = *(rt_uint8_t *)arg;
#if RT_THREAD_PRIORITY_MAX > 32
thread->number = thread->current_priority >> 3; /* 5bit */
thread->number_mask = 1 << thread->number;
thread->high_mask = 1 << (thread->current_priority & 0x07); /* 3bit */
#else
thread->number_mask = 1 << thread->current_priority;
#endif
}
/* 退出临界区*/
rt_hw_interrupt_enable(temp);
break;
case RT_THREAD_CTRL_STARTUP:
/*启动线程*/
return rt_thread_startup(thread);
case RT_THREAD_CTRL_CLOSE:
/*关闭线程*/
if (rt_object_is_systemobject((rt_object_t)thread) == RT_TRUE)// 静态线程
{
return rt_thread_detach(thread);
}
#ifdef RT_USING_HEAP
else
{
return rt_thread_delete(thread);// 动态线程
}
#endif
default:
break;
}
return RT_EOK;
}
5.9 线程关闭接口
有些线程是临时建立的,完成使命后不再使用,可以进行关闭。
5.9.1 静态线程关闭接口
注:接口rt_thread_detach也可以用于动态线程关闭。
cpp
t_err_t rt_thread_detach(rt_thread_t thread)
{
rt_base_t lock;
/* 线程断言 */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
RT_ASSERT(rt_object_is_systemobject((rt_object_t)thread));
if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_CLOSE)
return RT_EOK;
/* 非初始化线程,先从就绪链表移除*/
if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_INIT)
{
rt_schedule_remove_thread(thread);
}
/* 线程清除回调,由用户自定义*/
_thread_cleanup_execute(thread);
/* 释放线程定时器 */
rt_timer_detach(&(thread->thread_timer));
/* 线程状态修改为关闭态*/
thread->stat = RT_THREAD_CLOSE;
/* 静态线程:将线程基类从线程容器移除*/
if (rt_object_is_systemobject((rt_object_t)thread) == RT_TRUE)
{
rt_object_detach((rt_object_t)thread);
}
/* 动态线程:将线程插入到无效线程链表*/
else
{
/* 进入临界 */
lock = rt_hw_interrupt_disable();
/* 插入线程到无效线程链表rt_thread_defunct */
rt_list_insert_after(&rt_thread_defunct, &(thread->tlist));
/* 进入临界退出 */
rt_hw_interrupt_enable(lock);
}
return RT_EOK;
}
5.9.2 动态线程关闭接口
cpp
/*删除线程*/
rt_err_t rt_thread_delete(rt_thread_t thread)
{
rt_base_t lock;
/*断言*/
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
RT_ASSERT(rt_object_is_systemobject((rt_object_t)thread) == RT_FALSE);
if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_CLOSE)
return RT_EOK;
/* 非初始化状态线程,从就绪链表移除 */
if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_INIT)
{
rt_schedule_remove_thread(thread);
}
/* 线程清除回调,由用户自定义*/
_thread_cleanup_execute(thread);
/* 释放线程定时器*/
rt_timer_detach(&(thread->thread_timer));
/* 进入临界区 */
lock = rt_hw_interrupt_disable();
/* 设置线程状态为关闭态 */
thread->stat = RT_THREAD_CLOSE;
/*线程移至阻塞链表rt_thread_defunct */
rt_list_insert_after(&rt_thread_defunct, &(thread->tlist));
/* 退出临界区*/
rt_hw_interrupt_enable(lock);
return RT_EOK;
}
5.10 线程退出接口
与线程关闭不同,线程退出接口指针挂接在CPU的LR寄存器,当线程回调函数退出时自动执行。而线程关闭是个主动执行的过程,线程回调函数不会退出,故执行线程关闭接口不会调用到线程退出。
线程退出可能出现的情况
(1)程序员故意设计的执行,即线程进行有限次循环后退出。
(2)线程循因环异常退出(break)。
cpp
/* 线程退出 */
void rt_thread_exit(void)
{
struct rt_thread *thread;
register rt_base_t level;
/* 当前线程 */
thread = rt_current_thread;
/* 进入临界区:关中断*/
level = rt_hw_interrupt_disable();
/* 线程清理*/
_thread_cleanup_execute(thread);
/* 从就绪链表移除*/
rt_schedule_remove_thread(thread);
/* 线程状态修改为关闭状态 */
thread->stat = RT_THREAD_CLOSE;
/* 从线程定时器移除线程条目*/
rt_timer_detach(&thread->thread_timer);
/* 如果是静态线程,从线程容器移除该基类*/
if (rt_object_is_systemobject((rt_object_t)thread) == RT_TRUE)
{
rt_object_detach((rt_object_t)thread);
}
/*如果是动态线程,将线程插入到失效链表*/
else
{
/* 插入到失效链表 */
rt_list_insert_after(&rt_thread_defunct, &(thread->tlist));
}
/* 切换线程 */
rt_schedule();
/* 退出临界区:开中断*/
rt_hw_interrupt_enable(level);
}
5.11 线程清除接口
用户根据需求可以设置线程清除函数,设置后,当线程关闭或退出时会自动调用该函数,可以当做线程退出的钩子看待。
cpp
/* 线程清理(退出时必须清理) */
static void _thread_cleanup_execute(rt_thread_t thread)
{
register rt_base_t level;
/* 进入临界区:关中断*/
level = rt_hw_interrupt_disable();
/* 调用线程清理函数 */
if (thread->cleanup != RT_NULL)
thread->cleanup(thread);
/* 退出临界区:开中断*/
rt_hw_interrupt_enable(level);
}
5.12 线程查询
根据线程名称获取线程句柄,用于遍历和监视线程。
cpp
/*定位线程(通过名称)*/
rt_thread_t rt_thread_find(char *name)
{
return (rt_thread_t)rt_object_find(name, RT_Object_Class_Thread);
}
6. 系统线程
一般实时操作系统会自带2个系统线程:空闲线程、定时器线程。
6.1 空闲线程
空闲线程优先级设置为最低。
6.1.1 空闲线程作用
1.执行钩子函数:钩子函数由用户定义,可以是一些优先级很低的功能模块,如空闲时间计算监视等,但是和其他钩子函数一样,不得有进入阻塞状态或访问系统临界区的代码。
2.释放无效的线程:动态线程关闭、退出操作,本质上是将线程插入到无效链表rt_thread_defunct;在此处对动态线的内存释放统一释放,并将线程从线程容器移除。
3.进入芯片低功耗模式:当无就绪线程,且线程长期处于阻塞状态时,可以从空闲线程进入低功耗模式,此时芯片可以暂时关闭系统心跳。
6.1.2 空闲线程创建和启动
cpp
void rt_thread_idle_init(void)
{
/* 初始化空闲线程(静态) */
rt_thread_init(&idle, /*空闲线程句柄*/
"tidle", /*空闲线程名称*/
rt_thread_idle_entry, /*空闲线程入口函数*/
RT_NULL, /*空闲线程入口函数参数*/
&rt_thread_stack[0], /*空闲线程栈低地址,全局数组*/
sizeof(rt_thread_stack), /*空闲线程栈大小*/
RT_THREAD_PRIORITY_MAX - 1, /*空闲线程优先级,最低优先级*/
32); /*空闲线程定时器线程统计轮询周期 32个时间片*/
/*启动线程*/
rt_thread_startup(&idle);
}
6.1.3 空闲线程入口函数
cpp
static void rt_thread_idle_entry(void *parameter)
{
while (1)
{
/*1.空闲线程钩子函数扫描和执行*/
#ifdef RT_USING_IDLE_HOOK
rt_size_t i;
for (i = 0; i < RT_IDLE_HOOK_LIST_SIZE; i++)
{
if (idle_hook_list[i] != RT_NULL)
{
idle_hook_list[i]();
}
}
#endif
/*2.释放无效线程的内存*/
rt_thread_idle_excute();
/*3.低功耗模块*/
#ifdef RT_USING_PM
rt_system_power_manager();
#endif
}
}
6.2 软件定时器线程
为保证定时器时效性,定时器线程优先级一般设置为最高。
6.2.1 定时器线程作用
1、实现线程延时阻塞:线程延时阻塞是通过线程控制块定时器(rt_thread->thread_timer)实现,回调函数固定为rt_thread_timeout(void *parameter),其作用阻塞间到后,将线程移至就绪链表,并将线程状态修改为就绪状态。
2、管理软件定时器:用户可以自定义软件定时器,定时器单位为时间片,定时时间到,执行回调函数;对循环定时器,执行完回调函数后,重新启动。
6.2.2 定时器线程创建和启动
cpp
// 定时器进程
void rt_system_timer_thread_init(void)
{
#ifdef RT_USING_TIMER_SOFT
int i;
/*1、初始化软件定时器链表 */
for (i = 0;i < sizeof(rt_soft_timer_list) / sizeof(rt_soft_timer_list[0]); i++)
{
rt_list_init(rt_soft_timer_list + i);
}
/*2、初始化软件定时器线程(静态)*/
rt_thread_init(&timer_thread, /* 定时器线程句柄*/
"timer", /* 定时器线程名称*/
rt_thread_timer_entry, /* 定时器线程入口函数*/
RT_NULL, /* 定时器线程入口函数参数,无参数*/
&timer_thread_stack[0], /* 定时器线程栈低地址*/
sizeof(timer_thread_stack),/* 定时器线程栈大小,默认为512B*/
RT_TIMER_THREAD_PRIO, /* 定时器线程优先级,此处为0,优先级最高*/
10 /* 定时器线程统计轮询周期 10个时间片*/
);
/*3、启动线程 */
rt_thread_startup(&timer_thread);
#endif
}
6.2.3 定时器入口函数
cpp
static void rt_thread_timer_entry(void *parameter)
{
rt_tick_t next_timeout;
while (1)
{
/*1.获取下一个定时时刻*/
next_timeout = rt_timer_list_next_timeout(rt_soft_timer_list);
/*2.无有效定时器运行*/
if (next_timeout == RT_TICK_MAX)
{
/*2.1 没有软件定时器,定时器线程阻塞,直至有定时器启动后才解除阻塞*/
rt_thread_suspend(rt_thread_self());
/*2.2 申请调度*/
rt_schedule();
}
/*3.有有效定时器运行*/
else
{
rt_tick_t current_tick;
/* 3.1 取当前时间片*/
current_tick = rt_tick_get();
/* 3.2 定时时刻未到*/
if((next_timeout - current_tick) < RT_TICK_MAX / 2)
{
/* 3.2.1 定时器线程延时(阻塞),阻塞时间为next_timeout - current_tick*/
next_timeout = next_timeout - current_tick;
rt_thread_delay(next_timeout);
}
}
/*4. 检查软件定时器
4.1 遍历软件定时器
4.2 有定时器时间到,执行其回调函数
4.3 对循环定时器,执行其回调函数后,重新启动*/
rt_soft_timer_check();
}
}
#endif