多级反馈队列调度器(multilevel feedback queue scheduler)
在多级反馈队列调度器中,线程的优先级是通过下面的公式动态计算的:
priority = PRI_MAX - (recent_cpu/4) - (nice * 2)
recent_cpu = (2*load_avg)/(2*load_avg + 1)*recent_cpu + nice
load_avg= (59/60)*load_avg + (1/60)*ready_threads
- priority即为线程的优先级,每4个timer tick,按上式计算一次。
- nice为线程属性,取值[-20,+20],越大表示该线程出让更多的CPU时间。
- recent_cpu表示线程消耗的timer tick,每一次timer tick中断,该值加1,每一秒,recent_cpu按照上式计算一次。
- load_avg表示过去的一分钟内处于就绪状态的线程数,初始值为0,每一秒按上式计算一次。
公式计算的过程中,需要小数的支持,而pintos不支持浮点数运算,所以我们需要实现定点小数的运算。
首先,实现定点小数运算fixed_point.h
:
#ifndef __FIXED_POINT_H
#define __FIXED_POINT_H
typedef long long fp_t;
#define FD_SHIFT_AMOUNT 12
/*
* X is a fp_t variable
* N is a int variable
*/
#define FD_CONV_N(N) ((fp_t)((N)<<FD_SHIFT_AMOUNT))
#define FD_CONV_X_ZERO(X) ((X)>>FD_SHIFT_AMOUNT)
#define FD_CONV_X_ROUND(X) \
((X)>=0 ?\
(((X)+(1<<(FD_SHIFT_AMOUNT-1)))>>FD_SHIFT_AMOUNT): \
(((X)-(1<<(FD_SHIFT_AMOUNT-1)))>>FD_SHIFT_AMOUNT) )
#define FD_ADD(X, Y) ((X)+(Y))
#define FD_SUB(X, Y) ((X)-(Y))
#define FD_ADD_N(X, N) ((X) + (N<<FD_SHIFT_AMOUNT))
#define FD_SUB_N(X, N) ((X) - (N<<FD_SHIFT_AMOUNT))
#define FD_MUL(X, Y) (((fp_t)X)*(Y)>>FD_SHIFT_AMOUNT)
#define FD_MUL_N(X, N) ((X)*N)
#define FD_DIV(X, Y) ((((fp_t)X)<<FD_SHIFT_AMOUNT)/(Y))
#define FD_DIV_N(X, N) ((X)/N)
#endif
在thread.c中定义load_avg:
/* The average number of threads ready to run over the past minute.*/
static fp_t load_avg = 0;
在struct thread中增加nice 和 recent_cpu的定义:
fp_t recent_cpu; /* Inc per ticks and recalculate per second.*/
int nice; /* Between -20 to 20.*/
同时在init_thread()中对其进行初始化。
thread_tick()函数每个timer tick被调用一次,所以我们在其中完成:
- 当前线程recent_cpu++
- 每一秒,更新recent_cpu和load_avg
- 每4个ticks,更新所有线程的priority
void
thread_tick (void)
{
struct thread *t = thread_current ();
/* Update statistics. */
if (t == idle_thread)
idle_ticks++;
#ifdef USERPROG
else if (t->pagedir != NULL)
user_ticks++;
#endif
else
kernel_ticks++;
/* Enforce preemption. */
if (++thread_ticks >= TIME_SLICE)
intr_yield_on_return ();
/* Inc per ticks.*/
if(t != idle_thread){
t->recent_cpu = FD_ADD_N(t->recent_cpu, 1);
//msg("%d %d",t, FD_CONV_X_ROUND(t->recent_cpu));
}
int ticks = timer_ticks();
/* Calculate the load_avg and recent_cpu per second.*/
if(thread_mlfqs && ticks % 100 == 0){
size_t ready_threads = 0; /* The number of threads that are either running or ready. */
ready_threads += list_size(&ready_list);
if(t != idle_thread)
ready_threads++;
load_avg = FD_DIV_N(FD_ADD_N(FD_MUL_N(load_avg, 59),ready_threads), 60);
enum intr_level old_level = intr_disable ();
thread_foreach(thread_update_recent_cpu, NULL);
intr_set_level (old_level);
}
/* Calculate priority per fourth tick.*/
if(thread_mlfqs && ticks % 4 == 0){
enum intr_level old_level = intr_disable ();
thread_foreach(thread_update_priority, NULL);
intr_set_level (old_level);
}
/* Find if a thread need to be awake from block
* by block ticks over.
*/
enum intr_level old_level = intr_disable ();
thread_foreach(thread_dec_blockticks, NULL);
intr_set_level (old_level);
}
其中thread_mlfqs
为多级反馈调度指示变量,当前为true。
OK其实重新定义函数看起来会干净一些
thread_update_recent_cpu()
实现如下:
/*
* Calculate threads recent_cpu.
*/
void thread_update_recent_cpu(struct thread *t, void *aux){
if(t == idle_thread) return;
fp_t recent_cpu = t->recent_cpu;
int nice = t->nice;
//t->recent_cpu = (2*load_avg)/(2*load_avg+1)*recent_cpu + nice;
t->recent_cpu = FD_ADD_N(FD_MUL(FD_DIV(FD_MUL_N(load_avg, 2), FD_ADD_N(FD_MUL_N(load_avg, 2), 1)), recent_cpu), nice);
}
thread_update_priority()
实现如下:
/*
* Calculate threads priority by multi-level feedback queue scheduler.
*/
void thread_update_priority(struct thread *t, void *aux){
if(t == idle_thread) return;
fp_t recent_cpu = t->recent_cpu;
int nice = t->nice;
//int priority = PRI_MAX - (recent_cpu/4) - (nice*2);
int priority = FD_CONV_X_ROUND(FD_SUB(FD_CONV_N(PRI_MAX), FD_ADD_N(FD_DIV_N(recent_cpu, 4), (nice*2))));
if(priority > PRI_MAX) priority = PRI_MAX;
if(priority < PRI_MIN) priority = PRI_MIN;
t->priority = priority;
}
之后还有四个辅助函数实现如下:
/* Sets the current thread's nice value to NICE. */
void
thread_set_nice (int nice UNUSED)
{
enum intr_level old_level = intr_disable ();
thread_current ()->nice = nice;
thread_update_priority(thread_current(), NULL);
thread_yield();
intr_set_level (old_level);
}
/* Returns the current thread's nice value. */
int
thread_get_nice (void)
{
return thread_current ()->nice;
}
/* Returns 100 times the system load average. */
int
thread_get_load_avg (void)
{
return FD_CONV_X_ROUND(FD_MUL_N(load_avg, 100));
}
/* Returns 100 times the current thread's recent_cpu value. */
int
thread_get_recent_cpu (void)
{
return FD_CONV_X_ROUND(FD_MUL_N(thread_current()->recent_cpu, 100));
}
需要注意的是设置nice值后线程的优先级发生了改变,需要重新计算优先级并调度。
由于多级反馈队列调度不与优先级捐赠同时存在,所以有关优先级捐赠的部分均使用thread_mlfqs变量进行判断,在此不再赘述。
mlfqs-block测试中,要求阻塞在一个锁的线程在锁被释放的时候立即被调度,所以需要将Mesa style的sema改为Hoare style:
void
sema_up (struct semaphore *sema)
{
enum intr_level old_level;
ASSERT (sema != NULL);
old_level = intr_disable ();
if (!list_empty (&sema->waiters))
thread_unblock (list_entry (list_pop_front (&sema->waiters),
struct thread, elem));
sema->value++;
intr_set_level (old_level);
thread_yield();
}
在sema_up完成时进行一次调度即可。
通过测试:mlfqs-*
至此,threads全部27个测试通过: