作業系統課程設計——pintos原始碼的分析與修改
Task one
今天來談第一個project 的第二個問題--優先順序排程問題。
一、內容介紹
現代作業系統,併發是基本特徵。當有多個執行緒在系統中執行時,一般是通過排隊的方式採用RR(時間片輪轉法)策略輪流佔用CPU的,其中最簡單,看似最公平的排程策略是FCFS,但是這種方法不能夠區分執行緒執行的輕重緩急,看似公平實則不公平,容易造成系統性能低下。因此,作業系統設計者通常會賦予執行緒優先順序,並採用高優先順序者先呼叫的策略,兼顧公平與效率。在原始的Pintos系統中關於優先順序排程沒有考慮優先順序問題,本實現的內容是為pintos建立優先順序排程機制,並確保在任何時刻在CPU上執行的都是最高優先順序執行緒。
二、分析與設計
首先回顧一下執行緒的狀態轉換,執行緒的常見狀態分為以下三種running,ready,block.在之前的作業系統課程中,我們已經學過基本的狀態轉換,如下。
CPU的排程是指從ready中選擇執行緒佔有CPU,狀態轉換是ready->run,原則是高優先順序的執行緒先於低優先順序的執行緒排程,所以當出現最高優先順序的執行緒可以搶佔CPU。這個修改核心思想分為兩個方面,一是.當新優先順序的執行緒執行緒出現時,要與原有的最高優先順序執行緒進行比較,判斷是否進行CPU搶佔,二是進行結果的處理,對於高優先順序執行緒,佔有CPU,對於低優先順序執行緒要有序插入ready_list
A.在判斷搶佔CPU時,於是出現兩種可能的情況:
(1)有新執行緒生成,執行緒的最後狀態是ready,此時系統中的最高優先順序執行緒是正在執行的執行緒,因此需要新執行緒與runnning執行緒進行優先順序比較。新執行緒的優先順序的高於當前正在執行的執行緒,則新的執行緒應該迫使當前的執行緒讓出CPU,在thread_yield()中會進行重新的執行緒排程,由於此時新執行緒優先順序最高,則必定排程新執行緒,即出現新執行緒搶佔CPU的現象。
(2)當前正在執行的執行緒優先順序改變,尤其指優先順序降低時,此時系統中的最高優先順序執行緒是ready_list的隊頭執行緒,(注意ready_list
分析如下圖示:
B.在進行結果處理時,出現了兩種可能情況。
(1)對於搶佔上CPU的執行緒,那麼自然就是在CPU上運行了,其狀態是running.我們不需要進行特殊改變。
(2) 對於沒有搶佔上CPU的執行緒,自然要放在ready_list中了。上文已經提到過,我們在進行放入時要進行有序放入ready_list中,原始碼中採用的是list_push_back(),此函式只是簡單的將執行緒放入隊首,不能滿足我們的要求。為了進行有序排放,我們可以選用list_insert_order()函式,通過比較優先順序,將執行緒插入到ready_list中的合適位置。
實現如下圖示功能:
三、詳細實現
Pintos系統中函式呼叫關係如下:
具體實現分以下幾種情況討論:
關於所用到的各種函式呼叫關係
1.有新生執行緒生成
thread_create(); //thread.c
分析:
在thread_creat()中,建立新執行緒的過程是:
init_thread()(設定執行緒的狀態是THREAD_BLOCKED,設定執行緒的初始優先順序,將此執行緒放入all_list佇列中))--->設定執行緒的上下文環境---> thread_unblock()(解阻塞該執行緒,放入ready_list佇列中)
狀態轉換是block->ready->running或者block->ready
屬於上述中A類中(1)的新生執行緒(新優先順序執行緒的第一種情況,系統建立新執行緒)
理論修改:
當新執行緒解阻塞之後放入reday_list中時,應該先比較該新執行緒的優先順序與當前CPU正在執行的執行緒的優先順序,是否發生CPU搶佔。對於結果需要分情況處理
原始碼修改:
.....
Thread_unblock();
/*************************************************************
//add
If(priority>thread_current()->prioriity) //if--搶佔CPU
Thread_yield();
//else--thread_unblock()中已經將其先放入ready_list隊中了,那麼此時不做處理
//************************************************************
........
1.當前執行緒自己改變優先順序
thread_set_priority() //thread.c
分析:
此函式是修改當前執行緒的優先順序,那麼當增高優先順序時,自然還是最高優先順序執行緒,不做處理,當優先順序下降時,可能不是系統中的最高優先順序,需要進行處理。
屬於上述中A類中(2)
狀態轉換是running或者running->block
理論修改:
在設定完新執行緒的優先順序之後,與ready_list隊頭的執行緒進行優先順序比較,是否讓出CPU,結果分情況處理。
原始碼修改:
Thread_current()->pirority=new_priority;
//***********************************************************************
//add
if(list_entry(list_begin(&ready_list),struct thread,elem)->priority >=new_priority)
thread_yield();
//**********************************************************************
此處對list_entry()函式做格外說明,函式原型為巨集定義,在list.h中可以找到;
我們一直提到的list佇列,包括ready_list,all_list或者waiters等,直觀意義上說是指的是執行緒佇列,那麼在list結構體中真的存的是執行緒體嗎,或者說list中的元素elem(也可稱為結點)是一個執行緒體嗎?答案是否定的,在list中的元素是struct list_elem,佇列成員是list_elem,那麼list_elem又是什麼呢?它與thread又有什麼聯絡呢?下面我們看一下thread的結構體定義:
struct thread
{
tid_t tid; /* Thread identifier. */
enum thread_status status; /* Thread state. */
char name[16]; /* Name (for debugging purposes). */
uint8_t *stack; /* Saved stack pointer. */
int priority; /* Priority. */
struct list_elem allelem; /* List element for all threads list. */
struct list_elem elem; /* List element. */
}
現在應該可以直接看出list_elem與thread的關係,每一個thread都會有一個專屬於自己的elem成員,這個elem成員用於作為各種佇列元素來代表自己,其實這裡我想到了關於健值的含義,個人感覺elem好像是thread的健值一樣。
分析了這麼多,list_entry()函式的作用到底是什麼,可以看到我們通過使用list_entry()函式得到了一個執行緒(list_entry(...)->priority),再看list_entry()的三個傳入引數list_begin(&ready_list),struct thread,elem,那麼我們是不是可以大膽猜想list_entry()函式通過elem在ready_list中查詢,找到了begin元素相應的thread.其實list_entry()函式的作用就是如此,不僅可以在ready_list中查詢,還可以在all_list,和waiters中查詢相應的實體結構。
2.關於結果處理情況
主要是在插入佇列中時,應該有序插入,所有關於插入情況討論如下:
所謂發生佇列插入,可以向ready_list或者waiter中加入新的元素,由上述轉換圖可以看出,分為兩大類情況
A.向ready_list中插入新的元素
(1)running->ready
可能發生在A-2中,當前執行緒的優先順序被超越,因此thread_yield(),在thread_yield()中已經將執行緒的狀態由running->ready,因此我們不需要改變。
(2)block->ready.
自然會想到unblock,即解阻塞,將執行緒狀態由阻塞變為reday,插入ready_list中,至於之後應該怎麼排程,那就不是unblock的工作了,unblock只負責狀態轉換一次,其實,最早考慮到要不要在unblock的函式中加入重新排程的函式,表面上看似乎是合理的,但是仔細分析,這樣做的話,其實你已經改變了原始碼中thread_unblock()的含義,此函式可以將執行緒的狀態由block變為ready!這樣做即使不影響現在的函式使用,但是有可能影響原始碼中其他關於thread_unblock的使用,你不經意改變了原始碼中函式的意思,必然與原始碼中的此函式其它使用情況產生衝突,這個道理是我想到狀態轉換圖時突然明白的,之前試著改了,但是一直疑惑為什麼沒有結果。
原始碼修改:
thread_unblock()
/*list_push_back (&ready_list, &t->elem); */(原程式)
list_insert_ordered(&ready_list, &t->elem, pri_more, NULL);(新的改法)
這裡涉及了一個pri_more函式,這個函式是優先順序比較函式
pri_more(const struct list_elem *a,const struct list_elem *b,void *aux) //作用物件:執行緒
{
struct thread *a_thread,*b_thread;
a_thread=list_entry(a,struct thread, elem);
b_thread=list_entry(b,struct thread, elem);
return (a_thread->priority > b_thread->priority);
}
函式直觀很好理解,在此不做說明。
B.向阻塞佇列中插入新的元素,與訊號量函式有關
(1)sema_down() -----P操作
在申請訊號量時,若訊號量不夠,則發生阻塞,對sema->waiter中的執行緒按照優先順序有序插入
原始碼修改:
//************************************************************************
//list_push_back (&sema->waiters, &thread_current ()->elem) (原程式)
list_insert_ordered(&sema->waiters, &thread_current ()->elem, pri_more, NULL); (新方法)
//**************************************************************************
(2)sema_up() -----V操作
一個訊號量可能會阻塞多個執行緒,應將優先順序最高的執行緒從 block 態轉換為ready 態。
原始碼修改:
加入定義:
//******************************************************************
struct thread *t=NULL;
修改if (!list_empty (&sema->waiters)) 條件分支中:
/* thread_unblock (list_entry (list_pop_front (&sema->waiters),struct thread, elem)); */(原程式)
{
t = list_entry (list_pop_front (&sema->waiters), struct thread, elem);
thread_unblock (t);
}
在sema->value++; 後加入:
if(t != NULL && t->priority > thread_current()->priority)
thread_yield();
//*******************************************************************
(3)cond_wait()
對等待條件變數的list(waiters)按照優先順序有序插入
原始碼修改:
//*******************************************************************
在 struct semaphore_elem 中加入新屬性:
int sema_priority; //表示訊號量的優先順序
/* list_push_back (&cond->waiters, &waiter.elem); */ (原始碼)
waiter.sema_priority = thread_current ()->priority;
list_insert_ordered (&cond->waiters, &waiter.elem, cond_priority, NULL);
//*******************************************************************
在這裡同樣會設計一個優先順序比較函式,是比較執行緒關於訊號量的優先順序,與上文不同。
Bool cond_priority (const struct list_elem *lhs, const struct list_elem *rhs, void *aux UNUSED)
//功能與pri_more 類似,作用物件:semaphore_elem
{
struct semaphore_elem *l, *r;
l = list_entry (lhs, struct semaphore_elem, elem);
r = list_entry (rhs, struct semaphore_elem, elem);
return (l->sema_priority > r->sema_priority);
}
四、實驗結果
實驗結果截圖如下: