在多進程的運行環境下,進程是併發執行的,不一樣進程間存在着不一樣的相互制約關係。爲了協調進程之間的相互制約關係,達到資源共享和進程協做,避免進程之間的衝突,引入了進程同步的概念。併發
多個進程能夠共享系統中的各類資源,但其中許多資源一次只能爲一個進程所使用,咱們把一次只容許一個進程使用的資源成爲臨界資源。 對臨界資源的訪問,必須互斥的進行。每一個進程中,訪問臨界資源的那段代碼成爲臨界區。函數
爲了保證臨界資源的正確使用,能夠把臨界資源的訪問過程分爲四個部分。spa
通常實現進程的同步有這幾種方法:操作系統
下面的代碼就分別是生產者進程和消費者進程,而buffer就是臨界資源線程
當生產者要訪問臨界資源時,會先判斷buffer是否是已經滿了,而消費者則判斷buffer是否是空的,這就是訪問臨界資源的進入區指針
而中間對buffer的操做就是臨界區code
最後對counter的加減就是設置對臨界區訪問的標誌隊列
可是這裏依舊也有可能出現問題,好比當進程走到in = (in + 1) % BUFFER_SIZE;的時候,這時候操做系統進行調度,這時候的counter的值可能仍是0,因此消費者進程可能就會出現問題進程
這裏的處理能夠是利用關閉中斷來限制進程的切換,可是在多核CPU下同樣無論用資源
這裏就涉及到了臨界區的保護了
#define BUFFER_SIZE 10
typedef struct { . . . } item;
item buffer[BUFFER_SIZE];
int in = out = counter = 0
複製代碼
while (true) {
while(counter== BUFFER_SIZE)
;
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE;
counter++;
}
複製代碼
while (true) {
while(counter== 0)
;
item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
counter--;
}
複製代碼
爲了防止出現因多個程序同時訪問一個共享資源而引起的一系列問題,咱們須要一種方法,它能夠經過生成並使用令牌來受權,在任一時刻只能有一個執行線程訪問代碼的臨界區。
爲了不像上面同樣會發生競爭條件,程序對信號量訪問都是原子操做,且只容許對它進行等待(即P(信號變量))和發送(即V(信號變量))信息操做。
因爲信號量只能進行兩種操做等待和發送信號,即P(sv)和V(sv),他們的行爲是這樣的:
舉個例子,就是兩個進程共享信號量sv,一旦其中一個進程執行了P(sv)操做,它將獲得信號量,並能夠進入臨界區,使sv減1。而第二個進程將被阻止進入臨界區,由於當它試圖執行P(sv)時,sv爲0,它會被掛起以等待第一個進程離開臨界區域並執行V(sv)釋放信號量,這時第二個進程就能夠恢復執行。
在Linux 0.11裏是沒有實現信號量的,考慮後面會本身實現一個。這裏先看一下Linux 0.11裏用來進行進程同步的兩個函數
p實際上指的是一個等待隊列
若是當前進程是進程0或者無效,就直接退出
而後把要等待的進程放到等待隊列的頭節點,把狀態設置爲不可中斷的等待狀態
這裏隊列的造成很是很是的隱蔽,首先把用tmp指向以前的進程,在把當前要睡眠的進程放入,而之因此能造成隊列,是由於如今放入隊列的進程的tmp做爲局部變量是保存在這個進程的堆棧中的,這樣在把進程切換回來的時候,tmp就天然的指向上一個進程了。
最後當這個進程被喚醒的時候,會回到if語句喚醒等待隊列中全部進程
void sleep_on(struct task_struct **p) {
struct task_struct *tmp;
if (!p)
return;
if (current == &(init_task.task))
panic("task[0] trying to sleep");
tmp = *p;
*p = current;
current->state = TASK_UNINTERRUPTIBLE;
schedule();
if (tmp)
tmp->state=0;
}
複製代碼
void wake_up(struct task_struct **p) {
if (p && *p) {
(**p).state=0; // 置爲就緒(可運行)狀態TASK_RUNNING.
*p=NULL;
}
}
複製代碼
首先居然有了多進程,那在訪問共享資源的時候天然就會發生制約關係,因此才引入了進程同步的概念。
而進程同步的關鍵就是對臨界區的保護,信號量就是一種能夠很好的實現對臨界區保護的方法