Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
嵌入式操作系统
【学习分享】初探RT-Thread系统调度
发布于 2024-03-29 11:30:49 浏览:826
订阅该版
[tocm] ## 引言 RT-Thread作为一个嵌入式实时系统,任务调度是其最为核心的组成部分,那么它是如何工作的呢?就由我带大家了解一下吧。 ## 相关函数 先简单介绍一下接下来会用到的函数以及结构体 | 函数声明/结构体 | 详细说明 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | rt_thread_t | 线程句柄(线程结构体的指针) | | 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) | 创建指定参数的线程 参数:线程名,任务(函数),任务的参数,线程大小,线程优先级,线程运行时间片长度 | | rt_err_t rt_thread_start_up(rt_thread_t thread) | 启动线程 参数:线程句柄 | ## 代码实现 有了这些函数之后我们就可以创建我们自己的线程了 ``` #include
// 线程的栈大小,优先级,时间片长度 static const int THREAD_STACK_SIZE = 1024; static const int THREAD_PRIORITY = 25; static const int THREAD_TIMESLICE = 10; // 三个线程的句柄 static rt_thread_t t1, t2, t3; // 线程1,2,3的任务 static void thread1_func(void* para) { rt_kprintf("I'm %s\n", rt_thread_self()->parent.name); while(1); } static void thread2_func(void* para) { rt_kprintf("I'm %s\n", rt_thread_self()->parent.name); while(1); } static void thread3_func(void* para) { rt_kprintf("I'm %s\n", rt_thread_self()->parent.name); while(1) { rt_thread_mdelay(1000); } } // 创建并启动三个线程 void trace_thread_sample(void) { t1 = rt_thread_create("t1", thread1_func, NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); t2 = rt_thread_create("t2", thread2_func, NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); t3 = rt_thread_create("t3", thread3_func, NULL, THREAD_STACK_SIZE, THREAD_PRIORITY - 1, THREAD_TIMESLICE); if(t1 && t2 && t3) { rt_thread_startup(t3); rt_thread_startup(t1); rt_thread_startup(t2); } } int main(void) { trace_thread_sample(); return 0; } ``` ## 代码分析 **观察声明:** 我分别对线程栈空间大小,优先级,时间片长度进行了声明,便于后面创建线程时进行使用,那么它们分别都有什么意义呢? 1. **栈空间大小**:它定义了一个线程,在系统能够使用它时所能使用的空间大小,像函数调用,局部变量,临时变量,都是在栈空间中进行储存的。 2. **优先级**:在嵌入式操作系统中,每个优先级都有一个队列,创建线程的函数会根据传入的优先级选择将线程放入对应的队列中,在调度的时候系统会优先从优先级数值最低的队列中取出一个线程运行。 - 线程加入优先级队列后的状态大概如下图所示: - 优先级队列的队头处于一个数组当中,也就是说优先级是有限的,而加入指定优先级队列的线程则是以链表节点插入的形式加入的,因此加入优先级队列的线程可以是无限的。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240408/b8ff4056f4bb6c31e26f46739283ef33.png) - 如果此时发生调度,那么调度器就会取出优先级为1的队列中的线程x运行。 3. **时间片**:时间片的长度决定了当线程拥有cpu时的正常运行时间(为什么说是正常运行时间呢?因为运行期间可能被高优先级的线程抢占),(在线程都处于相同优先级的情况下)它保证了所有线程都有机会运行,避免出现有些线程一直占用cpu,而有的线程却一直没办法运行的情况(旱的旱死,涝的涝死),从而提高整个系统的处理能力和处理效率。 **观察代码:** 首先观察线程执行的任务:线程一和线程二一直跑在一个死循环当中,任务三则每次循环都会延时1000ms, 再看到线程初始化:线程一和线程二的优先级都为25,而线程三则比线程一二高一个优先级为24, 然后是启动顺序方面:我们将**线程加入调度队列的顺序为: t3 --> t1 --> t2。** 对代码有个基本的了解之后,我们就可以开始运行程序了。 RTT 启动! ## 代码运行 启动程序后,我们打开串口助手可以看见我们启动三个线程时打印的线程名: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240408/983e4a50e6ad53c090892657b580eb35.png) 我们由此可以分析出三个**线程的运行顺序:t3 --> t2 --> t1** 这里可能大家就会比较奇怪了: 按照线程加入调度的顺序来说t1要比t2更早加入调度啊,为什么t2更先运行呢,既然t1和t2的顺序是和加入调度时的顺序相反的为什么t3又是最先运行的呢? 通过查阅RT-Thread文档中心线程相关的文档,我们发现RT-Thread的调度是 ***时间片轮转+优先级抢占式*** 的。 我们获取这个知识后回到我们的程序: t3是最高优先级的线程,不管什么加入顺序,它都会马上抢过cpu去优先执行它的任务。 t1和t2在相同的优先级队列中,那么就要考虑加入的顺序了,因为t1先加入t1后执行,t2后加入但却先执行,我们是不是可以合理猜测一下他们加入调度队列的顺序,就是先加入的线程在尾部,后加入的线程在首部,什么插入方式的实现效果是这样的呢?——— 链表的头插法?其实不准确,实际上是有一个头部节点(队头),每次有新节点则加入到队头的后面,同样,取出节点也是每次从队头后一个取。这种实现方式,不仅便于将新节点插到队列首部,同时也方便将以及运行完的节点放在队列尾部。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240408/ea835a176763f4efc7971b1e03fc3038.png) 我们可以模拟一下三个线程加入调度的过程: ------------------------------------------------------ > t3启动: > 优先级为24的队列:t3 <-- > 优先级为25的队列:<-- ------------------------------------------------------ > t1启动: > 优先级为24的队列:t3 <-- > 优先级为25的队列:t1 <-- ------------------------------------------------------ > t2启动: > 优先级为24的队列:t3 <-- > 优先级为25的队列:t2 <-- t1 <-- ------------------------------------------------------ 大家可能又会疑惑,怎么三个线程都加入调度队列中了,一个都还没执行呢? 因为我们是在main线程中进行初始化的,main线程的优先级可是高贵的10, 它们在加入的时候,cpu可都是main线程的形状呢。 ## 观察线程运行图: 通过线程运行的分析工具,我们得到了一张线程运行图: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240408/2f13686f1b4a31c3497f650ee114c90c.png.webp) 初看这张图容易一脸懵,那么我们就逐步解析一下。 我们先放大看一个t1线程运行一个时间片的长度: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240408/2982cd83244b3f38fdc109f724e00d3a.png) 通过截图我们发现线程t1的时间片长度为10us,然后就切换到线程t2运行了,很明显它的时间单位出错了,t1的运行时间片长度应该是10ms而不是10us,如果是10us的话,1ms要切换100次线程,那cpu得累死(当然也希望以后能研发出那种能够承载10us切换一次线程的国产cpu)。也就是说,t1和t2轮流获取cpu操控权10ms,那么t3呢,t3哪里去了? 还记得我们在代码中给t3的延时吗?t3挂起到等待队列中,只有等待满了1000ms,它才能获取一次cpu的使用权,与其说获得就不如直接说是抢的,从t2线程手上抢过来的,但是很快t3就被抓回等待队列去了,所以t2很快又拿回了它的cpu使用权。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240408/69a456165e42f76647ce38dc6127c15e.png) 如果我们仔细算,两次获取cpu使用权的时间间隔,t1和t2的时间片加起来就刚好是100个。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240408/0183c0a2e0a1f13d91bf19cb317a751a.png) 这篇文章含有比较多我对系统的一些理解,如果有些地方理解的不到位,或者是有错的,麻烦及时联系我指出,感谢。
5
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
比特饼干
这家伙很懒,什么也没写!
文章
10
回答
1
被采纳
0
关注TA
发私信
相关文章
1
嵌入式系统中如何管理芯片级通讯总线协议
2
邮箱 队列能否实现一个任务发送,多个任务接受呢
3
超低功耗物联网功能的RTT
4
求rt_thread 可以在STC32上实现吗,
5
申请邮箱rt_mb_create 邮箱内容大小的问题
6
rt thread操作系统是运行在什么里面?上位机还是单片机
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
a1012112796
20
个答案
3
次被采纳
张世争
11
个答案
3
次被采纳
踩姑娘的小蘑菇
7
个答案
3
次被采纳
rv666
9
个答案
2
次被采纳
用户名由3_15位
13
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
RTT_逍遥
1
篇文章
6
次点赞
大龄码农
1
篇文章
5
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部