1.什么是临界区?
临界区是指访问共用资源的程序片段。通俗来说,临界区是一段程序代码,但这段代码在访问公共的资源(如全局变量,如某个硬件设备),因此,这段代码的执行必须加锁以保证互斥访问。
2.什么是调度器?
调度器的本质就是一个调度函数,该调度函数的本质就是按照一定的调度算法选出一个最优的个体(线程、进程或任务),并切换到该实体去执行,实质就是让该实体获得CPU的使用权。那么调度器在系统中什么时候会被调用呢?最容易想到的就是系统时钟中断处理程序中,正占用CPU的实体主要让出CPU时,有新的实体就绪时。
3.RT-Thread的调度算法分析?
RT-Thread的调度器是一个名为rt_schedule的函数,RT-Thread是一个基于优先级调度的实时操作系统,因此调度算法的核心是找出系统就绪线程中的最高优先级,通过优先级找到对应的线程,最终切换到新线程中去运行。
void rt_schedule(void) {
rt_base_t level;
struct rt_thread *to_thread;
struct rt_thread *from_thread;
/* 禁止全局中断 /
level = rt_hw_interrupt_disable();
/** 检查调度器是否上锁,如果没有上锁执行调度
* 如果被上锁,直接退出
*/
if (rt_scheduler_lock_nest == 0) {
register rt_ubase_t highest_ready_priority; /** 计算出系统线程中的最高优先级 */
if RT_THREAD_PRIORITY_MAX <= 32
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
else
register rt_ubase_t number; number = __rt_ffs(rt_thread_ready_priority_group) - 1; highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
endif
/** 由优先级找到对应的线程句柄 */ to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next, struct rt_thread, tlist); /** 如果最高优先级线程非当前线程,切换到新线程去执行 */ if (to_thread != rt_current_thread) { rt_current_priority = (rt_uint8_t)highest_ready_priority; from_thread = rt_current_thread; rt_current_thread = to_thread; RT_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread)); RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("[%d]switch to priority#%d " "thread:%.*s(sp:0x%p), " "from thread:%.*s(sp: 0x%p)\n", rt_interrupt_nest, highest_ready_priority, RT_NAME_MAX, to_thread->name, to_thread->sp, RT_NAME_MAX, from_thread->name, from_thread->sp));
ifdef RT_USING_OVERFLOW_CHECK
_rt_scheduler_stack_check(to_thread);
endif
/** 如果中断嵌套的层数为0,为正常的上下文切换 */ if (rt_interrupt_nest == 0) { rt_hw_context_switch((rt_uint32_t)&from_thread->sp, (rt_uint32_t)&to_thread->sp); } else { /** 如果中断嵌套的层数不为0,说明是在中断处理中进行的任务切换 * 调用中断上下文切换函数 */ RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n")); rt_hw_context_switch_interrupt((rt_uint32_t)&from_thread->sp, (rt_uint32_t)&to_thread->sp); } } } /** 使能全局中断 */ rt_hw_interrupt_enable(level); }
此处最关心的就是系统是如何找出最高的就绪优先级,涉及几个全局变量:rt_thread_ready_priority_group,rt_thread_ready_table[]
以及一个函数:__rt_ffs
首先我们需要看懂一个数组:
const rt_uint8_t __lowest_bit_bitmap[] = {
0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 00 - 15*/
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 16 - 31 */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 32 - 47 */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 48 - 63 */
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 64 - 79 */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 80 - 95 */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 96 - 111 */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 112 - 127 */
7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 128 - 143 */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 144 - 159 */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 160 - 175 */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 176 - 191 */
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 192 - 207 */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 208 - 223 */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 224 - 239 */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 /* 240 - 255 */ };
该数组含有256个元素,记录了0-255这256个数对应的二进制数从右到左第一个为1的bit的编号,一个无符号的char型由8bit组成(bit0 - bit7)。如果你对此有疑问,完全可以拿一个数试验以下。我们以63为例,十进制的63对应的二进制为:0111 1111。因此,63对应的第一个为1的位为0,其实这个数组就是通过这种方式计算出来的。那么这里有几个疑问?
1.为什么从0开始?因为数组的下标是从开始的,因此RT-Thread的最高优先级为0
2.使用这个数组有什么用?
rt_uint32_t rt_thread_ready_priority_group; rt_uint8_t
rt_thread_ready_table[32];
就绪表是一个含有32个元素的8bit无符号数,可以看成32组,每一组为8bit,可以代表256个位图。因此,rt_thread_ready_priority_group是一个32位的无符号数。系统中任何一个任务就绪后都会影响到rt_thread_ready_table和rt_thread_ready_priority_group。相反通过查找就绪组和就绪表就能找出最高的优先级。下面以例子为证:假设当前系统中有一个优先级为23的线程就绪了。首先需要找出优先级23对应就绪表中的位置:23 / 8 <==> 23 >> 3 = 2,说明对应在就绪表中的行坐标为rt_thread_ready_table[2],列坐标为rt_thread_ready_table[2].bit[7]。而23对应的二进制值为:0001 0111,让我们更为惊讶的是二进制的高5位表示的是该优先级所在的组号,而低3位表示的是该优先级在组中的位号。00010 = 2 (组号) 111 = 7 (位号)。在RT-Thread线程结构中,有几项成员专门来记录线程优先级的组号,位号信息。
if RT_THREAD_PRIORITY_MAX > 32
rt_uint8_t number; /**< 线程优先级对应的组号: current_priority >> 3 */ rt_uint8_t high_mask; /**< 线程位号掩码: (1 << 位号) 位号: (current_priority & 7) */
endif
rt_uint32_t number_mask; /**< 组号掩码: (1 << 组号) */
if RT_THREAD_PRIORITY_MAX > 32
thread->number = thread->current_priority >> 3; /* 5bit */ thread->number_mask = 1 << thread->number; thread->high_mask = 1 << (thread->current_priority & 0x07); /* 3bit */
else
thread->number_mask = 1 << thread->current_priority;
endif
当线程就绪时,就会设置就绪表和就绪组:
/* 设置就绪表和就绪组 /
if RT_THREAD_PRIORITY_MAX > 32
rt_thread_ready_table[thread->number] |= thread->high_mask; /** 设置位号 */
endif
rt_thread_ready_priority_group |= thread->number_mask; /** 设置组号 */
反过来通过__rt_ffs找出最高优先级的过程:
number = __rt_ffs(rt_thread_ready_priority_group) - 1;
highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
int __rt_ffs(int value)
{
if (value == 0) return 0;
if (value & 0xff)
return __lowest_bit_bitmap[value & 0xff] + 1;
if (value & 0xff00)
return __lowest_bit_bitmap[(value & 0xff00) >> 8] + 9;
if (value & 0xff0000)
return __lowest_bit_bitmap[(value & 0xff0000) >> 16] + 17;
return __lowest_bit_bitmap[(value & 0xff000000) >> 24] + 25;
}
虽然rt_thread_ready_priority_group为32位无符号数,已经超出__lowest_bit_bitmap数组的数组元素(0 - 255)但是可以将32位看成是4个连续的8位,进而求出32位数的第一个为1的位。通过组号再调用依次__lowest_bit_bitmap即可计算出位号。在由:优先级 = (组号 << 3) + 位号 就可以计算出最高的优先级了。
版权声明:本文为博主原创文章,未经博主允许不得转载。