在本章之前,RT-Thread还没有支持多优先级,我们手动指定了第一个运行的线程,并在此之后三个线程(包括空闲线程)互相切换,在本章中我们加入优先级的功能,第一个运行的程序是就绪列表里优先级最高的程线程,线程的切换也是切换到已经就绪的线程中优先级最高的一个。

就绪列表实际上由线程就绪优先级组rt_thread_ready_priority_group和线程优先级表rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]组成,我们在本章之前说的就绪列表是指这里的线程优先级表rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]。

线程就绪优先级组就是一个32位的整形数,每一个位对应一个优先级,位0对应优先级0,位1对应优先级1,以此类推。比如,当优先级为10的线程已经准备好,那么就将线程就绪优先级组的位10置1,表示线程已经就绪,然后根据10这个索引值,在线程优先级表10(rt_thread_priority_table[10])的这个位置插入线程。

本章我们为线程控制块增加与优先级相关的成员,其中还增加了错误码和线程状态成员,具体见下图加粗部分:

1.线程就绪优先级组

__rt_ffs函数是用来寻找32位整形数第一个(从低位开始)置1的位号,目的是找出线程就绪优先级组里优先级最高的线程,其代码如下:

/*** 该函数用于从一个32位的数中寻找第一个被置1的位(从低位开始),* 然后返回该位的索引(即位号) ** @return 返回第一个置1位的索引号。如果全为0,则返回0。 */
int __rt_ffs(int value)
{/* 如果值为0,则直接返回0 */if (value == 0) return 0;/* 检查 bits [07:00] 这里加1的原因是避免当第一个置1的位是位0时返回的索引号与值都为0时返回的索引号重复 */if (value & 0xff)return __lowest_bit_bitmap[value & 0xff] + 1;/* 检查 bits [15:08] */if (value & 0xff00)return __lowest_bit_bitmap[(value & 0xff00) >> 8] + 9;    //9==8+1/* 检查 bits [23:16] */if (value & 0xff0000)return __lowest_bit_bitmap[(value & 0xff0000) >> 16] + 17;    //17==16+1/* 检查 bits [31:24] */return __lowest_bit_bitmap[(value & 0xff000000) >> 24] + 25;       //25==24+1
}
/* * __lowest_bit_bitmap[] 数组的解析* 将一个8位整形数的取值范围0~255作为数组的索引,索引值第一个出现1(从最低位开始)的位号作为该数组索引下的成员值。* 举例:十进制数10的二进制为:0000 1010,从最低位开始,第一个出现1的位号为bit1,则有__lowest_bit_bitmap[10]=1* 注意:只需要找到第一个出现1的位号即可*/
const rt_uint8_t __lowest_bit_bitmap[] =
{/* 00 */ 0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 10 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 20 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 30 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 40 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 50 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 60 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 70 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 80 */ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 90 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* A0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* B0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* C0 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* D0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* E0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* F0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};

2.线程1优先级表

线程优先级表即我们之前说的就绪列表,每个索引号对应线程的优先级,该索引下维护着一条双向链表,当线程就绪时,线程就会根据优先级插入到对应索引的链表,同一个优先级的线程都会被插入到同一条链表中(当同一个优先级下有多个线程时,需要时间片的支持,这个在后面章节我们在进一步讲解)。

①将线程插入到线程优先级表和移除分别由rt_schedule_insert_thread()和rt_schedule_remove_thread()这两个函数实现,它们的具体定义如下:

void rt_schedule_insert_thread(struct rt_thread *thread)
{register rt_base_t temp;/* 关中断 */temp = rt_hw_interrupt_disable();/* 设置线程状态为就绪态 */thread->stat = RT_THREAD_READY;/* 根据线程的当前优先级将线程插入到就绪列表的优先级表对应的链表上 */rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),&(thread->tlist));/* 设置线程就绪优先级组中对应的位 */rt_thread_ready_priority_group |= thread->number_mask;/* 开中断 */rt_hw_interrupt_enable(temp);
}void rt_schedule_remove_thread(struct rt_thread *thread)
{register rt_base_t temp;/* 关中断 */temp = rt_hw_interrupt_disable();/* 将线程从就绪列表删除 */rt_list_remove(&(thread->tlist));if (rt_list_isempty(&(rt_thread_priority_table[thread->current_priority]))){rt_thread_ready_priority_group &= ~thread->number_mask;}/* 开中断 */rt_hw_interrupt_enable(temp);
}

②修改调度器初始化函数rt_system_scheduler_init(),设置当前优先级为空闲线程的优先级(即最低)。

③修改线程初始化函数rt_thread_init()

④添加先启动函数rt_thread_startup(),该函数设置了当前优先级掩码并调用了rt_thread_resume函数恢复线程,该函数再调用了rt_schedule_insert_thread函数(该函数把线程设置为就绪态,此前是挂起态,然后根据线程的当前优先级把线程插入到对应的优先级表链表并设置对应优先级组的位)

⑤修改空闲线程初始化函数rt_thread_idle_init()

⑥修改启动系统调度器函数t_system_scheduler_start()

⑦修改系统调度函数rt_schedule()

⑧修改阻塞延时函数rt_thread_delay(),设置挂起时长并把线程状态设置为挂起态,且把线程就绪优先级组对应位设置为0

⑨修改时基更新函数rt_tick_increase(),如果挂起时长到了,则线程就绪优先级组对应位设置为1(但本章的代码把线程的状态改为挂起后好像没有改回来成就绪,但书里P103提到一点本来应该是把线程从优先级表中移除的,在定时器章节才会讲到,如果是移除后重新插入倒是会被设置为就绪态)

3.main函数

/************************************************************************* @brief  main函数* @param  无* @retval 无** @attention*********************************************************************** */
int main(void)
{   /* 硬件初始化 *//* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 *//* 关中断 */rt_hw_interrupt_disable();/* SysTick中断频率设置 */SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );/* 调度器初始化 */rt_system_scheduler_init();/* 初始化空闲线程 */    rt_thread_idle_init();  /* 初始化线程 */rt_thread_init( &rt_flag1_thread,                 /* 线程控制块 */"rt_flag1_thread",                /* 线程名字,字符串形式 */flag1_thread_entry,               /* 线程入口地址 */RT_NULL,                          /* 线程形参 */&rt_flag1_thread_stack[0],        /* 线程栈起始地址 */sizeof(rt_flag1_thread_stack),    /* 线程栈大小,单位为字节 */2);                               /* 优先级 *//* 将线程插入到就绪列表 *///rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );rt_thread_startup(&rt_flag1_thread);/* 初始化线程 */rt_thread_init( &rt_flag2_thread,                 /* 线程控制块 */"rt_flag2_thread",                /* 线程名字,字符串形式 */flag2_thread_entry,               /* 线程入口地址 */RT_NULL,                          /* 线程形参 */&rt_flag2_thread_stack[0],        /* 线程栈起始地址 */sizeof(rt_flag2_thread_stack),    /* 线程栈大小,单位为字节 */3);                               /* 优先级 *//* 将线程插入到就绪列表 *///rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );rt_thread_startup(&rt_flag2_thread);/* 启动系统调度器 */rt_system_scheduler_start();
}
/* 线程1 */
void flag1_thread_entry( void *p_arg )
{for( ;; ){flag1 = 1;rt_thread_delay(2);       flag1 = 0;rt_thread_delay(2);       }
}/* 线程2 */
void flag2_thread_entry( void *p_arg )
{for( ;; ){flag2 = 1;rt_thread_delay(2);       flag2 = 0;rt_thread_delay(2);        }
}void SysTick_Handler(void)
{/* 进入中断 */rt_interrupt_enter();/* 更新时基 */rt_tick_increase();/* 离开中断 */rt_interrupt_leave();
}

4.实验现象

总结:本章通过在优先级组对应的位置1表示该线程已经就绪,系统调度函数在就绪的线程里面选出优先级最高的线程去执行。我们挂起线程的时候会把线程的状态改为挂起,并把其在优先级组对应的位置0,当挂起的时间结束后又把相因位设置为1表示线程已经就绪(但本章的代码把线程的状态改为挂起后好像没有改回来成就绪,但书里P103提到一点本来应该是把线程从优先级表中移除的,在定时器章节才会讲到,如果是移除后重新插入倒是会被设置为就绪态),等待调度器来调用。本章实验现象和前一章是一样的,区别就是本章是优先执行优先级最高的就绪线程,如果该线程被挂起才会去执行比它低优先级的线程。

最后声明一下,我这里只是对学习的知识点进行总结,本文章的大多数知识来自于野火公司出版的《RT-Thread 内核实现与应用开发实战—基于STM32》,这本书非常不错,有志学习RT-Thread物联网操作系统的人可以考虑一下。

从0到1写RT-Thread内核——支持多优先级相关推荐

  1. 从0到1写RT-Thread内核——空闲线程与阻塞延时的实现

    在之前写的另外一篇文章--<从0到1写RT-Thread内核--线程定义及切换的实现>中线程体内的延时使用的是软件延时,即还是让CPU空等来达到延时的效果.RTOS中的延时叫阻塞延时,即线 ...

  2. 从0到1写RT-Thread内核——线程定义及切换的实现

    从0写RT-Thread内核之线程定义及切换的实现具体可以分为以下六步来实现 一:分别定义线程栈.线程函数.线程控制块: ALIGN(RT_ALIGN_SIZE)//设置4字节对齐 /* 定义线程栈 ...

  3. 关于RT thread系统节拍时钟的配置

    关于RT thread系统节拍时钟的配置                  -----本文基于rt-thread-3.1.3版本编写 首先,使用RTthread OS时,要配置(或者明白)它的系统节拍 ...

  4. rust 手动关闭子线程_从零开始写 OS (9) —— 内核线程

    上一篇 小源:从零开始写 OS (8) -- 创建页表​zhuanlan.zhihu.com 本章代码对应 commit :de86ae6e1e8bdfe3388530f82b2081fe29e40b ...

  5. rt thread studio使用QBOOT和片外flash实现OTA升级

    我们这里要使用单片机外部flash作为OTA的下载分区,外部flash硬件连接关系 PB3-->SPI3_CLK PB4-->SPI3_MISO PB5-->SPI3_MOSI PE ...

  6. rt thread 使用FAL遇到fal_init() undefined reference

    rt thread FAL 0.5版,之前有没有不知道,遇到一个坑. 在main.cpp里面已经 #include <fal.h> fal_init() 编译报错,说 fal_init() ...

  7. RT Thread Free Modbus移植问题整理

    RT Thread Free Modbus移植问题整理 问题描述: 在读写寄存器中,写数据正常,只能读1个寄存器的值,多个值会异常. 在移植过程中发现串口(或RS485)数据接收长度异常. 一.环境描 ...

  8. Yeelink平台使用——远程控制 RT Thread + LwIP+ STM32

    1.前言     [2014年4月重写该博文]     经过若干时间的努力终于搞定了STM32+LwIP和yeelink平台的数据互通,在学习的过程中大部分时间花在以太网协议栈学习上,但是在RT Th ...

  9. stm32f407单片机rt thread 片外spi flash OTA升级配置示例

    参考地址https://www.rt-thread.org/document/site/application-note/system/rtboot/an0028-rtboot/ 第一步,生成Boot ...

最新文章

  1. 2019世界机器人大赛圆满落幕,荆州中学等15支队伍获「全能奖」
  2. 基于DotNet构件技术的企业级敏捷软件开发平台 - AgileEAS.NET平台开发指南 - 实现插件...
  3. 022_html计算机输出标签
  4. Win32 API、VC++、C# 文件操作函数的初步比较
  5. IOS15之swift的Alamofire 5.4框架的网络封装
  6. Spring Cloud OpenFeign夺命连环9问,这谁受得了?
  7. Spark DataFrame小试牛刀
  8. 锤子濒危、金立倒闭,华米 OV 们如何艰难求生?
  9. 单点登录(SSO)解决方案之 CAS服务端数据源设置及页面改造
  10. EasyUI笔记(六)数据表格
  11. 互联网经济催生了一些新职业,带来新机遇!
  12. java 获取年和季度_java获取当前时间的年周月季度等的开始结束时间
  13. 坚定推动DDD落地的企业,70%代码效率翻倍了!
  14. 中英文姓名正则表达式
  15. 2022-07-31 零基础吉他入门知识
  16. android airplay音乐播放器,您需要知道的关于使用AirPlay播放音乐的一切 | MOS86
  17. 高级查询组件dynamicCondition升级为v2.0.0版本(一)——使用步骤
  18. 目前主流手机操作系统介绍-手机平台
  19. 实现一个 柯里化函数
  20. 朴素贝叶斯-后验概率最大化

热门文章

  1. jquery获得下拉框的值
  2. 基于Maven的spring_security入门
  3. 找不到显示桌面的快捷方式怎么办|显示桌面的快捷方式找不到解决方法|显示桌面代码|...
  4. 详细讲解Java中log4j的使用方法
  5. c 连接mysql.mwb_CodeSmith连接mysql提示“找不到请求的 .Net Framework Data Provider”的解决方法...
  6. android 自定义 theme,Android使用Theme自定义Activity进入退出动画的方法
  7. 虚幻4毛发系统_虚幻引擎复活!苹果与Epic对决,有哪些游戏险些中枪?
  8. java linkedhashset_java之LinkedHashSet
  9. c语言getch在哪个头文件,用getch()需要头文件吗?
  10. 单路电压表c语言编程,用AT89C51单片机制作的数字电压表