Merge branch 'task1/priority-donation' into 'master'

Merge 'task1/priority-donation' into 'master'

See merge request lab2425_autumn/pintos_22!14
This commit is contained in:
Dias Alberto, Ethan
2024-10-23 16:15:44 +00:00
4 changed files with 276 additions and 16 deletions

7
.gitignore vendored
View File

@@ -4,6 +4,13 @@
#ignore pdf files (just keep source files) #ignore pdf files (just keep source files)
*.pdf *.pdf
#ignore Mac OS generated files
.DS_Store
#ignore code editor generated directories
.idea
.vscode
#ignore junk files from latex output #ignore junk files from latex output
*.out *.out
*.log *.log

View File

@@ -106,18 +106,31 @@ sema_try_down (struct semaphore *sema)
This function may be called from an interrupt handler. */ This function may be called from an interrupt handler. */
void void
sema_up (struct semaphore *sema) sema_up (struct semaphore *sema)
{ {
enum intr_level old_level; enum intr_level old_level;
ASSERT (sema != NULL); ASSERT (sema != NULL);
old_level = intr_disable (); old_level = intr_disable ();
if (!list_empty (&sema->waiters)) if (!list_empty (&sema->waiters))
thread_unblock (list_entry (list_pop_front (&sema->waiters), {
struct thread, elem)); /* Enforces wake-up of the highest priority thread waiting for the
semaphore. */
struct list_elem *e = list_min (&sema->waiters, priority_more, NULL);
list_remove (e);
thread_unblock (list_entry (e, struct thread, elem));
}
sema->value++; sema->value++;
intr_set_level (old_level); intr_set_level (old_level);
/* Yields the CPU in case the thread that has been woken up has a higher
priority that the current running thread, including the case when called
within an interrupt handler. */
if (intr_context ())
intr_yield_on_return ();
else
thread_yield ();
} }
static void sema_test_helper (void *sema_); static void sema_test_helper (void *sema_);
@@ -181,6 +194,47 @@ lock_init (struct lock *lock)
sema_init (&lock->semaphore, 1); sema_init (&lock->semaphore, 1);
} }
/* Current thread donates its priority to donee, iteratively
propagating the donation in the case of chains in the wait-for graph.
Also keeps track of the donation by updating the donors list. Expects
interrupts to be disabled. */
static void
donate_priority (struct thread *donee) {
ASSERT (intr_get_level () == INTR_OFF);
struct thread *donor = thread_current ();
list_push_back (&donee->donors_list, &donor->donor_elem);
while (donee != NULL)
{
/* Stop propagation of donation once a donee is reached that has
a higher effective priority (as its donees can't have less
priority than that being donated). */
if (donor->priority <= donee->priority)
break;
/* Also stop propagation of donation once a donee is reached with
no donees of its own (sink node in WFG). */
if (donee->waiting_lock == NULL)
{
/* Only the sink node of the WFG isn't waiting for a lock and
could be on the ready list. Thus, as its priority changed,
it must be reinserted into the list. */
enum intr_level old_level = intr_disable ();
donee->priority = donor->priority;
ready_list_reinsert (donee);
intr_set_level (old_level);
donee = NULL;
}
else
{
donee->priority = donor->priority;
donee = donee->waiting_lock->holder;
}
}
}
/* Acquires LOCK, sleeping until it becomes available if /* Acquires LOCK, sleeping until it becomes available if
necessary. The lock must not already be held by the current necessary. The lock must not already be held by the current
thread. thread.
@@ -196,8 +250,20 @@ lock_acquire (struct lock *lock)
ASSERT (!intr_context ()); ASSERT (!intr_context ());
ASSERT (!lock_held_by_current_thread (lock)); ASSERT (!lock_held_by_current_thread (lock));
struct thread *t = thread_current ();
enum intr_level old_level = intr_disable ();
if (lock->holder != NULL)
{
t->waiting_lock = lock;
donate_priority (lock->holder);
}
intr_set_level (old_level);
sema_down (&lock->semaphore); sema_down (&lock->semaphore);
lock->holder = thread_current (); lock->holder = thread_current ();
t->waiting_lock = NULL;
} }
/* Tries to acquires LOCK and returns true if successful or false /* Tries to acquires LOCK and returns true if successful or false
@@ -231,6 +297,51 @@ lock_release (struct lock *lock)
ASSERT (lock != NULL); ASSERT (lock != NULL);
ASSERT (lock_held_by_current_thread (lock)); ASSERT (lock_held_by_current_thread (lock));
struct thread *current_thread = thread_current ();
struct thread *max_donor = NULL;
struct list orphan_list;
list_init (&orphan_list);
enum intr_level old_level = intr_disable ();
/* Loop through current thread's donors, removing the ones waiting for the
lock being released and keeping track of them (within orphan_list).
Also identifies the highest priority donor thread among them. */
struct list_elem *tail = list_tail (&current_thread->donors_list);
struct list_elem *e = list_begin (&current_thread->donors_list);
while (e != tail)
{
struct thread *donor = list_entry (e, struct thread, donor_elem);
struct list_elem *next = list_next (e);
/* Excludes donors that aren't waiting for the lock being released,
and tracks the rest. */
if (donor->waiting_lock == lock)
{
list_remove (e);
list_push_back (&orphan_list, e);
/* Identify highest priority donor. */
if (max_donor == NULL || donor->priority > max_donor->priority)
max_donor = donor;
}
e = next;
}
/* If there exists a maximum donor thread waiting for this lock to be
released, transfer the remaining orphaned donors to its donor list. */
if (max_donor != NULL)
{
while (!list_empty (&orphan_list))
list_push_back (&max_donor->donors_list, list_pop_front (&orphan_list));
}
intr_set_level (old_level);
/* Removal of donors to this thread may change its effective priority,
so recalculate. */
thread_recalculate_priority ();
lock->holder = NULL; lock->holder = NULL;
sema_up (&lock->semaphore); sema_up (&lock->semaphore);
} }
@@ -253,6 +364,36 @@ struct semaphore_elem
struct semaphore semaphore; /* This semaphore. */ struct semaphore semaphore; /* This semaphore. */
}; };
/* Function that compares the two *semaphores* associated with the provided
list_elem structures. [i.e., takes list_elem of semaphore_elem, and]
Returns true if the thread associated with the semaphore associated with a_
has a higher priority than that of b_.
If aux is provided, then it is a pointer to an integer representing the
priority of the first semaphore. This is useful when the thread has not been
sema'd down yet. */
static bool
sema_priority_more(const struct list_elem *a, const struct list_elem *b,
void *inserting_telem)
{
struct list_elem *te_a, *te_b;
te_b = list_front (
&list_entry (b, struct semaphore_elem, elem)->semaphore.waiters);
if (inserting_telem == NULL)
{
te_a = list_front (
&list_entry (a, struct semaphore_elem, elem)->semaphore.waiters);
}
else
{
te_a = inserting_telem;
}
return priority_more (te_a, te_b, NULL);
}
/* Initializes condition variable COND. A condition variable /* Initializes condition variable COND. A condition variable
allows one piece of code to signal a condition and cooperating allows one piece of code to signal a condition and cooperating
code to receive the signal and act upon it. */ code to receive the signal and act upon it. */
@@ -316,9 +457,14 @@ cond_signal (struct condition *cond, struct lock *lock UNUSED)
ASSERT (!intr_context ()); ASSERT (!intr_context ());
ASSERT (lock_held_by_current_thread (lock)); ASSERT (lock_held_by_current_thread (lock));
if (!list_empty (&cond->waiters)) if (!list_empty (&cond->waiters))
sema_up (&list_entry (list_pop_front (&cond->waiters), {
struct semaphore_elem, elem)->semaphore); /* Enforce wake-up of highest priority thread within the singleton
semaphores waiting for condvar. */
struct list_elem *e = list_min (&cond->waiters, sema_priority_more, NULL);
list_remove (e);
sema_up (&list_entry (e, struct semaphore_elem, elem)->semaphore);
}
} }
/* Wakes up all threads, if any, waiting on COND (protected by /* Wakes up all threads, if any, waiting on COND (protected by

View File

@@ -70,6 +70,8 @@ static void *alloc_frame (struct thread *, size_t size);
static void schedule (void); static void schedule (void);
void thread_schedule_tail (struct thread *prev); void thread_schedule_tail (struct thread *prev);
static tid_t allocate_tid (void); static tid_t allocate_tid (void);
static bool donor_priority_less (const struct list_elem *a_,
const struct list_elem *b_, void *aux UNUSED);
/* Initializes the threading system by transforming the code /* Initializes the threading system by transforming the code
that's currently running into a thread. This can't work in that's currently running into a thread. This can't work in
@@ -219,6 +221,7 @@ thread_create (const char *name, int priority,
/* Add to run queue. */ /* Add to run queue. */
thread_unblock (t); thread_unblock (t);
thread_yield ();
return tid; return tid;
} }
@@ -256,7 +259,10 @@ thread_unblock (struct thread *t)
old_level = intr_disable (); old_level = intr_disable ();
ASSERT (t->status == THREAD_BLOCKED); ASSERT (t->status == THREAD_BLOCKED);
list_push_back (&ready_list, &t->elem);
/* Insert the thread back into the ready list in priority order. */
list_insert_ordered(&ready_list, &t->elem, priority_more, NULL);
t->status = THREAD_READY; t->status = THREAD_READY;
intr_set_level (old_level); intr_set_level (old_level);
} }
@@ -326,8 +332,11 @@ thread_yield (void)
ASSERT (!intr_context ()); ASSERT (!intr_context ());
old_level = intr_disable (); old_level = intr_disable ();
if (cur != idle_thread)
list_push_back (&ready_list, &cur->elem); /* Insert the thread back into the ready list in priority order. */
if (cur != idle_thread)
list_insert_ordered(&ready_list, &cur->elem, priority_more, NULL);
cur->status = THREAD_READY; cur->status = THREAD_READY;
schedule (); schedule ();
intr_set_level (old_level); intr_set_level (old_level);
@@ -350,20 +359,84 @@ thread_foreach (thread_action_func *func, void *aux)
} }
} }
/* Sets the current thread's priority to NEW_PRIORITY. */ /* Function that compares the two threads associated with the provided
void pointers to their 'elem' member. Returns true if the thread associated
thread_set_priority (int new_priority) with a_ has a higher priority than that of b_. */
bool
priority_more (const struct list_elem *a_, const struct list_elem *b_,
void *aux UNUSED)
{ {
thread_current ()->priority = new_priority; struct thread *a = list_entry (a_, struct thread, elem);
struct thread *b = list_entry (b_, struct thread, elem);
return a->priority > b->priority;
} }
/* Returns the current thread's priority. */ /* Function that compares the two threads associated with the provided
pointers to their 'donor_elem' member. Returns true if the thread associated
with a_ has a lower priority than that of b_. */
static bool
donor_priority_less (const struct list_elem *a_, const struct list_elem *b_,
void *aux UNUSED)
{
struct thread *a = list_entry (a_, struct thread, donor_elem);
struct thread *b = list_entry (b_, struct thread, donor_elem);
return a->priority < b->priority;
}
/* Sets the current thread's base priority to new_base_priority.
Updates the current thread's effective priority if necessary. */
void
thread_set_priority (int new_base_priority)
{
ASSERT (new_base_priority >= PRI_MIN);
ASSERT (new_base_priority <= PRI_MAX);
struct thread *t = thread_current ();
/* If the base priority is unchanged, do nothing. */
if (new_base_priority == t->base_priority)
return;
t->base_priority = new_base_priority;
thread_recalculate_priority ();
thread_yield ();
}
/* Returns the current thread's effective priority. */
int int
thread_get_priority (void) thread_get_priority (void)
{ {
return thread_current ()->priority; return thread_current ()->priority;
} }
/* Recalculates the effective priority of the current thread. */
void
thread_recalculate_priority (void)
{
struct thread *t = thread_current ();
enum intr_level old_level = intr_disable ();
t->priority = t->base_priority;
/* If there are no donors to the current thread, then the effective
priority is just the base priority. */
if (!list_empty (&t->donors_list))
{
int max_donated_priority =
list_entry (list_max (&t->donors_list, donor_priority_less, NULL),
struct thread, donor_elem)->priority;
/* The effective priority is the max donated priority if this is
higher than the base priority. */
if (max_donated_priority > t->priority)
t->priority = max_donated_priority;
}
intr_set_level (old_level);
}
/* Sets the current thread's nice value to NICE. */ /* Sets the current thread's nice value to NICE. */
void void
thread_set_nice (int nice UNUSED) thread_set_nice (int nice UNUSED)
@@ -395,6 +468,22 @@ thread_get_recent_cpu (void)
return 0; return 0;
} }
/* Reinsert thread t into the ready list at its correct position
in descending order of priority. Used when this thread's priority
may have changed. Must be called with interrupts disabled. */
void
ready_list_reinsert (struct thread *t)
{
ASSERT (intr_get_level () == INTR_OFF);
/* If the thread isn't ready to run, do nothing. */
if (t->status != THREAD_READY)
return;
list_remove (&t->elem);
list_insert_ordered (&ready_list, &t->elem, priority_more, NULL);
}
/* Idle thread. Executes when no other thread is ready to run. /* Idle thread. Executes when no other thread is ready to run.
The idle thread is initially put on the ready list by The idle thread is initially put on the ready list by
@@ -480,8 +569,12 @@ init_thread (struct thread *t, const char *name, int priority)
t->status = THREAD_BLOCKED; t->status = THREAD_BLOCKED;
strlcpy (t->name, name, sizeof t->name); strlcpy (t->name, name, sizeof t->name);
t->stack = (uint8_t *) t + PGSIZE; t->stack = (uint8_t *) t + PGSIZE;
t->priority = priority; t->base_priority = priority;
t->magic = THREAD_MAGIC; t->magic = THREAD_MAGIC;
list_init (&t->donors_list);
t->priority = t->base_priority;
t->waiting_lock = NULL;
old_level = intr_disable (); old_level = intr_disable ();
list_push_back (&all_list, &t->allelem); list_push_back (&all_list, &t->allelem);

View File

@@ -90,6 +90,15 @@ struct thread
int priority; /* Priority. */ int priority; /* Priority. */
struct list_elem allelem; /* List element for all threads list. */ struct list_elem allelem; /* List element for all threads list. */
/* Donation Related */
int base_priority; /* Base priority of the thread. */
struct list donors_list; /* List of threads that have donated
to this thread. */
struct lock *waiting_lock; /* The lock that the current thread is
waiting for. */
struct list_elem donor_elem; /* List element so that thread can be
enlisted in other donors list. */
/* Shared between thread.c and synch.c. */ /* Shared between thread.c and synch.c. */
struct list_elem elem; /* List element. */ struct list_elem elem; /* List element. */
@@ -131,12 +140,17 @@ void thread_yield (void);
typedef void thread_action_func (struct thread *t, void *aux); typedef void thread_action_func (struct thread *t, void *aux);
void thread_foreach (thread_action_func *, void *); void thread_foreach (thread_action_func *, void *);
bool priority_more (const struct list_elem *a_, const struct list_elem *b_,
void *aux UNUSED);
int thread_get_priority (void); int thread_get_priority (void);
void thread_set_priority (int); void thread_set_priority (int);
void thread_recalculate_priority (void);
int thread_get_nice (void); int thread_get_nice (void);
void thread_set_nice (int); void thread_set_nice (int);
int thread_get_recent_cpu (void); int thread_get_recent_cpu (void);
int thread_get_load_avg (void); int thread_get_load_avg (void);
void ready_list_reinsert (struct thread *t);
#endif /* threads/thread.h */ #endif /* threads/thread.h */