一个操作系统如果只是具备了高优先级任务能够“立即”获得处理器并得到执行的特点,那么它仍然不算是实时操作系统。因为这个查找最高优先级线程的过程决定了调度时间是否具有确定性,例如一个包含n个就绪任务的系统中,如果仅仅从头找到尾,那么这个时间将直接和n相关,而下一个就绪线程抉择时间的长短将会极大的影响系统的实时性。当所有就绪线程都链接在它们对应的优先级队列中时,抉择过程就将演变为在优先级数组中寻找具有最高优先级线程的非空链表。

RT-Thread内核中采用了基于位图(bitmap)的优先级算法(时间复杂度O(1),即与就绪线程的多少无关),通过位图的定位快速的获得优先级最高的线程。大致来说,就是每次调度的时间是恒定的:无论当前的系统中存在多少个线程,多少个优先级,rt-thread的调度函数总是可以在一个恒定的时间内选择出最高优先级的那个线程来执行。对不同优先级的线程,RT-Thread采用可抢占的方式:即高优先级的线程会“立刻”抢占低优先级的线程。

RT-Thread内核中也允许创建相同优先级的线程。相同优先级的线程采用时间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当前系统中无更高优先级就绪线程存在的情况下才有效。每个线程的时间片大小都可以在初始化或创建这个线程时指定。在src/scheduler.c中:

rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];//就绪线程优先级链表数组(在rt_schedule_insert_thread函数中将线程设置为就绪状态后,将当前线程链表节点插入对应优先级线程链表中)
struct rt_thread *rt_current_thread; //保存当前运行的线程(在线程跳转时设置为目标线程to_thread)
rt_uint8_t rt_current_priority; //保存当前运行线程优先级(在线程跳转时设置为目标线程to_thread的优先级) #if RT_THREAD_PRIORITY_MAX > 32
/* Maximum priority level, 256 */
rt_uint32_t rt_thread_ready_priority_group;//32位二级位图,用于查找一级位图中32个字节的最低非0字节(即当前所有就绪线程中最高优先级对应的字节)
rt_uint8_t rt_thread_ready_table[]; //256位一级位图,代表32个字节,分别对应256个线程优先级。比如第一个字节的bit0表示优先级0,bit7表示优先级7。第二个字节bit0表示优先级8,bit7表示优先级15
#else
/* Maximum priority level, 32 */
rt_uint32_t rt_thread_ready_priority_group;/32位位图变量,当Maximum priority level==32时,可看成一级位图,该变量32bit分别对应32个线程优先级(0-31)
#endif

1、当最大优先级为8时:

若最大优先级取8,则位图变量可用一个字节表示,且取值范围为0-255,字节的每一位分别对应优先级0-7。当位图变量取0-255之间的任意一个数字时,它的最低为1的BIT位置都是预知的。我们可以预先将这位图变量的所有取值对应的最低为1的BIT位置(最高优先级)计算出来,并存成一张表格,而只需要查表即可,这个执行时间自然是恒定的。实际上,查表法就是一种常用的用空间换取时间的方法。在src/kservice.c中:

const rt_uint8_t __lowest_bit_bitmap[] =
{
/* 00 */ , , , , , , , , , , , , , , , ,
/* 10 */ , , , , , , , , , , , , , , , ,
/* 20 */ , , , , , , , , , , , , , , , ,
/* 30 */ , , , , , , , , , , , , , , , ,
/* 40 */ , , , , , , , , , , , , , , , ,
/* 50 */ , , , , , , , , , , , , , , , ,
/* 60 */ , , , , , , , , , , , , , , , ,
/* 70 */ , , , , , , , , , , , , , , , ,
/* 80 */ , , , , , , , , , , , , , , , ,
/* 90 */ , , , , , , , , , , , , , , , ,
/* A0 */ , , , , , , , , , , , , , , , ,
/* B0 */ , , , , , , , , , , , , , , , ,
/* C0 */ , , , , , , , , , , , , , , , ,
/* D0 */ , , , , , , , , , , , , , , , ,
/* E0 */ , , , , , , , , , , , , , , , ,
/* F0 */ , , , , , , , , , , , , , , ,
};

上表可由下面的python简单程序生成:

#coding=gbk
#打印一个字节的最低bit位,可能的值为0,,,,,,,
samples = def getlowbit(byte):
c =
for i in range(,):
if(byte & 0x01):
return c
c = c+
byte = byte >>
return line =""
for i in range(,samples):
print "%d," %getlowbit(i),
if((i+)% == ):
print "\n"

2、当最大优先级为32时:

当进程优先级为8时,我们可以通过查表直接解决,但是当系统存在32个优先级时,如果直接制作表格的话,这个表格的元素个数将是 2^32 = 4294967296L= 4G字节,显然这是不可接受的。若当前最大优先级为32,即优先级位图变量可以使用u32型,也就是等价于4个字节,我们可以对这4个字节从字节0开始依次查表,如果字节0中非0,则最高优先级一定存在于字节0中,我们对字节0查表rt_lowest_bitmap,即可以得到最高优先级。 如果字节0为0,字节1非0,我们对字节1查表得到的是字节1中为1的最低bit位,然后加上8,就是系统的最高优先级。对字节2,字节3同样处理。当Maximum priority level==32时,则位图变量为:

/* Maximum priority level, 32 */
rt_uint32_t rt_thread_ready_priority_group; //32位位图变量,当Maximum priority level==32时,该变量32bit分别对应32个线程优先级
/*
* rt_thread_ready_priority_group 用来表示当前系统优先级位图。
* highest_ready_priority表示当前系统中最高优先级
*/
这里仅仅说明如何获取最高优先级,在实际源码中可能有一些小改动。
 if (rt_thread_ready_priority_group & 0xff)
{
highest_ready_priority = __lowest_bit_bitmap[rt_thread_ready_priority_group & 0xff];
}
else if (rt_thread_ready_priority_group & 0xff00)
{
highest_ready_priority = __lowest_bit_bitmap[(rt_thread_ready_priority_group >> ) & 0xff] + ;
}
else if (rt_thread_ready_priority_group & 0xff0000)
{
highest_ready_priority = __lowest_bit_bitmap[(rt_thread_ready_priority_group >> ) & 0xff] + ;
}
else
{
highest_ready_priority = __lowest_bit_bitmap[(rt_thread_ready_priority_group >> ) & 0xff] + ;
}

3、当最大优先级为256时:

现在我们解决了32个系统优先级时的调度问题,现在来考虑线程优先级为256的情况。读者可能会想了,这没什么不同,256个bit=32个字节,依然采用算法3的思路,对着32个字节依次查表。问题是,当位图变量有32个字节时,对这32个字节依次查表耗费的时间就不可以忽略了,为了提升系统实时调度的性能,我们需要对算法3进行改进。为了解决这个问题,我们使用二级位图。即,256个bit由32个字节存储,每一个字节的8个bit代表着位图变量中的8个优先级,如果某个字节非0,则表示其中必有非0的bit位。rtt中对应的数组为:

rt_uint8_t rt_thread_ready_table[];      //256位一级位图,代表32个字节,分别对应256个线程优先级。比如第一个字节的bit0表示优先级0,bit7表示优先级7。第二个字节bit0表示优先级8,bit7表示优先级15。

所谓二级位图,即我们首先确定32个字节中最低的非0的字节。为了实现这个效果,我们需要对这32个字节引入一个32个bit的位图变量,每一个bit位表示对应的字节是否为0。例如,这个32bit的位图变量的bit5为0,表示系统线程优先级256bit所分成的32个字节中的byte5为非0。为了区分,称这个32个bit的位图变量-字节位图变量 ,rt-thread中使用的是:

/* Maximum priority level, 256 */
rt_uint32_t rt_thread_ready_priority_group;//32位二级位图,用于查找一级位图就绪表中32个字节的最低非0字节(即当前所有就绪线程中最高优先级对应的字节)

显然我们查找系统系统最高优先级时,先确定非0的最低字节,这实际上依然是算法3,然后再对该字节进行查表,即得到该字节内最低为1的bit位,然后两者叠加(注意不是简单的加)即可。根据上面的分析,要想使用这个二级位图算法,rtt在跟踪线程的状态转换时,不仅需要维护256bit的位图变量数组rt_thread_ready_table[thread->number] |= thread->high_mask,还需要维护32bit的字节位图变量 rt_thread_ready_priority_group。参看如下代码

在rtdef.h中定义的线程控制发块
/* priority */
rt_uint8_t current_priority; /**< current priority */
rt_uint8_t init_priority; /**< initialized priority */
#if RT_THREAD_PRIORITY_MAX > 32
rt_uint8_t number;
rt_uint8_t high_mask;
#endif
rt_uint32_t number_mask;
注:只有当用户定义的最大优先级大于32个时,才会存在number和high_mask两个成员变量,这两个成员变量及另一个成员变量number_mask都是用来作位图运算用的,
只不过后面那个成员变量number_mask不管用户定义的优先级个数大于32还是在32个优先级以内都会存在。
在thread.c中_rt_thread_init函数:
thread->init_priority = priority;
thread->current_priority = priority;
在thread.c中rt_thread_startup函数:
/* set current priority to init priority */
thread->current_priority = thread->init_priority;//将当前优先级设置为初始值
/* calculate priority attribute */
#if RT_THREAD_PRIORITY_MAX > 32
thread->number = thread->current_priority >> ; /* high-5bit *///右移3位就是除以8,因为一个字节表示8个优先级。这样就可以得到当前这个优先级对应一级位图(32个字节)中的第几个字节
thread->number_mask = 1L << thread->number; //thread->number范围是0到31,将当前线程优先级所对应的一级位图字节在二级(32bit)位图变量中对应的bit置1,表示该字节代表的8个优先级至少存在一个就绪线程
thread->high_mask = 1L << (thread->current_priority & 0x07); /* low-3bit */ //current_priority的低3位表示这个优先级在上面字节中的第几个bit
#else
thread->number_mask = 1L << thread->current_priority; //将当前线程的优先级在位图变量rt_thread_ready_priority_group中对应的bit置1,表示该优先级存在就绪线程
#endif
在scheduler.c中rt_schedule_insert_thread函数:
在rt_thread_startup函数首先调用rt_thread_resume函数,在resume函数中调用rt_schedule_insert_thread函数,然后在insert函数中将线程状态设置为就绪状态,即所有在调度器中的线程均为就绪状态。
#if RT_THREAD_PRIORITY_MAX > 32
rt_thread_ready_table[thread->number] |= thread->high_mask;//将当前线程优先级所处一级位图(32个字节)中对应字节(由thread->number确定)的对应位(由thread->high_mask确定)置1,表示该优先级存在就绪线程
#endif
rt_thread_ready_priority_group |= thread->number_mask; //若最大优先级为32,则将当前线程优先级在位图变量rt_thread_ready_priority_group中对应的bit置1,表示该优先级存在就绪线程 //若最大优先级为256,则将当前线程优先级所对应的一级位图字节在二级(32bit)位图变量中对应的bit置1,表示该字节代表的8个优先级至少存在一个冰绪线程

上文已说明,thread->number就表示当前线程优先级在32个字节的位图数组中的字节位置。为了提高效率,rt-thread另外使用了一个u32类型的变量rt_thread_ready_priority_group 来加快速度。如果这32个bit中某一个bit为1,就表示对应的某个字节非0(想想看,这意味着该字节所表示的8个优先级中存在就绪线程)。rt_thread_ready_priority_group变量为32位宽度,长度上等于4个字节,因此可以对每一个字节查表(上面生成的表格)就可以得到为1的最低的bit位置。概括起来就是,rtt首先确定32个字节的位图中,非0的最低的那个字节,然后再查表得到这个字节非0的最低那个bit。这两步骤正好可以利用两次上面的表格__lowest_bit_bitmap。

4、在线程调度时获取所有就绪线程优先级的最高优先级:

在ksevicer.c中:
int __rt_ffs(int value)//该函数用于获取32位value第一个bit位为1的bit值加1。以低8位为例,0x00--0(特殊情况),0x01--0+1,0x02--1+1,0x03--0+1,0x04--2+1
{
if (value == ) return ; if (value & 0xff)
return __lowest_bit_bitmap[value & 0xff] + ; if (value & 0xff00)
return __lowest_bit_bitmap[(value & 0xff00) >> ] + ; if (value & 0xff0000)
return __lowest_bit_bitmap[(value & 0xff0000) >> ] + ; return __lowest_bit_bitmap[(value & 0xff000000) >> ] + ;
}
在scheduler.c中rt_system_scheduler_start函数:
#if RT_THREAD_PRIORITY_MAX > 32
register rt_ubase_t number;
  number = __rt_ffs(rt_thread_ready_priority_group) - ;//number为一中间参数,表示根据二级位图rt_thread_ready_priority_group查表得到一级位图32个字节中最低非0字节,取值范围为0-31
highest_ready_priority = (number << ) + __rt_ffs(rt_thread_ready_table[number]) - ;//查表得到最低非0字节所代表的8位中最低为1的位(取值范围为0-7),再与最低非0字节乘以8相加得到最高优先级(0-255)
#else
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - ;//若最大优先级为32,则直接根据32位位图变量查表得到32位中最低为1的位(取值范围为0-31),即最高优先级
#endif 在scheduler.c中rt_schedule函数:
#if RT_THREAD_PRIORITY_MAX <= 32
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - ;//若最大优先级为32,则直接根据32位位图变量查表得到32位中最低为1的位(取值范围为0-31),即最高优先级
#else
register rt_ubase_t number;
number = __rt_ffs(rt_thread_ready_priority_group) - ;//number为一中间参数,表示根据二级位图rt_thread_ready_priority_group查表得到一级位图32个字节中最低非0字节,取值范围为0-31
highest_ready_priority = (number << ) + __rt_ffs(rt_thread_ready_table[number]) - ;//查表得到最低非0字节所代表的8位中最低为1的位(取值范围为0-7),再与最低非0字节乘以8相加得到最高优先级(0-255)
#endif

 5、线程失去占用cpu时参数变化:

主动失去cpu:(1)调用sleep,delay函数使用线程放弃CPU;(2)等待信号量,互斥锁,事件,邮箱或消息队列过程中调用suspend使线程挂起。

在线程主动失去CPU时,程序都会在rt_thread_suspend函数中执行rt_schedule_remove_thread函数,将当前线程从调度器中移除。

在rt_schedule_remove_thread函数中执行rt_list_remove(&(thread->tlist));将当前线程从调度器中移除,同时将该线程优先级对应的位图变量所在位清0。

被动失去cpu:(1)线程的时间片耗尽,被迫放弃CPU;(2)系统产生中断,线程暂时失去CPU,一旦中断例程执行完,还是会还原,这些是由硬件自动完成的。

被动失去CPU时调用线程让出rt_thread_yield函数(这里指(1),(2)完全由硬件来完成,不需要软件干预),此函数中程序会执行rt_list_remove(&(thread->tlist));即将当前线程从调度器中移除,

然后再执行rt_list_insert_before((rt_thread_priority_table[thread->current_priority]),&(thread->tlist));将当前线程加入到调度器中对应优先级的就绪线程链表末尾

紧接着执行rt_schedule();重新调度线程。在被动失去CPU的过程中,程序并未操作与获取线程最高优先级算法相关的几个参数。

在scheduler.c中rt_schedule_remove_thread函数:
该函数在rt_thread_suspend函数中调用,调用之前在suspend函数中会将线程状态设置为挂起状态,即所有从调度器中移除的线程(不包括detach和delete函数中的移除)均为挂起状态。
/* remove thread from ready list */
rt_list_remove(&(thread->tlist));//重置线程链表节点为初始值,即节点next与prev均指向自身节点,即将当前线程从调度器中移除
if (rt_list_isempty(&(rt_thread_priority_table[thread->current_priority])))//若当前优先级线程链表中不存在就绪线程
{
#if RT_THREAD_PRIORITY_MAX > 32
rt_thread_ready_table[thread->number] &= ~thread->high_mask;//将该线程优先级在一级位图中对应字节的对应位清0
if (rt_thread_ready_table[thread->number] == ) //若该线程优先级在一级位图中对应字节的对应位清0后,该对应字节仍然为0,则说明该对应字节所代表的8个优先级均不存在就绪线程
{
rt_thread_ready_priority_group &= ~thread->number_mask; //若该线程优先级在一级位图中对应字节所代表的8个优先级均不存在就绪线程,则将二级位图中对应位清0
}
#else
rt_thread_ready_priority_group &= ~thread->number_mask; //若最大优先级为32,则将32位图变量中当前线程优先级的对应位清0,表示当前优先级不存在就绪线程
#endif
}

整个调度算法分析完毕,具体算法分析可参考http://blog.csdn.net/prife/article/details/7077120

RT-thread内核之线程调度算法的更多相关文章

  1. {Python之线程} 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Threading模块 九 锁 十 信号量 十一 事件Event 十二 条件Condition(了解) 十三 定时器

    Python之线程 线程 本节目录 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Thr ...

  2. 操作系统学习笔记5 | 用户级线程 && 内核级线程

    在上一部分中,我们了解到操作系统实现多进程图像需要组织.切换.考虑进程之间的影响,组织就是用PCB的队列实现,用到了一些简单的数据结构知识.而本部分重点就是进程之间的切换. 参考资料: 课程:哈工大操 ...

  3. C# Thread挂起线程和恢复线程

    前言 众所周知,Thread类中的挂起线程和恢复线程微软已标记过时,因为可能会造成问题   Resume()   恢复当前线程 已过时. Resumes a thread that has been ...

  4. 内核级线程(KLT)和用户级线程(ULT)

    内核级线程(KLT)和用户级线程(ULT) tags: KLT ULT 内核级线程 用户级线程 引言:本文涉及到操作系统的内核模式和用户模式,如果不太懂的话,可以参看我的这篇文章内核模式和用户模式,其 ...

  5. RT-thread内核之线程内核对象

    在RT-Thread实时操作系统中,任务采用了线程来实现,线程是RT-Thread中最基本的调度单位,它描述了一个任务执行的上下文关系,也描述了这个任务所处的优先等级.重要的任务能拥有相对较高的优先级 ...

  6. C#异步编程(三)内核模式线程同步

    其实,在开发过程中,无论是用户模式的同步构造还是内核模式,都应该尽量避免.因为线程同步都会造成阻塞,这就影响了我们的并发量,也影响整个应用的效率.不过有些情况,我们不得不进行线程同步. 内核模式 wi ...

  7. linux0.11内核源码——用户级线程及内核级线程

    参考资料:哈工大操作系统mooc 用户级线程 1.每个进程执行时会有一套自己的内存映射表,即我们所谓的资源,当执行多进程时切换要切换这套内存映射表,即所谓的资源切换 2.但是如果在这个进程中创建线程, ...

  8. java: Thread 和 runnable线程类

    java: Thread 和 runnable线程类 Java有2种实现线程的方法:Thread类,Runnable接口.(其实Thread本身就是Runnable的子类) Thread类,默认有ru ...

  9. [No00003A]操作系统Operating Systems 内核级线程Kernel Threads内核级线程实现Create KernelThreads

    开始核心级线程 内核级线程对多核的支持怎么样? 和用户级相比,核心级线程有什么不同? ThreadCreate 是系统调用,内核管理TCB ,内核负责切换线程 如何让切换成型? − − 内核栈,TCB ...

随机推荐

  1. 将 List<Obj> 集合, 导出至 Excel

    主代码在这:http://www.codeproject.com/Articles/120480/Export-to-Excel-Functionality-in-WPF-DataGrid 黑人老外写 ...

  2. java生成PDF,并下载到本地

    1.首先要写一个PDF工具类,以及相关工具 2.PDF所需jar包 iText是一种生成PDF报表的Java组件 freemarker是基于模板来生成文本输出 <dependency> & ...

  3. 北京Uber优步司机奖励政策(10月26日~11月1日)

    用户组:优步北京人民优步A组(适用于10月26日-11月1日) 滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/ ...

  4. 北京Uber优步司机奖励政策(9月14日~9月20日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...

  5. 6 生成器 yield 协程

    1.生成器 ----> 1 b = [x*2 for x in range(100000000000)] MemoryError: 想生成一个存放很多数据的列表,但是又不想内存占用太多 每次用一 ...

  6. VINS(六)边缘化

    通常的边缘化是将联合概率分布分解为边缘概率分布和条件概率分布的过程,这样可以将Sliding Window中较旧的状态边缘化出Sliding Window,同时保留其信息.并且保证了对应H海塞矩阵的稀 ...

  7. Linux 7.4配置VSFTP服务器

    vsftpd(very secure ftp daemon,非常安全的FTP守护进程)是一款运行在Linux操作系统上的FTP服务程序,不仅完全开源而且免费,此外,还具有很高的安全性.传输速度,以及支 ...

  8. dva框架之redux相关

    dva封装了redux,减少很多重复代码比如action reducers 常量等,本文简单介绍dva redux操作流程. 利用官网的一个加减操作小实例来操作: dva所有的redux操作是放在mo ...

  9. Qt-QML-安卓编译问题

    Qt的强大在于跨平台,但是在某些地方做的还是不好,想我这种白痴,在编译安卓的时候就遇到新的问题,我在PC上面编译没有问题的,跑到安卓上面就会出现问题,我猜测应该是Qt的下面的编译的时候,用的还是旧的安 ...

  10. Zookeeper与Eureka的区别

    Zookeeper与Eureka的区别 想要了解Zk与eureka的区别首先要知道CAP定理 CAP定理 Mysql强一致性(数据唯一出处),设计数据库设计的三范式 (表必须有主键:表不能有重复的列: ...