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

目录

1. 线程结构

2. 线程创建

2.1 静态线程创建

2.2 动态线程创建

2.3 源码分析

2.4 线程内存结构

3. 线程状态

3.1 线程状态分类

 3.2 就绪状态和运行态

 3.3 阻塞/挂起状态

3.3.1 阻塞工况

3.4 关闭状态

3.4.1 线程关闭接口

3.4.2 静态线程关闭

3.4.3 动态线程关闭

3.6 状态切换

4.线程调度

4.1 调度器相关变量

4.2 调度器初始化

4.3 调度器启动

4.4 调度切换

4.5 同优先级线程轮询

4.6 调度器逻辑图

5. 线程接口

5.1 设置钩子接口

5.2 线程新建

5.3 线程启动

5.4 线程阻塞接口

5.4.1 线程固定周期阻塞

5.4.2 线程固定延时阻塞

5.4.3 线程永久阻塞(挂起)

 5.5 线程强制恢复接口

5.6 线程定时器恢复

5.7 线程同优先级轮询

5.8 线程命令接口

5.9 线程关闭接口

5.9.1 静态线程关闭接口

5.9.2 动态线程关闭接口

5.10 线程退出接口

5.11 线程清除接口

5.12 线程查询

6. 系统线程

6.1 空闲线程

6.1.1 空闲线程作用

6.1.2 空闲线程创建和启动

6.1.3 空闲线程入口函数

6.2 软件定时器线程

6.2.1 定时器线程作用

6.2.2 定时器线程创建和启动

6.2.3 定时器入口函数


        本章讲解RT_Thread Nano实时操作系统的核心部分——线程。单核CPU裸核运行时,一般是多中断+单线程,即前后台系统;如实现多线程并发,须搭载操作系统。有的实时操作系统,使用的是任务并发,其实线程和任务在实时操作系统中本质是一样的。

本章基于RT_Thread Nano V3.1.5版本分析

1. 线程结构

        线程通过线程控制块(rt_thread)维护,rt_thread可分为几个部分:内核对象、线程链表节点、线程栈部分、线程状态部分、线程回调函数部分、线程计数器和定时器部分、线程事件部分、线程优先级部分等。声明如下:

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;#endifrt_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 静态线程创建

        通过静态接口创建线程,需先定义好线程控制器和线程栈,示例如下:

 /*****新建静态线程接口******/
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 动态线程创建

        动态创建线程,无需定义线程控制器和线程栈,二者均通过申请动态内存存储。

 /*新建动态线程接口*/
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)。

 /*新建静态线程*/
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)。

/*新建动态线程*/
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进行线程数据初始化,代码分析如下:

/* 线程数据初始化*/
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 > 32thread->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_timeoutthread,                  // 定时器回调函数参数,取线程指针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中线程挂起状态与阻塞状态宏定义一致,挂起状态实质为永久阻塞状态。

/*线程状态宏定义*/
#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,线程重新挂接到就绪链表,由阻塞/挂起态切换为就绪态,如果线程为最高优先级,且被调度选中执行,该线程为由就绪态切换为运行态。

        线程就绪链表实质为一链表数组,优先级作为数组下标,即每个优先级可以挂接多个线程。

extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];   // 线程就绪链表数组

        线程就绪态切换接口如下所示,具体代码分析参照章节5。

/*线程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。

/*线程延时(阻塞),调用周期固定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)内存申请阻塞线程:

        内存申请涉及临界数据防护,可能会阻塞线程。

void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time)

(3)IPC通信阻塞:

/*获取信号,当信号被占用时,线程阻塞,最大阻塞时间为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。

/*删除静态线程,即切换至关闭状态*/
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 调度器相关变量

        系统调度器涉及的变量如下:

/*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 调度器初始化

        调度器初始化主要对调度器相关变量初始化,接口分析如下:

/*初始化调度器*/
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 > 32rt_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线程模式;所以调度启动之后的代码不会被执行到。

/**调度器启动 */
void rt_system_scheduler_start(void)
{register struct rt_thread *to_thread;register rt_ubase_t highest_ready_priority;#if RT_THREAD_PRIORITY_MAX > 32register 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;
#elsehighest_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()进行线程轮询执行;

        调度器切换源码分析:

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 <= 32highest_ready_priority= __rt_ffs(rt_thread_ready_priority_group)-1;#elseregister 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)遍历硬件定时器,并对时间到的定时器进行回调,对时间到的循环定时器进行重置。

/*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 设置钩子接口

        用户可根据需求设置相关钩子。

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 线程启动

        线程启动前必须已经新建,调用启动后,将被挂接到就绪链表,由初始化状态切换为就绪态。

/*启动线程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;
#endifRT_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.

/*线程延时(阻塞),调用周期为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更方便跨平台使用。

/*线程延时(阻塞)一定时间,单位时间片*/
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)切回就绪态。

 /*挂起就绪线程(挂起态),如果挂起当前线程,挂起后,必须执行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)不同,该函数为手动强制切换,对于挂起的线程只能通过该接口恢复,该函数执行成功后一般需要执行调度切换。

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),在定时器线程中,定时时间到后自动调用,将线程切换到就绪链表,线程状态修改为就绪态(阻塞态->就绪态)。

 /*阻塞时间到,线程恢复*/
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 中提到,如果最高优先级线程有多个,时间片中断会对这几个线程轮流执行。

/*同优先级级切换*/
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版本中没有该功能。

 /*线程控制,控制命令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 > 32thread->number      = thread->current_priority >> 3;            /* 5bit */thread->number_mask = 1 << thread->number;thread->high_mask   = 1 << (thread->current_priority & 0x07);   /* 3bit */#elsethread->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 > 32thread->number      = thread->current_priority >> 3;            /* 5bit */thread->number_mask = 1 << thread->number;thread->high_mask   = 1 << (thread->current_priority & 0x07);   /* 3bit */#elsethread->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_HEAPelse{return rt_thread_delete(thread);// 动态线程}#endifdefault:break;}return RT_EOK;
}

5.9 线程关闭接口

        有些线程是临时建立的,完成使命后不再使用,可以进行关闭。

5.9.1 静态线程关闭接口

注:接口rt_thread_detach也可以用于动态线程关闭。

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 动态线程关闭接口

 /*删除线程*/
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)。

/* 线程退出 */
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 线程清除接口

        用户根据需求可以设置线程清除函数,设置后,当线程关闭或退出时会自动调用该函数,可以当做线程退出的钩子看待。

/* 线程清理(退出时必须清理) */
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 线程查询

        根据线程名称获取线程句柄,用于遍历和监视线程。

/*定位线程(通过名称)*/
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 空闲线程创建和启动

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 空闲线程入口函数

static void rt_thread_idle_entry(void *parameter)
{while (1){/*1.空闲线程钩子函数扫描和执行*/#ifdef RT_USING_IDLE_HOOKrt_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_PMrt_system_power_manager();
#endif}
}

6.2 软件定时器线程

        为保证定时器时效性,定时器线程优先级一般设置为最高。

6.2.1 定时器线程作用

        1、实现线程延时阻塞:线程延时阻塞是通过线程控制块定时器(rt_thread->thread_timer)实现,回调函数固定为rt_thread_timeout(void *parameter),其作用阻塞间到后,将线程移至就绪链表,并将线程状态修改为就绪状态。

        2、管理软件定时器:用户可以自定义软件定时器,定时器单位为时间片,定时时间到,执行回调函数;对循环定时器,执行完回调函数后,重新启动。

6.2.2 定时器线程创建和启动

// 定时器进程
void rt_system_timer_thread_init(void)
{#ifdef RT_USING_TIMER_SOFTint 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 定时器入口函数

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/477067.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity图形学之CubeMap立方体贴图

1.CubeMap&#xff1a;有六个面的贴图组成 2. 假反射&#xff1a;反射天空盒子 &#xff08;1&#xff09;正常UV采样&#xff1a; &#xff08;2&#xff09;Cube的采样&#xff1a;利用反射角采样&#xff0c;反射角X和Cube的交点采样 Shader "Custom/TestReflect"…

C语言基础学习:抽象数据类型(ADT)

基础概念 抽象数据类型&#xff08;ADT&#xff09;是一种数据类型&#xff0c;它定义了一组数据以及可以在这组数据上执行的操作&#xff0c;但隐藏了数据的具体存储方式和实现细节。在C语言中&#xff0c;抽象数据类型&#xff08;ADT&#xff09;是一种非常重要的概念&…

Qt-多元素控件

Qt中的多元素控件 Qt提供的多元素控件有&#xff1a; 这里的多元素控件都是两两一对的。 xxWidget和xxView的一个比较简单的理解就是&#xff1a; xxView是更底层的实现&#xff0c; xxWidget是基于xxView封装来的。 可以说&#xff0c;xxView使用起来比较麻烦&#xff0c;但…

2023年9月GESPC++一级真题解析

一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 题号 123456789101112131415 答案 CDBCDBACACBBDDA 1. 我们通常说的 “ 内存 ” 属于计算机中的&#xff08;&#xff09;。 A. 输出设备 B. 输 ⼊ 设备 C. 存储设备 D. 打印设备 【答案】 C 【考纲知识点】…

wend看源码-APISJON

项目地址 腾讯APIJSON官方网站 定义 APIJSON 可以定义为一个面向HTTP 协议的JSON 规范&#xff0c;一个面向数据访问层的ORM 框架。其主要工作流程包括&#xff1a;前端按照既定格式组装 JSON 请求报文&#xff0c;通过 APIJSON-ORM 将这些报文直接转换为 SQL 语句&#xff0c…

VMware虚拟机Ubuntu桥接模式突然连接不上网络解决办法

在Linux环境进行开发时突然发现虚拟机中的Ubuntu突然连接不上网络&#xff0c;图形化界面也找不到有线连接选项。在此记录解决办法。 解决办法 1. 在终端命令行输入以下命令&#xff1a; sudo service network-manager stop2. 然后编辑以下文件将其中NetworkingEnable fals…

丹摩征文活动|摩智算平台深度解析:Faster R-CNN模型的训练与测试实战

目录 文章前言Faster R-CNN的简介Faster RCNN的训练与测试提前准备1.1 mobaxterm&#xff08;远程连接服务器&#xff09;1.2 本文的源码下载 目标检测模型 Faster-Rcnn2.1云服务器平台 数据上传内置JupyterLab的使用本地连接使用DAMODEL实例获取实例的SSH访问信息通过SSH连接通…

【数据结构】归并排序 —— 递归及非递归解决归并排序

归并排序 一、归并排序1、归并排序的思想2、归并排序代码实现&#xff08;递归&#xff09;<1> 归并排序的递归区间<2> 归并排序的稳定性<3> 拷贝 3、归并排序代码实现&#xff08;非递归&#xff09;<1> 循环区间溢出问题 二、总结 一、归并排序 1、…

调大Vscode资源管理器字体

对于调整资源管理器字体大小&#xff08;也就是下图红框&#xff09;&#xff0c;查找了网上很多方法。要么介绍的方法是调整了代码字体&#xff0c;要么是调节了终端字体&#xff0c;要么是通过整体放缩实现的调整&#xff0c;总之都不合适。 唯一的调整方法是在几篇CSDN里看到…

【Linux】-学习笔记04

第十二章、磁盘管理 1.查看磁盘空间使用量 1.1df命令 作用&#xff1a; 列出文件系统的磁盘空间占用情况 df&#xff0c;disk free&#xff0c;通过文件系统来快速获取空间大小的信息&#xff0c;当我们删除一个文件的时候&#xff0c;这个文件 不是马上就在文件系统当中消…

centos 服务器 docker 使用代理

宿主机使用代理 在宿主机的全局配置文件中添加代理信息 vim /etc/profile export http_proxyhttp://127.0.0.1:7897 export https_proxyhttp://127.0.0.1:7897 export no_proxy"localhost,127.0.0.1,::1,172.171.0.0" docker 命令使用代理 例如我想在使用使用 do…

Vue中Select选择器el-option实现动态多选

效果如图&#xff1a; 前端列表块显示部分&#xff1a; <el-table :data"tableData" border stripe :header-cell-class-name"headerBg" selection-change"handleSelectionChange"><el-table-column type"selection" width…

【ubuntu24.04.1最简洁安装方案】

我的电脑配置&#xff1a; 128GB固态硬盘&#xff0c;1TB 机械硬盘&#xff0c;我把整个 windows 系统全噶掉了&#xff0c;只安装ubuntu24.04.1一个Linux系统噶windows系统&#xff0c; 推荐使用 DiskGenius这个工具&#xff0c;好用&#xff0c;但是也要弄明白了再用啊&#…

k8s集群加入node节点为ubuntu 22.04

文章目录 1.环境准备1.1 关闭无用服务1.2 环境和网络1.3 apt源1.4 系统优化 2. 装containerd3. 接入k8s集群3.1 kubelet、kubeadm、kubectl安装3.2 缺少一个镜像3.3 接入k8s集群 4. 一些相关问题 1.环境准备 rootcto-gpu-pro-n01:~# lsb_release -a No LSB modules are availa…

C#桌面应用制作计算器进阶版01

基于C#桌面应用制作计算器做出了少量改动&#xff0c;其主要改动为新增加了一个label控件&#xff0c;使其每一步运算结果由label2展示出来&#xff0c;而当点击“”时&#xff0c;最终运算结果将由label1展示出来&#xff0c;此时label清空。 修改后运行效果 修改后全篇代码 …

如何构建高效的接口自动化测试框架?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 在选择接口测试自动化框架时&#xff0c;需要根据团队的技术栈和项目需求来综合考虑。对于测试团队来说&#xff0c;使用Python相关的测试框架更为便捷。无论选…

数据结构-8.Java. 七大排序算法(上篇)

本篇博客给大家带来的是排序的知识点, 由于时间有限, 分两天来写, 上篇主要实现 前四种排序算法: 直接插入, 希尔, 选择, 堆排。 文章专栏: Java-数据结构 若有问题 评论区见 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 …

算法日记 32 day 动态规划(完全背包)

同样是背包问题&#xff0c;但01背包和完全背包是两个类型的问题。 完全背包&#xff1a; 完全背包与01背包的区别在于物品的个数是否是无限的。除此之外&#xff0c;在解决01背包的时候dp的背包遍历的顺利是倒序&#xff0c;为的是保证物品只被添加一次&#xff0c;而完全背包…

数据结构之树与二叉树

华子目录 1.树和二叉树的定义1.1树的定义1.2树的基本术语1.3线性结构和树结构1.4二叉树的定义 2.二叉树的性质和存储结构2.1二叉树的性质2.2二叉树的存储结构2.2.1顺序存储2.2.2链式存储 2.3遍历二叉树2.4大作业&#xff1a;二叉树的基本操作2.4.1代码思路&#xff08;仅供参考…

MYSQL——多表设计以及数据库中三种关系模型

大致介绍数据库中三种关系模型 一对多&#xff08;1:N&#xff09; 定义&#xff1a; 一个实体可以与另一个实体的多个实例相关联&#xff0c;而后者只能与前者的一个实例相关联。 例子&#xff1a; 学生和课程的关系。 学生&#xff08;1&#xff09;&#xff1a;每个学生…