臨界資源與臨界區
臨界資源(critical resource):一次只能供一個進程使用的資源。 如:硬件有打印機等,軟件有變量,磁盤文件(寫入的時候)。
臨界區(critical section):把進程中訪問臨界資源的那段代碼成為臨界區。
為了實現臨界資源的互斥訪問,只要做到進程互斥地進去自己的臨界區,便可以實現進程對臨界資源的互斥訪問。
同步機制
為實現各進程互質地訪問自己的臨界區,操作系統需要同步機制來協調各進程的運行。
1、同步機制的規則
(1)空閑讓進:當無進程處於臨界區,表明臨界資源處於空閑狀態,允許請求進去臨界區的進程進去臨界區
(2)忙則等待:當有進程處於臨界區,表明臨界資源正在訪問,其他請求進入臨界區的進程必須等待。
(3)有限等待:應使在等待的進程在有限的事件內進入自己的臨界區,避免"死等"狀態
(4)讓權等待:當進程不能進入自己的臨界區,應立即釋放處理機,以避免進入"忙等"狀態
2、硬件同步機制
為臨界資源設置一把鎖,當臨界資源無進程使用時,鎖是打開的,當臨界資源正在被進程使用時,則鎖是關閉的。
鎖可能是一個布爾變量lock,初始時,lock = FALSE,那么每次訪問臨界資源時,都要進程鎖測試。
bool lock = false; if(!lock) { lock = true; access critical section;//訪問臨界區資源 }
但是在多任務的處理機中,可能會發生錯誤。假設有2個進程都要訪問同一臨界資源,進程a通過了鎖測試,但是未將lock設為true時,發生了進程調度,進程b開始執行鎖測試,同樣通過了鎖測試,從而使得兩個進程訪問同一個臨界資源。
(1)關中斷:在進入鎖測試之前關閉中斷,直到完成鎖測試並且上鎖之后才打開中斷,但是這樣很不好,限制了CPU處理任務的能力,而且也很危險,如果用戶在中斷后寫個死循環,那不就死機了。
(2)利用Test-and-set指令:這是一條硬件指令,且該指令是原子操作(atomic operataion),原子操作指的是該執行過程要么不做,那么全做,即像原子一樣不可分割。下面的TS就是一個原子操作,所以不會發生上面所說的問題。
bool TS(bool *lock)//lock 為臨界資源的鎖 { bool old ; old = lock; lock = true;//如果鎖為true,那么這句不影響鎖,如果鎖為false,那么正好上鎖 return old; }
while(TS(&lock));//循環測試直到TS(&lock) 為false access critical section; lock = false;
(3)利用Swap指令:該指令也是硬件指令,且是原子操作。可以看出下面的代碼其實思想是和上面的代碼思想是一樣的。只不過實現方式不同而已。
void swap(bool *a, bool *b) { //如果a==true,那么swap語句不影響鎖,如果a==false,那么正好上鎖 bool tmp tmp = *a; *a = *b; *b = tmp; }
bool key = true; do{ swap(&lock,&key); }while(key!=false)
3、信號量機制
(1)整形信號量:用一個整型的信號量S來表示資源的個數,然后通過兩個原子操作來進行判斷,這兩個原子操作是wait(S)和singal(S),也叫做P和V
其實這些操作很容易想出來,但是你不能自己直接寫if-else來判斷,因為你的這些代碼可能不具備原子性,所以會發生錯誤。
wait(S) { while(S<=0); S--; } singal(S) { S++; } //使用wait(S)和singal(S)來做到互斥訪問 wait(S); access critical section; singal(S);
(2)記錄型信號量:在整形信號量中,只要S<0,那么wait原子操作就會不停地進行測試,直到S>=0,這種不斷測試,知道滿足條件的情況叫做忙等待。這樣不如使得該進程進入阻塞狀態,等滿足條件時再喚醒該進程。而且這樣更有利於提高CPU的效率。 這就是記錄型信號量所做的事情。
wait原語所做的操作是將S->value--,如果減完后小於0,那么說明臨界資源不夠,因此調用block原語,將當前進程阻塞,並記錄到鏈表S->list中
singal原語所做的操作是將S->value++,如果加完后小於等於0,那么說明鏈表中S->list有阻塞的進程,因此調用wakeup原語喚醒該進程來獲得臨界資源。
//記錄型數據結構 typedef struct { int value; struct process_control_block *list; }semaphore; wait(semaphore *S) { S->value--; if(S->value < 0) block(S->list); } singal(semaphore *S) { S->value++; if(S->value<=0) wakeup(S->list); }
其實硬件指令同步和軟件同步所做的操作都是一樣的,只是他們實現的方式不同而已,一個是硬件,一個是軟件。