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

       在本章之前,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物联网操作系统的人可以考虑一下。

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

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

相关文章

AD软件之模块化原理图

首先我们创建两个原理图文件 然后我们在Sheet2.SchDoc里放置一个页面符并双击绿色的方框 选择目标文件 我们选择我们刚才创建的Sheet4.SchDoc 然后在 视图——>面板——>Navigator选项 里点一下交互式导航 就可以看到Sheet4.SchDoc被添加到Sheet2.SchDoc下面了 通过上面…

AD软件操作技巧

本文介绍一些关于AD软件的实用小操作,这些小技巧可以大大的减少我们的工作量 一.批量操作丝印(或者操作别的东西也可以,主要是凸显批量操作的思想) 如下图假设我们工程里有很多丝印和焊盘等等,现在我想改批量地修改丝…

V4L2框架分析

V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动。v4L2是针对uvc(USB Video Class)免驱usb设备的编程框架,主要用于采集usb摄像头等。 下图是V4L2的框架,首先系统核心层分配设置注册一个名为cdev结构体变量&#x…

mjpg-streamer框架分析

mjpg-streamer程框架图如下所示: 程序运行起来后,主进程根据传入的参数设置的输入输出通道打开对应的输入输出动态链接库,并依次调用以下函数 1、输入---仓库-----输出(mjpg-streamer.h) (1)gl…

linux字符驱动之概念介绍

一、字符驱动框架 问:应用程序open、read、write如何找到驱动程序的open、read、write函数? 答:应用程序的open、read、write是在C库里面实现的,它里面通过swi val指令去触发一个异常,这个异常就会进入到内核空间,在内…

USB摄像头视频监控项目学习笔记

一个摄像头监控应用程序的系统调用如下所示: /* open * VIDIOC_QUERYCAP 确定它是否视频捕捉设备,支持哪种接口(streaming/read,write) * VIDIOC_ENUM_FMT 查询支持哪种格式 * VIDIOC_S_FMT 设置摄像头使用哪种格式 * VIDIOC_REQBUFS 申请buffer 对于 str…

图片缩放算法

项目背景:博主之前做过一个摄像头采集数据,然后在LCD上显示视频数据的项目,假如我们摄像头采集的一帧数据的分辨率比我们的LCD的分辨率要大,那么LCD则无法显示整个图像,这时候我们就要把这么一帧图片进行缩放&#xff…

数码相框项目之显示一张可放大、缩小、拖拽的图片

之前我做过一个电子相框的项目,涉及到的重难点主要为:在LCD上放大、缩小、移动图片。 首先我们得明白的一点是:无论是放大或缩小,实际上都是对原图进行等比例的缩小,然后在LCD上面显示,只不过缩小的程度不…

TCP协议-如何保证传输可靠性

TCP协议传输的特点主要就是面向字节流、传输可靠、面向连接。这篇博客,我们就重点讨论一下TCP协议如何确保传输的可靠性的。 确保传输可靠性的方式 TCP协议保证数据传输可靠性的方式主要有: 校验和序列号确认应答超时重传连接管理流量控制拥塞控制 校…

TCP协议-握手与挥手

认识TCP协议 TCP全称为“传输控制协议”,这是传输层的一个协议,对数据的传输进行一个详细的控制。 特点: 面向字节流安全可靠面向连接 TCP协议段格式 源端口号与目的端口号:这里与UDP的一样,每个数据都要知道从哪个…

ASOC注册过程

一、什么是ASOC 在嵌入式系统里面的声卡驱动为ASOC(ALSA System on Chip) ,它是在ALSA 驱动程序上封装的一层,分为3大部分,Machine,Platform和Codec ,三部分的关系如下图所示:其中Machine是指我…

ASOC调用过程

上一篇文章我们将了嵌入式系统注册声卡的过程:https://blog.csdn.net/qq_37659294/article/details/104748747 这篇文章我们以打开一个声卡的播放节点为例,讲解一下在APP调用open时,最终会如何调用到硬件相关的函数。 在上一篇文章最后我们说…

进程上下文与中断上下文的理解

一.什么是内核态和用户态 内核态:在内核空间执行,通常是驱动程序,中断相关程序,内核调度程序,内存管理及其操作程序。 用户态:用户程序运行空间。 二.什么是进程上下文与中断上下文 1.进程上下文&#xf…

内核的Makefile与Kconfig关系解析

在子目录下的Kconfig里添加make menuconfig的选项(如图一),并默认设置为y,make menuconfig的菜单里就会有该项并默认为选上状态,make menuconfig配置完之后在.config文件里就有该选项,并等于y(如…

Linux信号之signal函数

1. 信号概述 何为信号:信号就是由用户、系统或进程发送给目标进程的信息,以通知目标进程中某个状态的改变或是异常。 信号产生:总体来说,其产生的条件有两种,分别是:硬件和软件原因,又称为&…

Linux中wait()函数及waitpid()函数

编程过程中&#xff0c;有时需要让一个进程等待另一个进程&#xff0c;最常见的是父进程等待自己的子进程&#xff0c;或者父进程回收自己的子进程资源包括僵尸进程。这里简单介绍一下系统调用函数&#xff1a;wait() 函数原型是 #include <sys/types.h> #include <…

学习笔记 --- DM9000网卡原理与基地址设置

前面有文章分析了网卡也是属于类内存总线的设备&#xff0c;类内存总线的设备有地址总线和数据总线&#xff0c;先来看下DM9000的管脚&#xff1a; 从上面可以看出DM9000的地址总线就一根&#xff0c;它不像CS8900那样地址总线和数据总线都齐全。而这里只有一根地址线(CMD)&…

静态VLAN的配置

在一台交换机上连接3台PC机&#xff0c;然后创建两个VLAN&#xff0c;分别为VLAN 10 和VLAN 20&#xff0c;把第一台PC机分配给VLAN 10&#xff0c;把其他两台分配给VLAN 20.然后测试他们的互通情况。 在这里命令我用的都是简化命令&#xff0c;想卡完整版命令&#xff0c;请到…

静态路由原理

1、路由器的工作原理 路由工作简单原理图 1&#xff09;主机1.1要发生数据包给主机4.1.因为IP地址不在同一网段&#xff0c;所以主机会将数据包发送给本网段的网关路由器。 2&#xff09;路由器A 接收到数据包&#xff0c;先查看数据包IP首部中的目标IP地址。再查找自己的路由表…

静态路由和默认路由

一、静态路由的配置 下边实验对该拓扑图进行配置 实验目标&#xff1a;配置静态路由&#xff0c;实现全网互通 1、配置路由器R1 进入接口f0/0&#xff0c;配置IP&#xff0c;并开启。 进入接口f0/1&#xff0c;配置IP&#xff0c;并开启。 设置静态路由。 查看PC1的路由表 2、配…