多优先级是怎么实现的,简单来说,一个数组就可以组成一个优先级列表。比如a[10],可以支持10个优先级。数组中的每一个元素代表一个优先级,优先级就是将一些线程分为不同的响应级别,优先级越高越容易得到执行的可能。这样理解起来很简单,但是对于rt_thread来说,应该更精细,更完善。
rt_thread是怎么实现优先级的,rt_thread是怎么将这些优先级同线程串联到一起的,这个是我们多优先级是怎么实现的所关注的。为了防止大家傻傻分不清我接下来要说的三个表,先在第一个内容下标注出来,来引起大家的注意:
Kenrel下的scheduler.c文件
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; //线程优先级表 struct rt_thread *rt_current_thread; rt_uint8_t rt_current_priority; #if RT_THREAD_PRIORITY_MAX > 32 //支持大于32个优先级 /* Maximum priority level, 256 */ rt_uint32_t rt_thread_ready_priority_group; //线程就绪优先级表 rt_uint8_t rt_thread_ready_table[32]; //二级位图表 #else /* Maximum priority level, 32 */ //支持小于32个优先级 rt_uint32_t rt_thread_ready_priority_group; //线程就绪优先级表 #endif
- 第一个表 rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]
你总会看到这个表,这个表内存储的是为不同优先级设计的链表数组,不同级别的优先级会在不同的链表下,同一级别的优先级会在相同的链表内。具体的样子<传送门>- 第二个表 rt_uint32_t rt_thread_ready_priority_group
讲道理,你说这是一个表,已经不是特别合适了。因为他的操作已经不是像第一个表一样,是用数组一个字节来表示信息了;这个表是使用32位的每一位来表示的。一共是32位,用来代表32个优先级。每一位是一个优先级,快速查找优先级的算法就是利用这个来实现的。- 第三个表 rt_uint8_t rt_thread_ready_table[32]
通常,我们并不用这个,因为,我们在rtconfig.h中定义RT_THREAD_PRIORITY_MAX通常为32,。但是,实际上,是可以支持到256位的,秘诀就是这个表。这个个第二个表中不一样,这里使用rt_uint8_t申明了这个数组,一共是32个元素,每个元素有8位。8*32=256,正好是256个优先级。RT_Thread实现256个优先级,是每8位整合为一位,塞到rt_thread_ready_priority_group中,因为这样可以直接借用__rt_ffs,而不用再重新编写一个新的位图。
这样就先简单提一下,便于后面的深入理解。
/**
* This function finds the first bit set (beginning with the least significant bit)
* in value and return the index of that bit.
*
* Bits are numbered starting at 1 (the least significant bit). A return value of
* zero from any of these functions means that the argument was zero.
*
* @return return the index of the first bit set. If value is 0, then this function
* shall return 0.
*/
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;
}
这里频繁调用了__lowest_bir_bitmap[],我们看看它是什么
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
};
这是一个常量表,这里就没有运算,就是读值。这个表的意思就是,你给一个uint8_t型的数,就给你返回这个数的二进制形态下从最低位数,第一个不为零的位数是哪一位。所以,这个返回的数只可能是0,1,2,3,4,5,6,7。我们从表上看也是这样的。嗯,高大上的说法是:时间换空间,通俗点说是不用for循环来判断到底是最低的哪一位为1,时间更快,也更稳定。总之的结果就是,对于实时嵌入系统,这样可以保证时间的准确,不会因为随机情况而产生时间花费不一致的情况。
const rt_uint8_t __lowest_bit_bitmap[]适用于8位,对于一个32位的数,需要转换4次。也就是int __rt_ffs(int value)函数。
这个流程图画出来,看起来挺丑的。嗯嗯~~不过,是这个意思。所以,在优先级大于32而小于256的时候,需要rt_uint8_t rt_thread_ready_table[32]
下面来看看具体的实现代码。
//scheduler.c下的rt_scheduler()函数
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
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;
highest_ready_priority就是返回的一个值,这个值在小于32个优先级时,是一个小于32的值,代表最高优先级就绪位。小于32个优先级的很容易看懂,对于256个优先级时候,每一句是什么意思呢?这一句需要看懂,需要看这256个优先级是怎么样转换的?
//rtdef.h下rt_thread的struct
/* 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;
//thread.c下的_rt_thread_init函数
/* priority init */
RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);
thread->init_priority = priority;
thread->current_priority = priority;
thread->number_mask = 0;
#if RT_THREAD_PRIORITY_MAX > 32
thread->number = 0;
thread->high_mask = 0;
#endif
上面个是在线程控制块TCB中struct rt_thread中添加支持256优先级的number和high_mask。并且在rt_thread_init中进行初始化。
//scheduler.c
/**
* This function will start a thread and put it to system ready queue
*
* @param thread the thread to be started
*
* @return the operation status, RT_EOK on OK, -RT_ERROR on error
*/
rt_err_t rt_thread_startup(rt_thread_t thread)
{
/* thread check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
/* 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 >> 3; /* 5bit */
thread->number_mask = 1L << thread->number;
thread->high_mask = 1L << (thread->current_priority & 0x07); /* 3bit */
#else
thread->number_mask = 1L << thread->current_priority;
#endif
RT_DEBUG_LOG(RT_DEBUG_THREAD, ("startup a thread:%s with priority:%d\n",
thread->name, thread->init_priority));
/* change thread stat */
thread->stat = RT_THREAD_SUSPEND;
/* then resume it */
rt_thread_resume(thread);
if (rt_thread_self() != RT_NULL)
{
/* do a scheduling */
rt_schedule();
}
return RT_EOK;
}
thread->number = thread->current_priority >> 3 ,位移操作,向右位移3位,用算术的角度描述是除以8(当然是商部分,不包含余数的)。记住这个数代表前5位数据,用thread->number来存储。对于256位的优先级来说,priority>>3的数据将会是一个小于32的数据,经thread->number_mask = 1L << thread->number运算会存储一个数据作为number_mask代表高五位,thread->high_mask = 1L << (thread->current_priority & 0x07) 作为低三位。
//scheduler.c
/*
* This function will insert a thread to system ready queue. The state of
* thread will be set as READY and remove from suspend queue.
*
* @param thread the thread to be inserted
* @note Please do not invoke this function in user application.
*/
void rt_schedule_insert_thread(struct rt_thread *thread)
{
register rt_base_t temp;
RT_ASSERT(thread != RT_NULL);
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* change stat */
thread->stat = RT_THREAD_READY | (thread->stat & ~RT_THREAD_STAT_MASK);
/* insert thread to ready list */
rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),
&(thread->tlist));
/* set priority mask */
#if RT_THREAD_PRIORITY_MAX <= 32
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("insert thread[%.*s], the priority: %d\n",
RT_NAME_MAX, thread->name, thread->current_priority));
#else
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
("insert thread[%.*s], the priority: %d 0x%x %d\n",
RT_NAME_MAX,
thread->name,
thread->number,
thread->number_mask,
thread->high_mask));
#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;
/* enable interrupt */
rt_hw_interrupt_enable(temp);
}
这里的与位操作,另外,high_mask和number_mask都是被我们存储过的。至此已经完全加入到就绪列表,经由rt_scheduler()调度,并找出highest_ready_priority,开始进入**rt_thread_priority_table[]找到这条被标记的优先级下的链表,然后经由上下文切换函数rt_hw_context_switch()**切换到需要执行的函数。
看完支持优先级之后,是不是有一点疑惑,难道同一个优先级下只有一个线程吗?怎么保证我切进这个优先级后能找到我需要的那个函数呢?RT-Thread支持时间片,如果不支持时间片,在同一个优先级下的两条线程是怎么进行执行的?
RTThread物联网操作系统<传送门> ↩︎