既然是依據優先級運行線程,那我們就來看看優先級在線程中是怎么存在的
1 struct thread 2 { 3 /* Owned by thread.c. */ 4 tid_t tid; /* Thread identifier. */ 5 enum thread_status status; /* Thread state. */ 6 char name[16]; /* Name (for debugging purposes). */ 7 uint8_t *stack; /* Saved stack pointer. */ 8 int priority; /* Priority. */ 9 struct list_elem allelem; /* List element for all threads list. */ 10 11 /* Shared between thread.c and synch.c. */ 12 struct list_elem elem; /* List element. */ 13 14 #ifdef USERPROG 15 /* Owned by userprog/process.c. */ 16 uint32_t *pagedir; /* Page directory. */ 17 #endif 18 19 20 int64_t sleep_ticks; /* # of timer ticks since blocked. */ 21 22 23 /* Owned by thread.c. */ 24 unsigned magic; /* Detects stack overflow. */ 25 };
在線程結構體里面有優先級這一成員,簡簡單單一個整型數據,那就不需要我們去定義了,教員講了眾多線程的組織形式是通用鏈表,當一個線程所有資源都准備好了之后就會放到一個就緒隊列里面,當CUP空出來的時候,就會從就緒隊列里面取一個來運行,至於取哪一個就要看定義的策略了
我們先來看看pintos現在是如何實現或者說如何維護就緒隊列的
參考了前輩的經驗得知在兩個地方會涉及到將准備好的線程插入到就緒隊列里面去:thread_unblock和thread_yield
首先來看thread_unblock
1 void 2 thread_unblock (struct thread *t) //解除阻塞態,線程A調用來解除B的阻塞態 3 { 4 enum intr_level old_level; 5 ASSERT (is_thread (t)); 6 old_level = intr_disable (); //關中斷 7 ASSERT (t->status == THREAD_BLOCKED); 8 list_push_back (&ready_list, &t->elem); //放在就緒隊列尾部 9 t->status = THREAD_READY; 10 intr_set_level (old_level); //開中斷 11 }
thread_unblock會把被阻塞的線程解除阻塞態,然后放到就緒隊列里面去等待CPU運行,我們跟進list_push_back看看具體是怎么放的
1 void 2 list_push_back (struct list *list, struct list_elem *elem) 3 { 4 list_insert (list_end (list), elem); 5 }
繼續跟進
1 void 2 list_insert (struct list_elem *before, struct list_elem *elem) 3 { 4 ASSERT (is_interior (before) || is_tail (before)); 5 ASSERT (elem != NULL); 6 7 elem->prev = before->prev; //依據通用鏈表連接規則,將elem插入到before前面 8 elem->next = before; //即把他作為鏈表的最后一個元素 9 before->prev->next = elem; 10 before->prev = elem; 11 } 12 13 struct list_elem * //即取鏈表的尾部tail 14 list_end (struct list *list) 15 { 16 ASSERT (list != NULL); 17 return &list->tail; 18 }
分析下來,線程解除阻塞之后直接被放到了就緒隊列的末尾
再看thread_yield,發現做法如出一轍
1 void 2 thread_yield (void) //讓出CPU 3 { 4 struct thread *cur = thread_current (); //獲取當前運行的線程 5 enum intr_level old_level; 6 7 ASSERT (!intr_context ()); 8 9 old_level = intr_disable (); //關中斷 10 if (cur != idle_thread) 11 list_push_back (&ready_list, &cur->elem); //放在就緒隊列尾部 12 cur->status = THREAD_READY; //就緒態 13 schedule (); //調度,從就緒隊列里面取一個 14 intr_set_level (old_level); 15 }
接着看pintos是如何從就緒隊列里面取出線程的
找來找去,只有發現schedule才會調度新的線程讓CPU運行
1 static void 2 schedule (void) 3 { 4 struct thread *cur = running_thread (); //調用schedule之前被阻塞的線程 5 struct thread *next = next_thread_to_run (); //下一個要運行的線程 6 struct thread *prev = NULL; 7 8 ASSERT (intr_get_level () == INTR_OFF); 9 ASSERT (cur->status != THREAD_RUNNING); 10 ASSERT (is_thread (next)); 11 12 if (cur != next) 13 prev = switch_threads (cur, next); //線程切換 14 thread_schedule_tail (prev); //殺死線程 15 }
不難發現取線程的關鍵在於next_thread_to_run,跟進看一下
1 static struct thread * 2 next_thread_to_run (void) 3 { 4 if (list_empty (&ready_list)) 5 return idle_thread; 6 else 7 return list_entry (list_pop_front (&ready_list), struct thread, elem); 8 /*當就緒隊列非空時使用宏定義(list_entry)取出其中的第一個線程*/ 9 } 10 11 struct list_elem * 12 list_pop_front (struct list *list) 13 { 14 struct list_elem *front = list_front (list); //取list里面的第一個元素 15 list_remove (front); //刪除list的第一個元素 16 return front; //第一個元素作為返回值 17 }
到這里基本上可以形成兩種思路:
- 在放入隊列時即按照優先級的順序插入,取的時候從頭開始
- 放的時候直接放在尾巴上即可,取得時候拿優先級最高的
個人認為實現后者較為簡單方便,這里就先實現第二種思路,主要對next_thread_to_run做手腳,將取第一個元素修改為取優先級最高的,即將lisi_pop_front修改為list_max_priority
在實踐過程中,翻閱list.c里面的函數,發現了一個函數list_max
1 /* Returns the element in LIST with the largest value according 2 to LESS given auxiliary data AUX. If there is more than one 3 maximum, returns the one that appears earlier in the list. If 4 the list is empty, returns its tail. */ 5 struct list_elem * 6 list_max (struct list *list, list_less_func *less, void *aux) 7 { 8 struct list_elem *max = list_begin (list); 9 if (max != list_end (list)) 10 { 11 struct list_elem *e; 12 13 for (e = list_next (max); e != list_end (list); e = list_next (e)) 14 if (less (max, e, aux)) 15 max = e; 16 } 17 return max; 18 }
這就很nice了,先看less是什么
1 /* Compares the value of two list elements A and B, given 2 auxiliary data AUX. Returns true if A is less than B, or 3 false if A is greater than or equal to B. */ 4 typedef bool list_less_func (const struct list_elem *a, 5 const struct list_elem *b, 6 void *aux);
如果a<b,則返回true,那么list_max函數功能即遍歷list,找出最不less的,也就是for循環里面的if,如果max<e,則max=e,注意這里的<不能帶=,因為這里的max和e都是鏈表元素,而我們要比較的是線程的優先級,就需要我們通過線程的elem找到其結構體成員priority,我這里借用了一下laiy的優先級比較函數,返回值和less同步,即a<b時返回true
1 /* priority compare function. */ 2 bool 3 thread_cmp_priority (const struct list_elem *a, const struct list_elem *b, void *aux UNUSED) 4 { 5 return list_entry(a, struct thread, elem)->priority <
list_entry(b, struct thread, elem)->priority; 6 }
直接修改next_thread_to_run,並在thread.c中引用了頭文件list.h
1 static struct thread * 2 next_thread_to_run (void) 3 { 4 if (list_empty (&ready_list)) 5 return idle_thread; 6 else 7 return list_entry (list_max (&ready_list,thread_cmp_priority,NULL), struct thread, elem); 8 }
完成后直接運行測試,發現測試陷入了死循環,久久不能跳出,對比list_max和list_pop_front發現,list_max在返回之前少了一句list_remove,這就使得就緒的線程運行完之后並不會被刪掉,從而在下一輪繼續運行,使測試程序陷入死循環,為了不影響整體代碼的可擴展性,我這里手動復制了一下list_max,並更名為list_max_priority,直接放在next_thread_to_run上面
1 struct list_elem * 2 list_max_priority (struct list *list, list_less_func *less, void *aux) 3 { 4 struct list_elem *max = list_begin (list); 5 if (max != list_end (list)) 6 { 7 struct list_elem *e; 8 9 for (e = list_next(max); e != list_end (list); e = list_next(e)) 10 if (less (max, e, aux)) 11 max = e; 12 } 13 list_remove (max); 14 return max; 15 }
將list_max_priority和thread_cmp_priority的聲明加在thread.c的頭上
1 struct list_elem *list_max_priority (struct list *, list_less_func *, void *aux); 2 bool thread_cmp_priority (const struct list_elem *, const struct list_elem *, void *aux);
保存,測試,通過alarm-priority
至此,我們就有了一個利器,即切換線程時按照優先級降序執行,現在來分析priority-change,pintos里面的函數實現為thread-set-priority
1 /* Sets the current thread's priority to NEW_PRIORITY. */ 2 void 3 thread_set_priority (int new_priority) 4 { 5 thread_current ()->priority = new_priority; 6 }
這里就一個動作,即該調用函數后將當前線程的優先級修改為新的優先級,這時就牽扯到優先級高低和程序執行的問題了,如果修改后的優先級仍為就緒隊列里面的第一名(或是並列第一),則繼續運行該進程;反之,則調度新的第一名運行,而當前進程被放入就緒隊列,此時自然而然想到函數thread_yield
1 void 2 thread_set_priority (int new_priority) 3 { 4 thread_current ()->priority = new_priority; 5 thread_yield (); 6 }
prioirty-preempt,優先級搶占式調度,即當創建一個新的進程時,如果新進程的優先級高於當前正在運行進程的優先級,則當前線程讓出CPU,讓新線程運行,那么我們只需要在創建線程之后加入一個條件語句即可,加在thread_unblock之后
1 if (thread_current ()->priority < priority) //搶占 2 thread_yield (); 3 return tid;
保存,測試,通過priority-change和priority-preempt
接下來是priority-sema,先來看看sema_down
1 void 2 sema_down (struct semaphore *sema) 3 { 4 enum intr_level old_level; 5 6 ASSERT (sema != NULL); 7 ASSERT (!intr_context ()); 8 9 old_level = intr_disable (); 10 while (sema->value == 0) //注意用while而不用if的原因 11 { 12 list_push_back (&sema->waiters, &thread_current ()->elem); //如果沒資源了,就把當前線程放在等待隊列的末尾 13 thread_block (); //阻塞該進程,被喚醒之后,繼續從這里開始執行 14 } 15 sema->value--; 16 intr_set_level (old_level); 17 }
接着看看sema_up
1 void 2 sema_up (struct semaphore *sema) 3 { 4 enum intr_level old_level; 5 6 ASSERT (sema != NULL); 7 8 old_level = intr_disable (); //關中斷 9 if (!list_empty (&sema->waiters)) //如果有進程在等待 10 thread_unblock (list_entry (list_pop_front (&sema->waiters), //則從等待隊列里面去第一個放入就緒隊列里面去 11 struct thread, elem)); //等待調度 12 sema->value++; 13 intr_set_level (old_level); 14 }
思路應該已經很清晰了,從等待隊列里面取是應該拿優先級最大的,放入就緒隊列之后應進行優先級調度,只需修改sema_up即可
1 void 2 sema_up (struct semaphore *sema) 3 { 4 enum intr_level old_level; 5 6 ASSERT (sema != NULL); 7 8 old_level = intr_disable (); 9 if (!list_empty (&sema->waiters)) 10 thread_unblock (list_entry (list_max_priority ((&sema->waiters),thread_cmp_priority,NULL), 11 struct thread, elem)); 12 sema->value++; 13 thread_yield(); 14 intr_set_level (old_level); 15 }
測試,通過!
至此,4項實驗內容均已完成
