1. 程式人生 > >作業系統課程設計——pintos原始碼的分析與修改

作業系統課程設計——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

此處為有序佇列),若低於ready_list對列中的最高優先順序,則出現新執行緒讓出CPU的執行緒。

    分析如下圖示:

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_elemthread的關係,每一個thread都會有一個專屬於自己的elem成員,這個elem成員用於作為各種佇列元素來代表自己,其實這裡我想到了關於健值的含義,個人感覺elem好像是thread的健值一樣。

分析了這麼多,list_entry()函式的作用到底是什麼,可以看到我們通過使用list_entry()函式得到了一個執行緒(list_entry(...)->priority,再看list_entry()的三個傳入引數list_begin(&ready_list)struct thread,elem,那麼我們是不是可以大膽猜想list_entry()函式通過elemready_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.向阻塞佇列中插入新的元素,與訊號量函式有關

1sema_down()     -----P操作

在申請訊號量時,若訊號量不夠,則發生阻塞,對sema->waiter中的執行緒按照優先順序有序插入

原始碼修改:

//************************************************************************

//list_push_back (&sema->waiters, &thread_current ()->elem) (原程式)

list_insert_ordered(&sema->waiters, &thread_current ()->elem, pri_more, NULL); (新方法)

//**************************************************************************

2sema_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);

}

四、實驗結果

實驗結果截圖如下