1. 程式人生 > >面試整理之作業系統(一)

面試整理之作業系統(一)

前言

前幾天騰訊第3次一面通過了然後被取消趕著走校招路線= =

還是要繼續努力的,把之前整理的作業系統面試知識點放在部落格上

知識點主要整理自教材,以及零零碎碎參考了一些部落格,等晚點再放參考連結。

1、程序和執行緒的區別

  • 程序:一個程式在一個數據集上的一次執行過程。系統資源分配的單位。

    一個程式在不同資料集合上執行或一個程式在同樣資料集上的多次執行都是不同的程序。

    程序是獨立的,有自己的記憶體空間和上下文環境,無法獲取其他程序的儲存空間。同一程序的兩段程式碼不能同時執行,除非引入執行緒。

  • 執行緒:程序的一個實體,是被系統獨立排程和執行的基本單位,CPU使用的基本單位。

    同一程序的執行緒可以共享同一記憶體空間。執行緒是屬於程序的,當程序退出時該程序所產生的執行緒都會被強制退出並清除。執行緒佔用的資源少於程序佔用的資源

程序和執行緒可以有優先順序。

2、記憶體中的程序結構

  • 自上而下: 棧 –> <– 堆 資料 文字

  • 棧:函式引數、返回地址、區域性變數(執行入口知道大小)

  • 堆:執行期間動態分配的記憶體空間(執行的時候才知道大小)

  • 資料段:全域性變數、靜態變數、常量(編譯後知道大小)(未初始化的在一個區域(.bss),初始化的在相鄰區域(.data))

    • 全域性變數:定義在函式外面,其他檔案也能使用(external linkage)
    • 靜態變數:static 關鍵字修飾的變數:
      函式外定義:全域性變數,只在當前檔案中可見( internal linkage)
      函式內定義:全域性變數,只在此函式內可見
      (C++)類中定義:全域性變數,只在此類中可見
  • 文字段:程式的二進位制程式碼段

程式還包含當前活動,通過程式計數器的值和處理器暫存器的內容表。

程序地址空間:核心地址空間+使用者地址空間(程式碼段、資料段、堆、棧、共享庫)

堆和棧的區別

棧:函式引數、返回地址、區域性變數(執行入口知道大小)
1. 編譯器自動分配釋放,存放函式的引數值,區域性變數的值等。
2. 申請後的響應:若棧的剩餘空間大於申請空間,系統將為程式提供記憶體,否則提示棧溢位
3. 大小限制:向低地址擴充套件,連續的記憶體區域,棧頂地址和棧最大容量是系統事先規定好的。如果申請的空間超過棧的剩餘空間將棧溢位
4. 申請效率:系統自動分配,速度快,程式設計師無法控制
5. 儲存的內容:函式呼叫時進棧順序:主函式下一條指令的地址(函式呼叫語句的下一條可執行語句)、函式的各個引數(大多數c編譯器中引數是從右往左入棧)、函式的區域性變數。
呼叫結束的出棧順序:區域性變數、函式引數(從左到右)、棧頂指標指向最開始存的地址(即主函式的下一條指令)

堆:執行期間動態分配的記憶體空間(執行的時候才知道大小)
1. 程式設計師自己分配釋放,分配方式類似於連結串列
2. 申請後的響應:作業系統有一個記錄空閒記憶體地址的連結串列,當系統收到程式的申請時會遍歷該連結串列,尋找第一個空間大於所申請空間的堆結點,將該結點從空閒結點連結串列刪除,分配該結點的空間給程式。會在這塊記憶體空間中的首地址處記錄本次分配的大小。
這樣delete語句才能正確釋放本記憶體空間。由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動將多餘的那部分重新放入空閒連結串列中
3. 大小限制:向高地址擴充套件,不連續的記憶體區域,用連結串列儲存,遍歷方向是由低地址向高地址。堆大小受限於計算機系統的有效虛擬記憶體。獲得的空間更大更靈活
4. 申請效率:用new分配,速度慢,容易產生內部碎片,使用方便
5. 儲存的內容:一般在堆的頭部用一個位元組放堆的大小

3、pcb程序控制塊

  1. 程序標誌 程序狀態 程式計數器 暫存器
  2. cpu排程資訊:程序優先順序、排程佇列指標、其他排程引數
  3. 記憶體管理資訊:基址暫存器 界限暫存器 頁表/段表
  4. 記賬資訊:cpu時間、實際使用時間、時間界限、記賬資料、作業或程序數量
  5. I/O狀態資訊:分配給程序的IO裝置列表、開啟檔案列表

上下文context切換

cpu硬體狀態從一個程序換到另一個,執行的程序硬體狀態儲存在cpu暫存器中,不執行時儲存在pcb中,之後可推送至cpu暫存器

將cpu切換到另一個程序需要儲存當前程序的狀態並恢復另一個程序的狀態。當發生一箇中斷,系統需要儲存當前執行在cpu中程序的上下文,從而在處理完後能恢復上下文,即先中斷程序再繼續。

程序上下文用pcb表示

程序切換:切換全域性頁目錄載入新地址空間,切換核心棧和硬體上下文;程序a切換到b,儲存a上下文環境,更新a的pcb,a移至合適佇列,b設為執行態,從b的pcb恢復上下文。

3、程序狀態

建立(資訊設定完但資源有限)執行(佔用cpu)就緒(等待分配cpu) 等待(等待某個是啊金的傳送) 終止(程序完成執行)

程序狀態圖

程序的建立需資源:cpu時間、記憶體、檔案、IO裝置
在一個程序建立子程序時,子程序可能從作業系統那裡直接獲得資源,或從父程序獲取資源;父程序可能在子程序之間分配共享資源;
初始化資料由父程序傳遞給子程序

  • 程序層次結構:程序由其他程序建立,unix:以init為根,windows各程序地位相同

  • 程序映像:程序地址空間、硬體暫存器、pcb和各種資料結構、進入程序時所需的核心棧

4、程序間的通訊如何實現?(IPC)

程序間的通訊方式有:訊號,訊號量、訊息佇列、共享記憶體。

訊號和訊號量可以實現同步互斥,訊號是用訊號處理器來進行的,訊號量是用PV操作來實現的;
訊息佇列是比較高階的通訊方法,可被多個程序共享(IPC),一個程序訊息太多,可以用於多於一個的訊息佇列;
共享訊息佇列的程序所傳送的訊息除了message本身外還有一個標誌,這個標誌可以指明該訊息將由哪個程序或哪類程序接受,每個共享訊息佇列的程序針對這個佇列也有自己的標誌,可以用來宣告自己的身份。

管道(pipe):半雙工通訊,資料單向流動;只能父子程序通訊;速度慢
流管道(s_pipe):去除第一種,可雙向傳遞
有名管道(FIFO):任何程序都能通訊;速度慢

訊號量(semophore):計數器,控制多個程序對共享資源的訪問(多程序或執行緒的同步方法);不能傳遞複雜訊息

訊息佇列:訊息的連結串列,存放在核心中並由訊息佇列識別符號標識。訊息佇列克服了訊號傳遞少、管道只能承載無格式位元組流以及緩衝區大小受限的缺點;容量受限,第一次讀的時候要考慮上一次沒有讀完資料的問題
需要訊息複製,不需考慮同步問題,不適宜資訊量大或操作頻繁的場合

訊號:用於通知接收程序某個事件已經發送

共享記憶體:對映一段能被其他程序訪問的記憶體,由一個程序建立,可被多個程序訪問,要保持同步。與訊號量結合使用,用來達到程序間的同步及互斥,最快的IPC方式
不需要訊息複製,資訊量大,快捷,在任意數量的程序之間進行高效雙向通訊的機制。

socket:可用於不同機器間的程序通訊,由ip地址和埠號連線而成
telnet 23 http 80 ftp 21 ssh22 hhtps 443
所有連線都有唯一的一對socket

5、程序排程

選擇一個可用的程序到cpu上執行
程序進入系統,會被加入到作業佇列(包括系統的所有程序)佇列通常用連結串列實現,頭結點指向的連結串列的第一個和最後一個pcb塊的指標,每個pcb包括一個指向就緒佇列的下一個pcb的指標域

執行—>就緒:IO請求(–>IO佇列–>IO結束);時間片結束;建立一個子程序(等待子程序結束);等待中斷(中斷髮生)

6、執行緒

  • 執行緒由執行緒ID、程式計數器、暫存器集合(處理器狀態)和棧(使用者棧+核心堆疊),私有儲存區域(為各種執行時庫和動態連結庫(DLL)所用)組成;共享程序的地址空間,程式碼段、資料段等其他資源,如開啟檔案和訊號;程式以單執行緒程序開始,執行緒由執行緒建立和撤銷

    執行緒的上下文:暫存器集合、棧、私有儲存區域

  • 多執行緒程式設計:
    響應度高:部分執行緒阻塞或執行較冗長的操作,該程式仍能繼續執行,增加對使用者的響應度
    資源共享:執行緒預設共享它們所屬程序的記憶體和資源
    經濟:程序建立所需要的記憶體和資源分配比較昂貴,執行緒能共享資源,建立和切換執行緒開銷小
    多處理器體系結構的利用:在多cpu上使用多執行緒增強了併發功能

  • 執行緒池
    多執行緒伺服器d)在程序開始時建立一定數量的執行緒,並放入到池中以等待工作,伺服器收到請求的時候會喚醒池中的一個執行緒並將處理的請求傳給它;執行緒完成服務後返回池中再等待工作。如果池中沒有可用的執行緒,那麼伺服器會一直等待直到有可用執行緒。

  • 引入執行緒:減少開銷(建立和切換花費時間少,通訊不需要核心)、提高效能(多處理器)、應用需要(web伺服器)

  • 執行緒的實現:
    unix:使用者級別,核心無法感知執行緒存在,切換較快,同進程的執行緒不能分到多cpu上,阻塞會阻塞整個程序;
    windows:核心級執行緒,核心中包含執行緒表,排程以執行緒為單位

7、互斥和同步

sem_post(sem_t *sem)

增加訊號量的值,當有執行緒阻塞在這個訊號量時,呼叫這個函式會使其中一個執行緒不再阻塞(選擇機制由執行緒排程機制決定)

sem_wait(sem_t *sem)

等待訊號量的值大於0,以原子操作將訊號量減1

對二進位制訊號量mutex:

pthread_mutex_lock(pthread_mutex_t *mutex);加鎖
pthread_mutex_unlock(pthread_mutex_t *mutex);釋放鎖

消費者生產者程式碼

producer:
sem_wait(&empty);
pthread_mutex_lock(&mutex);
// producing...
pthread_mutex_unlock(&mutex);
sem_post(&full);

consumer:
sem_wait(&full);
pthread_mutex_lock(&mutex);
// consuming...
pthread_mutex_unlock(&mutex);
sem_post(&empty);

寫者讀者程式碼

寫寫互斥、讀寫互斥、讀讀允許

  • 讀者優先:除非寫者正在寫檔案,否則讀者不需要等待

用一個read_count記錄讀者數目,為0時方可釋放等待佇列的一個寫者,讀者讀檔案時read_count++,因此需要一個mutex來實現對全域性變數read_coun修改時的互斥。

寫寫互斥:臨界物件wmutex

讀寫互斥:臨界物件fmutex

read_file() {
    pthread_mutex_lock(&mutex);
    if (read_count == 0) {
        sem_wait(&fmutex);// 等待寫者釋放fmutex
    }
    ++read_count;
    pthread_mutex_unlock(&mutex);
    // reading...
    pthread_mutex_lock(&mutex);
    --read_count;
    if (read_count == 0) {
        sem_post(&fmutex);  // 讀完就釋放fmutex
    }
    pthread_mutex_unlock(&mutex);
}

write_file() {
    sem_wait(&wmutex);// 阻塞寫程序,保證只有一個寫程序阻塞在fmutex
    sem_wait(&fmutex);
    // writing...
    sem_post(&fmutex);
    sem_post(&wmutex);
}
  • 寫者優先:除非讀者正在寫檔案,否則讀者不需要等待

用一個write_count記錄寫者數目,為0時方可釋放等待佇列的一個讀者,寫者寫檔案時write_count++,因此需要一個mutex來實現對全域性變數write_coun修改時的互斥。

寫寫互斥:臨界物件wmutex

讀寫互斥:臨界物件fmutex

read_file() {
    while (true) {
        pthread_mutex_lock(&mutex);
        pthread_mutex_unlock(&mutex);
        pthread_mutex_lock(&mutex1);
        if (read_count == 0) {
            sem_wait(&fmutex);
        }
        pthread_mutex_unlock(&mutex1);
        // reading...
        pthread_mutex_lock(&mutex1);
        --read_count;
        if (read_count == 0) {
            sem_wait(&fmutex);
        }
        pthread_mutex_unlock(&mutex1);
    }
}

write_file() {
    pthread_mutex_lock(&mutex);
    if (write_count == 0) {
        sem_wait(&fmutex);
    }
    ++write_count;
    pthread_mutex_unlock(&mutex);

    sem_wait(&wmutex);  // 阻塞寫程序,只有一個能等fmutex的釋放  
    // writing...
    sem_post(&wmutex);

    pthread_mutex_lock(&mutex);
    --write_count;
    if (write_count) {
        sem_post(&fmutex);
    }
    pthread_mutex_unlock(&mutex);
}

8、執行緒同步的方式

互斥量:採用互斥物件機制,只有擁有互斥物件的執行緒才有訪問公共資源的許可權。互斥物件只有一個,所以可以保證公共資源不會被多個執行緒同時訪問。

訊號量:允許多個執行緒訪問同一資源,但需要控制同一時刻訪問此資源的最大執行緒數量。

事件(訊號):通過通知操作的方式來保持多執行緒同步,還能方便實現多執行緒優先順序的比較操作

自旋鎖
多處理器:獲取/釋放自旋鎖;單處理器:禁止/使用核心搶佔

  • mutex(互斥器)和臨界區(critical section)區別

mutex是用於程序之間互斥
臨界區是用於執行緒之間互斥

  • 處理器排程
    cpu排程:在合適的排程時機,按排程演算法,排程就緒佇列中的程序進cpu

9、排程演算法:

FCFS先來先服務、SJF最短作業優先(下一cpu區間長度)

優先順序排程
無窮阻塞/飢餓:可以執行無窮等待cpu——>解決=>ji老化:增加在等待很長時間的程序的優先順序

多級佇列排程:將就緒佇列分成多個獨立佇列【分優先順序,優先順序高的佇列先執行/佇列之間劃分時間片】

多級反饋佇列:根據不同cpu區間的特點劃分程序。如果程序使用過多的cpu時間,會被轉移到更低優先順序佇列

SRTN最短剩餘時間優先、最高響應比優先HRRN 批處理看重吞吐量、週轉時間、cpu利用率、平衡

輪轉RR、最高優先順序HPF、最短程序優先SPN 互動式系統看重響應時間、平衡

  • LAST最短剩餘時間作業優先

  • 垃圾回收的優點和原理,兩種回收機制

垃圾回收可以有效防止記憶體洩露,有效使用可用的記憶體。
垃圾回收器作為一個單獨的低級別的執行緒執行,在不可預知的情況下對記憶體堆中已經死亡的或者長時間沒有使用的物件進行清除和回收,程式設計師不能實時呼叫垃圾回收器對某個物件或所有物件進行垃圾回收。
回收機制有分代複製垃圾回收,標記垃圾回收和增量垃圾回收3種。

  • 快表—-cache OS
    為提高系統的存取速度,在地址對映機制中增加一個小容量的聯想暫存器(快表),用來存放當前訪問最頻繁的少數活動頁面的頁號。當某使用者需要存取資料時,根據資料所在的邏輯頁號在塊表中找到其對應的記憶體塊號,再聯絡頁內地址,形成實體地址。如果在快表中沒有相應的邏輯頁號,地址對映仍可以通過記憶體中的頁表進行,得到空閒塊號後將該塊號填入塊表的空閒塊中。如果快表中沒有空閒塊,則根據淘汰演算法淘汰某一行,再填入新的頁號和塊號。

  • 高速緩衝儲存器—-Cache CPU
    位於cpu和記憶體之間的臨時儲存器,容量小於記憶體但交換速度快,在cache中的資料是記憶體中的一小部分,但這一小部分是短時間內cpu即將訪問的。當cpu呼叫大量資料時,可以避開記憶體直接從cache中呼叫,加快讀取速度。

  • DLL檔案 動態連結庫檔案
    不能單獨執行的檔案,允許程式共享執行特殊任務所必需的程式碼和其他資源,比較大的應用程式都由很多模組組成

靜態呼叫方式:由編譯系統完成對dll的載入和應用程式結束時DLL解除安裝的編碼
動態呼叫方式:由編譯者用API函式載入和解除安裝DLL來達到呼叫DLL的目的,使用上較複雜,更能有效使用記憶體

  • cpu狀態:核心態、使用者態

  • 中斷機制:cpu暫停執行當前程式,保留現場,硬體自動轉去處理程式,處理完後回到斷點,繼續被打斷的程式

10、臨界區問題

【共享資料的互斥】
臨界區:在該區中程序可能改變共同變數、更新一個表、寫一個檔案;沒有兩個程序能同時在臨界區內執行。

do {
    【進入區】 // 請求允許進入其臨界區
    臨界區
    【退出區】
    剩餘區
} while (true);

eg. Peterson演算法

int turn;           // 表示哪個程序可以進入臨界區
boolean flag[2];    // 表示哪個程序想要進入臨界區

// Pi程序的結構-------------------------------

do {
    // 進入區
    flag[i] = true;
    turn = j;       
    while (flag[i] && turn == j);

    // 臨界區
    flag[i] = false;        // Pi最多在Pj進入臨界區一次後就能進入---有限等待
    // 剩餘區
} while (true);

滿足3個要求:
互斥(程序在臨界區內執行,其他程序就不能在其臨界區內執行)
前進(如果沒有程序在其臨界區執行且有程序需進入臨界區,那麼只有那些不在剩餘區內執行的程序可參加選擇,以確定誰能下一個進入臨界區,且這種不能無線)
有限等待:從一個程序請求允許進入臨界區到進入臨界區為止,其他程序允許進入其臨界區的次數有限

  • 訊號量
    訊號量S是一個整數型變數,訊號量分為計數訊號量(初始化為可用資源的數量)和二進位制訊號量(互斥鎖)。
    除了初始化外,只能通過兩個標準【原子】操作:wait()和signal()來訪問(這些操作被成為P測試和V增加)
// 程序需要資源的時候
wait(S) {
    while (S <= 0); // 被阻塞----忙等待
    S--;
}
// 程序釋放資源的時候
signal(S) {
    S++;
}

這裡定義的訊號量【自旋鎖】的主要缺點:
忙等待:當一個程序位於其臨界區內時,其他試圖進入臨界區的程序需要在進入區連續第迴圈,浪費了cpu時鐘
優點:程序在等待鎖時不需要上下文切換,節省時間(如果鎖佔用時間短)

克服忙等:程序訊號量不為正時不忙等二十阻塞自己,放入一個與訊號量相關的等待佇列中,狀態為等待。

死鎖

是多個程序無限等待一個事件,而該事件只能由這些程序之一產生。

處理死鎖的方法:防止發生或發生後處理

產生死鎖有4個必要條件

  1. 互斥條件:一個資源每次只能被一個程序使用【無法被破壞】
  2. 請求與保持條件:一個程序因請求被其他程序佔有的資源而阻塞時,對已獲得的資源保持不放【資源靜態分配、防止程序在等待狀態下佔用資源】
  3. 非搶佔條件:資源只能在程序完成時自動釋放,不能被搶佔【允許進搶佔其他程序佔有的資源】
  4. 迴圈等待條件:若干個程序之間形成一個首尾相接的迴圈等待關係【資源有序分配】

哲學家進餐問題
五個哲學家圓桌5個筷子,只有同時拿到左右兩個筷子才可以吃飯;

semaphore chopstick[5];
do {
    wait(chopstick[i]);
    wait(chopstick[i+1]%5);
    // eat
    signal(chopstick[i]);
    signal(chopstick[i+1]%5);
    // think
} while (true);

假如5個哲學家同時拿起左邊的筷子,所有哲學家都在等待右邊的筷子,會永遠等待。

解決方法:最多值允許4個哲學家同時坐在桌上;只有兩個筷子都可以使用時才允許一個哲學家拿起它們;使用非對稱解決方法:奇數哲學家先拿起左邊的筷子再拿起右邊的筷子,偶數哲學家相反。

銀行家演算法 檢查申請者對資源的最大需求量,若系統現存的各類資源可以滿足申請者的需求,就滿足申請者的需求。這樣申請者可以很快完成計算,然後釋放它佔用的資源,從而保證了系統中所有程序能完成,避免死鎖的發生。

  • 死鎖

四個條件:

互斥:資源一次只有一個程序使用,不能共享
佔有並等待:一個程序至少佔有一個資源並等待另一資源,而該資源被另一程序佔有【一個程序申請一個資源時不能佔有其他資源】
非搶佔:資源只能等程序完成任務自動釋放,不能搶佔【可以搶佔】
迴圈等待:有一組迴圈等待的程序【對所有資源型別進行完全排序,要求每個程序按遞增順序來申請資源】

四個條件全部滿足才會出現死鎖

10、記憶體管理


  • 基地址暫存器:最小的合法實體記憶體地址
    界限地址暫存器:決定範圍大小

MMU(記憶體管理單元):虛擬地址到實體地址的對映

動態儲存分配:首次適應、最佳適應、最差適應

緊縮:移動記憶體內容,以便所有空閒空間合併成一塊(程序移到記憶體的一端,將所有的孔移到記憶體的另一端,已生成一個大的空閒塊)。僅在重定位是動態並在執行時可採用。

  • 分頁
    所要求的內容不是頁的整數倍,最後一個幀用不完,會有內部碎片

邏輯記憶體:頁 —————-> 實體記憶體:幀
【頁號】[頁表的索引]—->基地址+【頁偏移】

訪問一個位元組:—>頁表—>位元組(兩次記憶體訪問)===>TLB

  • 分段

不同長度沒有一定順序的段的集合,每個段都是動態分配,產生外部碎片

邏輯地址 ——————–> 實體地址
段號s和段內偏移d ——->【段表】->基地址+界限暫存器—->d是否合法

  • 頁面排程演算法

    • FIFO演算法:先入先出,淘汰最早調入的頁面
    • OPT演算法:預測未來,選最遠將使用的頁淘汰(最優)
    • LRU演算法:用過去預測未來,將最近最長時間沒有使用的頁淘汰(最近最少使用)
  • 顛簸(抖動):頻繁的頁面更換

  • Belady現象:對有的頁面置換演算法,頁錯誤率可能會隨著分配幀數增加而增加。
    FIFO會產生Belady異常;棧式演算法無Belady異常:LRU,LFU(最不經常使用),OPT。

參考