安装最新版本的pintos。可以 git clone git://pintos-os.org/pintos-anon,或者其它方式。
主要是bochsGUI界面显示不出来导致无法执行测试命令,记录其安装位置,执行命令./configure –with -nogui –enable-gdb-stub 禁用其GUI,但是仍然可能无法解决问题,可以到test目录下更改Make文件使其不调用gui
更改/pintos/src/utils/pintos文件
在275行, kernel.bin 改为 $HOME/ospg/pintos/src/threads/build/kernel.bin (或者为绝对路径)
更改/pintos/src/utils/Pintos.pm文件
在362行, loader.bin 改为 $HOME/ospg/pintos/src/threads/build/loader.bin (或者为绝对路径)
重新实现timer_sleep(),在devices/timer.c中定义。虽然提供了一个工作实现,但它会“busy waits”,也就是说,它会不断循环检查当前时间并调用thread_yield()函数,直到有足够的时间过去。重新实现使其避免“busy waiting”。
调用timer_sleep的时候直接把线程阻塞掉,然后给线程结构体加一个成员ticks_blocked来记录这个线程被sleep了多少时间, 然后利用操作系统自身的时钟中断(每个tick会执行一次)加入对线程状态的检测, 每次检测将ticks_blocked减1, 如果减到0就唤醒这个线程。
具体代码:
/* Sleeps for approximately TICKS timer ticks. Interrupts must
be turned on. */
void
timer_sleep (int64_t ticks)
{
if (ticks <= 0)
{
return;
}
ASSERT (intr_get_level () == INTR_ON);
enum intr_level old_level = intr_disable ();
struct thread *current_thread = thread_current ();
current_thread->ticks_blocked = ticks;
thread_block ();
intr_set_level (old_level);
}
注意这里调用的thread_block:
/* Puts the current thread to sleep. It will not be scheduled
again until awoken by thread_unblock().
This function must be called with interrupts turned off. It
is usually a better idea to use one of the synchronization
primitives in synch.h. */
void
thread_block (void)
{
ASSERT (!intr_context ());
ASSERT (intr_get_level () == INTR_OFF);
thread_current ()->status = THREAD_BLOCKED;
schedule ();
}
给线程的结构体加上我们的ticks_blocked成员:
/* Record the time the thread has been blocked. */
int64_t ticks_blocked;
然后在线程被创建的时候初始化ticks_blocked为0, 加在thread_create函数内:
t->ticks_blocked = 0;
然后修改时钟中断处理函数, 加入线程sleep时间的检测, 加在timer_interrupt内:
thread_foreach (blocked_thread_check, NULL);
这里的thread_foreach就是对每个线程都执行blocked_thread_check这个函数:
/* Invoke function 'func' on all threads, passing along 'aux'.
This function must be called with interrupts off. */
void
thread_foreach (thread_action_func *func, void *aux)
{
struct list_elem *e;
ASSERT (intr_get_level () == INTR_OFF);
for (e = list_begin (&all_list); e != list_end (&all_list);
e = list_next (e))
{
struct thread *t = list_entry (e, struct thread, allelem);
func (t, aux);
}
}
aux就是传给这个函数的参数。
然后, 给thread添加一个方法blocked_thread_check即可:
thread.h中声明:
void blocked_thread_check (struct thread *t, void *aux UNUSED);
thread.c:
/* Check the blocked thread */
void
blocked_thread_check (struct thread *t, void *aux UNUSED)
{
if (t->status == THREAD_BLOCKED && t->ticks_blocked > 0)
{
t->ticks_blocked--;
if (t->ticks_blocked == 0)
{
thread_unblock(t);
}
}
}
thread_unblock就是把线程丢到就绪队列里继续跑:
/* Transitions a blocked thread T to the ready-to-run state.
This is an error if T is not blocked. (Use thread_yield() to
make the running thread ready.)
This function does not preempt the running thread. This can
be important: if the caller had disabled interrupts itself,
it may expect that it can atomically unblock a thread and
update other data. */
void
thread_unblock (struct thread *t)
{
enum intr_level old_level;
ASSERT (is_thread (t));
old_level = intr_disable ();
ASSERT (t->status == THREAD_BLOCKED);
list_push_back (&ready_list, &t->elem);
t->status = THREAD_READY;
intr_set_level (old_level);
}
这样timer_sleep函数唤醒机制就实现了。
测试结果:
当一个线程被添加到具有比当前运行的线程更高优先级的就绪列表时,当前线程应该立即将处理器分给新线程中。类似地,当线程正在等待锁,信号量或条件变量时,应首先唤醒优先级最高的等待线程。线程应可以随时提高或降低自己的优先级,但降低其优先级而不再具有最高优先级时必须放弃CPU。
我们实现线程优先级调度需要时刻维持就绪队列为一个优先级队列,所以在插入线程到就绪队列的时候需要继续维持这个队列的优先级有序性质。我们研究发现在以下三种方法中会把一个线程插入到就绪队列中:
我们可以找到thread_unblock方法中线程插入队列的代码:
list_push_back(&ready_list, &t->elem);
这段代码会把线程直接插入到队列尾部,而调度下一个thread会直接取队列头部,显然不符合优先级有序性。
我们研究发现pintos的队列实现中发现有这样的方法:
void list_insert_ordered(struct list *, struct list_elem *, list_less_func *, void *aux);
这个方法可以保证在插入队列后,队列中的线程仍然具有有序性,可以用于维持队列优先级有序性。
所以我们把thread_unblock方法中的list_push_back修改为:
list_insert_ordered(&ready_list, &t->elem, (list_less_func *) &thread_cmp_priority, NULL);
然后还需要实现比较函数:
bool thread_cmp_priority(const struct list_elem *a, const struct list_elem *b, void *aux UNUSED){
return list_entry(a, struct thread, elem)->priority > list_entry(b, struct thread, elem)->priority;
}
最后再对thread_yield方法和thread_init方法作相同的修改。把init_thread方法中的list_push_back修改为:
list_insert_ordered(&all_list, &t->allelem, (list_less_func *) &thread_cmp_priority, NULL);
把thread_yield方法中的list_push_back修改为:
list_insert_ordered(&ready_list, &cur->elem, (list_less_func *) &thread_cmp_priority, NULL);
修改代码后,alarm_priority这个测试样例就可以pass了。
通过分析关于抢占式调度的测试样例:我们需要保证在创建一个新线程的时候,如果新线程优先级高于当前线程优先级,就要阻塞当前线程转为先执行新线程,所以每次设置一个新线程就要立即重新安排所有线程的优先级顺序。
我们的实现方案是在新线程设置优先级的时候调用thread_yield方法,这样就可以把当前线程重新丢到就绪队列中继续执行,相当于重新制定了所有线程优先级顺序,而且在创建新线程的时候,如果新线程比主线程优先级高也需要调用thread_yield方法。
修改thread_set_priority方法为:
void thread_set_priority(int new_priority){
thread_current()->priority = new_priority;
thread_yield();
}
还需要在thread_create方法把创建的新线程unblock后加上以下代码:
if (thread_current()->priority < priority){
thread_yield();
}
修改代码后,priority_change/priority_preempt这两个测试样例就都可以pass了。
当发现高优先级的任务因为低优先级任务占用资源而阻塞时,就将低优先级任务的优先级提升到等待它所占有的资源的最高优先级任务的优先级。
先来看测试代码:
void
test_priority_donate_one (void)
{
struct lock lock;
/* This test does not work with the MLFQS. */
ASSERT (!thread_mlfqs);
/* Make sure our priority is the default. */
ASSERT (thread_get_priority () == PRI_DEFAULT);
lock_init (&lock);
lock_acquire (&lock);
thread_create ("acquire1", PRI_DEFAULT + 1, acquire1_thread_func, &lock);
msg ("This thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 1, thread_get_priority ());
thread_create ("acquire2", PRI_DEFAULT + 2, acquire2_thread_func, &lock);
msg ("This thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 2, thread_get_priority ());
lock_release (&lock);
msg ("acquire2, acquire1 must already have finished, in that order.");
msg ("This should be the last line before finishing this test.");
}
static void
acquire1_thread_func (void *lock_)
{
struct lock *lock = lock_;
lock_acquire (lock);
msg ("acquire1: got the lock");
lock_release (lock);
msg ("acquire1: done");
}
static void
acquire2_thread_func (void *lock_)
{
struct lock *lock = lock_;
lock_acquire (lock);
msg ("acquire2: got the lock");
lock_release (lock);
msg ("acquire2: done");
}
分析: 首先当前线程(称为original_thread)是一个优先级为PRI_DEFAULT的线程, 然后第4行创建了一个锁, 接着创建一个线程acquire1,优先级为PRI_DEFAULT+1, 传了一个参数为这个锁的函数过去(线程acquire1执行的时候会调用)。
好, 我们之前实现的抢占式调度会让acquire1马上执行, 来看acquire1_thread_func干了什么, 这里直接获取了这个锁, 来看lock_acquire函数:
/* Acquires LOCK, sleeping until it becomes available if
necessary. The lock must not already be held by the current
thread.
This function may sleep, so it must not be called within an
interrupt handler. This function may be called with
interrupts disabled, but interrupts will be turned back on if
we need to sleep. */
void
lock_acquire (struct lock *lock)
{
ASSERT (lock != NULL);
ASSERT (!intr_context ());
ASSERT (!lock_held_by_current_thread (lock));
sema_down (&lock->semaphore);
lock->holder = thread_current ();
}
这里如我们所想, 直接调用信号量PV操作中的P操作, 来看P操作sema_down:
/* Down or "P" operation on a semaphore. Waits for SEMA's value
to become positive and then atomically decrements it.
This function may sleep, so it must not be called within an
interrupt handler. This function may be called with
interrupts disabled, but if it sleeps then the next scheduled
thread will probably turn interrupts back on. */
void
sema_down (struct semaphore *sema)
{
enum intr_level old_level;
ASSERT (sema != NULL);
ASSERT (!intr_context ());
old_level = intr_disable ();
while (sema->value == 0)
{
list_push_back (&sema->waiters, &thread_current ()->elem);
thread_block ();
}
sema->value--;
intr_set_level (old_level);
}
和课上描述的一致, 把线程丢到这个信号量的队列waiters里, 阻塞该线程等待唤醒, value–。
注意, 这里acquire1_thread_func阻塞了, msg这个时候并不会输出, 这时会继续执行original_thread, 然后输出msg, 输出当前线程应该的优先级和实际的优先级。
然后继续创建一个线程acquire2, 优先级为PRI_DEFAULT+2, 这里调用和acquire1一致, 然后original_thread继续输出msg。
好, 然后original_thread释放了这个锁(V操作), 释放的过程会触发被锁着的线程acquire1, acquire2, 然后根据优先级调度, 先执行acquire2, 再acquire1, 最后再执行original_thread。
那么这里应该是acquire2, acquire1分别释放锁然后输出msg, 最后original_thread再输出msg。
好, 我们已经把这个测试程序分析完了, 我们来看它希望的输出:
# -*- perl -*-
use strict;
use warnings;
use tests::tests;
check_expected ([<<'EOF']);
(priority-donate-one) begin
(priority-donate-one) This thread should have priority 32. Actual priority: 32.
(priority-donate-one) This thread should have priority 33. Actual priority: 33.
(priority-donate-one) acquire2: got the lock
(priority-donate-one) acquire2: done
(priority-donate-one) acquire1: got the lock
(priority-donate-one) acquire1: done
(priority-donate-one) acquire2, acquire1 must already have finished, in that order.
(priority-donate-one) This should be the last line before finishing this test.
(priority-donate-one) end
EOF
pass;
输出行为和我们分析的一致, 来看7,8行, original_thread的优先级分别变成了PRI_DEFAULT+1和PRI_DEFAULT+2。
我们来根据这个结果分析一下优先级捐赠行为:
original_thread拥有的锁被acquire1获取之后, 因为acquire1线程被阻塞于这个锁, 那么acquire1的执行必须要original_thread继续执行释放这个锁, 从优先级的角度来说, original_thread的优先级应该提升到acquire1的优先级,
因为original_thread本身的执行包含了acquire1执行的阻塞, 所以此时acquire1对original_thread做了捐赠, 优先级提到PRI_DEFAULT+1, acquire2行为类似。
好, 支持priority-donate-one分析结束, 我们来分析一下实现:
具体行为肯定是被锁定在了锁的获取和释放上了, 我们的实现思路是:
在一个线程获取一个锁的时候, 如果拥有这个锁的线程优先级比自己低就提高它的优先级,然后在这个线程释放掉这个锁之后把原来拥有这个锁的线程改回原来的优先级。
void
test_priority_donate_multiple (void)
{
struct lock a, b;
/* This test does not work with the MLFQS. */
ASSERT (!thread_mlfqs);
/* Make sure our priority is the default. */
ASSERT (thread_get_priority () == PRI_DEFAULT);
lock_init (&a);
lock_init (&b);
lock_acquire (&a);
lock_acquire (&b);
thread_create ("a", PRI_DEFAULT + 1, a_thread_func, &a);
msg ("Main thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 1, thread_get_priority ());
thread_create ("b", PRI_DEFAULT + 2, b_thread_func, &b);
msg ("Main thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 2, thread_get_priority ());
lock_release (&b);
msg ("Thread b should have just finished.");
msg ("Main thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 1, thread_get_priority ());
lock_release (&a);
msg ("Thread a should have just finished.");
msg ("Main thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT, thread_get_priority ());
}
static void
a_thread_func (void *lock_)
{
struct lock *lock = lock_;
lock_acquire (lock);
msg ("Thread a acquired lock a.");
lock_release (lock);
msg ("Thread a finished.");
}
static void
b_thread_func (void *lock_)
{
struct lock *lock = lock_;
lock_acquire (lock);
msg ("Thread b acquired lock b.");
lock_release (lock);
msg ("Thread b finished.");
}
一样, original_thread是优先级为PRI_DEFAULT的线程, 然后创建2个锁, 接着创建优先级为PRI_DEFAULT+1的线程a, 把锁a丢给这个线程的执行函数。
这时候线程a抢占式地调用a_thread_func, 获取了a这个锁, 阻塞。
然后original_thread输出线程优先级的msg。
然后再创建一个线程优先级为PRI_DEFAULT+2的线程b, 和a一样做同样的操作。
好, 然后original_thread释放掉了锁b, 此时线程b被唤醒, 抢占式执行b_thread_func。
然后original再输出msg, a同上, 此时我们来看一下测试希望的输出是什么:
# -*- perl -*-
use strict;
use warnings;
use tests::tests;
check_expected ([<<'EOF']);
(priority-donate-multiple) begin
(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32.
(priority-donate-multiple) Main thread should have priority 33. Actual priority: 33.
(priority-donate-multiple) Thread b acquired lock b.
(priority-donate-multiple) Thread b finished.
(priority-donate-multiple) Thread b should have just finished.
(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32.
(priority-donate-multiple) Thread a acquired lock a.
(priority-donate-multiple) Thread a finished.
(priority-donate-multiple) Thread a should have just finished.
(priority-donate-multiple) Main thread should have priority 31. Actual priority: 31.
(priority-donate-multiple) end
EOF
pass;
好, 这里输出和我们的分析依然是一致的。 重点在于original_thread的优先级变化, 第一次输出是正常的, priority-donate-one已经测试了这个逻辑。
这里特别的是original_thread拥有两把锁分别给a, b两个线程占有了。
后面是释放了b之后, original_thread的优先级恢复到32, 即当前线程的优先级还是被a的优先级所捐赠着的,最后释放了a之后才回到原来的优先级。
这里测试的行为实际是: 多锁情况下优先级逻辑的正确性。
那么我们对应的实现思路是: 释放一个锁的时候, 将该锁的拥有者改为该线程被捐赠的第二优先级,若没有其余捐赠者, 则恢复原始优先级。
那么我们的线程必然需要一个数据结构来记录所有对这个线程有捐赠行为的线程。
void
test_priority_donate_multiple2 (void)
{
struct lock a, b;
/* This test does not work with the MLFQS. */
ASSERT (!thread_mlfqs);
/* Make sure our priority is the default. */
ASSERT (thread_get_priority () == PRI_DEFAULT);
lock_init (&a);
lock_init (&b);
lock_acquire (&a);
lock_acquire (&b);
thread_create ("a", PRI_DEFAULT + 3, a_thread_func, &a);
msg ("Main thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 3, thread_get_priority ());
thread_create ("c", PRI_DEFAULT + 1, c_thread_func, NULL);
thread_create ("b", PRI_DEFAULT + 5, b_thread_func, &b);
msg ("Main thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 5, thread_get_priority ());
lock_release (&a);
msg ("Main thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 5, thread_get_priority ());
lock_release (&b);
msg ("Threads b, a, c should have just finished, in that order.");
msg ("Main thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT, thread_get_priority ());
}
static void
a_thread_func (void *lock_)
{
struct lock *lock = lock_;
lock_acquire (lock);
msg ("Thread a acquired lock a.");
lock_release (lock);
msg ("Thread a finished.");
}
static void
b_thread_func (void *lock_)
{
struct lock *lock = lock_;
lock_acquire (lock);
msg ("Thread b acquired lock b.");
lock_release (lock);
msg ("Thread b finished.");
}
static void
c_thread_func (void *a_ UNUSED)
{
msg ("Thread c finished.");
}
有了之前的分析这里简单说一下: original_thread拥有2个锁, 然后创建PRI_DEFAULT+3的线程a去拿a这个锁, PRI_DEFAULT+5的线程b去拿b这个锁, 中间创建了一个PRI_DEFAULT+1的c线程, 但是因为创建的时候当前线程的优先级已经被a线程捐赠了所以抢占调度并没有发生。 然后分别释放掉a和b, 释放a的时候线程a被唤醒, 但是优先级依然不如当前线程, 此时当前线程优先级仍然被b捐赠着, 优先级最高继续执行, 然后释放掉b, 释放掉b之后,original_thread的优先级降到初始,应该最后被调用, 线程b抢占调度, 然后线程a, 再是线程c, 最后才original_thread输出msg。
# -*- perl -*-
use strict;
use warnings;
use tests::tests;
check_expected ([<<'EOF']);
(priority-donate-multiple2) begin
(priority-donate-multiple2) Main thread should have priority 34. Actual priority: 34.
(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36.
(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36.
(priority-donate-multiple2) Thread b acquired lock b.
(priority-donate-multiple2) Thread b finished.
(priority-donate-multiple2) Thread a acquired lock a.
(priority-donate-multiple2) Thread a finished.
(priority-donate-multiple2) Thread c finished.
(priority-donate-multiple2) Threads b, a, c should have just finished, in that order.
(priority-donate-multiple2) Main thread should have priority 31. Actual priority: 31.
(priority-donate-multiple2) end
EOF
pass;
这里依然测试的是多锁情况下优先级逻辑的正确性。
在一个线程获取一个锁的时候, 如果拥有这个锁的线程优先级比自己低就提高它的优先级,并且如果这个锁还被别的锁锁着, 将会递归地捐赠优先级, 然后在这个线程释放掉这个锁之后恢复未捐赠逻辑下的优先级。
如果一个线程被多个线程捐赠, 维持当前优先级为捐赠优先级中的最大值(acquire和release之时)。
在对一个线程进行优先级设置的时候, 如果这个线程处于被捐赠状态, 则对original_priority进行设置, 然后如果设置的优先级大于当前优先级, 则改变当前优先级, 否则在捐赠状态取消的时候恢复original_priority。
在释放锁对一个锁优先级有改变的时候应考虑其余被捐赠优先级和当前优先级。
将信号量的等待队列实现为优先级队列。
将condition的waiters队列实现为优先级队列。
释放锁的时候若优先级改变则可以发生抢占。
int base_priority; /* Base priority. */
struct list locks; /* Locks that the thread is holding. */
struct lock *lock_waiting; /* The lock that the thread is waiting for. */
struct list_elem elem; /* List element for priority donation. */
int max_priority; /* Max priority among the threads acquiring the lock. */
void
lock_acquire (struct lock *lock)
{
struct thread *current_thread = thread_current ();
struct lock *l;
enum intr_level old_level;
ASSERT (lock != NULL);
ASSERT (!intr_context ());
ASSERT (!lock_held_by_current_thread (lock));
if (lock->holder != NULL && !thread_mlfqs)
{
current_thread->lock_waiting = lock;
l = lock;
while (l && current_thread->priority > l->max_priority)
{
l->max_priority = current_thread->priority;
thread_donate_priority (l->holder);
l = l->holder->lock_waiting;
}
}
sema_down (&lock->semaphore);
old_level = intr_disable ();
current_thread = thread_current ();
if (!thread_mlfqs)
{
current_thread->lock_waiting = NULL;
lock->max_priority = current_thread->priority;
thread_hold_the_lock (lock);
}
lock->holder = current_thread;
intr_set_level (old_level);
}
在P操作之前递归地实现优先级捐赠, 然后在被唤醒之后(此时这个线程已经拥有了这个锁),成为这个锁的拥有者。
这里thread_donate_priority和thread_hold_the_lock封装成函数,注意一下这里优先级捐赠是通过直接修改锁的最高优先级, 然后调用update的时候把现成优先级更新实现的,update下面会写, 实现如下:
/* Let thread hold a lock */
void
thread_hold_the_lock(struct lock *lock)
{
enum intr_level old_level = intr_disable ();
list_insert_ordered (&thread_current ()->locks, &lock->elem, lock_cmp_priority, NULL);
if (lock->max_priority > thread_current ()->priority)
{
thread_current ()->priority = lock->max_priority;
thread_yield ();
}
intr_set_level (old_level);
}
/* Donate current priority to thread t. */
void
thread_donate_priority (struct thread *t)
{
enum intr_level old_level = intr_disable ();
thread_update_priority (t);
if (t->status == THREAD_READY)
{
list_remove (&t->elem);
list_insert_ordered (&ready_list, &t->elem, thread_cmp_priority, NULL);
}
intr_set_level (old_level);
}
/* lock comparation function */
bool
lock_cmp_priority (const struct list_elem *a, const struct list_elem *b, void *aux UNUSED)
{
return list_entry (a, struct lock, elem)->max_priority > list_entry (b, struct lock, elem)->max_priority;
}
然后在lock_release函数加入以下语句:
if (!thread_mlfqs)
thread_remove_lock (lock);
thread_remove_lock实现如下:
/* Remove a lock. */
void
thread_remove_lock (struct lock *lock)
{
enum intr_level old_level = intr_disable ();
list_remove (&lock->elem);
thread_update_priority (thread_current ());
intr_set_level (old_level);
}
当释放掉一个锁的时候, 当前线程的优先级可能发生变化, 我们用thread_update_priority来处理这个逻辑:
/* Update priority. */
void
thread_update_priority (struct thread *t)
{
enum intr_level old_level = intr_disable ();
int max_priority = t->base_priority;
int lock_priority;
if (!list_empty (&t->locks))
{
list_sort (&t->locks, lock_cmp_priority, NULL);
lock_priority = list_entry (list_front (&t->locks), struct lock, elem)->max_priority;
if (lock_priority > max_priority)
max_priority = lock_priority;
}
t->priority = max_priority;
intr_set_level (old_level);
}
这里如果这个线程还有锁, 就先获取这个线程拥有锁的最大优先级(可能被更高级线程捐赠), 然后如果这个优先级比base_priority大的话更新的应该是被捐赠的优先级。
然后在init_thread中加入初始化:
t->base_priority = priority;
list_init (&t->locks);
t->lock_waiting = NULL;
修改一下thread_set_priority:
void
thread_set_priority (int new_priority)
{
if (thread_mlfqs)
return;
enum intr_level old_level = intr_disable ();
struct thread *current_thread = thread_current ();
int old_priority = current_thread->priority;
current_thread->base_priority = new_priority;
if (list_empty (¤t_thread->locks) || new_priority > old_priority)
{
current_thread->priority = new_priority;
thread_yield ();
}
intr_set_level (old_level);
}
好, 至此整个捐赠的逻辑都写完了, 还差两个优先级队列的实现, 搞起~
然后把condition的队列改成优先级队列
void
cond_signal (struct condition *cond, struct lock *lock UNUSED)
{
ASSERT (cond != NULL);
ASSERT (lock != NULL);
ASSERT (!intr_context ());
ASSERT (lock_held_by_current_thread (lock));
if (!list_empty (&cond->waiters))
{
list_sort (&cond->waiters, cond_sema_cmp_priority, NULL);
sema_up (&list_entry (list_pop_front (&cond->waiters), struct semaphore_elem, elem)->semaphore);
}
}
/* cond sema comparation function */
bool
cond_sema_cmp_priority (const struct list_elem *a, const struct list_elem *b, void *aux UNUSED)
{
struct semaphore_elem *sa = list_entry (a, struct semaphore_elem, elem);
struct semaphore_elem *sb = list_entry (b, struct semaphore_elem, elem);
return list_entry(list_front(&sa->semaphore.waiters), struct thread, elem)->priority > list_entry(list_front(&sb->semaphore.waiters), struct thread, elem)->priority;
}
然后把信号量的等待队列实现为优先级队列:
void
sema_up (struct semaphore *sema)
{
enum intr_level old_level;
ASSERT (sema != NULL);
old_level = intr_disable ();
if (!list_empty (&sema->waiters))
{
list_sort (&sema->waiters, thread_cmp_priority, NULL);
thread_unblock (list_entry (list_pop_front (&sema->waiters), struct thread, elem));
}
sema->value++;
thread_yield ();
intr_set_level (old_level);
}
void
sema_down (struct semaphore *sema)
{
enum intr_level old_level;
ASSERT (sema != NULL);
ASSERT (!intr_context ());
old_level = intr_disable ();
while (sema->value == 0)
{
list_insert_ordered (&sema->waiters, &thread_current ()->elem, thread_cmp_priority, NULL);
thread_block ();
}
sema->value--;
intr_set_level (old_level);
}
做完这些其实是一气呵成的, 因为之前多测试需求有足够多的分析了, 来看测试结果:
Picture
original_thread 获取了锁a。线程 medium 获取了锁b,被锁a阻塞。于是 original_thread 的优先级被提升到跟 medium 相同。然后线程 high 被 medium 阻塞,medium 的优先级被提升到跟 high 相同后,low 的优先级也应该被提升到跟 high 相同的等级。
void
test_priority_donate_one (void)
{
struct lock lock;
/* This test does not work with the MLFQS. */
ASSERT (!thread_mlfqs);
/* Make sure our priority is the default. */
ASSERT (thread_get_priority () == PRI_DEFAULT);
lock_init (&lock);
lock_acquire (&lock);
thread_create ("acquire1", PRI_DEFAULT + 1, acquire1_thread_func, &lock);
msg ("This thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 1, thread_get_priority ());
thread_create ("acquire2", PRI_DEFAULT + 2, acquire2_thread_func, &lock);
msg ("This thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 2, thread_get_priority ());
lock_release (&lock);
msg ("acquire2, acquire1 must already have finished, in that order.");
msg ("This should be the last line before finishing this test.");
}
lock_and_sema是包含一个锁和一个信号量的结构体,线程会被sema_down(P)阻塞,而sema_up(V)操作会唤醒相应的线程。
void
test_priority_donate_sema (void)
{
struct lock_and_sema ls;
/* This test does not work with the MLFQS. */
ASSERT (!thread_mlfqs);
/* Make sure our priority is the default. */
ASSERT (thread_get_priority () == PRI_DEFAULT);
lock_init (&ls.lock);
sema_init (&ls.sema, 0);
thread_create ("low", PRI_DEFAULT + 1, l_thread_func, &ls);
thread_create ("med", PRI_DEFAULT + 3, m_thread_func, &ls);
thread_create ("high", PRI_DEFAULT + 5, h_thread_func, &ls);
sema_up (&ls.sema);
msg ("Main thread finished.");
}
这里当前线程有一个锁, 然后创建acquire抢占式获取了这个锁阻塞, 然后此时original_thread优先级为PRI_DEFAULT+10, 然后这里调用thread_set_priority, 此时函数过后优先级还是PRI_DEFAULT+10, 然后释放掉锁, acquire抢占输出和释放, 然后original_thread的优先级应该变成了PRI_DEFAULT-10。也就是说修改一个被捐赠的线程的优先级时,应该修改其原本的优先级,所以需要一个base_priority成员来记录其原本的优先级。
void
test_priority_donate_lower (void)
{
struct lock lock;
/* This test does not work with the MLFQS. */
ASSERT (!thread_mlfqs);
/* Make sure our priority is the default. */
ASSERT (thread_get_priority () == PRI_DEFAULT);
lock_init (&lock);
lock_acquire (&lock);
thread_create ("acquire", PRI_DEFAULT + 10, acquire_thread_func, &lock);
msg ("Main thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 10, thread_get_priority ());
msg ("Lowering base priority...");
thread_set_priority (PRI_DEFAULT - 10);
msg ("Main thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT + 10, thread_get_priority ());
lock_release (&lock);
msg ("acquire must already have finished.");
msg ("Main thread should have priority %d. Actual priority: %d.",
PRI_DEFAULT - 10, thread_get_priority ());
}
void
test_priority_sema (void)
{
int i;
/* This test does not work with the MLFQS. */
ASSERT (!thread_mlfqs);
sema_init (&sema, 0);
thread_set_priority (PRI_MIN);
for (i = 0; i < 10; i++)
{
int priority = PRI_DEFAULT - (i + 3) % 10 - 1;
char name[16];
snprintf (name, sizeof name, "priority %d", priority);
thread_create (name, priority, priority_sema_thread, NULL);
}
for (i = 0; i < 10; i++)
{
sema_up (&sema);
msg ("Back in main thread.");
}
}
static void
priority_sema_thread (void *aux UNUSED)
{
sema_down (&sema);
msg ("Thread %s woke up.", thread_name ());
}
这里创建10个线程阻塞于P操作, 然后用一个循环V操作, 然后看结果:
use strict;
use warnings;
use tests::tests;
check_expected ([<<‘EOF’]);
(priority-sema) begin
(priority-sema) Thread priority 30 woke up.
(priority-sema) Back in main thread.
(priority-sema) Thread priority 29 woke up.
(priority-sema) Back in main thread.
(priority-sema) Thread priority 28 woke up.
(priority-sema) Back in main thread.
(priority-sema) Thread priority 27 woke up.
(priority-sema) Back in main thread.
(priority-sema) Thread priority 26 woke up.
(priority-sema) Back in main thread.
(priority-sema) Thread priority 25 woke up.
(priority-sema) Back in main thread.
(priority-sema) Thread priority 24 woke up.
(priority-sema) Back in main thread.
(priority-sema) Thread priority 23 woke up.
(priority-sema) Back in main thread.
(priority-sema) Thread priority 22 woke up.
(priority-sema) Back in main thread.
(priority-sema) Thread priority 21 woke up.
(priority-sema) Back in main thread.
(priority-sema) end
EOF
pass;
也就是说V唤醒的时候也是优先级高的先唤醒, 换句话说, 信号量的等待队列是优先级队列。
###实验思路
condition里面装的是一个waiters队列, 看一下con_wait和cond_signal函数:
cond_wait (struct condition *cond, struct lock *lock)
{
struct semaphore_elem waiter;
ASSERT (cond != NULL);
ASSERT (lock != NULL);
ASSERT (!intr_context ());
ASSERT (lock_held_by_current_thread (lock));
sema_init (&waiter.semaphore, 0);
list_push_back (&cond->waiters, &waiter.elem);
lock_release (lock);
sema_down (&waiter.semaphore);
lock_acquire (lock);
}
cond_signal (struct condition *cond, struct lock *lock UNUSED)
{
ASSERT (cond != NULL);
ASSERT (lock != NULL);
ASSERT (!intr_context ());
ASSERT (lock_held_by_current_thread (lock));
if (!list_empty (&cond->waiters))
sema_up (&list_entry (list_pop_front (&cond->waiters),
struct semaphore_elem, elem)->semaphore);
}
cond_wait和cond_signal就是释放掉锁, 等待signal唤醒, 然后再重新获取锁。
这里的代码逻辑是: 创建10个线程, 每个线程调用的时候获取锁, 然后调用cond_wait把锁释放阻塞于cond_signal唤醒, 然后连续10次循环调用cond_signal。
use strict;
use warnings;
use tests::tests;
check_expected ([<<‘EOF’]);
(priority-condvar) begin
(priority-condvar) Thread priority 23 starting.
(priority-condvar) Thread priority 22 starting.
(priority-condvar) Thread priority 21 starting.
(priority-condvar) Thread priority 30 starting.
(priority-condvar) Thread priority 29 starting.
(priority-condvar) Thread priority 28 starting.
(priority-condvar) Thread priority 27 starting.
(priority-condvar) Thread priority 26 starting.
(priority-condvar) Thread priority 25 starting.
(priority-condvar) Thread priority 24 starting.
(priority-condvar) Signaling…
(priority-condvar) Thread priority 30 woke up.
(priority-condvar) Signaling…
(priority-condvar) Thread priority 29 woke up.
(priority-condvar) Signaling…
(priority-condvar) Thread priority 28 woke up.
(priority-condvar) Signaling…
(priority-condvar) Thread priority 27 woke up.
(priority-condvar) Signaling…
(priority-condvar) Thread priority 26 woke up.
(priority-condvar) Signaling…
(priority-condvar) Thread priority 25 woke up.
(priority-condvar) Signaling…
(priority-condvar) Thread priority 24 woke up.
(priority-condvar) Signaling…
(priority-condvar) Thread priority 23 woke up.
(priority-condvar) Signaling…
(priority-condvar) Thread priority 22 woke up.
(priority-condvar) Signaling…
(priority-condvar) Thread priority 21 woke up.
(priority-condvar) end
EOF
pass;
从结果来看, 这里要求的实质就是: condition的waiters队列是优先级队列
##priority-donate-chain
1. 首先lock_pair是包含两个lock指针的结构体, 然后将当前线程优先级设为PRI_MIN, 然后这里有个locks数组, 容量为7, 然后lock_pairs数组用来装lock_pair, 容量也是7。
2. 然后当前线程获取locks[0]这个锁, 接着跳到7次循环里, 每次循环thread_priority为PRI_MIN+i*3, 也就是3,6,9,12... 然后对应的lock_pairs[i]的first记录locks[i]的指针, second记录locks[i-1]指针,
3. 然后创建线程, 优先级为thread_priority, 执行参数传的是&lock_pairs[i], 这里由于优先级每次都底层, 所以每次循环都会抢占调用donor_thread_func, 然后分别获取lock_pairs[i]里装的锁, 然后每次循环先获取first, 即locks[i], 然后获取second, 由于second是前一个, 而前一个的拥有者一定是前一次循环创建的线程, 第一次拿得的是locks[0], 最后一次循环first为NULL, second为locks[6], 即最后一个线程不拥有锁, 但是阻塞于前一个创建的线程, 这里还会输出信息, 即创建的线程阻塞之后会输出当前线程的优先级msg, 所以这里必然是每一次都提升了的, 所以每次都是thread_priority。
4. 然后每次循环最后还创建了1个线程, 优先级为thread_priority-1, 但是这里由于上一个线程创建和阻塞的过程中优先级捐献已经发生, 所以这里并不发生抢占, 只是创建出来了而已。
5. 然后original_thread释放掉locks[0], 释放掉这个之后thread1得到了唤醒, 输出信息, 释放掉这个锁, 然后输出当前优先级, 由于这个线程还是被后面最高优先级的线程说捐赠的, 所以每次往后优先级都是21, 然后释放掉first, 这里又触发下一个线程继续跑, 注意当后面的全部跑完的时候当前线程的优先级其实是不被捐赠的, 这里就变成了原来的优先级, 但是是所有线程都释放了之后才依次返回输出结束msg。
实现多级反馈队列调度程序,减少在系统上运行作业的平均响应时间。这里维持了64个队列, 每个队列对应一个优先级, 从PRI_MIN到PRI_MAX。通过一些公式计算来计算出线程当前的优先级, 系统调度的时候会从高优先级队列开始选择线程执行, 这里线程的优先级随着操作系统的运转数据而动态改变。
每个线程有一个整数 nice值,用于确定线程对其他线程的友好程度。其取值范围为20 到 -20,越是友好越倾向于降低自己的优先级、CPU使用时间供其它线程使用。
初始进程 nice 值为0,子进程的nice值继承于父进程的nice值。
int thread_get_nice(void)
返回当前线程的nice值。
void thread_set_nice(int new_nice)
将当前线程的nice值设置为new_nice,并根据新值重新计算线程的优先级
// thread结构体加入 nice
Void thread_set_nice (int new_nice UNUSED) {
enum intr_level old_level = intr_disable ();
thread_current()->nice = new_nice;
mlfqs_priority(thread_current());
test_max_priority();
intr_set_level (old_level);
}
Int thread_get_nice (void) {
enum intr_level old_level = intr_disable ();
int result = thread_current()->nice;
intr_set_level (old_level);
return result;
}
用于估计准备在过去一分钟内运行的平均线程数
load_avg =(59/60)* load_avg +(1/60)* ready_threads
int thread_get_load_avg(void)
返回当前系统负载平均值的100倍,四舍五入
// 添加全局变量 load_avg
int thread_get_load_avg (void) {
enum intr_level old_level = intr_disable ();
int result = fp_round(fp_mult_mixed(load_avg, 100) );
intr_set_level (old_level);
return result;
}
每个进程最近使用CPU的时间。
recent_cpu =(2 * load_avg)/(2 * load_avg + 1)* recent_cpu + nice
int thread_get_recent_cpu(void)
返回当前线程的recent_cpu值的100倍,四舍五入
int thread_get_recent_cpu (void) {
enum intr_level old_level = intr_disable ();
int result = fp_to_int_round( mult_mixed(thread_current()->recent_cpu, 100) );
intr_set_level (old_level);
return result;
}
调度程序有64个优先级,拥有64个就绪队列。
priority = PRI_MAX- (recent_cpu / 4) - (nice * 2)
线程优先级最初在线程初始化时计算。对于每个线程,每四个时钟周期也会重新计算一次。
if (thread_mlfqs) {
mlfqs_recent_cpu_increase();
if (ticks % TIMER_FREQ == 0) {
mlfqs_update_load_avg_and_recent_cpu ();
}
if (ticks % RECALC_FREQ == 0) {
mlfqs_update_priority(thread_current());
}
}
当前活跃进程自增1。
Void mlfqs_recent_cpu_increase {
ASSERT (thread_mlfqs);
ASSERT (intr_context ());
struct thread *t = thread_current ();
if (t != idle_thread)
t->recent_cpu = FP_ADD_MIX (t->recent_cpu, 1);
}
每1秒执行1次,对上述值进行更新。
void mlfqs_update_load_avg_and_recent_cpu {
ASSERT (thread_mlfqs);
ASSERT (intr_context ());
int size = list_size(&ready_list);
if (thread_current() != idle_thread) {
size++;
}
load_avg = add_fp(mult_fp(div_mixed(int_to_fp(59), 60), load_avg), div_mixed(int_to_fp(size, 60));
struct list_elem *e;
for (e = list_begin(&all_list); e != list_end(&all_list); e = list_next(e)) {
struct thread *t = list_entry(e, struct thread, allelem);
t->recent_cpu = add_mixed(mult_fp(div_fp(mult_mixed(load_avg, 2), add_mixed(mult_mixed(load_avg, 2), 1) ), t->recent_cpu), t->nice);
}
}
每4个时钟周期更新1次。
void mlfqs_ update_priority (struct thread *t) {
if (t == idle_thread) {
return;
}
t->priority = fp_to_int(sub_mixed(sub_fp(int_to_fp(PRI_MAX), div_mixed( t->recent_cpu, 4)), 2*t->nice));
if (t->priority < PRI_MIN) {
t->priority = PRI_MIN;
}
else if (t->priority > PRI_MAX) {
t->priority = PRI_MAX;
}
}
使用的函数实现,单独放在一个文件中执行。
定点小数和定点小数的运算和普通操作没有区别。
定点小数和整型混合运算时需要进行转化,利用c语言中的位移运算<<和>>来进行转化,具体位移数和定点小数的小数部分占比有关系。
如将定点小数转化为整数的操作,其实是一个将数值本身二进制缩小的过程。
#define F (1 << 14)
int fp_to_int(int x) {
return x / F;
}
而混合的类型相加就可以调用转化函数进行一步操作
int add_mixed(int x, int n) {
return x + int_to_fp(n);
}