赞
踩
目录
对于整个单片机程序,我们称之为application,应用程序。
使用RT-Thread时,我们可以在application中创建多个线程(thread),有些文档也把线程称之为任务(task).
以日常生活为例,比如这个母亲要同时做两件事,
这可以引入很多概念:
线程状态(state):
当前正在喂饭,它是一个runnning状态,另一个回消息的线程就是not running状态
not running状态可以细分为:
init:初始状态
ready:就绪状态
suspended:挂起状态,等待某些资源暂时不能运行
close:关闭状态,线程退出
优先级(priority):
我生活工作兼顾:喂饭和回消息优先级一样,轮流做
我忙里偷闲,还有空闲线程,休息一下
厨房着火,什么都别说,先灭火,优先级更高
栈(Stack)
喂小孩时,我要记得上一口喂了米饭,这口要喂青菜了
回消息时,我要记得刚刚聊的是啥
做不同的线程,这些细节不一样
对于人来收,当然是记在脑子里
对于程序来说,是记在栈里
每个线程有自己的栈
事件驱动
孩子吃饭太慢,先休息一会,等他咽下去了,等他提醒我,再喂下一口
在RT-Thread中,线程是RT-Thread中最基本的调度单位,使用rt_thread结构体表示线程。
rt_thread描述了一个线程的运行环境,也描述了这个线程所处的优先级
系统中总共有两种线程,分别是系统线程和用户线程
这两类线程都会从内核对象容器中分配线程对象,如下图所示:
每个线程由三部分组成:线程控制块,线程栈和入口函数
线程控制块
线程控制块由结构体rt_thread表示,线程控制块是操作系统用于管理线程的一个数据结构
它存放一些现成的信息,例如线程优先级,线程名称,线程状态等,也包括线程与线程之间连接用的链表结构,线程等待事件集合等
它在rtdef.h中定义:
在裸机系统中,涉及局部变量、子函数调用或中断发生,就需要用到栈,
在RTOS系统中,每个线程运行时,也是普通函数调用,也涉及局部变量、子函数调用、中断,也要用到栈。
但不同于裸机系统,RTOS存在多个线程,每个线程互不干扰的,因此需要为每个线程都分配独立的栈空间,这就是线程栈。
可以使用两种方法提供线程栈:静态分配和动态分配。栈的大小通常由用户定义,如下使用全局数组定义一个静态栈,大小为512字节。
rt_uint32_t test_stack[512]
对于资源较大的MCU,可以适当的设置较大的线程栈。
也可以在初始化时设置为较大的栈,比如1K或者2K,在进入系统后,通过终端的list_thread命令可以查看当前线程的栈的最大使用率,如果使用率超过了70%,将线程的栈在设置大一点,如果小于70%,将线程栈设置小一点。
入口函数是线程要运行的函数,由用户自行设计。
可分为无限循环模式和顺序执行模式
- void thread_entry(void *parameter)
- {
- while(1)
- {
- /*等待事件发生*/
- /*对事件进行服务,进行处理*/
- }
-
-
- }
使用这种模式时,需要注意,一个实时操作系统,不应该让一个线程一直处于最高优先级占用CPU,让其他线程得不到执行。
因此在这种模式下,需要调用延时函数或者主动挂起。这种无限循环模式设计的目的是让这个线程一直循环调度运行,而不结束。
顺序执行模式
- static void thread_entry(void *parameter)
- {
- /*处理事务1*/
- ...
- /*处理事务2*/
- ...
- /*处理事务3*/
- }
使用这种模式时线程不会一直循环,最后一定会执行完毕。
执行完毕之后,线程将被系统自动删除。
以上就是线程的三要素:线程控制块,线程栈,入口函数;
线程的五种状态:初始态,就绪态,运行态,挂起态,关闭态。
线程的三种基本形式:单次执行,周期执行,资源驱动(时间也是资源的一种)
RT-Thread提供两种线程的创建方式:
这两种方式的区别就是分配线程控制块和线程栈的分配方式是静态的还是动态的,动态线程是系统自动从动态内存堆上分配栈空间与线程句柄,静态线程是由用户分配栈空间和线程句柄。
静态线程初始化函数如下:
参数说明
参数 | 描述 |
thread | 线程句柄,指向线程控制块内存地址,由用户传入 |
name | 线程名字,由rtconfig.h中定义的RT_NAME_MAX宏指定最大长度 |
entry | 线程入口函数 |
parameter | 线程入口函数参数 |
stack_start | 线程栈起始地址 |
stack_size | 线程栈大小,单位是字节 |
priority | 线程优先级,由rtconfig.h中定义的RT_THREAD_PRIORITY_MAX宏指定优先级范围,假设支持的是256级优先级,范围是0-255,数值越小,优先级越高,0代表最高优先级 |
tick | 线程的时间片大小,时间片(Tick)是操作系统的时钟节拍,当系统中存在优先级相同的线程时,时间片大小指定线程一次调度能够运行的最大时间长度。当在时间片运行结束之后,运行另外的同优先级线程。 |
返回值 | 成功:RT_EOK 失败:RT_ERROR |
动态创建函数如下:
参数说明:
参数 | 描述 |
name | 线程名字,在rtconfig.h中定义的RT_NAME_MAX宏定义指定最大长度 |
entry | 线程入口函数 |
parameter | 线程入口函数参数 |
stack_size | 线程栈大小,单位是字节 |
priority | 线程优先级,由rtconofig.h中定义的RT_THREAD_PRIORITY_MAX宏指定优先级范围,假设支 持的是256级优先级,那么范围是0-255,数值越小,优先级越高 |
tick | 线程的的时间片大小,时间片(Tick)是操作系统的时钟节拍,当系统中存在 相同优先级的线程时,时间片大小指定线程一次调度能够运行的最大时间长度 当前线程的时间片运行结束后,运行另外的同优先级线程 |
返回值 | 成功:thread,线程句柄 失败:RT_NULL |
创建线程后,还需要启动线程,才能让线程运行起来。
启动线程函数如下:
rt_err_t rt_thread_startup(rt_thread_t thread)
参数说明:
参数 | 描述 |
thread | 线程句柄 |
返回值 | 成功:RT_EOK 失败:-RT_ERROR |
示例1:创建线程
使用静态创建和动态创建两种方法分别创建两个线程
线程1的代码:
- /*线程1的入口函数*/
- static void thread1_entry(void *parameter)
- {
-
- const char *thread_name = "Thread1_run\r\n";
- volatile rt_uint32_t cnt = 0;
-
-
- /*线程1*/
- while(1)
- {
- /*打印线程1的信息*/
- rt_kprintf(thread_name);
-
- /*延迟一会(比较简单粗暴)*/
- for(cnt=0;cnt<100000;cnt++)
- {
-
- }
- }
-
- }
线程2的代码:
- /*线程2入口函数*/
- static void thread_entry(void *parametr)
- {
- const char *thread_name = "Thread2 run";
- volatile rt_uint32_t cnt = 0;
-
- /*线程2*/
-
- while(1)
- {
- /*打印线程2的信息*/
- rt_kprintf(thread_name);
-
- }
-
- /*延迟一会,简单粗暴*/
- for(cnt = 0;cnt<100000;cnt++)
- {
-
- }
-
- }
静态创建线程:
- rt_thread_init(&thread1, //线程句柄
- "thread1" //线程名字
- thread1_entry, //入口函数
- RT_NULL, //入口函数参数
- &thread1_stack[0], //线程栈起始地址
- sizeof(thread1_stack), //栈大小
- THREAD_PRIORITY, //线程优先级
- THREAD_TIMESLICE //线程时间片大小
-
- );
-
- rt_thread_startup(&thread1);
动态创建线程:
- thread2 = rt_thread_create("thread2", //线程名字
- thread2_entry, //入口函数
- RT_NULL, //入口函数参数
- THREAD__STACK_SIZE, //栈大小
- THREAD_PRIORITY, //线程优先级
- THREAD_TIMESLICE //线程时间片
- )
RT-Thread的线程优先级是指线程被调度的优先程度
每个线程都有优先级,线程的重要性越高,优先级应该设置的越高,被调度的可能才会更大。
由rtconfig.h中定义的RT_THREAD_PRIORITY宏指定优先级范围,RT-Thread最大支持256级优先级,数值越小的优先级越高,0为最高优先级。
在学习调度方法之前,只要粗略的知道,RT-Thread会确保优先级最高,可运行的线程,马上就能执行。
RT-Thread会确保优先级最高的,可运行的线程马上就能执行。
对于相同优先级的,可运行的线程,轮流执行。
举个例子,
厨房着火了,当然优先灭火
喂饭,回复信息同样重要,轮流做
RT-Thread中也有心跳,它使定时器产生固定间隔的中断,这叫Tick,滴答,比如每1ms发生一次时钟中断。
简单分为运行态和非运行态,具体的非运行态又可以细分,
初始态(RT_THREAD_INIT):创建线程的时候会将线程的状态设置为初始态
就绪态(RT_THREAD_READY):该线程就在就绪列表中,就绪的线程已经具备执行的能力,只等待CPU
运行态(RT_THREAD_RUNNING):该线程正在运行,此时它正占用CPU
挂起态(RT_THREAD_SUSPEND):如果线程当前正在等待某个时序或外部中断,我们就说这个线程处于挂起状态,该线程不在就绪列表中,包含线程被挂起、线程被延时,线程正在等待信号量、读写队列或者等待读写事件
关闭态(RT_THREAD_CL):该线程运行结束,等待系统回收资源。
线程状态迁移
RT_Thread系统中的每一个线程都有多种运行状态,他们之间的转换关系如下图所示,从运行态变成阻塞态、或者从阻塞态变成就绪态,这些线程状态是怎么迁移的呢?
空闲线程是系统中一个比较特殊的线程,它具有最低的优先级,当系统中无其他线程可运行时,调度器将调度到空闲线程。空闲线程通常是一个死循环,永远不挂起。
RT_Thread实时操作系统为空闲线程提供了一个钩子函数(钩子函数:用户提供的一段代码,在系统运行的某一路径上设置一个钩子,当系统经过这个位置时,转而执行这个钩子函数,然后再返回到它的正常路径上) ,可以让系统在空闲的时候执行一些特定的任务,例如系统运行指示灯闪烁,电源管理等,除了调用钩子函数,RT_Thread也把线程清理(rt_thread_cleanup)函数,真正的线程删除动作放到了空闲线程中(在删除线程时,仅改变线程的状态为关闭状态不在参与系统调度)。
调度器相关接口
调度器初始化
在系统启动时需要执行调度器的初始化,以初始化系统调度器用到的一些全局变量。调度器初始化可以调用下面的接口:
void rt_system_schduler_init(void)
启动调度器
void rt_system_scheduler(void)
执行调度
void rt_schedule(void)
设置调度器钩子函数
在整个系统的运行时,系统都处于线程运行、中断触发-响应中断、切换到其他线程,甚至是线程间的切换过程中,或者说系统的上下文切换是系统中最普遍的事。有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个响应的钩子函数。在系统线程切换时,这个钩子函数将被调用
void rt_scheduler_sethook(void (*hook)(struct rt_thread* from,struct rt_thread* to))
创建线程主要可以看成四个步骤:
1、动态分配或者静态分配线程控制块
2、动态分配或者静态分配线程栈
3、创建入口函数
4、构造栈内容(线程控制块结构体的成员)
上述函数的作用:调整sp和虚构栈内容
线程调度
由抢占式优先级和时间片轮转调度算法进行调度
RT-Thread启动流程RT-Thread启动流程_~Old的博客-CSDN博客
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。