我是如何學習寫一個操做系統(七):進程的同步與信號量

前言

在多進程的運行環境下,進程是併發執行的,不一樣進程間存在着不一樣的相互制約關係。爲了協調進程之間的相互制約關係,達到資源共享和進程協做,避免進程之間的衝突,引入了進程同步的概念。併發

臨界資源

多個進程能夠共享系統中的各類資源,但其中許多資源一次只能爲一個進程所使用,咱們把一次只容許一個進程使用的資源成爲臨界資源。 對臨界資源的訪問,必須互斥的進行。每一個進程中,訪問臨界資源的那段代碼成爲臨界區。函數

爲了保證臨界資源的正確使用,能夠把臨界資源的訪問過程分爲四個部分。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),他們的行爲是這樣的:

  • P(sv):若是sv的值大於零,就給它減1;若是它的值爲零,就掛起該進程的執行
  • V(sv):若是有其餘進程因等待sv而被掛起,就讓它恢復運行,若是沒有進程因等待sv而掛起,就給它加1.

舉個例子,就是兩個進程共享信號量sv,一旦其中一個進程執行了P(sv)操做,它將獲得信號量,並能夠進入臨界區,使sv減1。而第二個進程將被阻止進入臨界區,由於當它試圖執行P(sv)時,sv爲0,它會被掛起以等待第一個進程離開臨界區域並執行V(sv)釋放信號量,這時第二個進程就能夠恢復執行。

Linux 0.11的進程同步

在Linux 0.11裏是沒有實現信號量的,考慮後面會本身實現一個。這裏先看一下Linux 0.11裏用來進行進程同步的兩個函數

sleep_on

  • 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;
}
複製代碼

wake_up

  • 喚醒 *p 指向的任務。 *p是任務等待隊列頭指針。因爲新等待任務是插入在等待隊列頭指針處的,
void wake_up(struct task_struct **p) {
	if (p && *p) {
		(**p).state=0;          // 置爲就緒(可運行)狀態TASK_RUNNING.
		*p=NULL;
	}
}
複製代碼

小結

首先居然有了多進程,那在訪問共享資源的時候天然就會發生制約關係,因此才引入了進程同步的概念。

而進程同步的關鍵就是對臨界區的保護,信號量就是一種能夠很好的實現對臨界區保護的方法

相關文章
相關標籤/搜索