diff --git a/.gitignore b/.gitignore index 5d8d05c..8d2540f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,13 @@ #ignore pdf files (just keep source files) *.pdf +#ignore Mac OS generated files +.DS_Store + +#ignore code editor generated directories +.idea +.vscode + #ignore junk files from latex output *.out *.log diff --git a/src/threads/synch.c b/src/threads/synch.c index 7776cb0..1246206 100644 --- a/src/threads/synch.c +++ b/src/threads/synch.c @@ -106,18 +106,31 @@ sema_try_down (struct semaphore *sema) This function may be called from an interrupt handler. */ void -sema_up (struct semaphore *sema) +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)); + if (!list_empty (&sema->waiters)) + { + /* 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++; 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_); @@ -181,6 +194,47 @@ lock_init (struct lock *lock) 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 necessary. The lock must not already be held by the current thread. @@ -196,8 +250,20 @@ lock_acquire (struct lock *lock) ASSERT (!intr_context ()); 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); lock->holder = thread_current (); + t->waiting_lock = NULL; } /* 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_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 (¤t_thread->donors_list); + struct list_elem *e = list_begin (¤t_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; sema_up (&lock->semaphore); } @@ -253,6 +364,36 @@ struct semaphore_elem 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 allows one piece of code to signal a condition and cooperating 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 (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); + if (!list_empty (&cond->waiters)) + { + /* 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 diff --git a/src/threads/thread.c b/src/threads/thread.c index 30ca2bd..dd594d9 100644 --- a/src/threads/thread.c +++ b/src/threads/thread.c @@ -70,6 +70,8 @@ static void *alloc_frame (struct thread *, size_t size); static void schedule (void); void thread_schedule_tail (struct thread *prev); 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 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. */ thread_unblock (t); + thread_yield (); return tid; } @@ -256,7 +259,10 @@ thread_unblock (struct thread *t) old_level = intr_disable (); 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; intr_set_level (old_level); } @@ -326,8 +332,11 @@ thread_yield (void) ASSERT (!intr_context ()); 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; schedule (); 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. */ -void -thread_set_priority (int new_priority) +/* Function that compares the two threads associated with the provided + pointers to their 'elem' member. Returns true if the thread associated + 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 thread_get_priority (void) { 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. */ void thread_set_nice (int nice UNUSED) @@ -395,6 +468,22 @@ thread_get_recent_cpu (void) 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. 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; strlcpy (t->name, name, sizeof t->name); t->stack = (uint8_t *) t + PGSIZE; - t->priority = priority; + t->base_priority = priority; t->magic = THREAD_MAGIC; + + list_init (&t->donors_list); + t->priority = t->base_priority; + t->waiting_lock = NULL; old_level = intr_disable (); list_push_back (&all_list, &t->allelem); diff --git a/src/threads/thread.h b/src/threads/thread.h index f36d7ac..ffeb2b3 100644 --- a/src/threads/thread.h +++ b/src/threads/thread.h @@ -90,6 +90,15 @@ struct thread int priority; /* Priority. */ 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. */ struct list_elem elem; /* List element. */ @@ -131,12 +140,17 @@ void thread_yield (void); typedef void thread_action_func (struct thread *t, void *aux); 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); void thread_set_priority (int); +void thread_recalculate_priority (void); int thread_get_nice (void); void thread_set_nice (int); int thread_get_recent_cpu (void); int thread_get_load_avg (void); +void ready_list_reinsert (struct thread *t); + #endif /* threads/thread.h */