RT_Thread内核源码分析(三)——线程

目录

[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.线程调度

[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
相关推荐
白书宇3 小时前
19.QT程序简单的运行脚本
linux·arm开发·嵌入式硬件·物联网·arm
大梦百万秋4 小时前
嵌入式系统与单片机工作原理详解
单片机·嵌入式硬件
陌夏微秋5 小时前
硬件基础22 反馈放大电路
单片机·嵌入式硬件·硬件架构·硬件工程·智能硬件
&春风有信5 小时前
FreeRTOS之链表源码分析
c语言·数据结构·嵌入式硬件·链表
美式小田6 小时前
Altium Designer学习笔记 21.PCB板框的评估及叠层设置
笔记·嵌入式硬件·学习·ad
小郭爱学习@7 小时前
RLC串联谐振,品质因数的影响
嵌入式硬件·硬件架构·硬件工程
电子工程师UP学堂7 小时前
电子应用设计方案-24:智能防火系统方案设计
人工智能·单片机·嵌入式硬件
Winter、rui7 小时前
STM32F103C8T6实时时钟RTC
stm32·单片机·嵌入式硬件
tao_sc8 小时前
stm32启动过程解析startup启动文件
javascript·stm32·嵌入式硬件