搞定操做系統面試,看這篇就夠了

<section data-role="outer" label="Powered by 135editor.com">git

<section data-role="outer" label="Powered by 135editor.com">程序員

點擊上方「程序員江湖」,選擇「置頂或者星標」github

你關注的就是我關心的!面試

做者:CyC2018算法

連接:https://github.com/CyC2018/CS...瀏覽器

<section>緩存

1、概述

基本特徵

1. 併發

併發是指宏觀上在一段時間內能同時運行多個程序,而並行則指同一時刻能運行多個指令。安全

並行須要硬件支持,如多流水線、多核處理器或者分佈式計算系統。服務器

操做系統經過引入進程和線程,使得程序可以併發運行。網絡

2. 共享

共享是指系統中的資源能夠被多個併發進程共同使用。

有兩種共享方式:互斥共享和同時共享。

互斥共享的資源稱爲臨界資源,例如打印機等,在同一時間只容許一個進程訪問,須要用同步機制來實現對臨界資源的訪問。

3. 虛擬

虛擬技術把一個物理實體轉換爲多個邏輯實體。

主要有兩種虛擬技術:時分複用技術和空分複用技術。

多個進程能在同一個處理器上併發執行使用了時分複用技術,讓每一個進程輪流佔有處理器,每次只執行一小個時間片並快速切換。

虛擬內存使用了空分複用技術,它將物理內存抽象爲地址空間,每一個進程都有各自的地址空間。地址空間的頁被映射到物理內存,地址空間的頁並不須要所有在物理內存中,當使用到一個沒有在物理內存的頁時,執行頁面置換算法,將該頁置換到內存中。

4. 異步

異步指進程不是一次性執行完畢,而是走走停停,以不可知的速度向前推動。

基本功能

1. 進程管理

進程控制、進程同步、進程通訊、死鎖處理、處理機調度等。

2. 內存管理

內存分配、地址映射、內存保護與共享、虛擬內存等。

3. 文件管理

文件存儲空間的管理、目錄管理、文件讀寫管理和保護等。

4. 設備管理

完成用戶的 I/O 請求,方便用戶使用各類設備,並提升設備的利用率。

主要包括緩衝管理、設備分配、設備處理、虛擬設備等。

系統調用

若是一個進程在用戶態須要使用內核態的功能,就進行系統調用從而陷入內核,由操做系統代爲完成。

<figure></figure>

Linux 的系統調用主要有如下這些:

Task Commands
進程控制 fork(); exit(); wait();
進程通訊 pipe(); shmget(); mmap();
文件操做 open(); read(); write();
設備操做 ioctl(); read(); write();
信息維護 getpid(); alarm(); sleep();
安全 chmod(); umask(); chown();

大內核和微內核

1. 大內核

大內核是將操做系統功能做爲一個緊密結合的總體放到內核。

因爲各模塊共享信息,所以有很高的性能。

2. 微內核

因爲操做系統不斷複雜,所以將一部分操做系統功能移出內核,從而下降內核的複雜性。移出的部分根據分層的原則劃分紅若干服務,相互獨立。

在微內核結構下,操做系統被劃分紅小的、定義良好的模塊,只有微內核這一個模塊運行在內核態,其他模塊運行在用戶態。

由於須要頻繁地在用戶態和核心態之間進行切換,因此會有必定的性能損失。

<figure></figure>

中斷分類

1. 外中斷

由 CPU 執行指令之外的事件引發,如 I/O 完成中斷,表示設備輸入/輸出處理已經完成,處理器可以發送下一個輸入/輸出請求。此外還有時鐘中斷、控制檯中斷等。

2. 異常

由 CPU 執行指令的內部事件引發,如非法操做碼、地址越界、算術溢出等。

3. 陷入

在用戶程序中使用系統調用。

2、進程管理

進程與線程

1. 進程

進程是資源分配的基本單位。

進程控制塊 (Process Control Block, PCB) 描述進程的基本信息和運行狀態,所謂的建立進程和撤銷進程,都是指對 PCB 的操做。

下圖顯示了 4 個程序建立了 4 個進程,這 4 個進程能夠併發地執行。

<figure></figure>

2. 線程

線程是獨立調度的基本單位。

一個進程中能夠有多個線程,它們共享進程資源。

QQ 和瀏覽器是兩個進程,瀏覽器進程裏面有不少線程,例如 HTTP 請求線程、事件響應線程、渲染線程等等,線程的併發執行使得在瀏覽器中點擊一個新連接從而發起 HTTP 請求時,瀏覽器還能夠響應用戶的其它事件。

<figure></figure>

3. 區別

Ⅰ 擁有資源

進程是資源分配的基本單位,可是線程不擁有資源,線程能夠訪問隸屬進程的資源。

Ⅱ 調度

線程是獨立調度的基本單位,在同一進程中,線程的切換不會引發進程切換,從一個進程中的線程切換到另外一個進程中的線程時,會引發進程切換。

Ⅲ 系統開銷

因爲建立或撤銷進程時,系統都要爲之分配或回收資源,如內存空間、I/O 設備等,所付出的開銷遠大於建立或撤銷線程時的開銷。相似地,在進行進程切換時,涉及當前執行進程 CPU 環境的保存及新調度進程 CPU 環境的設置,而線程切換時只需保存和設置少許寄存器內容,開銷很小。

Ⅳ 通訊方面

線程間能夠經過直接讀寫同一進程中的數據進行通訊,可是進程通訊須要藉助 IPC。

進程狀態的切換

<figure></figure>

  • 就緒狀態(ready):等待被調度
  • 運行狀態(running)
  • 阻塞狀態(waiting):等待資源

應該注意如下內容:

  • 只有就緒態和運行態能夠相互轉換,其它的都是單向轉換。就緒狀態的進程經過調度算法從而得到 CPU 時間,轉爲運行狀態;而運行狀態的進程,在分配給它的 CPU 時間片用完以後就會轉爲就緒狀態,等待下一次調度。
  • 阻塞狀態是缺乏須要的資源從而由運行狀態轉換而來,可是該資源不包括 CPU 時間,缺乏 CPU 時間會從運行態轉換爲就緒態。

進程調度算法

不一樣環境的調度算法目標不一樣,所以須要針對不一樣環境來討論調度算法。

1. 批處理系統

批處理系統沒有太多的用戶操做,在該系統中,調度算法目標是保證吞吐量和週轉時間(從提交到終止的時間)。

1.1 先來先服務 first-come first-serverd(FCFS)

按照請求的順序進行調度。

有利於長做業,但不利於短做業,由於短做業必須一直等待前面的長做業執行完畢才能執行,而長做業又須要執行很長時間,形成了短做業等待時間過長。

1.2 短做業優先 shortest job first(SJF)

按估計運行時間最短的順序進行調度。

長做業有可能會餓死,處於一直等待短做業執行完畢的狀態。由於若是一直有短做業到來,那麼長做業永遠得不到調度。

1.3 最短剩餘時間優先 shortest remaining time next(SRTN)

按估計剩餘時間最短的順序進行調度。

2. 交互式系統

交互式系統有大量的用戶交互操做,在該系統中調度算法的目標是快速地進行響應。

2.1 時間片輪轉

將全部就緒進程按 FCFS 的原則排成一個隊列,每次調度時,把 CPU 時間分配給隊首進程,該進程能夠執行一個時間片。當時間片用完時,由計時器發出時鐘中斷,調度程序便中止該進程的執行,並將它送往就緒隊列的末尾,同時繼續把 CPU 時間分配給隊首的進程。

時間片輪轉算法的效率和時間片的大小有很大關係:

  • 由於進程切換都要保存進程的信息而且載入新進程的信息,若是時間片過小,會致使進程切換得太頻繁,在進程切換上就會花過多時間。
  • 而若是時間片過長,那麼實時性就不能獲得保證。

<figure></figure>

2.2 優先級調度

爲每一個進程分配一個優先級,按優先級進行調度。

爲了防止低優先級的進程永遠等不到調度,能夠隨着時間的推移增長等待進程的優先級。

2.3 多級反饋隊列

一個進程須要執行 100 個時間片,若是採用時間片輪轉調度算法,那麼須要交換 100 次。

多級隊列是爲這種須要連續執行多個時間片的進程考慮,它設置了多個隊列,每一個隊列時間片大小都不一樣,例如 1,2,4,8,..。進程在第一個隊列沒執行完,就會被移到下一個隊列。這種方式下,以前的進程只須要交換 7 次。

每一個隊列優先權也不一樣,最上面的優先權最高。所以只有上一個隊列沒有進程在排隊,才能調度當前隊列上的進程。

能夠將這種調度算法當作是時間片輪轉調度算法和優先級調度算法的結合。

<figure></figure>

3. 實時系統

實時系統要求一個請求在一個肯定時間內獲得響應。

分爲硬實時和軟實時,前者必須知足絕對的截止時間,後者能夠容忍必定的超時。

進程同步

1. 臨界區

對臨界資源進行訪問的那段代碼稱爲臨界區。

爲了互斥訪問臨界資源,每一個進程在進入臨界區以前,須要先進行檢查。

// entry section// critical section;// exit section

2. 同步與互斥

  • 同步:多個進程按必定順序執行;
  • 互斥:多個進程在同一時刻只有一個進程能進入臨界區。

3. 信號量

信號量(Semaphore)是一個整型變量,能夠對其執行 down 和 up 操做,也就是常見的 P 和 V 操做。

  • down  : 若是信號量大於 0 ,執行 -1 操做;若是信號量等於 0,進程睡眠,等待信號量大於 0;
  • up :對信號量執行 +1 操做,喚醒睡眠的進程讓其完成 down 操做。

down 和 up 操做須要被設計成原語,不可分割,一般的作法是在執行這些操做的時候屏蔽中斷。

若是信號量的取值只能爲 0 或者 1,那麼就成爲了  互斥量(Mutex) ,0 表示臨界區已經加鎖,1 表示臨界區解鎖。

typedef int semaphore;semaphore mutex = 1;void P1() {    down(&mutex);    // 臨界區    up(&mutex);}void P2() {    down(&mutex);    // 臨界區    up(&mutex);}

 使用信號量實現生產者-消費者問題   

問題描述:使用一個緩衝區來保存物品,只有緩衝區沒有滿,生產者才能夠放入物品;只有緩衝區不爲空,消費者才能夠拿走物品。

由於緩衝區屬於臨界資源,所以須要使用一個互斥量 mutex 來控制對緩衝區的互斥訪問。

爲了同步生產者和消費者的行爲,須要記錄緩衝區中物品的數量。數量可使用信號量來進行統計,這裏須要使用兩個信號量:empty 記錄空緩衝區的數量,full 記錄滿緩衝區的數量。其中,empty 信號量是在生產者進程中使用,當 empty 不爲 0 時,生產者才能夠放入物品;full 信號量是在消費者進程中使用,當 full 信號量不爲 0 時,消費者才能夠取走物品。

注意,不能先對緩衝區進行加鎖,再測試信號量。也就是說,不能先執行 down(mutex) 再執行 down(empty)。若是這麼作了,那麼可能會出現這種狀況:生產者對緩衝區加鎖後,執行 down(empty) 操做,發現 empty = 0,此時生產者睡眠。消費者不能進入臨界區,由於生產者對緩衝區加鎖了,消費者就沒法執行 up(empty) 操做,empty 永遠都爲 0,致使生產者永遠等待下,不會釋放鎖,消費者所以也會永遠等待下去。

#define N 100typedef int semaphore;semaphore mutex = 1;semaphore empty = N;semaphore full = 0;void producer() {    while(TRUE) {        int item = produce_item();        down(&empty);        down(&mutex);        insert_item(item);        up(&mutex);        up(&full);    }}void consumer() {    while(TRUE) {        down(&full);        down(&mutex);        int item = remove_item();        consume_item(item);        up(&mutex);        up(&empty);    }}

4. 管程

使用信號量機制實現的生產者消費者問題須要客戶端代碼作不少控制,而管程把控制的代碼獨立出來,不只不容易出錯,也使得客戶端代碼調用更容易。

c 語言不支持管程,下面的示例代碼使用了類 Pascal 語言來描述管程。示例代碼的管程提供了 insert() 和 remove() 方法,客戶端代碼經過調用這兩個方法來解決生產者-消費者問題。

monitor ProducerConsumer    integer i;    condition c;    procedure insert();    begin        // ...    end;    procedure remove();    begin        // ...    end;end monitor;

管程有一個重要特性:在一個時刻只能有一個進程使用管程。進程在沒法繼續執行的時候不能一直佔用管程,否者其它進程永遠不能使用管程。

管程引入了  條件變量  以及相關的操做:wait() 和 signal() 來實現同步操做。對條件變量執行 wait() 操做會致使調用進程阻塞,把管程讓出來給另外一個進程持有。signal() 操做用於喚醒被阻塞的進程。

使用管程實現生產者-消費者問題 

// 管程monitor ProducerConsumer    condition full, empty;    integer count := 0;    condition c;    procedure insert(item: integer);    begin        if count = N then wait(full);        insert_item(item);        count := count + 1;        if count = 1 then signal(empty);    end;    function remove: integer;    begin        if count = 0 then wait(empty);        remove = remove_item;        count := count - 1;        if count = N -1 then signal(full);    end;end monitor;// 生產者客戶端procedure producerbegin    while true do    begin        item = produce_item;        ProducerConsumer.insert(item);    endend;// 消費者客戶端procedure consumerbegin    while true do    begin        item = ProducerConsumer.remove;        consume_item(item);    endend;

經典同步問題

生產者和消費者問題前面已經討論過了。

1. 讀者-寫者問題

容許多個進程同時對數據進行讀操做,可是不容許讀和寫以及寫和寫操做同時發生。

一個整型變量 count 記錄在對數據進行讀操做的進程數量,一個互斥量 count_mutex 用於對 count 加鎖,一個互斥量 data_mutex 用於對讀寫的數據加鎖。

typedef int semaphore;semaphore count_mutex = 1;semaphore data_mutex = 1;int count = 0;void reader() {    while(TRUE) {        down(&count_mutex);        count++;        if(count == 1) down(&data_mutex); // 第一個讀者須要對數據進行加鎖,防止寫進程訪問        up(&count_mutex);        read();        down(&count_mutex);        count--;        if(count == 0) up(&data_mutex);        up(&count_mutex);    }}void writer() {    while(TRUE) {        down(&data_mutex);        write();        up(&data_mutex);    }}

如下內容由 @Bandi Yugandhar 提供。

The first case may result Writer to starve. This case favous Writers i.e no writer, once added to the queue, shall be kept waiting longer than absolutely necessary(only when there are readers that entered the queue before the writer).

int readcount, writecount;                   //(initial value = 0)semaphore rmutex, wmutex, readLock, resource; //(initial value = 1)//READERvoid reader() {<ENTRY Section> down(&readLock);                 //  reader is trying to enter down(&rmutex);                  //   lock to increase readcount  readcount++;                   if (readcount == 1)             down(&resource);              //if you are the first reader then lock  the resource up(&rmutex);                  //release  for other readers up(&readLock);                 //Done with trying to access the resource<CRITICAL Section>//reading is performed<EXIT Section> down(&rmutex);                  //reserve exit section - avoids race condition with readers readcount--;                       //indicate you're leaving  if (readcount == 0)          //checks if you are last reader leaving   up(&resource);              //if last, you must release the locked resource up(&rmutex);                  //release exit section for other readers}//WRITERvoid writer() {  <ENTRY Section>  down(&wmutex);                  //reserve entry section for writers - avoids race conditions  writecount++;                //report yourself as a writer entering  if (writecount == 1)         //checks if you're first writer   down(&readLock);               //if you're first, then you must lock the readers out. Prevent them from trying to enter CS  up(&wmutex);                  //release entry section<CRITICAL Section> down(&resource);                //reserve the resource for yourself - prevents other writers from simultaneously editing the shared resource  //writing is performed up(&resource);                //release file<EXIT Section>  down(&wmutex);                  //reserve exit section  writecount--;                //indicate you're leaving  if (writecount == 0)         //checks if you're the last writer   up(&readLock);               //if you're last writer, you must unlock the readers. Allows them to try enter CS for reading  up(&wmutex);                  //release exit section}

We can observe that every reader is forced to acquire ReadLock. On the otherhand, writers doesn’t need to lock individually. Once the first writer locks the ReadLock, it will be released only when there is no writer left in the queue.

From the both cases we observed that either reader or writer has to starve. Below solutionadds the constraint that no thread shall be allowed to starve; that is, the operation of obtaining a lock on the shared data will always terminate in a bounded amount of time.

int readCount;                  // init to 0; number of readers currently accessing resource// all semaphores initialised to 1Semaphore resourceAccess;       // controls access (read/write) to the resourceSemaphore readCountAccess;      // for syncing changes to shared variable readCountSemaphore serviceQueue;         // FAIRNESS: preserves ordering of requests (signaling must be FIFO)void writer(){     down(&serviceQueue);           // wait in line to be servicexs    // <ENTER>    down(&resourceAccess);         // request exclusive access to resource    // </ENTER>    up(&serviceQueue);           // let next in line be serviced    // <WRITE>    writeResource();            // writing is performed    // </WRITE>    // <EXIT>    up(&resourceAccess);         // release resource access for next reader/writer    // </EXIT>}void reader(){     down(&serviceQueue);           // wait in line to be serviced    down(&readCountAccess);        // request exclusive access to readCount    // <ENTER>    if (readCount == 0)         // if there are no readers already reading:        down(&resourceAccess);     // request resource access for readers (writers blocked)    readCount++;                // update count of active readers    // </ENTER>    up(&serviceQueue);           // let next in line be serviced    up(&readCountAccess);        // release access to readCount    // <READ>    readResource();             // reading is performed    // </READ>    down(&readCountAccess);        // request exclusive access to readCount    // <EXIT>    readCount--;                // update count of active readers    if (readCount == 0)         // if there are no readers left:        up(&resourceAccess);     // release resource access for all    // </EXIT>    up(&readCountAccess);        // release access to readCount}

2. 哲學家進餐問題

<figure></figure>

五個哲學家圍着一張圓桌,每一個哲學家面前放着食物。哲學家的生活有兩種交替活動:吃飯以及思考。當一個哲學家吃飯時,須要先拿起本身左右兩邊的兩根筷子,而且一次只能拿起一根筷子。

下面是一種錯誤的解法,考慮到若是全部哲學家同時拿起左手邊的筷子,那麼就沒法拿起右手邊的筷子,形成死鎖。

#define N 5void philosopher(int i) {    while(TRUE) {        think();        take(i);       // 拿起左邊的筷子        take((i+1)%N); // 拿起右邊的筷子        eat();        put(i);        put((i+1)%N);    }}

爲了防止死鎖的發生,能夠設置兩個條件:

  • 必須同時拿起左右兩根筷子;
  • 只有在兩個鄰居都沒有進餐的狀況下才容許進餐。
#define N 5#define LEFT (i + N - 1) % N // 左鄰居#define RIGHT (i + 1) % N    // 右鄰居#define THINKING 0#define HUNGRY   1#define EATING   2typedef int semaphore;int state[N];                // 跟蹤每一個哲學家的狀態semaphore mutex = 1;         // 臨界區的互斥semaphore s[N];              // 每一個哲學家一個信號量void philosopher(int i) {    while(TRUE) {        think();        take_two(i);        eat();        put_two(i);    }}void take_two(int i) {    down(&mutex);    state[i] = HUNGRY;    test(i);    up(&mutex);    down(&s[i]);}void put_two(i) {    down(&mutex);    state[i] = THINKING;    test(LEFT);    test(RIGHT);    up(&mutex);}void test(i) {         // 嘗試拿起兩把筷子    if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) {        state[i] = EATING;        up(&s[i]);    }}

進程通訊

進程同步與進程通訊很容易混淆,它們的區別在於:

  • 進程同步:控制多個進程按必定順序執行;
  • 進程通訊:進程間傳輸信息。

進程通訊是一種手段,而進程同步是一種目的。也能夠說,爲了可以達到進程同步的目的,須要讓進程進行通訊,傳輸一些進程同步所須要的信息。

1. 管道

管道是經過調用 pipe 函數建立的,fd[0] 用於讀,fd[1] 用於寫。

#include <unistd.h>int pipe(int fd[2]);

它具備如下限制:

  • 只支持半雙工通訊(單向交替傳輸);
  • 只能在父子進程中使用。

<figure></figure>

2. FIFO

也稱爲命名管道,去除了管道只能在父子進程中使用的限制。

#include <sys/stat.h>int mkfifo(const char *path, mode_t mode);int mkfifoat(int fd, const char *path, mode_t mode);

FIFO 經常使用於客戶-服務器應用程序中,FIFO 用做匯聚點,在客戶進程和服務器進程之間傳遞數據。

<figure></figure>

3. 消息隊列

相比於 FIFO,消息隊列具備如下優勢:

  • 消息隊列能夠獨立於讀寫進程存在,從而避免了 FIFO 中同步管道的打開和關閉時可能產生的困難;
  • 避免了 FIFO 的同步阻塞問題,不須要進程本身提供同步方法;
  • 讀進程能夠根據消息類型有選擇地接收消息,而不像 FIFO 那樣只能默認地接收。

4. 信號量

它是一個計數器,用於爲多個進程提供對共享數據對象的訪問。

5. 共享存儲

容許多個進程共享一個給定的存儲區。由於數據不須要在進程之間複製,因此這是最快的一種 IPC。

須要使用信號量用來同步對共享存儲的訪問。

多個進程能夠將同一個文件映射到它們的地址空間從而實現共享內存。另外 XSI 共享內存不是使用文件,而是使用使用內存的匿名段。

6. 套接字

與其它通訊機制不一樣的是,它可用於不一樣機器間的進程通訊。

3、死鎖

必要條件

<figure></figure>

  • 互斥:每一個資源要麼已經分配給了一個進程,要麼就是可用的。
  • 佔有和等待:已經獲得了某個資源的進程能夠再請求新的資源。
  • 不可搶佔:已經分配給一個進程的資源不能強制性地被搶佔,它只能被佔有它的進程顯式地釋放。
  • 環路等待:有兩個或者兩個以上的進程組成一條環路,該環路中的每一個進程都在等待下一個進程所佔有的資源。

處理方法

主要有如下四種方法:

  • 鴕鳥策略
  • 死鎖檢測與死鎖恢復
  • 死鎖預防
  • 死鎖避免

鴕鳥策略

把頭埋在沙子裏,僞裝根本沒發生問題。

由於解決死鎖問題的代價很高,所以鴕鳥策略這種不採起任務措施的方案會得到更高的性能。

當發生死鎖時不會對用戶形成多大影響,或發生死鎖的機率很低,能夠採用鴕鳥策略。

大多數操做系統,包括 Unix,Linux 和 Windows,處理死鎖問題的辦法僅僅是忽略它。

死鎖檢測與死鎖恢復

不試圖阻止死鎖,而是當檢測到死鎖發生時,採起措施進行恢復。

1. 每種類型一個資源的死鎖檢測

<figure></figure>

上圖爲資源分配圖,其中方框表示資源,圓圈表示進程。資源指向進程表示該資源已經分配給該進程,進程指向資源表示進程請求獲取該資源。

圖 a 能夠抽取出環,如圖 b,它知足了環路等待條件,所以會發生死鎖。

每種類型一個資源的死鎖檢測算法是經過檢測有向圖是否存在環來實現,從一個節點出發進行深度優先搜索,對訪問過的節點進行標記,若是訪問了已經標記的節點,就表示有向圖存在環,也就是檢測到死鎖的發生。

2. 每種類型多個資源的死鎖檢測

<figure></figure>

上圖中,有三個進程四個資源,每一個數據表明的含義以下:

  • E 向量:資源總量
  • A 向量:資源剩餘量
  • C 矩陣:每一個進程所擁有的資源數量,每一行都表明一個進程擁有資源的數量
  • R 矩陣:每一個進程請求的資源數量

進程 P1 和 P2 所請求的資源都得不到知足,只有進程 P3 能夠,讓 P3 執行,以後釋放 P3 擁有的資源,此時 A = (2 2 2 0)。P2 能夠執行,執行後釋放 P2 擁有的資源,A = (4 2 2 1) 。P1 也能夠執行。全部進程均可以順利執行,沒有死鎖。

算法總結以下:

每一個進程最開始時都不被標記,執行過程有可能被標記。當算法結束時,任何沒有被標記的進程都是死鎖進程。

  1. 尋找一個沒有標記的進程 Pi,它所請求的資源小於等於 A。
  2. 若是找到了這樣一個進程,那麼將 C 矩陣的第 i 行向量加到 A 中,標記該進程,並轉回 1。
  3. 若是沒有這樣一個進程,算法終止。

3. 死鎖恢復

  • 利用搶佔恢復
  • 利用回滾恢復
  • 經過殺死進程恢復

死鎖預防

在程序運行以前預防發生死鎖。

1. 破壞互斥條件

例如假脫機打印機技術容許若干個進程同時輸出,惟一真正請求物理打印機的進程是打印機守護進程。

2. 破壞佔有和等待條件

一種實現方式是規定全部進程在開始執行前請求所須要的所有資源。

3. 破壞不可搶佔條件

4. 破壞環路等待

給資源統一編號,進程只能按編號順序來請求資源。

死鎖避免

在程序運行時避免發生死鎖。

1. 安全狀態

<figure></figure>

圖 a 的第二列 Has 表示已擁有的資源數,第三列 Max 表示總共須要的資源數,Free 表示還有可使用的資源數。從圖 a 開始出發,先讓 B 擁有所需的全部資源(圖 b),運行結束後釋放 B,此時 Free 變爲 5(圖 c);接着以一樣的方式運行 C 和 A,使得全部進程都能成功運行,所以能夠稱圖 a 所示的狀態時安全的。

定義:若是沒有死鎖發生,而且即便全部進程忽然請求對資源的最大需求,也仍然存在某種調度次序可以使得每個進程運行完畢,則稱該狀態是安全的。

安全狀態的檢測與死鎖的檢測相似,由於安全狀態必需要求不能發生死鎖。下面的銀行家算法與死鎖檢測算法很是相似,能夠結合着作參考對比。

2. 單個資源的銀行家算法

一個小城鎮的銀行家,他向一羣客戶分別承諾了必定的貸款額度,算法要作的是判斷對請求的知足是否會進入不安全狀態,若是是,就拒絕請求;不然予以分配。

<figure></figure>

上圖 c 爲不安全狀態,所以算法會拒絕以前的請求,從而避免進入圖 c 中的狀態。

3. 多個資源的銀行家算法

<figure></figure>

上圖中有五個進程,四個資源。左邊的圖表示已經分配的資源,右邊的圖表示還須要分配的資源。最右邊的 E、P 以及 A 分別表示:總資源、已分配資源以及可用資源,注意這三個爲向量,而不是具體數值,例如 A=(1020),表示 4 個資源分別還剩下 1/0/2/0。

檢查一個狀態是否安全的算法以下:

  • 查找右邊的矩陣是否存在一行小於等於向量 A。若是不存在這樣的行,那麼系統將會發生死鎖,狀態是不安全的。
  • 倘若找到這樣一行,將該進程標記爲終止,並將其已分配資源加到 A 中。
  • 重複以上兩步,直到全部進程都標記爲終止,則狀態時安全的。

若是一個狀態不是安全的,須要拒絕進入這個狀態。

4、內存管理

虛擬內存

虛擬內存的目的是爲了讓物理內存擴充成更大的邏輯內存,從而讓程序得到更多的可用內存。

爲了更好的管理內存,操做系統將內存抽象成地址空間。每一個程序擁有本身的地址空間,這個地址空間被分割成多個塊,每一塊稱爲一頁。這些頁被映射到物理內存,但不須要映射到連續的物理內存,也不須要全部頁都必須在物理內存中。當程序引用到不在物理內存中的頁時,由硬件執行必要的映射,將缺失的部分裝入物理內存並從新執行失敗的指令。

從上面的描述中能夠看出,虛擬內存容許程序不用將地址空間中的每一頁都映射到物理內存,也就是說一個程序不須要所有調入內存就能夠運行,這使得有限的內存運行大程序成爲可能。例若有一臺計算機能夠產生 16 位地址,那麼一個程序的地址空間範圍是 0~64K。該計算機只有 32KB 的物理內存,虛擬內存技術容許該計算機運行一個 64K 大小的程序。

<figure></figure>

分頁系統地址映射

內存管理單元(MMU)管理着地址空間和物理內存的轉換,其中的頁表(Page table)存儲着頁(程序地址空間)和頁框(物理內存空間)的映射表。

一個虛擬地址分紅兩個部分,一部分存儲頁面號,一部分存儲偏移量。

下圖的頁表存放着 16 個頁,這 16 個頁須要用 4 個比特位來進行索引定位。例如對於虛擬地址(0010 000000000100),前 4 位是存儲頁面號 2,讀取表項內容爲(110 1),頁表項最後一位表示是否存在於內存中,1 表示存在。後 12 位存儲偏移量。這個頁對應的頁框的地址爲 (110 000000000100)。

<figure></figure>

頁面置換算法

在程序運行過程當中,若是要訪問的頁面不在內存中,就發生缺頁中斷從而將該頁調入內存中。此時若是內存已無空閒空間,系統必須從內存中調出一個頁面到磁盤對換區中來騰出空間。

頁面置換算法和緩存淘汰策略相似,能夠將內存當作磁盤的緩存。在緩存系統中,緩存的大小有限,當有新的緩存到達時,須要淘汰一部分已經存在的緩存,這樣纔有空間存放新的緩存數據。

頁面置換算法的主要目標是使頁面置換頻率最低(也能夠說缺頁率最低)。

1. 最佳

OPT, Optimal replacement algorithm

所選擇的被換出的頁面將是最長時間內再也不被訪問,一般能夠保證得到最低的缺頁率。

是一種理論上的算法,由於沒法知道一個頁面多長時間再也不被訪問。

舉例:一個系統爲某進程分配了三個物理塊,並有以下頁面引用序列:

開始運行時,先將 7, 0, 1 三個頁面裝入內存。當進程要訪問頁面 2 時,產生缺頁中斷,會將頁面 7 換出,由於頁面 7 再次被訪問的時間最長。

2. 最近最久未使用

LRU, Least Recently Used

雖然沒法知道未來要使用的頁面狀況,可是能夠知道過去使用頁面的狀況。LRU 將最近最久未使用的頁面換出。

爲了實現 LRU,須要在內存中維護一個全部頁面的鏈表。當一個頁面被訪問時,將這個頁面移到鏈表表頭。這樣就能保證鏈表表尾的頁面是最近最久未訪問的。

由於每次訪問都須要更新鏈表,所以這種方式實現的 LRU 代價很高。

<figure></figure>

3. 最近未使用

NRU, Not Recently Used

每一個頁面都有兩個狀態位:R 與 M,當頁面被訪問時設置頁面的 R=1,當頁面被修改時設置 M=1。其中 R 位會定時被清零。能夠將頁面分紅如下四類:

  • R=0,M=0
  • R=0,M=1
  • R=1,M=0
  • R=1,M=1

當發生缺頁中斷時,NRU 算法隨機地從類編號最小的非空類中挑選一個頁面將它換出。

NRU 優先換出已經被修改的髒頁面(R=0,M=1),而不是被頻繁使用的乾淨頁面(R=1,M=0)。

4. 先進先出

FIFO, First In First Out

選擇換出的頁面是最早進入的頁面。

該算法會將那些常常被訪問的頁面也被換出,從而使缺頁率升高。

5. 第二次機會算法

FIFO 算法可能會把常用的頁面置換出去,爲了不這一問題,對該算法作一個簡單的修改:

當頁面被訪問 (讀或寫) 時設置該頁面的 R 位爲 1。須要替換的時候,檢查最老頁面的 R 位。若是 R 位是 0,那麼這個頁面既老又沒有被使用,能夠馬上置換掉;若是是 1,就將 R 位清 0,並把該頁面放到鏈表的尾端,修改它的裝入時間使它就像剛裝入的同樣,而後繼續從鏈表的頭部開始搜索。

<figure></figure>

6. 時鐘

Clock

第二次機會算法須要在鏈表中移動頁面,下降了效率。時鐘算法使用環形鏈表將頁面鏈接起來,再使用一個指針指向最老的頁面。

<figure></figure>

分段

虛擬內存採用的是分頁技術,也就是將地址空間劃分紅固定大小的頁,每一頁再與內存進行映射。

下圖爲一個編譯器在編譯過程當中創建的多個表,有 4 個表是動態增加的,若是使用分頁系統的一維地址空間,動態增加的特色會致使覆蓋問題的出現。

<figure></figure>

分段的作法是把每一個表分紅段,一個段構成一個獨立的地址空間。每一個段的長度能夠不一樣,而且能夠動態增加。

<figure></figure>

段頁式

程序的地址空間劃分紅多個擁有獨立地址空間的段,每一個段上的地址空間劃分紅大小相同的頁。這樣既擁有分段系統的共享和保護,又擁有分頁系統的虛擬內存功能。

分頁與分段的比較

  • 對程序員的透明性:分頁透明,可是分段須要程序員顯示劃分每一個段。
  • 地址空間的維度:分頁是一維地址空間,分段是二維的。
  • 大小是否能夠改變:頁的大小不可變,段的大小能夠動態改變。
  • 出現的緣由:分頁主要用於實現虛擬內存,從而得到更大的地址空間;分段主要是爲了使程序和數據能夠被劃分爲邏輯上獨立的地址空間而且有助於共享和保護。

5、設備管理

磁盤結構

  • 盤面(Platter):一個磁盤有多個盤面;
  • 磁道(Track):盤面上的圓形帶狀區域,一個盤面能夠有多個磁道;
  • 扇區(Track Sector):磁道上的一個弧段,一個磁道能夠有多個扇區,它是最小的物理儲存單位,目前主要有 512 bytes 與 4 K 兩種大小;
  • 磁頭(Head):與盤面很是接近,可以將盤面上的磁場轉換爲電信號(讀),或者將電信號轉換爲盤面的磁場(寫);
  • 制動手臂(Actuator arm):用於在磁道之間移動磁頭;
  • 主軸(Spindle):使整個盤面轉動。

<figure></figure>

磁盤調度算法

讀寫一個磁盤塊的時間的影響因素有:

  • 旋轉時間(主軸轉動盤面,使得磁頭移動到適當的扇區上)
  • 尋道時間(制動手臂移動,使得磁頭移動到適當的磁道上)
  • 實際的數據傳輸時間

其中,尋道時間最長,所以磁盤調度的主要目標是使磁盤的平均尋道時間最短。

1. 先來先服務

FCFS, First Come First Served

按照磁盤請求的順序進行調度。

優勢是公平和簡單。缺點也很明顯,由於未對尋道作任何優化,使平均尋道時間可能較長。

2. 最短尋道時間優先

SSTF, Shortest Seek Time First

優先調度與當前磁頭所在磁道距離最近的磁道。

雖然平均尋道時間比較低,可是不夠公平。若是新到達的磁道請求老是比一個在等待的磁道請求近,那麼在等待的磁道請求會一直等待下去,也就是出現飢餓現象。具體來講,兩端的磁道請求更容易出現飢餓現象。

<figure></figure>

3. 電梯算法

SCAN

電梯老是保持一個方向運行,直到該方向沒有請求爲止,而後改變運行方向。

電梯算法(掃描算法)和電梯的運行過程相似,老是按一個方向來進行磁盤調度,直到該方向上沒有未完成的磁盤請求,而後改變方向。

由於考慮了移動方向,所以全部的磁盤請求都會被知足,解決了 SSTF 的飢餓問題。

<figure></figure>

6、連接

編譯系統

如下是一個 hello.c 程序:

#include <stdio.h>int main(){    printf("hello, world");    return 0;}

在 Unix 系統上,由編譯器把源文件轉換爲目標文件。

gcc -o hello hello.c

這個過程大體以下:

<figure></figure>

  • 預處理階段:處理以 # 開頭的預處理命令;
  • 編譯階段:翻譯成彙編文件;
  • 彙編階段:將彙編文件翻譯成可重定向目標文件;
  • 連接階段:將可重定向目標文件和 printf.o 等單獨預編譯好的目標文件進行合併,獲得最終的可執行目標文件。

靜態連接

靜態連接器以一組可重定向目標文件爲輸入,生成一個徹底連接的可執行目標文件做爲輸出。連接器主要完成如下兩個任務:

  • 符號解析:每一個符號對應於一個函數、一個全局變量或一個靜態變量,符號解析的目的是將每一個符號引用與一個符號定義關聯起來。
  • 重定位:連接器經過把每一個符號定義與一個內存位置關聯起來,而後修改全部對這些符號的引用,使得它們指向這個內存位置。

<figure></figure>

目標文件

  • 可執行目標文件:能夠直接在內存中執行;
  • 可重定向目標文件:可與其它可重定向目標文件在連接階段合併,建立一個可執行目標文件;
  • 共享目標文件:這是一種特殊的可重定向目標文件,能夠在運行時被動態加載進內存並連接;

動態連接

靜態庫有如下兩個問題:

  • 當靜態庫更新時那麼整個程序都要從新進行連接;
  • 對於 printf 這種標準函數庫,若是每一個程序都要有代碼,這會極大浪費資源。

共享庫是爲了解決靜態庫的這兩個問題而設計的,在 Linux 系統中一般用 .so 後綴來表示,Windows 系統上它們被稱爲 DLL。它具備如下特色:

  • 在給定的文件系統中一個庫只有一個文件,全部引用該庫的可執行目標文件都共享這個文件,它不會被複制到引用它的可執行文件中;
  • 在內存中,一個共享庫的 .text 節(已編譯程序的機器代碼)的一個副本能夠被不一樣的正在運行的進程共享。

<figure></figure>

</section>

<section data-role="outer">

文能碼字,武能coding,是我黃小斜,不是黃老邪噢。

推薦閱讀:

是時候爲本身的後半生考慮了——致奔三的互聯網人

搞定計算機網絡面試,看這篇就夠了

</section>

  

<section data-tools="135編輯器" data-id="94248">

<section>

<section></section>

<section>

<section>

<section data-brushtype="text">喜歡本文的話,就點一下「在看」吧</section>

</section>

</section>

</section>

</section>

</section>

</section>

相關文章
相關標籤/搜索