从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,一经查实,立即删除!

相关文章

Linux串口阻塞与非阻塞

Linux串口编程的阻塞与否可以在open函数中设置,例如: 打开时使用: fd open(USAR1, O_RDWR | O_NOCTTY );//阻塞式读写fd open("/dev/ttyAT2",O_RDWR|O_NOCTTY|O_NDELAY); //非阻塞读写 除了用open函数之外还可以在open函数之…

vi中如何实现批量替换

将文件tihuan(假设此文本中字符a)中的所有字符a换成字符w,其命令为: 1。vi tihuan 2。按esc键 3。按shift: 4。在:后输入 %s/a/w/g 其中s为:substitute,%表示所有行,…

C++里数组名+1和数组名的地址+1的区别

C/C里面的数组名字会退化为指针,所以数组名a实际指的是数组的第一个元素的地址。而数组名作为指针来讲有特殊性,它正在它所指向的内存区域中,&a的值和a的数值是相同的(可以输出观察一下),但是类型和意义…

栈空间和堆空间的区别

栈空间用于存储函数参数和局部变量,所需空间由系统自动分配,回收也由系统管理,无需人工干预;堆空间用于存储动态分配的内存块,分配和释放空间均由程序员控制,有可能产生内存泄漏。 栈空间作为一个严格后进…

AD软件之模块化原理图

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

进程与线程的区别(面试题)

进程与线程的区别 1.进程是资源分配最小单位,线程是程序执行的最小单位; 2..进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的…

查找表的原理与结构 什么是竞争与冒险现象?怎样判断?如何消除?

查找表的原理与结构: 查找表(look-up-table)简称为LUT,LUT本质上就是一个RAM。目前FPGA中多使用4输入的LUT,所以每一个LUT可以看成一个有 4位地址线的16x1的RAM。当用户通过原理图或HDL语言描述了一个逻辑电路以…

AD软件操作技巧

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

冒泡排序算法,C语言冒泡排序算法详解

冒泡排序是最简单的排序方法,理解起来容易。虽然它的计算步骤比较多,不是最快的,但它是最基本的,初学者一定要掌握。 冒泡排序的原理是:从左到右,相邻元素进行比较。每次比较一轮,就会找到序列中…

白话经典算法系列之六 快速排序 快速搞定

快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,再加上快速排序思想----分治法也确实实用,因此很多软件公司的笔试面试,包括像腾讯,微软等知名IT公司都喜欢考这个,还有大大小…

c语言中判断一个字符串是否包含另一个字符串

1. 使用库函数 string.h strstr函数 函数名: strstr 功 能: 在串中查找指定字符串的第一次出现 用 法: char *strstr(char *str1, char *str2); 说明:返回指向第一次出现str2位置的指针,如果没找到则返回NULL。 调用函数,判断返回值是否等于NULL…

C语言截取从某位置开始指定长度子字符串方法

c语言标准库没有截取部分字符串的函数,为啥?因为用现有函数strncpy,很容易做到! char dest[4] {""}; char src[] {"123456789"}; strncpy(dest, src, 3); puts(dest); 输出结果为 123 看到了吗&#xff…

Modbus通讯协议详细解释

https://blog.csdn.net/rxiang12/article/details/79125813

V4L2框架分析

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

Linux下IO多路复用之select函数的使用

select函数的作用: 如果我们的程序里有两个需要阻塞的地方,例如要从服务器读数据,同时还要从键盘上读数据(若不采用阻塞而用查询的方式则大量占用系统资源)。这个时候我们就有两处阻塞,你当然可以用多线程或…

条件变量实现线程同步

(1) 什么是条件变量实现线程同步?   假如我们的程序中有两个线程,一个是生产者线程,另一个是消费者线程,生产者线程每隔一段时间把数据写入到缓冲区buffer中,而消费者线程则每隔一段时间从buffer中取出数据,为了避免…

mjpg-streamer框架分析

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

用strace工具跟踪系统调用

Linux下可以用strace工具查看应用程序的系统调用。 strace -h 查看能调用的参数 1.strace -o xwatv.log xwatv //-o xwatv.log 是指定将跟踪信息存放在xwatv.log中,xwatv是指要跟踪的命令或应用程序 2.把生成的log文件拷贝回windows下进行分析 主要分析open…

linux字符驱动之概念介绍

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

linux字符驱动之自动创建设备节点

上一节中,我们是手工创建设备节点,大家肯定也会觉得这样做太麻烦了。 上一节文章链接:https://blog.csdn.net/qq_37659294/article/details/104302700 问:能不能让系统自动创建设备节点? 答:可以&#x…