Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
RT-Thread学习营
任务调度
相同优先级时间片轮转调度
星火①号RT-Thread入门_调度情况
发布于 2023-12-29 16:12:10 浏览:1054
订阅该版
[tocm] # 星火①号RT-Thread入门_调度情况 本文章讲解了RT-Thread在任务运行过程中的调度情况,是操作系统小白入门级的。星火①号学习RT-Thread的方法十分容易,在官网下载源码后,进入到stm32f407-rt-spark目录下,使用ENV工具生成MDK文件,即可在星火1号开发板中运行RT-Thread。本文章的实验讲述了RT-Thread对于不同任务优先级及相同任务优先级的调度情况。 **注意:在本文章的描述中,线程的概念等同于任务。(如任务优先级=线程优先级)** ## MDK工程生成 MDK工程的生成如下图所示: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20231229/461489d926cdb19fab0f9dbc1a3bf34e.png.webp) 可以在该根目录下输入menuconfig对RT-Thread进行配置,输入scons --target=mdk5即可生成MDK5文件。 ## 任务调度情况 在rtconfig.h文件中定义了RT-Thread的最大任务优先级图所示: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20231229/c8b1ede7eba58dbb2ee1409dce507d73.png) 任务的优先级数越小则代表任务优先级最高。 ### 任务创建 ``` tid1 = rt_thread_create("thread1", th1_entry, (void*)1, 512, 20, 10); if(tid1 != RT_NULL){ rt_thread_startup(tid1); rt_kprintf("rt_thread1 create success!\n"); } tid2 = rt_thread_create("thread2", th2_entry, (void*)2, 512, 20, 10); if(tid2 != RT_NULL){ rt_thread_startup(tid2); rt_kprintf("rt_thread2 create success!\n"); } tid3 = rt_thread_create("thread3", th3_entry, RT_NULL, 512, 15, 5); if(tid3 != RT_NULL){ rt_kprintf("rt_thread3 create success!\n"); rt_thread_startup(tid3); } ``` 在main()函数中创建三个任务,任务1、2优先级最低且相等。每个任务的时间片为10个Tick。 任务3优先级最高。且没有与他拥有相同任务优先级的任务。 任务1与任务2,统一采用for(;;){}死循环,且不执行任何操作。 任务3则每秒钟打印一次”task3...”具体代码如下所示: ``` rt_thread_t tid1; rt_thread_t tid2; rt_thread_t tid3; void th1_entry(void *params) { rt_uint32_t value; rt_uint32_t count = 0; value = (rt_uint32_t) params; for(;;){ } } void th2_entry(void *params) { rt_uint32_t value; rt_uint32_t count = 0; value = (rt_uint32_t) params; for(;;){ } } void th3_entry(void *params) { for(;;){ rt_kprintf("task3...\n"); rt_thread_mdelay(10000); } } ``` ### 调度器钩子函数 调用rt_scheduler_sethook(scheduler_hook)函数可以启动调度器的钩子函数,每次任务运行时会运行参数中的函数。scheduler_hook函数如下所示: ``` void scheduler_hook(struct rt_thread *from, struct rt_thread *to){ rt_tick_t time; time = rt_tick_get(); rt_kprintf("tick: %d from: %s ---> to: %s\n", time, from->parent.name, to->parent.name); } ``` 该函数可以实现在每次调度器调度任务时,打印出从那个任务调度到哪个任务,从而更清晰的展现了任务的调度过程。 ### 运行结果 最终运行结果打印在控制台中的结果如下所示: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20231229/457b2dcd844f053ab735e5305504bb2e.png.webp) 在1处:main()函数创建完三个任务后便停止运行。同时,调度器会选择任务优先级最高的任务3进行处理。 在2处:任务3在打印”task3...\n”后,调用函数rt_thread_mdelay(1000)延时1000ms,并进入挂起状态。此时,任务3会让出CPU资源给任务1和2。 (注意:高优先级的任务必须有让出CPU的情况,否则低优先级的任务将无法运行)。 在3处:可以看到每经过10个tick(10个tick便是创建任务时给出的10个时间片参数)便会进行一次任务调度,由于任务2和任务1是两个死循环函数,因此会一直进行切换,直到被高优先级的任务打断。 (注意:我们在创建任务的时候是任务1先被创建,任务2后创建,由此也可以推断出,RT-Thread在运行任务时,在相同优先级的情况下,后创建的任务先运行。) 任务3挂起的时间达到1000ms时,会打断任务1和任务2,占用CPU资源,同时任务1和2会进入就绪状态,如下图所示: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20231229/768d1d3aff8b2bcc63bbe8ebf5883dfd.png.webp) 由前张图可知,任务3休眠的时刻为21tick,而1处任务三再次被调度时处于1021tick。因此也可以判断RT-Thread的时钟是非常精确的。任务3结束后,会再次将CPU资源让出。 (注意:在1处任务3抢占了任务2,而任务3退出后,会继续执行任务2。同理可知,如果任务3延迟结束前是任务1,则在任务3结束后会继续执行任务1)。 可以使用UML时序图,清晰展现任务的切换过程,如下所示: ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20231229/0d0444fc6b9d961e51ee4fc1246e5f9a.png.webp) ## 高优先级任务让出CPU的过程 ### rt_thread_mdelay()函数 rt_thread_mdelay()的函数源码如下: ![screenshot_9633385aa1a10ef95460ace198c855c3.png](https://oss-club.rt-thread.org/uploads/20240102/9633385aa1a10ef95460ace198c855c3.png) rt_thread_mdelay()函数的核心是调用rt_thread_sleep()函数,将线程切换至挂起状态,并切换至优先级最高且处于就绪状态的任务。rt_tick_from_millisecond()函数的作用是将延迟的毫秒数转换为tick数。 ### rt_thread_sleep()函数 rt_thread_sleep()函数的源码如下: ``` /** * @brief This function will let current thread sleep for some ticks. Change current thread state to suspend, * when the thread timer reaches the tick value, scheduler will awaken this thread. * * @param tick is the sleep ticks. * * @return Return the operation status. If the return value is RT_EOK, the function is successfully executed. * If the return value is any other values, it means this operation failed. */ rt_err_t rt_thread_sleep(rt_tick_t tick) { rt_base_t level; struct rt_thread *thread; int err; if (tick == 0) { return -RT_EINVAL; } /* 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); /* current context checking */ RT_DEBUG_SCHEDULER_AVAILABLE(RT_TRUE); /* disable interrupt */ level = rt_hw_interrupt_disable(); /* reset thread error */ thread->error = RT_EOK; /* suspend thread */ err = rt_thread_suspend_with_flag(thread, RT_INTERRUPTIBLE); /* reset the timeout of thread timer and start it */ if (err == RT_EOK) { rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &tick); rt_timer_start(&(thread->thread_timer)); /* enable interrupt */ rt_hw_interrupt_enable(level); thread->error = -RT_EINTR; rt_schedule(); /* clear error number of this thread to RT_EOK */ if (thread->error == -RT_ETIMEOUT) thread->error = RT_EOK; } else { rt_hw_interrupt_enable(level); } return err; } ``` 其主要完成三个步骤: 1. 调用rt_thread_suspend_with_flag()函数将当前线程设置为挂起状态 2. 挂起状态设置成功后打开线程定时器开始计时 3. 调用rt_schedule()函数切换线程。 注意:在设置线程延迟的过程中,程序属于临界区(即不能被外界中断),因此在此期间,需要将中断关闭,避免中断的产生导致线程处理失败。如程序36行和45行与57行。 ### rt_schedule()函数 rt_schedule()函数会选择当前优先级最高的线程来运行。 其主要完成以下三个步骤: 1. 从线程就绪优先级队列中获取就绪的最高优先级。 2. 获取最高优先级对应的线程控制块并保存至to_thread中。 3. 检查to_thread是否为当前线程,如果是,则进行线程切换;否,则重新开启中断,继续执行当前线程。 注意:在这个函数执行过程中,也不能被外界中断。因此在函数入口处调用了rt_hw_interrupt_disable()关闭了中断进入临界区。并在处理完毕后调用rt_hw_interrupt_enable()函数打开了中断。 ### 总结 RT-Thread在调度线程的过程中,会优先选择优先级最高的线程进行处理。因此,当系统中既有高优先级线程,又有低优先级的线程时,高优先级线程必须有让出处理器资源的过程。否则,低优先级的线程将永远无法获得CPU资源,导致线程被“饿死”。 如文章的例程中线程3的优先级最高,线程1和2的优先级相同且低于线程3。因此线程3必须要有让出CPU资源的过程(即,调用函数rt_thread_mdelay(n))。线程3在调用延迟函数后,RT-Thread会将线程设置为挂起状态,并启动该线程的线程定时器延迟n毫秒,在延迟时间结束后,继续执行该线程。在RT-Thread将线程设置为挂起状态并启用定时器后,将调用rt_schedule()函数找到就绪优先级队列中的最高优先级线程,并将其线程控制块设置为to_thread。rt_schedule()会检查to_thread线程是否为当前运行的线程,如果不是则进行任务切换,如果是则继续执行该任务。 在调用rt_thread_mdelay()函数后,线程3进入挂起状态,因此to_thread必然不会是当前运行的线程,因此,会进行线程的切换。此时,线程1与线程2才会得到CPU资源,并进行任务处理。根据上文描述的内容也可以知道,线程2后创建,因此会比线程1先获得CPU资源。线程1与线程2优先级相同,此时创建线程时输入的时间片参数发挥作用,线程在执行时间达到自己的时间片数后会进行切换。 RT-Thread对于不同优先级与相同优先级的任务调度大致如上所示,以上也是我有关RT-Thread任务调度的学习心得。 最后附上main.c文件的所有代码程序 ``` /* * Copyright (c) 2023, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2023-07-06 Supperthomas first version */ #include
#include
#include
#include "rtconfig.h" rt_thread_t tid1; rt_thread_t tid2; rt_thread_t tid3; void scheduler_hook(struct rt_thread *from, struct rt_thread *to){ rt_tick_t time; time = rt_tick_get(); rt_kprintf("tick: %d from: %s ---> to: %s\n", time, from->parent.name, to->parent.name); } void th1_entry(void *params) { rt_uint32_t value; rt_uint32_t count = 0; value = (rt_uint32_t) params; for(;;){ } } void th2_entry(void *params) { rt_uint32_t value; rt_uint32_t count = 0; value = (rt_uint32_t) params; for(;;){ } } void th3_entry(void *params) { for(;;){ rt_kprintf("task3...\n"); rt_thread_mdelay(1000); } } int main(void) { rt_tick_t time; rt_scheduler_sethook(scheduler_hook); tid1 = rt_thread_create("thread1", th1_entry, (void*)1, 512, 20, 123); if(tid1 != RT_NULL){ rt_thread_startup(tid1); rt_kprintf("rt_thread1 create success!\n"); } tid2 = rt_thread_create("thread2", th2_entry, (void*)2, 512, 20, 123); if(tid2 != RT_NULL){ rt_thread_startup(tid2); rt_kprintf("rt_thread2 create success!\n"); } tid3 = rt_thread_create("thread3", th3_entry, RT_NULL, 512, 15, 5); if(tid3 != RT_NULL){ rt_kprintf("rt_thread3 create success!\n"); rt_thread_startup(tid3); } return 0; } ```
2
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
顾小小
这家伙很懒,什么也没写!
文章
4
回答
0
被采纳
0
关注TA
发私信
相关文章
1
stm32f407+lan8720 lwip2.0 作业提交
2
玩溜GD32303E-EVAL BSP系列(五)----设备连接网络
3
onenet应用连不上云端
4
【文件系统】晴天文件匹配
5
rtt semc sdram 基于操作系统怎么初始化驱动设备
6
RT-Thread移植笔记
7
RT-Thread内核移植+LoIIs+STM32F103C8+StdLib
8
EVN编译报错,求助大神
9
【内核和外设学习营】十里 简单LED闪亮测试
10
【内核和外设学习营】 十里 串口指令控制RGB灯点亮的颜色
推荐文章
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
编译报错
SFUD
msh
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
出出啊
1517
个答案
342
次被采纳
小小李sunny
1443
个答案
289
次被采纳
张世争
805
个答案
174
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
4
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
1
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部