5萬字、97 張圖總結操做系統核心知識點

5萬字、97 張圖總結操做系統核心知識點

這是Java建設者的第101篇原創node

這不是一篇教你如何建立一個操做系統的文章,相反,這是一篇指導性文章,教你從幾個方面來理解操做系統。首先你須要知道你爲何要看這篇文章以及爲何要學習操做系統。程序員

5萬字、97 張圖總結操做系統核心知識點

高清大圖見文末算法

搞清楚幾個問題
首先你要搞明白你學習操做系統的目的是什麼?操做系統的重要性如何?學習操做系統會給我帶來什麼?下面我會從這幾個方面爲你回答下。shell

操做系統也是一種軟件,可是操做系統是一種很是複雜的軟件。操做系統提供了幾種抽象模型數據庫

  • 文件:對 I/O 設備的抽象
  • 虛擬內存:對程序存儲器的抽象
  • 進程:對一個正在運行程序的抽象
  • 虛擬機:對整個操做系統的抽象
    這些抽象和咱們的平常開發息息相關。搞清楚了操做系統是如何抽象的,才能培養咱們的抽象性思惟和開發思路。

不少問題都和操做系統相關,操做系統是解決這些問題的基礎。若是你不學習操做系統,可能會想着從框架層面來解決,那是你瞭解的還不夠深刻,當你學習了操做系統後,可以培養你的全局性思惟。編程

學習操做系統咱們可以有效的解決併發問題,併發幾乎是互聯網的重中之重了,這也從側面說明了學習操做系統的重要性。數組

學習操做系統的重點不是讓你從頭製造一個操做系統,而是告訴你「操做系統是如何工做的」,可以讓你對計算機底層有所瞭解,打實你的基礎。緩存

相信你必定清楚什麼是編程安全

「Data structures + Algorithms = Programming」服務器

操做系統內部會涉及到衆多的數據結構和算法描述,可以讓你瞭解算法的基礎上,讓你編寫更優秀的程序。

我認爲能夠把計算機比做一棟樓

計算機的底層至關於就是樓的根基,計算機應用至關於就是樓的外形,而操做系統就至關因而告訴你大樓的構造原理,編寫高質量的軟件就至關因而告訴你構建一個穩定的房子。

認識操做系統
在瞭解操做系統前,你須要先知道一下什麼是計算機系統:現代計算機系統由「一個或多個處理器、主存、打印機、鍵盤、鼠標、顯示器、網絡接口以及各類輸入/輸出設備構成的系統」。這些都屬於硬件的範疇。咱們程序員不會直接和這些硬件打交道,而且每位程序員不可能會掌握全部計算機系統的細節。

因此計算機科學家在硬件的基礎之上,安裝了一層軟件,這層軟件可以根據用戶輸入的指令達到控制硬件的效果,從而知足用戶的需求,這樣的軟件稱爲 操做系統,它的任務就是爲用戶程序提供一個更好、更簡單、更清晰的計算機模型。也就是說,操做系統至關因而一箇中間層,爲用戶層和硬件提供各自的藉口,屏蔽了不一樣應用和硬件之間的差別,達到統一標準的做用。

5萬字、97 張圖總結操做系統核心知識點

上面一個操做系統的簡化圖,最底層是硬件,硬件包括「芯片、電路板、磁盤、鍵盤、顯示器」等咱們上面提到的設備,在硬件之上是軟件。大部分計算機有兩種運行模式:內核態 和 用戶態,軟件中最基礎的部分是操做系統,它運行在 內核態 中。操做系統具備硬件的訪問權,能夠執行機器可以運行的任何指令。軟件的其他部分運行在 用戶態 下。

在大概瞭解到操做系統以後,咱們先來認識一下硬件都有哪些

計算機硬件
計算機硬件是計算機的重要組成部分,其中包含了 5 個重要的組成部分:「運算器、控制器、存儲器、輸入設備、輸出設備」。

  • 運算器:運算器最主要的功能是對數據和信息進行加工和運算。它是計算機中執行算數和各類邏輯運算的部件。運算器的基本運算包括加、減、乘、除、移位等操做,這些是由 算術邏輯單元(Arithmetic&logical Unit) 實現的。而運算器主要由算數邏輯單元和寄存器構成。
  • 控制器:指按照指定順序改變主電路或控制電路的部件,它主要起到了控制命令執行的做用,完成協調和指揮整個計算機系統的操做。控制器是由程序計數器、指令寄存器、解碼譯碼器等構成。

    運算器和控制器共同組成了 CPU

  • 存儲器:存儲器就是計算機的記憶設備,顧名思義,存儲器能夠保存信息。存儲器分爲兩種,一種是主存,也就是內存,它是 CPU 主要交互對象,還有一種是外存,好比硬盤軟盤等。下面是現代計算機系統的存儲架構

  • 輸入設備:輸入設備是給計算機獲取外部信息的設備,它主要包括鍵盤和鼠標。

  • 輸出設備:輸出設備是給用戶呈現根據輸入設備獲取的信息通過一系列的計算後獲得顯示的設備,它主要包括顯示器、打印機等。

這五部分也是馮諾伊曼的體系結構,它認爲計算機必須具備以下功能:

把須要的程序和數據送至計算機中。必須具備長期記憶程序、數據、中間結果及最終運算結果的能力。可以完成各類算術、邏輯運算和數據傳送等數據加工處理的能力。可以根據須要控制程序走向,並能根據指令控制機器的各部件協調操做。可以按照要求將處理結果輸出給用戶。
5萬字、97 張圖總結操做系統核心知識點

下面是一張 intel 家族產品圖,是一個詳細的計算機硬件分類,咱們在根據圖中涉及到硬件進行介紹

5萬字、97 張圖總結操做系統核心知識點

  • 總線(Buses):在整個系統中運行的是稱爲總線的電氣管道的集合,這些總線在組件之間來回傳輸字節信息。一般總線被設計成傳送定長的字節塊,也就是 字(word)。字中的字節數(字長)是一個基本的系統參數,各個系統中都不盡相同。如今大部分的字都是 4 個字節(32 位)或者 8 個字節(64 位)。
    5萬字、97 張圖總結操做系統核心知識點

  • I/O 設備(I/O Devices):Input/Output 設備是系統和外部世界的鏈接。上圖中有四類 I/O 設備:用於用戶輸入的鍵盤和鼠標,用於用戶輸出的顯示器,一個磁盤驅動用來長時間的保存數據和程序。剛開始的時候,可執行程序就保存在磁盤上。

每一個I/O 設備鏈接 I/O 總線都被稱爲控制器(controller) 或者是 適配器(Adapter)。控制器和適配器之間的主要區別在於封裝方式。控制器是 I/O 設備自己或者系統的主印製板電路(一般稱做主板)上的芯片組。而適配器則是一塊插在主板插槽上的卡。不管組織形式如何,它們的最終目的都是彼此交換信息。

  • 主存(Main Memory),主存是一個臨時存儲設備,而不是永久性存儲,磁盤是 永久性存儲 的設備。主存既保存程序,又保存處理器執行流程所處理的數據。從物理組成上說,主存是由一系列 DRAM(dynamic random access memory) 動態隨機存儲構成的集合。邏輯上說,內存就是一個線性的字節數組,有它惟一的地址編號,從 0 開始。通常來講,組成程序的每條機器指令都由不一樣數量的字節構成,C 程序變量相對應的數據項的大小根據類型進行變化。好比,在 Linux 的 x86-64 機器上,short 類型的數據須要 2 個字節,int 和 float 須要 4 個字節,而 long 和 double 須要 8 個字節。

  • 處理器(Processor),CPU(central processing unit) 或者簡單的處理器,是解釋(並執行)存儲在主存儲器中的指令的引擎。處理器的核心大小爲一個字的存儲設備(或寄存器),稱爲程序計數器(PC)。在任什麼時候刻,PC 都指向主存中的某條機器語言指令(即含有該條指令的地址)。

從系統通電開始,直到系統斷電,處理器一直在不斷地執行程序計數器指向的指令,再更新程序計數器,使其指向下一條指令。處理器根據其指令集體系結構定義的指令模型進行操做。在這個模型中,指令按照嚴格的順序執行,執行一條指令涉及執行一系列的步驟。處理器從程序計數器指向的內存中讀取指令,解釋指令中的位,執行該指令指示的一些簡單操做,而後更新程序計數器以指向下一條指令。指令與指令之間可能連續,可能不連續(好比 jmp 指令就不會順序讀取)

下面是 CPU 可能執行簡單操做的幾個步驟

  • 加載(Load):從主存中拷貝一個字節或者一個字到內存中,覆蓋寄存器先前的內容
  • 存儲(Store):將寄存器中的字節或字複製到主存儲器中的某個位置,從而覆蓋該位置的先前內容
  • 操做(Operate):把兩個寄存器的內容複製到 ALU(Arithmetic logic unit)。把兩個字進行算術運算,並把結果存儲在寄存器中,重寫寄存器先前的內容。

    算術邏輯單元(ALU)是對數字二進制數執行算術和按位運算的組合數字電子電路。

  • 跳轉(jump):從指令中抽取一個字,把這個字複製到程序計數器(PC) 中,覆蓋原來的值
    進程和線程
    關於進程和線程,你須要理解下面這張腦圖中的重點

5萬字、97 張圖總結操做系統核心知識點

進程
操做系統中最核心的概念就是 進程,進程是對正在運行中的程序的一個抽象。操做系統的其餘全部內容都是圍繞着進程展開的。

在多道程序處理的系統中,CPU 會在進程間快速切換,使每一個程序運行幾十或者幾百毫秒。然而,嚴格意義來講,在某一個瞬間,CPU 只能運行一個進程,然而咱們若是把時間定位爲 1 秒內的話,它可能運行多個進程。這樣就會讓咱們產生並行的錯覺。由於 CPU 執行速度很快,進程間的換進換出也很是迅速,所以咱們很難對多個並行進程進行跟蹤。因此,操做系統的設計者開發了用於描述並行的一種概念模型(順序進程),使得並行更加容易理解和分析。

進程模型
一個進程就是一個正在執行的程序的實例,進程也包括程序計數器、寄存器和變量的當前值。從概念上來講,每一個進程都有各自的虛擬 CPU,可是實際狀況是 CPU 會在各個進程之間進行來回切換。

5萬字、97 張圖總結操做系統核心知識點

如上圖所示,這是一個具備 4 個程序的多道處理程序,在進程不斷切換的過程當中,程序計數器也在不一樣的變化。

5萬字、97 張圖總結操做系統核心知識點

在上圖中,這 4 道程序被抽象爲 4 個擁有各自控制流程(即每一個本身的程序計數器)的進程,而且每一個程序都獨立的運行。固然,實際上只有一個物理程序計數器,每一個程序要運行時,其邏輯程序計數器會裝載到物理程序計數器中。當程序運行結束後,其物理程序計數器就會是真正的程序計數器,而後再把它放回進程的邏輯計數器中。

從下圖咱們能夠看到,在觀察足夠長的一段時間後,全部的進程都運行了,「但在任何一個給定的瞬間僅有一個進程真正運行」。

5萬字、97 張圖總結操做系統核心知識點

所以,當咱們說一個 CPU 只能真正一次運行一個進程的時候,即便有 2 個核(或 CPU),「每個核也只能一次運行一個線程」。

因爲 CPU 會在各個進程之間來回快速切換,因此每一個進程在 CPU 中的運行時間是沒法肯定的。而且當同一個進程再次在 CPU 中運行時,其在 CPU 內部的運行時間每每也是不固定的。

這裏的關鍵思想是認識到一個進程所需的條件,進程是某一類特定活動的總和,它有程序、輸入輸出以及狀態。

進程的建立
操做系統須要一些方式來建立進程。下面是一些建立進程的方式

  • 系統初始化(init):啓動操做系統時,一般會建立若干個進程。
  • 正在運行的程序執行了建立進程的系統調用(好比 fork)
  • 用戶請求建立一個新進程:在許多交互式系統中,輸入一個命令或者雙擊圖標就能夠啓動程序,以上任意一種操做均可以選擇開啓一個新的進程,在基本的 UNIX 系統中運行 X,新進程將接管啓動它的窗口。
  • 初始化一個批處理工做
    從技術上講,在全部這些狀況下,讓現有流程執行流程是經過建立系統調用來建立新流程的。該進程多是正在運行的用戶進程,是從鍵盤或鼠標調用的系統進程或批處理程序。這些就是系統調用建立新進程的過程。該系統調用告訴操做系統建立一個新進程,並直接或間接指示在其中運行哪一個程序。

在 UNIX 中,僅有一個系統調用來建立一個新的進程,這個系統調用就是 fork。這個調用會建立一個與調用進程相關的副本。在 fork 後,一個父進程和子進程會有相同的內存映像,相同的環境字符串和相同的打開文件。

在 Windows 中,狀況正相反,一個簡單的 Win32 功能調用 CreateProcess,會處理流程建立並將正確的程序加載到新的進程中。這個調用會有 10 個參數,包括了須要執行的程序、輸入給程序的命令行參數、各類安全屬性、有關打開的文件是否繼承控制位、優先級信息、進程所須要建立的窗口規格以及指向一個結構的指針,在該結構中新建立進程的信息被返回給調用者。「在 Windows 中,從一開始父進程的地址空間和子進程的地址空間就是不一樣的」。

進程的終止
進程在建立以後,它就開始運行並作完成任務。然而,沒有什麼事兒是永不停歇的,包括進程也同樣。進程遲早會發生終止,可是一般是因爲如下狀況觸發的

  • 正常退出(自願的) :多數進程是因爲完成了工做而終止。當編譯器完成了所給定程序的編譯以後,編譯器會執行一個系統調用告訴操做系統它完成了工做。這個調用在 UNIX 中是 exit ,在 Windows 中是 ExitProcess。
  • 錯誤退出(自願的):好比執行一條不存在的命令,因而編譯器就會提醒並退出。
  • 嚴重錯誤(非自願的)
  • 被其餘進程殺死(非自願的) :某個進程執行系統調用告訴操做系統殺死某個進程。在 UNIX 中,這個系統調用是 kill。在 Win32 中對應的函數是 TerminateProcess(注意不是系統調用)。
    進程的層次結構
    在一些系統中,當一個進程建立了其餘進程後,父進程和子進程就會以某種方式進行關聯。子進程它本身就會建立更多進程,從而造成一個進程層次結構。

UNIX 進程體系
在 UNIX 中,進程和它的全部子進程以及子進程的子進程共同組成一個進程組。當用戶從鍵盤中發出一個信號後,該信號被髮送給當前與鍵盤相關的進程組中的全部成員(它們一般是在當前窗口建立的全部活動進程)。每一個進程能夠分別捕獲該信號、忽略該信號或採起默認的動做,即被信號 kill 掉。整個操做系統中全部的進程都隸屬於一個單個以 init 爲根的進程樹。

5萬字、97 張圖總結操做系統核心知識點

  • Windows 進程體系
    相反,Windows 中沒有進程層次的概念,Windows 中全部進程都是平等的,惟一相似於層次結構的是在建立進程的時候,父進程獲得一個特別的令牌(稱爲句柄),該句柄能夠用來控制子進程。然而,這個令牌可能也會移交給別的操做系統,這樣就不存在層次結構了。而在 UNIX 中,進程不能剝奪其子進程的 進程權。(這樣看來,仍是 Windows 比較渣)。

進程狀態
儘管每一個進程是一個獨立的實體,有其本身的程序計數器和內部狀態,可是,進程之間仍然須要相互幫助。當一個進程開始運行時,它可能會經歷下面這幾種狀態

5萬字、97 張圖總結操做系統核心知識點

圖中會涉及三種狀態

  1. 運行態,運行態指的就是進程實際佔用 CPU 時間片運行時
  2. 就緒態,就緒態指的是可運行,但由於其餘進程正在運行而處於就緒狀態
  3. 阻塞態,除非某種外部事件發生,不然進程不能運行
    進程的實現
    操做系統爲了執行進程間的切換,會維護着一張表,這張表就是 進程表(process table)。每一個進程佔用一個進程表項。該表項包含了進程狀態的重要信息,包括程序計數器、堆棧指針、內存分配情況、所打開文件的狀態、帳號和調度信息,以及其餘在進程由運行態轉換到就緒態或阻塞態時所必須保存的信息。

下面展現了一個典型系統中的關鍵字段

5萬字、97 張圖總結操做系統核心知識點

典型的進程表表項中的一些字段

第一列內容與進程管理有關,第二列內容與 存儲管理有關,第三列內容與文件管理有關。

如今咱們應該對進程表有個大體的瞭解了,就能夠在對單個 CPU 上如何運行多個順序進程的錯覺作更多的解釋。與每一 I/O 類相關聯的是一個稱做 中斷向量(interrupt vector) 的位置(靠近內存底部的固定區域)。它包含中斷服務程序的入口地址。假設當一個磁盤中斷髮生時,用戶進程 3 正在運行,則中斷硬件將程序計數器、程序狀態字、有時還有一個或多個寄存器壓入堆棧,計算機隨即跳轉到中斷向量所指示的地址。這就是硬件所作的事情。而後軟件就隨即接管一切剩餘的工做。

當中斷結束後,操做系統會調用一個 C 程序來處理中斷剩下的工做。在完成剩下的工做後,會使某些進程就緒,接着調用調度程序,決定隨後運行哪一個進程。而後將控制權轉移給一段彙編語言代碼,爲當前的進程裝入寄存器值以及內存映射並啓動該進程運行,下面顯示了中斷處理和調度的過程。

  1. 硬件壓入堆棧程序計數器等

  2. 硬件從中斷向量裝入新的程序計數器

  3. 彙編語言過程保存寄存器的值

  4. 彙編語言過程設置新的堆棧

  5. C 中斷服務器運行(典型的讀和緩存寫入)

  6. 調度器決定下面哪一個程序先運行

  7. C 過程返回至彙編代碼

  8. 彙編語言過程開始運行新的當前進程

一個進程在執行過程當中可能被中斷數千次,但關鍵每次中斷後,被中斷的進程都返回到與中斷髮生前徹底相同的狀態。

線程
在傳統的操做系統中,每一個進程都有一個地址空間和一個控制線程。事實上,這是大部分進程的定義。不過,在許多狀況下,常常存在同一地址空間中運行多個控制線程的情形,這些線程就像是分離的進程。下面咱們就着重探討一下什麼是線程

線程的使用
或許這個疑問也是你的疑問,爲何要在進程的基礎上再建立一個線程的概念,準確的說,這實際上是進程模型和線程模型的討論,回答這個問題,可能須要分三步來回答

  • 多線程之間會共享同一塊地址空間和全部可用數據的能力,這是進程所不具有的
  • 線程要比進程更輕量級,因爲線程更輕,因此它比進程更容易建立,也更容易撤銷。在許多系統中,建立一個線程要比建立一個進程快 10 - 100 倍。
  • 第三個緣由多是性能方面的探討,若是多個線程都是 CPU 密集型的,那麼並不能得到性能上的加強,可是若是存在着大量的計算和大量的 I/O 處理,擁有多個線程能在這些活動中彼此重疊進行,從而會加快應用程序的執行速度
    經典的線程模型
    進程中擁有一個執行的線程,一般簡寫爲 線程(thread)。線程會有程序計數器,用來記錄接着要執行哪一條指令;線程實際上 CPU 上調度執行的實體。

下圖咱們能夠看到三個傳統的進程,每一個進程有本身的地址空間和單個控制線程。每一個線程都在不一樣的地址空間中運行

5萬字、97 張圖總結操做系統核心知識點

下圖中,咱們能夠看到有一個進程三個線程的狀況。每一個線程都在相同的地址空間中運行。

5萬字、97 張圖總結操做系統核心知識點

線程不像是進程那樣具有較強的獨立性。同一個進程中的全部線程都會有徹底同樣的地址空間,這意味着它們也共享一樣的全局變量。因爲每一個線程均可以訪問進程地址空間內每一個內存地址,「所以一個線程能夠讀取、寫入甚至擦除另外一個線程的堆棧」。線程之間除了共享同一內存空間外,還具備以下不一樣的內容

5萬字、97 張圖總結操做系統核心知識點

上圖左邊的是同一個進程中每一個線程共享的內容,上圖右邊是每一個線程中的內容。也就是說左邊的列表是進程的屬性,右邊的列表是線程的屬性。

「線程之間的狀態轉換和進程之間的狀態轉換是同樣的」。

每一個線程都會有本身的堆棧,以下圖所示

5萬字、97 張圖總結操做系統核心知識點

線程系統調用
進程一般會從當前的某個單線程開始,而後這個線程經過調用一個庫函數(好比 thread_create)建立新的線程。線程建立的函數會要求指定新建立線程的名稱。建立的線程一般都返回一個線程標識符,該標識符就是新線程的名字。

當一個線程完成工做後,能夠經過調用一個函數(好比 thread_exit)來退出。緊接着線程消失,狀態變爲終止,不能再進行調度。在某些線程的運行過程當中,能夠經過調用函數例如 thread_join ,表示一個線程能夠等待另外一個線程退出。這個過程阻塞調用線程直到等待特定的線程退出。在這種狀況下,線程的建立和終止很是相似於進程的建立和終止。

另外一個常見的線程是調用 thread_yield,它容許線程自動放棄 CPU 從而讓另外一個線程運行。這樣一個調用仍是很重要的,由於不一樣於進程,線程是沒法利用時鐘中斷強制讓線程讓出 CPU 的。

POSIX 線程
POSIX 線程 一般稱爲 pthreads是一種獨立於語言而存在的執行模型,以及並行執行模型。

5萬字、97 張圖總結操做系統核心知識點

它容許程序控制時間上重疊的多個不一樣的工做流程。每一個工做流程都稱爲一個線程,能夠經過調用 POSIX Threads API 來實現對這些流程的建立和控制。能夠把它理解爲線程的標準。


POSIX Threads 的實如今許多相似且符合POSIX的操做系統上可用,例如 「FreeBSD、NetBSD、OpenBSD、Linux、macOS、Android、Solaris」,它在現有 Windows API 之上實現了「pthread」。

IEEE 是世界上最大的技術專業組織,致力於爲人類的利益而發展技術。

5萬字、97 張圖總結操做系統核心知識點
全部的 Pthreads 都有特定的屬性,每個都含有標識符、一組寄存器(包括程序計數器)和一組存儲在結構中的屬性。這個屬性包括堆棧大小、調度參數以及其餘線程須要的項目。

線程實現
主要有三種實現方式

  • 在用戶空間中實現線程;
  • 在內核空間中實現線程;
  • 在用戶和內核空間中混合實現線程。
    下面咱們分開討論一下

在用戶空間中實現線程
第一種方法是把整個線程包放在用戶空間中,內核對線程一無所知,它不知道線程的存在。全部的這類實現都有一樣的通用結構

5萬字、97 張圖總結操做系統核心知識點

在用戶空間中實現多線程

線程在運行時系統之上運行,運行時系統是管理線程過程的集合,包括前面提到的四個過程:pthread_create, pthread_exit, pthread_join 和 pthread_yield。

在內核中實現線程
當某個線程但願建立一個新線程或撤銷一個已有線程時,它會進行一個系統調用,這個系統調用經過對線程表的更新來完成線程建立或銷燬工做。

5萬字、97 張圖總結操做系統核心知識點

在內核中實現多線程

內核中的線程表持有每一個線程的寄存器、狀態和其餘信息。這些信息和用戶空間中的線程信息相同,可是位置卻被放在了內核中而不是用戶空間中。另外,內核還維護了一張進程表用來跟蹤系統狀態。

全部可以阻塞的調用都會經過系統調用的方式來實現,當一個線程阻塞時,內核能夠進行選擇,是運行在同一個進程中的另外一個線程(若是有就緒線程的話)仍是運行一個另外一個進程中的線程。可是在用戶實現中,運行時系統始終運行本身的線程,直到內核剝奪它的 CPU 時間片(或者沒有可運行的線程存在了)爲止。

混合實現
結合用戶空間和內核空間的優勢,設計人員採用了一種內核級線程的方式,而後將用戶級線程與某些或者所有內核線程多路複用起來

5萬字、97 張圖總結操做系統核心知識點

用戶線程與內核線程的多路複用

在這種模型中,編程人員能夠自由控制用戶線程和內核線程的數量,具備很大的靈活度。採用這種方法,內核只識別內核級線程,並對其進行調度。其中一些內核級線程會被多個用戶級線程多路複用。

進程間通訊
進程是須要頻繁的和其餘進程進行交流的。下面咱們會一塊兒討論有關 進程間通訊(Inter Process Communication, IPC) 的問題。大體來講,進程間的通訊機制能夠分爲 6 種
5萬字、97 張圖總結操做系統核心知識點

下面咱們分別對其進行概述

信號 signal
信號是 UNIX 系統最早開始使用的進程間通訊機制,由於 Linux 是繼承於 UNIX 的,因此 Linux 也支持信號機制,經過向一個或多個進程發送異步事件信號來實現,信號能夠從鍵盤或者訪問不存在的位置等地方產生;信號經過 shell 將任務發送給子進程。

你能夠在 Linux 系統上輸入 kill -l 來列出系統使用的信號,下面是我提供的一些信號
5萬字、97 張圖總結操做系統核心知識點

進程能夠選擇忽略發送過來的信號,可是有兩個是不能忽略的:SIGSTOP 和 SIGKILL 信號。SIGSTOP 信號會通知當前正在運行的進程執行關閉操做,SIGKILL 信號會通知當前進程應該被殺死。除此以外,進程能夠選擇它想要處理的信號,進程也能夠選擇阻止信號,若是不阻止,能夠選擇自行處理,也能夠選擇進行內核處理。若是選擇交給內核進行處理,那麼就執行默認處理。

操做系統會中斷目標程序的進程來向其發送信號、在任何非原子指令中,執行均可以中斷,若是進程已經註冊了新號處理程序,那麼就執行進程,若是沒有註冊,將採用默認處理的方式。

管道 pipe
Linux 系統中的進程能夠經過創建管道 pipe 進行通訊

在兩個進程之間,能夠創建一個通道,一個進程向這個通道里寫入字節流,另外一個進程從這個管道中讀取字節流。管道是同步的,當進程嘗試從空管道讀取數據時,該進程會被阻塞,直到有可用數據爲止。shell 中的管線 pipelines 就是用管道實現的,當 shell 發現輸出

sort <f | head

它會建立兩個進程,一個是 sort,一個是 head,sort,會在這兩個應用程序之間創建一個管道使得 sort 進程的標準輸出做爲 head 程序的標準輸入。sort 進程產生的輸出就不用寫到文件中了,若是管道滿了系統會中止 sort 以等待 head 讀出數據

5萬字、97 張圖總結操做系統核心知識點

管道實際上就是 |,兩個應用程序不知道有管道的存在,一切都是由 shell 管理和控制的。

共享內存 shared memory
兩個進程之間還能夠經過共享內存進行進程間通訊,其中兩個或者多個進程能夠訪問公共內存空間。兩個進程的共享工做是經過共享內存完成的,一個進程所做的修改能夠對另外一個進程可見(很像線程間的通訊)。
5萬字、97 張圖總結操做系統核心知識點

在使用共享內存前,須要通過一系列的調用流程,流程以下

  • 建立共享內存段或者使用已建立的共享內存段(shmget())
  • 將進程附加到已經建立的內存段中(shmat())
  • 從已鏈接的共享內存段分離進程(shmdt())
  • 對共享內存段執行控制操做(shmctl())
    先入先出隊列 FIFO
    先入先出隊列 FIFO 一般被稱爲 命名管道(Named Pipes),命名管道的工做方式與常規管道很是類似,可是確實有一些明顯的區別。未命名的管道沒有備份文件:操做系統負責維護內存中的緩衝區,用來將字節從寫入器傳輸到讀取器。一旦寫入或者輸出終止的話,緩衝區將被回收,傳輸的數據會丟失。相比之下,命名管道具備支持文件和獨特 API ,命名管道在文件系統中做爲設備的專用文件存在。當全部的進程通訊完成後,命名管道將保留在文件系統中以備後用。命名管道具備嚴格的 FIFO 行爲

5萬字、97 張圖總結操做系統核心知識點

寫入的第一個字節是讀取的第一個字節,寫入的第二個字節是讀取的第二個字節,依此類推。

消息隊列 Message Queue
一聽到消息隊列這個名詞你可能不知道是什麼意思,消息隊列是用來描述內核尋址空間內的內部連接列表。能夠按幾種不一樣的方式將消息按順序發送到隊列並從隊列中檢索消息。每一個消息隊列由 IPC 標識符惟一標識。消息隊列有兩種模式,一種是嚴格模式, 嚴格模式就像是 FIFO 先入先出隊列似的,消息順序發送,順序讀取。還有一種模式是 非嚴格模式,消息的順序性不是很是重要。

套接字 Socket
還有一種管理兩個進程間通訊的是使用 socket,socket 提供端到端的雙相通訊。一個套接字能夠與一個或多個進程關聯。就像管道有命令管道和未命名管道同樣,套接字也有兩種模式,套接字通常用於兩個進程之間的網絡通訊,網絡套接字須要來自諸如TCP(傳輸控制協議)或較低級別UDP(用戶數據報協議)等基礎協議的支持。

套接字有如下幾種分類

  • 順序包套接字(Sequential Packet Socket):此類套接字爲最大長度固定的數據報提供可靠的鏈接。此鏈接是雙向的而且是順序的。
  • 數據報套接字(Datagram Socket):數據包套接字支持雙向數據流。數據包套接字接受消息的順序與發送者可能不一樣。
  • 流式套接字(Stream Socket):流套接字的工做方式相似於電話對話,提供雙向可靠的數據流。
  • 原始套接字(Raw Socket):能夠使用原始套接字訪問基礎通訊協議。
    調度
    當一個計算機是多道程序設計系統時,會頻繁的有不少進程或者線程來同時競爭 CPU 時間片。當兩個或兩個以上的進程/線程處於就緒狀態時,就會發生這種狀況。若是隻有一個 CPU 可用,那麼必須選擇接下來哪一個進程/線程能夠運行。操做系統中有一個叫作 調度程序(scheduler) 的角色存在,它就是作這件事兒的,該程序使用的算法叫作 調度算法(scheduling algorithm) 。

調度算法的分類
毫無疑問,不一樣的環境下須要不一樣的調度算法。之因此出現這種狀況,是由於不一樣的應用程序和不一樣的操做系統有不一樣的目標。也就是說,在不一樣的系統中,調度程序的優化也是不一樣的。這裏有必要劃分出三種環境

  • 批處理(Batch) : 商業領域
  • 交互式(Interactive):交互式用戶環境
  • 實時(Real time)
    批處理中的調度
    如今讓咱們把目光從通常性的調度轉換爲特定的調度算法。下面咱們會探討在批處理中的調度。

先來先服務
最簡單的非搶佔式調度算法的設計就是 先來先服務(first-come,first-serverd)。當第一個任務從外部進入系統時,將會當即啓動並容許運行任意長的時間。它不會由於運行時間太長而中斷。當其餘做業進入時,它們排到就緒隊列尾部。當正在運行的進程阻塞,處於等待隊列的第一個進程就開始運行。當一個阻塞的進程從新處於就緒態時,它會像一個新到達的任務,會排在隊列的末尾,即排在全部進程最後。

5萬字、97 張圖總結操做系統核心知識點
這個算法的強大之處在於易於理解和編程,在這個算法中,一個單鏈表記錄了全部就緒進程。要選取一個進程運行,只要從該隊列的頭部移走一個進程便可;要添加一個新的做業或者阻塞一個進程,只要把這個做業或進程附加在隊列的末尾便可。這是很簡單的一種實現。

最短做業優先
批處理中,第二種調度算法是 最短做業優先(Shortest Job First),咱們假設運行時間已知。例如,一家保險公司,由於天天要作相似的工做,因此人們能夠至關精確地預測處理 1000 個索賠的一批做業須要多長時間。當輸入隊列中有若干個同等重要的做業被啓動時,調度程序應使用最短優先做業算法

5萬字、97 張圖總結操做系統核心知識點

須要注意的是,在全部的進程均可以運行的狀況下,最短做業優先的算法纔是最優的。


最短剩餘時間優先
最短做業優先的搶佔式版本被稱做爲 最短剩餘時間優先(Shortest Remaining Time Next) 算法。使用這個算法,調度程序老是選擇剩餘運行時間最短的那個進程運行。

交互式系統中的調度
交互式系統中在我的計算機、服務器和其餘系統中都是很經常使用的,因此有必要來探討一下交互式調度

輪詢調度
一種最古老、最簡單、最公平而且最普遍使用的算法就是 輪詢算法(round-robin)。每一個進程都會被分配一個時間段,稱爲時間片(quantum),在這個時間片內容許進程運行。若是時間片結束時進程還在運行的話,則搶佔一個 CPU 並將其分配給另外一個進程。若是進程在時間片結束前阻塞或結束,則 CPU 當即進行切換。輪詢算法比較容易實現。調度程序所作的就是維護一個可運行進程的列表,就像下圖中的 a,當一個進程用完時間片後就被移到隊列的末尾,就像下圖的 b。

5萬字、97 張圖總結操做系統核心知識點

優先級調度
輪詢調度假設了全部的進程是同等重要的。但事實狀況可能不是這樣。例如,在一所大學中的等級制度,首先是院長,而後是教授、祕書、後勤人員,最後是學生。這種將外部狀況考慮在內就實現了優先級調度(priority scheduling)

5萬字、97 張圖總結操做系統核心知識點

它的基本思想很明確,每一個進程都被賦予一個優先級,優先級高的進程優先運行。

多級隊列
最先使用優先級調度的系統是 CTSS(Compatible TimeSharing System)。CTSS 在每次切換前都須要將當前進程換出到磁盤,並從磁盤上讀入一個新進程。爲 CPU 密集型進程設置較長的時間片比頻繁地分給他們很短的時間要更有效(減小交換次數)。另外一方面,如前所述,長時間片的進程又會影響到響應時間,解決辦法是設置優先級類。屬於最高優先級的進程運行一個時間片,次高優先級進程運行 2 個時間片,再下面一級運行 4 個時間片,以此類推。當一個進程用完分配的時間片後,它被移到下一類。

最短進程優先
最短進程優先是根據進程過去的行爲進行推測,並執行估計運行時間最短的那一個。假設每一個終端上每條命令的預估運行時間爲 T0,如今假設測量到其下一次運行時間爲 T1,能夠用兩個值的加權來改進估計時間,即aT0+ (1- 1)T1。經過選擇 a 的值,能夠決定是儘快忘掉老的運行時間,仍是在一段長時間內始終記住它們。當 a = 1/2 時,能夠獲得下面這個序列

5萬字、97 張圖總結操做系統核心知識點

能夠看到,在三輪事後,T0 在新的估計值中所佔比重降低至 1/8。

保證調度
一種徹底不一樣的調度方法是對用戶作出明確的性能保證。一種實際並且容易實現的保證是:若用戶工做時有 n 個用戶登陸,則每一個用戶將得到 CPU 處理能力的 1/n。相似地,在一個有 n 個進程運行的單用戶系統中,若全部的進程都等價,則每一個進程將得到 1/n 的 CPU 時間。

彩票調度
對用戶進行承諾並在隨後兌現承諾是一件好事,不過很難實現。可是存在着一種簡單的方式,有一種既能夠給出預測結果而又有一種比較簡單的實現方式的算法,就是 彩票調度(lottery scheduling)算法。

其基本思想是爲進程提供各類系統資源(例如 CPU 時間)的彩票。當作出一個調度決策的時候,就隨機抽出一張彩票,擁有彩票的進程將得到該資源。在應用到 CPU 調度時,系統能夠每秒持有 50 次抽獎,每一箇中獎者將得到好比 20 毫秒的 CPU 時間做爲獎勵。

公平分享調度
到目前爲止,咱們假設被調度的都是各個進程自身,而不用考慮該進程的擁有者是誰。結果是,若是用戶 1 啓動了 9 個進程,而用戶 2 啓動了一個進程,使用輪轉或相同優先級調度算法,那麼用戶 1 將獲得 90 % 的 CPU 時間,而用戶 2 將之獲得 10 % 的 CPU 時間。

爲了阻止這種狀況的出現,一些系統在調度前會把進程的擁有者考慮在內。在這種模型下,每一個用戶都會分配一些CPU 時間,而調度程序會選擇進程並強制執行。所以若是兩個用戶每一個都會有 50% 的 CPU 時間片保證,那麼不管一個用戶有多少個進程,都將得到相同的 CPU 份額。
5萬字、97 張圖總結操做系統核心知識點

實時系統中的調度
實時系統(real-time) 是一個時間扮演了重要做用的系統。實時系統能夠分爲兩類,硬實時(hard real time) 和 軟實時(soft real time) 系統,前者意味着必需要知足絕對的截止時間;後者的含義是雖然不但願偶爾錯失截止時間,可是能夠容忍。

實時系統中的事件能夠按照響應方式進一步分類爲週期性(以規則的時間間隔發生)事件或 非週期性(發生時間不可預知)事件。一個系統可能要響應多個週期性事件流,根據每一個事件處理所需的時間,可能甚至沒法處理全部事件。例如,若是有 m 個週期事件,事件 i 以週期 Pi 發生,並須要 Ci 秒 CPU 時間處理一個事件,那麼能夠處理負載的條件是

5萬字、97 張圖總結操做系統核心知識點

只有知足這個條件的實時系統稱爲可調度的,這意味着它實際上可以被實現。一個不知足此檢驗標準的進程不能被調度,由於這些進程共同須要的 CPU 時間總和大於 CPU 能提供的時間。

下面咱們來了解一下內存管理,你須要知道的知識點以下

5萬字、97 張圖總結操做系統核心知識點

地址空間
若是要使多個應用程序同時運行在內存中,必需要解決兩個問題:保護和 重定位。第一種解決方式是用保護密鑰標記內存塊,並將執行過程的密鑰與提取的每一個存儲字的密鑰進行比較。這種方式只能解決第一種問題(破壞操做系統),可是不能解決多進程在內存中同時運行的問題。

還有一種更好的方式是創造一個存儲器抽象:地址空間(the address space)。就像進程的概念建立了一種抽象的 CPU 來運行程序,地址空間也建立了一種抽象內存供程序使用。

基址寄存器和變址寄存器
最簡單的辦法是使用動態重定位(dynamic relocation)技術,它就是經過一種簡單的方式將每一個進程的地址空間映射到物理內存的不一樣區域。還有一種方式是使用基址寄存器和變址寄存器。

  • 基址寄存器:存儲數據內存的起始位置
  • 變址寄存器:存儲應用程序的長度。
    每當進程引用內存以獲取指令或讀取、寫入數據時,CPU 都會自動將基址值添加到進程生成的地址中,而後再將其發送到內存總線上。同時,它檢查程序提供的地址是否大於或等於變址寄存器 中的值。若是程序提供的地址要超過變址寄存器的範圍,那麼會產生錯誤並停止訪問。

交換技術
在程序運行過程當中,常常會出現內存不足的問題。

針對上面內存不足的問題,提出了兩種處理方式:最簡單的一種方式就是交換(swapping)技術,即把一個進程完整的調入內存,而後再內存中運行一段時間,再把它放回磁盤。空閒進程會存儲在磁盤中,因此這些進程在沒有運行時不會佔用太多內存。另一種策略叫作虛擬內存(virtual memory),虛擬內存技術可以容許應用程序部分的運行在內存中。下面咱們首先先探討一下交換

交換過程
下面是一個交換過程

5萬字、97 張圖總結操做系統核心知識點

剛開始的時候,只有進程 A 在內存中,而後從建立進程 B 和進程 C 或者從磁盤中把它們換入內存,而後在圖 d 中,A 被換出內存到磁盤中,最後 A 從新進來。由於圖 g 中的進程 A 如今到了不一樣的位置,因此在裝載過程當中須要被從新定位,或者在交換程序時經過軟件來執行;或者在程序執行期間經過硬件來重定位。基址寄存器和變址寄存器就適用於這種狀況。

5萬字、97 張圖總結操做系統核心知識點

交換在內存建立了多個 空閒區(hole),內存會把全部的空閒區儘量向下移動合併成爲一個大的空閒區。這項技術稱爲內存緊縮(memory compaction)。可是這項技術一般不會使用,由於這項技術會消耗不少 CPU 時間。

空閒內存管理
在進行內存動態分配時,操做系統必須對其進行管理。大體上說,有兩種監控內存使用的方式

  • 位圖(bitmap)
  • 空閒列表(free lists)
    使用位圖的存儲管理
    使用位圖方法時,內存可能被劃分爲小到幾個字或大到幾千字節的分配單元。每一個分配單元對應於位圖中的一位,0 表示空閒, 1 表示佔用(或者相反)。一塊內存區域和其對應的位圖以下
    5萬字、97 張圖總結操做系統核心知識點

位圖提供了一種簡單的方法在固定大小的內存中跟蹤內存的使用狀況,由於「位圖的大小取決於內存和分配單元的大小」。這種方法有一個問題是,當決定爲把具備 k 個分配單元的進程放入內存時,內容管理器(memory manager) 必須搜索位圖,在位圖中找出可以運行 k 個連續 0 位的串。在位圖中找出制定長度的連續 0 串是一個很耗時的操做,這是位圖的缺點。(能夠簡單理解爲在雜亂無章的數組中,找出具備一大長串空閒的數組單元)

使用鏈表進行管理
另外一種記錄內存使用狀況的方法是,維護一個記錄已分配內存段和空閒內存段的鏈表,段會包含進程或者是兩個進程的空閒區域。可用上面的圖 c 「來表示內存的使用狀況」。鏈表中的每一項均可以表明一個 空閒區(H) 或者是進程(P)的起始標誌,長度和下一個鏈表項的位置。

5萬字、97 張圖總結操做系統核心知識點

當按照地址順序在鏈表中存放進程和空閒區時,有幾種算法能夠爲建立的進程(或者從磁盤中換入的進程)分配內存。咱們先假設內存管理器知道應該分配多少內存,最簡單的算法是使用 首次適配(first fit)。內存管理器會沿着段列表進行掃描,直到找個一個足夠大的空閒區爲止。 除非空閒區大小和要分配的空間大小同樣,不然將空閒區分爲兩部分,一部分供進程使用;一部分生成新的空閒區。首次適配算法是一種速度很快的算法,由於它會盡量的搜索鏈表。

首次適配的一個小的變體是 下次適配(next fit)。它和首次匹配的工做方式相同,只有一個不一樣之處那就是下次適配在每次找到合適的空閒區時就會記錄當時的位置,以便下次尋找空閒區時從上次結束的地方開始搜索,而不是像首次匹配算法那樣每次都會從頭開始搜索。

另一個著名的而且普遍使用的算法是 最佳適配(best fit)。最佳適配會從頭至尾尋找整個鏈表,找出可以容納進程的最小空閒區。

虛擬內存
儘管基址寄存器和變址寄存器用來建立地址空間的抽象,可是這有一個其餘的問題須要解決:管理軟件的不斷增大(managing bloatware)。虛擬內存的基本思想是,每一個程序都有本身的地址空間,這個地址空間被劃分爲多個稱爲頁面(page)的塊。每一頁都是連續的地址範圍。這些頁被映射到物理內存,但並非全部的頁都必須在內存中才能運行程序。當程序引用到一部分在物理內存中的地址空間時,硬件會馬上執行必要的映射。當程序引用到一部分不在物理內存中的地址空間時,由操做系統負責將缺失的部分裝入物理內存並從新執行失敗的指令。

分頁
大部分使用虛擬內存的系統中都會使用一種 分頁(paging) 技術。在任何一臺計算機上,程序會引用使用一組內存地址。當程序執行

MOV REG,1000

這條指令時,它會把內存地址爲 1000 的內存單元的內容複製到 REG 中(或者相反,這取決於計算機)。地址能夠經過索引、基址寄存器、段寄存器或其餘方式產生。

這些程序生成的地址被稱爲 虛擬地址(virtual addresses) 並造成虛擬地址空間(virtual address space),在沒有虛擬內存的計算機上,系統直接將虛擬地址送到內存中線上,讀寫操做都使用一樣地址的物理內存。「在使用虛擬內存時,虛擬地址不會直接發送到內存總線上」。相反,會使用 MMU(Memory Management Unit) 內存管理單元把「虛擬地址映射爲物理內存地址」,像下圖這樣

5萬字、97 張圖總結操做系統核心知識點

下面這幅圖展現了這種映射是如何工做的

5萬字、97 張圖總結操做系統核心知識點

頁表給出虛擬地址與物理內存地址之間的映射關係。每一頁起始於 4096 的倍數位置,結束於 4095 的位置,因此 4K 到 8K 實際爲 4096 - 8191 ,8K - 12K 就是 8192 - 12287

在這個例子中,咱們可能有一個 16 位地址的計算機,地址從 0 - 64 K - 1,這些是虛擬地址。然而只有 32 KB 的物理地址。因此雖然能夠編寫 64 KB 的程序,可是程序沒法所有調入內存運行,在磁盤上必須有一個最多 64 KB 的程序核心映像的完整副本,以保證程序片斷在須要時被調入內存。

頁表
虛擬頁號可做爲頁表的索引用來找到虛擬頁中的內容。由頁表項能夠找到頁框號(若是有的話)。而後把頁框號拼接到偏移量的高位端,以替換掉虛擬頁號,造成物理地址。

5萬字、97 張圖總結操做系統核心知識點

所以,頁表的目的是把虛擬頁映射到頁框中。從數學上說,頁表是一個函數,它的參數是虛擬頁號,結果是物理頁框號。

5萬字、97 張圖總結操做系統核心知識點

經過這個函數能夠把虛擬地址中的虛擬頁轉換爲頁框,從而造成物理地址。

頁表項的結構
下面咱們探討一下頁表項的具體結構,上面你知道了頁表項的大體構成,是由頁框號和在/不在位構成的,如今咱們來具體探討一下頁表項的構成

5萬字、97 張圖總結操做系統核心知識點

頁表項的結構是與機器相關的,可是不一樣機器上的頁表項大體相同。上面是一個頁表項的構成,不一樣計算機的頁表項可能不一樣,可是通常來講都是 32 位的。頁表項中最重要的字段就是頁框號(Page frame number)。畢竟,頁表到頁框最重要的一步操做就是要把此值映射過去。下一個比較重要的就是在/不在位,若是此位上的值是 1,那麼頁表項是有效的而且可以被使用。若是此值是 0 的話,則表示該頁表項對應的虛擬頁面不在內存中,訪問該頁面會引發一個缺頁異常(page fault)。

保護位(Protection) 告訴咱們哪種訪問是容許的,啥意思呢?最簡單的表示形式是這個域只有一位,「0 表示可讀可寫,1 表示的是隻讀」。

修改位(Modified) 和 訪問位(Referenced) 會跟蹤頁面的使用狀況。當一個頁面被寫入時,硬件會自動的設置修改位。修改位在頁面從新分配頁框時頗有用。若是一個頁面已經被修改過(即它是 髒 的),則必須把它寫回磁盤。若是一個頁面沒有被修改過(即它是 乾淨的),那麼從新分配時這個頁框會被直接丟棄,由於磁盤上的副本仍然是有效的。這個位有時也叫作 髒位(dirty bit),由於它反映了頁面的狀態。

訪問位(Referenced) 在頁面被訪問時被設置,無論是讀仍是寫。這個值可以幫助操做系統在發生缺頁中斷時選擇要淘汰的頁。再也不使用的頁要比正在使用的頁更適合被淘汰。這個位在後面要討論的頁面置換算法中做用很大。

最後一位用於禁止該頁面被高速緩存,這個功能對於映射到設備寄存器仍是內存中起到了關鍵做用。經過這一位能夠禁用高速緩存。具備獨立的 I/O 空間而不是用內存映射 I/O 的機器來講,並不須要這一位。

頁面置換算法
下面咱們就來探討一下有哪些頁面置換算法。

最優頁面置換算法
最優的頁面置換算法的工做流程以下:在缺頁中斷髮生時,這些頁面之一將在下一條指令(包含該指令的頁面)上被引用。其餘頁面則可能要到 十、100 或者 1000 條指令後纔會被訪問。每一個頁面均可以用在該頁首次被訪問前所要執行的指令數做爲標記。

最優化的頁面算法代表應該標記最大的頁面。若是一個頁面在 800 萬條指令內不會被使用,另一個頁面在 600 萬條指令內不會被使用,則置換前一個頁面,從而把須要調入這個頁面而發生的缺頁中斷推遲。計算機也像人類同樣,會把不肯意作的事情儘量的日後拖。

這個算法最大的問題時沒法實現。當缺頁中斷髮生時,操做系統沒法知道各個頁面的下一次將在何時被訪問。這種算法在實際過程當中根本不會使用。

最近未使用頁面置換算法
爲了可以讓操做系統收集頁面使用信息,大部分使用虛擬地址的計算機都有兩個狀態位,R 和 M,來和每一個頁面進行關聯。「每當引用頁面(讀入或寫入)時都設置 R,寫入(即修改)頁面時設置 M」,這些位包含在每一個頁表項中,就像下面所示

5萬字、97 張圖總結操做系統核心知識點

由於每次訪問時都會更新這些位,所以由硬件來設置它們很是重要。一旦某個位被設置爲 1,就會一直保持 1 直到操做系統下次來修改此位。

若是硬件沒有這些位,那麼能夠使用操做系統的缺頁中斷和時鐘中斷機制來進行模擬。當啓動一個進程時,將其全部的頁面都標記爲不在內存;一旦訪問任何一個頁面就會引起一次缺頁中斷,此時操做系統就能夠設置 R 位(在它的內部表中),修改頁表項使其指向正確的頁面,並設置爲 READ ONLY 模式,而後從新啓動引發缺頁中斷的指令。若是頁面隨後被修改,就會發生另外一個缺頁異常。從而容許操做系統設置 M 位並把頁面的模式設置爲 READ/WRITE。

能夠用 R 位和 M 位來構造一個簡單的頁面置換算法:當啓動一個進程時,操做系統將其全部頁面的兩個位都設置爲 0。R 位按期的被清零(在每一個時鐘中斷)。用來將最近未引用的頁面和已引用的頁面分開。

當出現缺頁中斷後,操做系統會檢查全部的頁面,並根據它們的 R 位和 M 位將當前值分爲四類:

  • 第 0 類:沒有引用 R,沒有修改 M
  • 第 1 類:沒有引用 R,已修改 M
  • 第 2 類:引用 R ,沒有修改 M
  • 第 3 類:已被訪問 R,已被修改 M
    儘管看起來好像沒法實現第一類頁面,可是當第三類頁面的 R 位被時鐘中斷清除時,它們就會發生。時鐘中斷不會清除 M 位,由於須要這個信息才能知道是否寫回磁盤中。清除 R 但不清除 M 會致使出現一類頁面。

NRU(Not Recently Used) 算法從編號最小的非空類中隨機刪除一個頁面。此算法隱含的思想是,在一個時鐘內(約 20 ms)淘汰一個已修改可是沒有被訪問的頁面要比一個大量引用的未修改頁面好,NRU 的主要優勢是「易於理解而且可以有效的實現」。

先進先出頁面置換算法
另外一種開銷較小的方式是使用 FIFO(First-In,First-Out) 算法,這種類型的數據結構也適用在頁面置換算法中。由操做系統維護一個全部在當前內存中的頁面的鏈表,最先進入的放在表頭,最新進入的頁面放在表尾。在發生缺頁異常時,會把頭部的頁移除而且把新的頁添加到表尾。

第二次機會頁面置換算法
咱們上面學到的 FIFO 鏈表頁面有個缺陷,那就是出鏈和入鏈並不會進行 check 檢查,這樣就會容易把常用的頁面置換出去,爲了不這一問題,咱們對該算法作一個簡單的修改:咱們檢查最老頁面的 R 位,若是是 0 ,那麼這個頁面就是最老的並且沒有被使用,那麼這個頁面就會被馬上換出。若是 R 位是 1,那麼就清除此位,此頁面會被放在鏈表的尾部,修改它的裝入時間就像剛放進來的同樣。而後繼續搜索。

這種算法叫作 第二次機會(second chance)算法,就像下面這樣,咱們看到頁面 A 到 H 保留在鏈表中,並按到達內存的時間排序。

5萬字、97 張圖總結操做系統核心知識點

a)按照先進先出的方法排列的頁面;b)在時刻 20 處發生缺頁異常中斷而且 A 的 R 位已經設置時的頁面鏈表。

假設缺頁異常發生在時刻 20 處,這時最老的頁面是 A ,它是在 0 時刻到達的。若是 A 的 R 位是 0,那麼它將被淘汰出內存,或者把它寫回磁盤(若是它已經被修改過),或者只是簡單的放棄(若是它是未被修改過)。另外一方面,若是它的 R 位已經設置了,則將 A 放到鏈表的尾部而且從新設置裝入時間爲當前時刻(20 處),而後清除 R 位。而後從 B 頁面開始繼續搜索合適的頁面。

尋找第二次機會的是在最近的時鐘間隔中未被訪問過的頁面。若是全部的頁面都被訪問過,該算法就會被簡化爲單純的 FIFO 算法。具體來講,假設圖 a 中全部頁面都設置了 R 位。操做系統將頁面依次移到鏈表末尾,每次都在添加到末尾時清除 R 位。最後,算法又會回到頁面 A,此時的 R 位已經被清除,那麼頁面 A 就會被執行出鏈處理,所以算法可以正常結束。

時鐘頁面置換算法
一種比較好的方式是把全部的頁面都保存在一個相似鐘面的環形鏈表中,一個錶針指向最老的頁面。以下圖所示

5萬字、97 張圖總結操做系統核心知識點

當缺頁錯誤出現時,算法首先檢查錶針指向的頁面,若是它的 R 位是 0 就淘汰該頁面,並把新的頁面插入到這個位置,而後把錶針向前移動一位;若是 R 位是 1 就清除 R 位並把錶針前移一個位置。重複這個過程直到找到了一個 R 位爲 0 的頁面位置。瞭解這個算法的工做方式,就明白爲何它被稱爲 時鐘(clokc)算法了。

最近最少使用頁面置換算法
在前面幾條指令中頻繁使用的頁面和可能在後面的幾條指令中被使用。反過來講,已經好久沒有使用的頁面有可能在將來一段時間內仍不會被使用。這個思想揭示了一個能夠實現的算法:在缺頁中斷時,置換未使用時間最長的頁面。這個策略稱爲 LRU(Least Recently Used) ,最近最少使用頁面置換算法。

雖然 LRU 在理論上是能夠實現的,可是從長遠看來代價比較高。爲了徹底實現 LRU,會在內存中維護一個全部頁面的鏈表,最頻繁使用的頁位於表頭,最近最少使用的頁位於表尾。困難的是在每次內存引用時更新整個鏈表。在鏈表中找到一個頁面,刪除它,而後把它移動到表頭是一個很是耗時的操做,即便使用硬件來實現也是同樣的費時。

用軟件模擬 LRU
儘管上面的 LRU 算法在原則上是能夠實現的,「可是不多有機器可以擁有那些特殊的硬件」。上面是硬件的實現方式,那麼如今考慮要用軟件來實現 LRU 。一種能夠實現的方案是 NFU(Not Frequently Used,最不經常使用)算法。它須要一個軟件計數器來和每一個頁面關聯,初始化的時候是 0 。在每一個時鐘中斷時,操做系統會瀏覽內存中的全部頁,會將每一個頁面的 R 位(0 或 1)加到它的計數器上。這個計數器大致上跟蹤了各個頁面訪問的頻繁程度。當缺頁異常出現時,則置換計數器值最小的頁面。

只須要對 NFU 作一個簡單的修改就可讓它模擬 LRU,這個修改有兩個步驟

  • 首先,在 R 位被添加進來以前先把計數器右移一位;
  • 第二步,R 位被添加到最左邊的位而不是最右邊的位。
    修改之後的算法稱爲 老化(aging) 算法,下圖解釋了老化算法是如何工做的。

5萬字、97 張圖總結操做系統核心知識點

咱們假設在第一個時鐘週期內頁面 0 - 5 的 R 位依次是 1,0,1,0,1,1,(也就是頁面 0 是 1,頁面 1 是 0,頁面 2 是 1 這樣類推)。也就是說,「在 0 個時鐘週期到 1 個時鐘週期之間,0,2,4,5 都被引用了」,從而把它們的 R 位設置爲 1,剩下的設置爲 0 。在相關的六個計數器被右移以後 R 位被添加到 左側 ,就像上圖中的 a。剩下的四列顯示了接下來的四個時鐘週期內的六個計數器變化。


CPU正在以某個頻率前進,該頻率的週期稱爲時鐘滴答或時鐘週期。一個 100Mhz 的處理器每秒將接收100,000,000個時鐘滴答。


當缺頁異常出現時,將置換(就是移除)計數器值最小的頁面。若是一個頁面在前面 4 個時鐘週期內都沒有被訪問過,那麼它的計數器應該會有四個連續的 0 ,所以它的值確定要比前面 3 個時鐘週期內都沒有被訪問過的頁面的計數器小。

這個算法與 LRU 算法有兩個重要的區別:看一下上圖中的 e,第三列和第五列
5萬字、97 張圖總結操做系統核心知識點

工做集時鐘頁面置換算法
當缺頁異常發生後,須要掃描整個頁表才能肯定被淘汰的頁面,所以基本工做集算法仍是比較浪費時間的。一個對基本工做集算法的提高是基於時鐘算法可是卻使用工做集的信息,這種算法稱爲WSClock(工做集時鐘)。因爲它的實現簡單而且具備高性能,所以在實踐中被普遍應用。

與時鐘算法同樣,所需的數據結構是一個以頁框爲元素的循環列表,就像下面這樣
5萬字、97 張圖總結操做系統核心知識點

工做集時鐘頁面置換算法的操做:a) 和 b) 給出 R = 1 時所發生的情形;c) 和 d) 給出 R = 0 的例子

最初的時候,該表是空的。當裝入第一個頁面後,把它加載到該表中。隨着更多的頁面的加入,它們造成一個環形結構。每一個表項包含來自基本工做集算法的上次使用時間,以及 R 位(已標明)和 M 位(未標明)。

與時鐘算法同樣,在每一個缺頁異常時,首先檢查指針指向的頁面。若是 R 位被是設置爲 1,該頁面在當前時鐘週期內就被使用過,那麼該頁面就不適合被淘汰。而後把該頁面的 R 位置爲 0,指針指向下一個頁面,並重復該算法。該事件序列化後的狀態參見圖 b。

如今考慮指針指向的頁面 R = 0 時會發生什麼,參見圖 c,若是頁面的使用期限大於 t 而且頁面爲被訪問過,那麼這個頁面就不會在工做集中,而且在磁盤上會有一個此頁面的副本。申請從新調入一個新的頁面,並把新的頁面放在其中,如圖 d 所示。另外一方面,若是頁面被修改過,就不能從新申請頁面,由於這個頁面在磁盤上沒有有效的副本。爲了不因爲調度寫磁盤操做引發的進程切換,指針繼續向前走,算法繼續對下一個頁面進行操做。畢竟,有可能存在一個老的,沒有被修改過的頁面能夠當即使用。

原則上來講,全部的頁面都有可能由於磁盤I/O 在某個時鐘週期內被調度。爲了下降磁盤阻塞,須要設置一個限制,即最大隻容許寫回 n 個頁面。一旦達到該限制,就不容許調度新的寫操做。

那麼就有個問題,指針會繞一圈回到原點的,若是回到原點,它的起始點會發生什麼?這裏有兩種狀況:

  • 至少調度了一次寫操做
  • 沒有調度過寫操做
    在第一種狀況中,指針僅僅是不停的移動,尋找一個未被修改過的頁面。因爲已經調度了一個或者多個寫操做,最終會有某個寫操做完成,它的頁面會被標記爲未修改。置換遇到的第一個未被修改過的頁面,這個頁面不必定是第一個被調度寫操做的頁面,由於硬盤驅動程序爲了優化性能可能會把寫操做重排序。

對於第二種狀況,全部的頁面都在工做集中,不然將至少調度了一個寫操做。因爲缺少額外的信息,最簡單的方法就是置換一個未被修改的頁面來使用,掃描中須要記錄未被修改的頁面的位置,若是不存在未被修改的頁面,就選定當前頁面並把它寫回磁盤。

頁面置換算法小結
咱們到如今已經研究了各類頁面置換算法,如今咱們來一個簡單的總結,算法的總結概括以下
5萬字、97 張圖總結操做系統核心知識點

  • 最優算法在當前頁面中置換最後要訪問的頁面。不幸的是,沒有辦法來斷定哪一個頁面是最後一個要訪問的,所以實際上該算法不能使用。然而,它能夠做爲衡量其餘算法的標準。

  • NRU 算法根據 R 位和 M 位的狀態將頁面氛圍四類。從編號最小的類別中隨機選擇一個頁面。NRU 算法易於實現,可是性能不是很好。存在更好的算法。

  • FIFO 會跟蹤頁面加載進入內存中的順序,並把頁面放入一個鏈表中。有可能刪除存在時間最長可是還在使用的頁面,所以這個算法也不是一個很好的選擇。

  • 第二次機會算法是對 FIFO 的一個修改,它會在刪除頁面以前檢查這個頁面是否仍在使用。若是頁面正在使用,就會進行保留。這個改進大大提升了性能。

  • 時鐘 算法是第二次機會算法的另一種實現形式,時鐘算法和第二次算法的性能差很少,可是會花費更少的時間來執行算法。

  • LRU 算法是一個很是優秀的算法,可是沒有特殊的硬件(TLB)很難實現。若是沒有硬件,就不能使用 LRU 算法。

  • NFU 算法是一種近似於 LRU 的算法,它的性能不是很是好。

  • 老化 算法是一種更接近 LRU 算法的實現,而且能夠更好的實現,所以是一個很好的選擇

  • 最後兩種算法都使用了工做集算法。工做集算法提供了合理的性能開銷,可是它的實現比較複雜。WSClock 是另一種變體,它不只可以提供良好的性能,並且能夠高效地實現。

總之,「最好的算法是老化算法和WSClock算法」。他們分別是基於 LRU 和工做集算法。他們都具備良好的性能而且可以被有效的實現。還存在其餘一些好的算法,但實際上這兩個多是最重要的。

下面來聊一聊文件系統,你須要知道下面這些知識點

5萬字、97 張圖總結操做系統核心知識點

文件
文件命名
文件是一種抽象機制,它提供了一種方式用來存儲信息以及在後面進行讀取。可能任何一種機制最重要的特性就是管理對象的命名方式。在建立一個文件後,它會給文件一個命名。當進程終止時,文件會繼續存在,而且其餘進程能夠使用名稱訪問該文件。

文件命名規則對於不一樣的操做系統來講是不同的,可是全部現代操做系統都容許使用 1 - 8 個字母的字符串做爲合法文件名。

某些文件區分大小寫字母,而大多數則不區分。UNIX 屬於第一類;歷史悠久的 MS-DOS 屬於第二類(順便說一句,儘管 MS-DOS 歷史悠久,但 MS-DOS 仍在嵌入式系統中很是普遍地使用,所以它毫不是過期的);所以,UNIX 系統會有三種不一樣的命名文件:maria、Maria、MARIA 。在 MS-DOS ,全部這些命名都屬於相同的文件。

5萬字、97 張圖總結操做系統核心知識點

許多操做系統支持兩部分的文件名,它們之間用 . 分隔開,好比文件名 prog.c。原點後面的文件稱爲 文件擴展名(file extension) ,文件擴展名一般表示文件的一些信息。一些經常使用的文件擴展名以及含義以下圖所示
5萬字、97 張圖總結操做系統核心知識點
在 UNIX 系統中,文件擴展名只是一種約定,操做系統並不強制採用。

文件結構
文件的構造有多種方式。下圖列出了經常使用的三種構造方式

5萬字、97 張圖總結操做系統核心知識點

三種不一樣的文件。a) 字節序列 。b) 記錄序列。c) 樹

上圖中的 a 是一種無結構的字節序列,操做系統不關心序列的內容是什麼,操做系統能看到的就是字節(bytes)。其文件內容的任何含義只在用戶程序中進行解釋。UNIX 和 Windows 都採用這種辦法。

圖 b 表示在文件結構上的第一部改進。在這個模型中,文件是具備固定長度記錄的序列,每一個記錄都有其內部結構。把文件做爲記錄序列的核心思想是:「讀操做返回一個記錄,而寫操做重寫或者追加一個記錄」。第三種文件結構如上圖 c 所示。在這種組織結構中,文件由一顆記錄樹構成,記錄樹的長度不必定相同,每一個記錄樹都在記錄中的固定位置包含一個key 字段。這棵樹按 key 進行排序,從而能夠對特定的 key 進行快速查找。

文件類型
不少操做系統支持多種文件類型。例如,UNIX(一樣包括 OS X)和 Windows 都具備常規的文件和目錄。除此以外,UNIX 還具備字符特殊文件(character special file) 和 塊特殊文件(block special file)。常規文件(Regular files) 是包含有用戶信息的文件。用戶通常使用的文件大都是常規文件,常規文件通常包括 「可執行文件、文本文件、圖像文件」,從常規文件讀取數據或將數據寫入時,內核會根據文件系統的規則執行操做,是寫入可能被延遲,記錄日誌或者接受其餘操做。

文件訪問
早期的操做系統只有一種訪問方式:序列訪問(sequential access)。在這些系統中,進程能夠按照順序讀取全部的字節或文件中的記錄,可是不能跳過並亂序執行它們。順序訪問文件是能夠返回到起點的,須要時能夠屢次讀取該文件。當存儲介質是磁帶而不是磁盤時,順序訪問文件很方便。

在使用磁盤來存儲文件時,能夠不按照順序讀取文件中的字節或者記錄,或者按照關鍵字而不是位置來訪問記錄。這種可以以任意次序進行讀取的稱爲隨機訪問文件(random access file)。許多應用程序都須要這種方式。

隨機訪問文件對許多應用程序來講都必不可少,例如,數據庫系統。若是乘客打電話預約某航班機票,訂票程序必須可以直接訪問航班記錄,而沒必要先讀取其餘航班的成千上萬條記錄。

有兩種方法能夠指示從何處開始讀取文件。第一種方法是直接使用 read 從頭開始讀取。另外一種是用一個特殊的 seek 操做設置當前位置,在 seek 操做後,從這個當前位置順序地開始讀文件。UNIX 和 Windows 使用的是後面一種方式。

文件屬性
文件包括文件名和數據。除此以外,全部的操做系統還會保存其餘與文件相關的信息,如文件建立的日期和時間、文件大小。咱們能夠稱這些爲文件的屬性(attributes)。有些人也喜歡把它們稱做 元數據(metadata)。文件的屬性在不一樣的系統中差異很大。文件的屬性只有兩種狀態:設置(set) 和 清除(clear)。

文件操做
使用文件的目的是用來存儲信息並方便之後的檢索。對於存儲和檢索,不一樣的系統提供了不一樣的操做。如下是與文件有關的最經常使用的一些系統調用:

  1. Create,建立不包含任何數據的文件。調用的目的是表示文件即將創建,並對文件設置一些屬性。
  2. Delete,當文件再也不須要,必須刪除它以釋放內存空間。爲此總會有一個系統調用來刪除文件。
  3. Open,在使用文件以前,必須先打開文件。這個調用的目的是容許系統將屬性和磁盤地址列表保存到主存中,用來之後的快速訪問。
  4. Close,當全部進程完成時,屬性和磁盤地址再也不須要,所以應關閉文件以釋放表空間。不少系統限制進程打開文件的個數,以此達到鼓勵用戶關閉再也不使用的文件。磁盤以塊爲單位寫入,關閉文件時會強制寫入最後一塊,即便這個塊空間內部還不滿。
  5. Read,數據從文件中讀取。一般狀況下,讀取的數據來自文件的當前位置。調用者必須指定須要讀取多少數據,而且提供存放這些數據的緩衝區。
  6. Write,向文件寫數據,寫操做通常也是從文件的當前位置開始進行。若是當前位置是文件的末尾,則會直接追加進行寫入。若是當前位置在文件中,則現有數據被覆蓋,而且永遠消失。
  7. append,使用 append 只能向文件末尾添加數據。
  8. seek,對於隨機訪問的文件,要指定從何處開始獲取數據。一般的方法是用 seek 系統調用把當前位置指針指向文件中的特定位置。seek 調用結束後,就能夠從指定位置開始讀寫數據了。
  9. get attributes,進程運行時一般須要讀取文件屬性。
  10. set attributes,用戶能夠本身設置一些文件屬性,甚至是在文件建立以後,實現該功能的是 set attributes 系統調用。
  11. rename,用戶能夠本身更改已有文件的名字,rename 系統調用用於這一目的。
    目錄
    文件系統一般提供目錄(directories) 或者 文件夾(folders) 用於記錄文件的位置,在不少系統中目錄自己也是文件,下面咱們會討論關於文件,他們的組織形式、屬性和能夠對文件進行的操做。

一級目錄系統
目錄系統最簡單的形式是有一個可以包含全部文件的目錄。這種目錄被稱爲根目錄(root directory),因爲根目錄的惟一性,因此其名稱並不重要。在最先期的我的計算機中,這種系統很常見,部分緣由是由於只有一個用戶。下面是一個單層目錄系統的例子
5萬字、97 張圖總結操做系統核心知識點

該目錄中有四個文件。這種設計的優勢在於簡單,而且可以快速定位文件,畢竟只有一個地方能夠檢索。這種目錄組織形式如今通常用於簡單的嵌入式設備(如數碼相機和某些便攜式音樂播放器)上使用。

層次目錄系統
對於簡單的應用而言,通常都用單層目錄方式,可是這種組織形式並不適合於現代計算機,由於現代計算機含有成千上萬個文件和文件夾。若是都放在根目錄下,查找起來會很是困難。爲了解決這一問題,出現了層次目錄系統(Hierarchical Directory Systems),也稱爲目錄樹。經過這種方式,能夠用不少目錄把文件進行分組。進而,若是多個用戶共享同一個文件服務器,好比公司的網絡系統,每一個用戶能夠爲本身的目錄樹擁有本身的私人根目錄。這種方式的組織結構以下

5萬字、97 張圖總結操做系統核心知識點

根目錄含有目錄 A、B 和 C ,分別屬於不一樣的用戶,其中兩個用戶個字建立了子目錄。用戶能夠建立任意數量的子目錄,現代文件系統都是按照這種方式組織的。

路徑名
當目錄樹組織文件系統時,須要有某種方法指明文件名。經常使用的方法有兩種,第一種方式是每一個文件都會用一個絕對路徑名(absolute path name),它由根目錄到文件的路徑組成。

另一種指定文件名的方法是 相對路徑名(relative path name)。它經常和 工做目錄(working directory) (也稱做 當前目錄(current directory))一塊兒使用。用戶能夠指定一個目錄做爲當前工做目錄。例如,若是當前目錄是 /usr/ast,那麼絕對路徑 /usr/ast/mailbox能夠直接使用 mailbox 來引用。

目錄操做
不一樣文件中管理目錄的系統調用的差異比管理文件的系統調用差異大。爲了瞭解這些系統調用有哪些以及它們怎樣工做,下面給出一個例子(取自 UNIX)。

  1. Create,建立目錄,除了目錄項 . 和 .. 外,目錄內容爲空。
  2. Delete,刪除目錄,只有空目錄能夠刪除。只包含 . 和 .. 的目錄被認爲是空目錄,這兩個目錄項一般不能刪除
  3. opendir,目錄內容可被讀取。例如,未列出目錄中的所有文件,程序必須先打開該目錄,而後讀其中所有文件的文件名。與打開和讀文件相同,在讀目錄前,必須先打開文件。
  4. closedir,讀目錄結束後,應該關閉目錄用於釋放內部表空間。
  5. readdir,系統調用 readdir 返回打開目錄的下一個目錄項。之前也採用 read 系統調用來讀取目錄,可是這種方法有一個缺點:程序員必須瞭解和處理目錄的內部結構。相反,不論採用哪種目錄結構,readdir 老是以標準格式返回一個目錄項。
  6. rename,在不少方面目錄和文件都類似。文件能夠更換名稱,目錄也能夠。
    link,連接技術容許在多個目錄中出現同一個文件。這個系統調用指定一個存在的文件和一個路徑名,並創建從該文件到路徑所指名字的連接。這樣,能夠在多個目錄中出現同一個文件。有時也被稱爲硬連接(hard link)。
  7. unlink,刪除目錄項。若是被解除連接的文件只出如今一個目錄中,則將它從文件中刪除。若是它出如今多個目錄中,則只刪除指定路徑名的連接,依然保留其餘路徑名的連接。在 UNIX 中,用於刪除文件的系統調用就是 unlink。
    文件系統的實現
    文件系統佈局
    文件系統存儲在磁盤中。大部分的磁盤可以劃分出一到多個分區,叫作磁盤分區(disk partitioning) 或者是磁盤分片(disk slicing)。每一個分區都有獨立的文件系統,每塊分區的文件系統能夠不一樣。磁盤的 0 號分區稱爲 主引導記錄(Master Boot Record, MBR),用來引導(boot) 計算機。在 MBR 的結尾是分區表(partition table)。每一個分區表給出每一個分區由開始到結束的地址。

當計算機開始引 boot 時,BIOS 讀入並執行 MBR。

引導塊
MBR 作的第一件事就是肯定活動分區,讀入它的第一個塊,稱爲引導塊(boot block) 並執行。引導塊中的程序將加載分區中的操做系統。爲了一致性,每一個分區都會從引導塊開始,即便引導塊不包含操做系統。引導塊佔據文件系統的前 4096 個字節,從磁盤上的字節偏移量 0 開始。引導塊可用於啓動操做系統。

除了從引導塊開始以外,磁盤分區的佈局是隨着文件系統的不一樣而變化的。一般文件系統會包含一些屬性,以下

5萬字、97 張圖總結操做系統核心知識點

超級塊
緊跟在引導塊後面的是 超級塊(Superblock),超級塊 的大小爲 4096 字節,從磁盤上的字節偏移 4096 開始。超級塊包含文件系統的全部關鍵參數

  • 文件系統的大小
  • 文件系統中的數據塊數
  • 指示文件系統狀態的標誌
  • 分配組大小
    在計算機啓動或者文件系統首次使用時,超級塊會被讀入內存。

空閒空間塊
接着是文件系統中空閒塊的信息,例如,能夠用位圖或者指針列表的形式給出。

「BitMap 位圖或者 Bit vector 位向量」

位圖或位向量是一系列位或位的集合,其中每一個位對應一個磁盤塊,該位能夠採用兩個值:0和1,0表示已分配該塊,而1表示一個空閒塊。下圖中的磁盤上給定的磁盤塊實例(分配了綠色塊)能夠用16位的位圖表示爲:0000111000000110。

5萬字、97 張圖總結操做系統核心知識點

「使用鏈表進行管理」

在這種方法中,空閒磁盤塊連接在一塊兒,即一個空閒塊包含指向下一個空閒塊的指針。第一個磁盤塊的塊號存儲在磁盤上的單獨位置,也緩存在內存中。

5萬字、97 張圖總結操做系統核心知識點

碎片
這裏不得不提一個叫作碎片(fragment)的概念,也稱爲片斷。通常零散的單個數據一般稱爲片斷。磁盤塊能夠進一步分爲固定大小的分配單元,片斷只是在驅動器上彼此不相鄰的文件片斷。

inode
而後在後面是一個 inode(index node),也稱做索引節點。它是一個數組的結構,每一個文件有一個 inode,inode 很是重要,它說明了文件的方方面面。每一個索引節點都存儲對象數據的屬性和磁盤塊位置

有一種簡單的方法能夠找到它們 ls -lai 命令。讓咱們看一下根文件系統:

5萬字、97 張圖總結操做系統核心知識點

inode 節點主要包括瞭如下信息

  • 模式/權限(保護)
  • 全部者 ID
  • 組 ID
  • 文件大小
  • 文件的硬連接數
  • 上次訪問時間
  • 最後修改時間
  • inode 上次修改時間
    文件分爲兩部分,索引節點和塊。一旦建立後,每種類型的塊數是固定的。你不能增長分區上 inode 的數量,也不能增長磁盤塊的數量。

緊跟在 inode 後面的是根目錄,它存放的是文件系統目錄樹的根部。最後,磁盤的其餘部分存放了其餘全部的目錄和文件。

文件的實現
最重要的問題是記錄各個文件分別用到了哪些磁盤塊。不一樣的系統採用了不一樣的方法。下面咱們會探討一下這些方式。分配背後的主要思想是有效利用文件空間和快速訪問文件 ,主要有三種分配方案

  • 連續分配
  • 鏈表分配
  • 索引分配
    連續分配
    最簡單的分配方案是把每一個文件做爲一連串連續數據塊存儲在磁盤上。所以,在具備 1KB 塊的磁盤上,將爲 50 KB 文件分配 50 個連續塊。

5萬字、97 張圖總結操做系統核心知識點

上面展現了 40 個連續的內存塊。從最左側的 0 塊開始。初始狀態下,尚未裝載文件,所以磁盤是空的。接着,從磁盤開始處(塊 0 )處開始寫入佔用 4 塊長度的內存 A 。而後是一個佔用 6 塊長度的內存 B,會直接在 A 的末尾開始寫。

注意每一個文件都會在新的文件塊開始寫,因此若是文件 A 只佔用了 3 又 1/2 個塊,那麼最後一個塊的部份內存會被浪費。在上面這幅圖中,總共展現了 7 個文件,每一個文件都會從上個文件的末尾塊開始寫新的文件塊。

連續的磁盤空間分配有兩個優勢。

  • 第一,連續文件存儲實現起來比較簡單,只須要記住兩個數字就能夠:一個是第一個塊的文件地址和文件的塊數量。給定第一個塊的編號,能夠經過簡單的加法找到任何其餘塊的編號。

  • 第二點是讀取性能比較強,能夠經過一次操做從文件中讀取整個文件。只須要一次尋找第一個塊。後面就再也不須要尋道時間和旋轉延遲,因此數據會以全帶寬進入磁盤。

所以,連續的空間分配具備實現簡單、高性能的特色。

不幸的是,連續空間分配也有很明顯的不足。隨着時間的推移,磁盤會變得很零碎。下圖解釋了這種現象

5萬字、97 張圖總結操做系統核心知識點

這裏有兩個文件 D 和 F 被刪除了。當刪除一個文件時,此文件所佔用的塊也隨之釋放,就會在磁盤空間中留下一些空閒塊。磁盤並不會在這個位置擠壓掉空閒塊,由於這會複製空閒塊以後的全部文件,可能會有上百萬的塊,這個量級就太大了。

鏈表分配
第二種存儲文件的方式是爲每一個文件構造磁盤塊鏈表,每一個文件都是磁盤塊的連接列表,就像下面所示

5萬字、97 張圖總結操做系統核心知識點

每一個塊的第一個字做爲指向下一塊的指針,塊的其餘部分存放數據。若是上面這張圖你看的不是很清楚的話,能夠看看整個的鏈表分配方案

5萬字、97 張圖總結操做系統核心知識點

與連續分配方案不一樣,這一方法能夠充分利用每一個磁盤塊。除了最後一個磁盤塊外,不會由於磁盤碎片而浪費存儲空間。一樣,在目錄項中,只要存儲了第一個文件塊,那麼其餘文件塊也可以被找到。

另外一方面,在鏈表的分配方案中,儘管順序讀取很是方便,可是隨機訪問卻很困難(這也是數組和鏈表數據結構的一大區別)。

還有一個問題是,因爲指針會佔用一些字節,每一個磁盤塊實際存儲數據的字節數並再也不是 2 的整數次冪。雖然這個問題並不會很嚴重,可是這種方式下降了程序運行效率。許多程序都是以長度爲 2 的整數次冪來讀寫磁盤,因爲每一個塊的前幾個字節被指針所使用,因此要讀出一個完成的塊大小信息,就須要當前塊的信息和下一塊的信息拼湊而成,所以就引起了查找和拼接的開銷。

使用內存表進行鏈表分配
因爲連續分配和鏈表分配都有其不可忽視的缺點。因此提出了使用內存中的表來解決分配問題。取出每一個磁盤塊的指針字,把它們放在內存的一個表中,就能夠解決上述鏈表的兩個不足之處。下面是一個例子
5萬字、97 張圖總結操做系統核心知識點

上圖表示了鏈表造成的磁盤塊的內容。這兩個圖中都有兩個文件,文件 A 依次使用了磁盤塊地址 「四、七、 二、 十、 12」,文件 B 使用了「六、三、11 和 14」。也就是說,文件 A 從地址 4 處開始,順着鏈表走就能找到文件 A 的所有磁盤塊。一樣,從第 6 塊開始,順着鏈走到最後,也可以找到文件 B 的所有磁盤塊。你會發現,這兩個鏈表都以不屬於有效磁盤編號的特殊標記(-1)結束。內存中的這種表格稱爲 文件分配表(File Application Table,FAT)。

目錄的實現
文件只有打開後纔可以被讀取。在文件打開後,操做系統會使用用戶提供的路徑名來定位磁盤中的目錄。目錄項提供了查找文件磁盤塊所須要的信息。根據系統的不一樣,提供的信息也不一樣,可能提供的信息是整個文件的磁盤地址,或者是第一個塊的數量(兩個鏈表方案)或 inode的數量。不過無論用那種狀況,目錄系統的主要功能就是 「將文件的 ASCII 碼的名稱映射到定位數據所需的信息上」。

共享文件
當多個用戶在同一個項目中工做時,他們一般須要共享文件。若是這個共享文件同時出如今多個用戶目錄下,那麼他們協同工做起來就很方便。下面的這張圖咱們在上面提到過,可是有一個更改的地方,就是 「C 的一個文件也出如今了 B 的目錄下」。
5萬字、97 張圖總結操做系統核心知識點

若是按照如上圖的這種組織方式而言,那麼 B 的目錄與該共享文件的聯繫稱爲 連接(link)。那麼文件系統如今就是一個 有向無環圖(Directed Acyclic Graph, 簡稱 DAG),而不是一棵樹了。

日誌結構文件系統
技術的改變會給當前的文件系統帶來壓力。這種狀況下,CPU 會變得愈來愈快,磁盤會變得愈來愈大而且愈來愈便宜(但不會愈來愈快)。內存容量也是以指數級增加。可是磁盤的尋道時間(除了固態盤,由於固態盤沒有尋道時間)並無得到提升。

爲此,Berkeley 設計了一種全新的文件系統,試圖緩解這個問題,這個文件系統就是 日誌結構文件系統(Log-structured File System, LFS)。旨在解決如下問題。

  • 不斷增加的系統內存

  • 順序 I/O 性能賽過隨機 I/O 性能

  • 現有低效率的文件系統

  • 文件系統不支持 RAID(虛擬化)

另外一方面,當時的文件系統不管是 UNIX 仍是 FFS,都有大量的隨機讀寫(在 FFS 中建立一個新文件至少須要5次隨機寫),所以成爲整個系統的性能瓶頸。同時由於 Page cache的存在,做者認爲隨機讀不是主要問題:隨着愈來愈大的內存,大部分的讀操做都能被 cache,所以 LFS 主要要解決的是減小對硬盤的隨機寫操做。

在這種設計中,inode 甚至具備與 UNIX 中相同的結構,可是如今它們分散在整個日誌中,而不是位於磁盤上的固定位置。因此,inode 很定位。爲了可以找到 inode ,維護了一個由 inode 索引的 inode map(inode 映射)。表項 i 指向磁盤中的第 i 個 inode 。這個映射保存在磁盤中,可是也保存在緩存中,所以,使用最頻繁的部分大部分時間都在內存中。

5萬字、97 張圖總結操做系統核心知識點

到目前爲止,全部寫入最初都緩存在內存中,而且追加在日誌末尾,全部緩存的寫入都按期在單個段中寫入磁盤。因此,如今打開文件也就意味着用映射定位文件的索引節點。一旦 inode 被定位後,磁盤塊的地址就可以被找到。全部這些塊自己都將位於日誌中某處的分段中。

真實狀況下的磁盤容量是有限的,因此最終日誌會佔滿整個磁盤空間,這種狀況下就會出現沒有新的磁盤塊被寫入到日誌中。幸運的是,許多現有段可能具備再也不須要的塊。例如,若是一個文件被覆蓋了,那麼它的 inode 將被指向新的塊,可是舊的磁盤塊仍在先前寫入的段中佔據着空間。

爲了處理這個問題,LFS 有一個清理(clean)線程,它會循環掃描日誌並對日誌進行壓縮。首先,經過查看日誌中第一部分的信息來查看其中存在哪些索引節點和文件。它會檢查當前 inode 的映射來查看 inode 否在在當前塊中,是否仍在被使用。若是不是,該信息將被丟棄。若是仍然在使用,那麼 inode 和塊就會進入內存等待寫回到下一個段中。而後原來的段被標記爲空閒,以便日誌能夠用來存放新的數據。用這種方法,清理線程遍歷日誌,從後面移走舊的段,而後將有效的數據放入內存等待寫到下一個段中。由此一來整個磁盤會造成一個大的環形緩衝區,寫線程將新的段寫在前面,而清理線程則清理後面的段。

5萬字、97 張圖總結操做系統核心知識點

日誌文件系統
雖然日誌結構系統的設計很優雅,可是因爲它們和現有的文件系統不相匹配,所以尚未普遍使用。不過,從日誌文件結構系統衍生出來一種新的日誌系統,叫作日誌文件系統,它會記錄系統下一步將要作什麼的日誌。微軟的 NTFS 文件系統、Linux 的 ext3 就使用了此日誌。OS X 將日誌系統做爲可供選項。爲了看清它是如何工做的,咱們下面討論一個例子,好比 移除文件 ,這個操做在 UNIX 中須要三個步驟完成:

  • 在目錄中刪除文件
  • 釋放 inode 到空閒 inode 池
  • 將全部磁盤塊歸還給空閒磁盤池。
    虛擬文件系統
    UNIX 操做系統使用一種 虛擬文件系統(Virtual File System, VFS) 來嘗試將多種文件系統構成一個有序的結構。關鍵的思想是抽象出全部文件系統都共有的部分,並將這部分代碼放在一層,這一層再調用具體文件系統來管理數據。下面是一個 VFS 的系統結構

5萬字、97 張圖總結操做系統核心知識點

仍是那句經典的話,在計算機世界中,任何解決不了的問題均可以加個代理來解決。全部和文件相關的系統調用在最初的處理上都指向虛擬文件系統。這些來自用戶進程的調用,都是標準的 POSIX 系統調用,好比 open、read、write 和 seek 等。VFS 對用戶進程有一個 上層 接口,這個接口就是著名的 POSIX 接口。

文件系統的管理和優化
可以使文件系統工做是一回事,可以使文件系統高效、穩定的工做是另外一回事,下面咱們就來探討一下文件系統的管理和優化。

磁盤空間管理
文件一般存在磁盤中,因此如何管理磁盤空間是一個操做系統的設計者須要考慮的問題。在文件上進行存有兩種策略:「分配 n 個字節的連續磁盤空間;或者把文件拆分紅多個並不必定連續的塊」。在存儲管理系統中,主要有分段管理和 分頁管理 兩種方式。

正如咱們所看到的,按連續字節序列存儲文件有一個明顯的問題,當文件擴大時,有可能須要在磁盤上移動文件。內存中分段也有一樣的問題。不一樣的是,相對於把文件從磁盤的一個位置移動到另外一個位置,內存中段的移動操做要快不少。所以,幾乎全部的文件系統都把文件分割成固定大小的塊來存儲。

塊大小
一旦把文件分爲固定大小的塊來存儲,就會出現問題,塊的大小是多少?按照「磁盤組織方式,扇區、磁道和柱面顯然均可以做爲分配單位」。在分頁系統中,分頁大小也是主要因素。

擁有大的塊尺寸意味着每一個文件,甚至 1 字節文件,都要佔用一個柱面空間,也就是說小文件浪費了大量的磁盤空間。另外一方面,小塊意味着大部分文件將會跨越多個塊,所以須要屢次搜索和旋轉延遲才能讀取它們,從而下降了性能。所以,若是分配的塊太大會浪費空間;分配的塊過小會浪費時間。

記錄空閒塊
一旦指定了塊大小,下一個問題就是怎樣跟蹤空閒塊。有兩種方法被普遍採用,以下圖所示

5萬字、97 張圖總結操做系統核心知識點

第一種方法是採用磁盤塊鏈表,鏈表的每一個塊中包含很可能多的空閒磁盤塊號。對於 1 KB 的塊和 32 位的磁盤塊號,空閒表中每一個塊包含有 255 個空閒的塊號。考慮 1 TB 的硬盤,擁有大概十億個磁盤塊。爲了存儲所有地址塊號,若是每塊能夠保存 255 個塊號,則須要將近 400 萬個塊。一般,空閒塊用於保存空閒列表,所以存儲基本上是空閒的。

另外一種空閒空間管理的技術是位圖(bitmap),n 個塊的磁盤須要 n 位位圖。在位圖中,空閒塊用 1 表示,已分配的塊用 0 表示。對於 1 TB 硬盤的例子,須要 10 億位表示,即須要大約 130 000 個 1 KB 塊存儲。很明顯,和 32 位鏈表模型相比,位圖須要的空間更少,由於每一個塊使用 1 位。只有當磁盤快滿的時候,鏈表須要的塊纔會比位圖少。

磁盤配額
爲了防止一些用戶佔用太多的磁盤空間,多用戶操做一般提供一種磁盤配額(enforcing disk quotas)的機制。系統管理員爲每一個用戶分配「最大的文件和塊分配」,而且操做系統確保用戶不會超過其配額。咱們下面會談到這一機制。

在用戶打開一個文件時,操做系統會找到文件屬性和磁盤地址,並把它們送入內存中的打開文件表。其中一個屬性告訴文件全部者是誰。任何有關文件的增長都會記到全部者的配額中。

5萬字、97 張圖總結操做系統核心知識點

第二張表包含了每一個用戶當前打開文件的配額記錄,即便是其餘人打開該文件也同樣。如上圖所示,該表的內容是從被打開文件的全部者的磁盤配額文件中提取出來的。當全部文件關閉時,該記錄被寫回配額文件。

當在打開文件表中創建一新表項時,會產生一個指向全部者配額記錄的指針。每次向文件中添加一個塊時,文件全部者所用數據塊的總數也隨之增長,並會同時增長硬限制和軟限制的檢查。能夠超出軟限制,但硬限制不能夠超出。當已達到硬限制時,再往文件中添加內容將引起錯誤。一樣,對文件數目也存在相似的檢查。

文件系統備份
作文件備份很耗費時間並且也很浪費空間,這會引發下面幾個問題。首先,是要「備份整個文件仍是僅備份一部分呢」?通常來講,只是備份特定目錄及其下的所有文件,而不是備份整個文件系統。

其次,對上次未修改過的文件再進行備份是一種浪費,於是產生了一種增量轉儲(incremental dumps) 的思想。最簡單的增量轉儲的形式就是週期性的作全面的備份,而天天只對增量轉儲完成後發生變化的文件作單個備份。

稍微好一點的方式是隻備份最近一次轉儲以來更改過的文件。固然,這種作法極大的縮減了轉儲時間,但恢復起來卻更復雜,由於「最近的全面轉儲先要所有恢復,隨後按逆序進行增量轉儲」。爲了方便恢復,人們每每使用更復雜的轉儲模式。

第三,既然待轉儲的每每是海量數據,那麼在將其寫入磁帶以前對文件進行壓縮就頗有必要。可是,若是在備份過程當中出現了文件損壞的狀況,就會致使破壞壓縮算法,從而使整個磁帶沒法讀取。因此在備份前是否進行文件壓縮需慎重考慮。

第四,對正在使用的文件系統作備份是很難的。若是在轉儲過程當中要添加,刪除和修改文件和目錄,則轉儲結果可能不一致。所以,由於轉儲過程當中須要花費數個小時的時間,因此有必要在晚上將系統脫機進行備份,然而這種方式的接受程度並不高。因此,人們修改了轉儲算法,記下文件系統的瞬時快照,即複製關鍵的數據結構,而後須要把未來對文件和目錄所作的修改複製到塊中,而不是處處更新他們。

磁盤轉儲到備份磁盤上有兩種方案:「物理轉儲和邏輯轉儲」。物理轉儲(physical dump) 是從磁盤的 0 塊開始,依次將全部磁盤塊按照順序寫入到輸出磁盤,並在複製最後一個磁盤時中止。這種程序的萬無一失性是其餘程序所不具有的。

第二個須要考慮的是「壞塊的轉儲」。製造大型磁盤而沒有瑕疵是不可能的,因此也會存在一些壞塊(bad blocks)。有時進行低級格式化後,壞塊會被檢測出來並進行標記,這種狀況的解決辦法是用磁盤末尾的一些空閒塊所替換。

然而,一些塊在格式化後會變壞,在這種狀況下操做系統能夠檢測到它們。一般狀況下,它能夠經過建立一個由全部壞塊組成的文件來解決問題,確保它們不會出如今空閒池中而且永遠不會被分配。「那麼此文件是徹底不可讀的」。若是磁盤控制器將全部的壞塊從新映射,物理轉儲仍是可以正常工做的。

Windows 系統有分頁文件(paging files) 和 休眠文件(hibernation files) 。它們在文件還原時不發揮做用,同時也不該該在第一時間進行備份。

文件系統的一致性
影響可靠性的一個因素是文件系統的一致性。許多文件系統讀取磁盤塊、修改磁盤塊、再把它們寫回磁盤。若是系統在全部塊寫入以前崩潰,文件系統就會處於一種不一致(inconsistent)的狀態。若是某些還沒有寫回的塊是索引節點塊,目錄塊或包含空閒列表的塊,則此問題是很嚴重的。

爲了處理文件系統一致性問題,大部分計算機都會有應用程序來檢查文件系統的一致性。例如,UNIX 有 fsck;Windows 有 sfc,每當引導系統時(尤爲是在崩潰後),均可以運行該程序。

能夠進行兩種一致性檢查:「塊的一致性檢查和文件的一致性檢查」。爲了檢查塊的一致性,應用程序會創建兩張表,每一個包含一個計數器的塊,最初設置爲 0 。第一個表中的計數器跟蹤該塊在文件中出現的次數,第二張表中的計數器記錄每一個塊在空閒列表、空閒位圖中出現的頻率。

文件系統性能
訪問磁盤的效率要比內存滿的多,是時候又祭出這張圖了

5萬字、97 張圖總結操做系統核心知識點
從內存讀一個 32 位字大概是 10ns,從硬盤上讀的速率大概是 100MB/S,對每一個 32 位字來講,效率會慢了四倍,另外,還要加上 5 - 10 ms 的尋道時間等其餘損耗,若是隻訪問一個字,內存要比磁盤快百萬數量級。因此磁盤優化是頗有必要的,下面咱們會討論幾種優化方式

高速緩存
最經常使用的減小磁盤訪問次數的技術是使用 塊高速緩存(block cache) 或者 緩衝區高速緩存(buffer cache)。高速緩存指的是一系列的塊,它們在邏輯上屬於磁盤,但實際上基於性能的考慮被保存在內存中。

管理高速緩存有不一樣的算法,經常使用的算法是:檢查所有的讀請求,查看在高速緩存中是否有所須要的塊。若是存在,可執行讀操做而無須訪問磁盤。若是檢查塊再也不高速緩存中,那麼首先把它讀入高速緩存,再複製到所需的地方。以後,對同一個塊的請求都經過高速緩存來完成。

高速緩存的操做以下圖所示
5萬字、97 張圖總結操做系統核心知識點

因爲在高速緩存中有許多塊,因此須要某種方法快速肯定所需的塊是否存在。經常使用方法是將設備和磁盤地址進行散列操做,而後,在散列表中查找結果。具備相同散列值的塊在一個鏈表中鏈接在一塊兒(這個數據結構是否是很像 HashMap?),這樣就能夠沿着衝突鏈查找其餘塊。

若是高速緩存已滿,此時須要調入新的塊,則要把原來的某一塊調出高速緩存,若是要調出的塊在上次調入後已經被修改過,則須要把它寫回磁盤。

塊提早讀
第二個明顯提升文件系統的性能是,在須要用到塊以前,試圖提早將其寫入高速緩存,從而提升命中率。許多文件都是順序讀取。若是請求文件系統在某個文件中生成塊 k,文件系統執行相關操做而且在完成以後,會檢查高速緩存,以便肯定塊 k + 1 是否已經在高速緩存。若是不在,文件系統會爲 k + 1 安排一個預讀取,由於文件但願在用到該塊的時候可以直接從高速緩存中讀取。

固然,塊提早讀取策略只適用於實際順序讀取的文件。對隨機訪問的文件,提早讀絲絕不起做用。甚至還會形成阻礙。

減小磁盤臂運動
高速緩存和塊提早讀並非提升文件系統性能的惟一方法。另外一種重要的技術是「把有可能順序訪問的塊放在一塊兒,固然最好是在同一個柱面上,從而減小磁盤臂的移動次數」。當寫一個輸出文件時,文件系統就必須按照要求一次一次地分配磁盤塊。若是用位圖來記錄空閒塊,而且整個位圖在內存中,那麼選擇與前一塊最近的空閒塊是很容易的。若是用空閒表,而且鏈表的一部分存在磁盤上,要分配緊鄰的空閒塊就會困難不少。

磁盤碎片整理
在初始安裝操做系統後,文件就會被不斷的建立和清除,因而磁盤會產生不少的碎片,在建立一個文件時,它使用的塊會散佈在整個磁盤上,下降性能。刪除文件後,回收磁盤塊,可能會形成空穴。

磁盤性能能夠經過以下方式恢復:移動文件使它們相互挨着,並把全部的至少是大部分的空閒空間放在一個或多個大的連續區域內。Windows 有一個程序 defrag 就是作這個事兒的。Windows 用戶會常用它,SSD 除外。

磁盤碎片整理程序會在讓文件系統上很好地運行。Linux 文件系統(特別是 ext2 和 ext3)因爲其選擇磁盤塊的方式,在磁盤碎片整理上通常不會像 Windows 同樣困難,所以不多須要手動的磁盤碎片整理。並且,固態硬盤並不受磁盤碎片的影響,事實上,在固態硬盤上作磁盤碎片整理反

下面咱們來探討一下 I/O 流程問題。

5萬字、97 張圖總結操做系統核心知識點

I/O 設備
什麼是 I/O 設備?I/O 設備又叫作輸入/輸出設備,它是人類用來和計算機進行通訊的外部硬件。輸入/輸出設備可以向計算機發送數據(輸出)並從計算機接收數據(輸入)。

I/O 設備(I/O devices)能夠分紅兩種:塊設備(block devices) 和 字符設備(character devices)。

塊設備
塊設備是一個能存儲固定大小塊信息的設備,它支持「以固定大小的塊,扇區或羣集讀取和(可選)寫入數據」。每一個塊都有本身的物理地址。一般塊的大小在 512 - 65536 之間。全部傳輸的信息都會以連續的塊爲單位。塊設備的基本特徵是每一個塊都較爲對立,可以獨立的進行讀寫。常見的塊設備有 「硬盤、藍光光盤、USB 盤」

與字符設備相比,塊設備一般須要較少的引腳。

5萬字、97 張圖總結操做系統核心知識點

塊設備的缺點
基於給定固態存儲器的塊設備比基於相同類型的存儲器的字節尋址要慢一些,由於必須在塊的開頭開始讀取或寫入。因此,要讀取該塊的任何部分,必須尋找到該塊的開始,讀取整個塊,若是不使用該塊,則將其丟棄。要寫入塊的一部分,必須尋找到塊的開始,將整個塊讀入內存,修改數據,再次尋找到塊的開頭處,而後將整個塊寫回設備。

字符設備
另外一類 I/O 設備是字符設備。字符設備以字符爲單位發送或接收一個字符流,而不考慮任何塊結構。字符設備是不可尋址的,也沒有任何尋道操做。常見的字符設備有 「打印機、網絡設備、鼠標、以及大多數與磁盤不一樣的設備」。
5萬字、97 張圖總結操做系統核心知識點

設備控制器
設備控制器是處理 CPU 傳入和傳出信號的系統。設備經過插頭和插座鏈接到計算機,而且插座鏈接到設備控制器。設備控制器從鏈接的設備處接收數據,並將其存儲在控制器內部的一些特殊目的寄存器(special purpose registers) 也就是本地緩衝區中。

每一個設備控制器都會有一個應用程序與之對應,設備控制器經過應用程序的接口經過中斷與操做系統進行通訊。設備控制器是硬件,而設備驅動程序是軟件。

內存映射 I/O
每一個控制器都會有幾個寄存器用來和 CPU 進行通訊。經過寫入這些寄存器,操做系統能夠命令設備發送數據,接收數據、開啓或者關閉設備等。經過從這些寄存器中讀取信息,操做系統可以知道設備的狀態,是否準備接受一個新命令等。

爲了控制寄存器,許多設備都會有數據緩衝區(data buffer),來供系統進行讀寫。

那麼問題來了,CPU 如何與設備寄存器和設備數據緩衝區進行通訊呢?存在兩個可選的方式。第一種方法是,每一個控制寄存器都被分配一個 I/O 端口(I/O port)號,這是一個 8 位或 16 位的整數。全部 I/O 端口的集合造成了受保護的 I/O 端口空間,以便普通用戶程序沒法訪問它(只有操做系統能夠訪問)。使用特殊的 I/O 指令像是

IN REG,PORT

CPU 能夠讀取控制寄存器 PORT 的內容並將結果放在 CPU 寄存器 REG 中。相似的,使用

OUT PORT,REG

CPU 能夠將 REG 的內容寫到控制寄存器中。大多數早期計算機,包括幾乎全部大型主機,如 IBM 360 及其全部後續機型,都是以這種方式工做的。

第二個方法是 PDP-11 引入的,它將「全部控制寄存器映射到內存空間」中。

直接內存訪問
不管一個 CPU 是否具備內存映射 I/O,它都須要尋址設備控制器以便與它們交換數據。CPU 能夠從 I/O 控制器每次請求一個字節的數據,可是這麼作會浪費 CPU 時間,因此常常會用到一種稱爲直接內存訪問(Direct Memory Access) 的方案。爲了簡化,咱們假設 CPU 經過單一的系統總線訪問全部的設備和內存,該總線鏈接 CPU 、內存和 I/O 設備,以下圖所示
5萬字、97 張圖總結操做系統核心知識點

現代操做系統實際更爲複雜,可是原理是相同的。若是硬件有DMA 控制器,那麼操做系統只能使用 DMA有時這個控制器會集成到磁盤控制器和其餘控制器中,但這種設計須要在每一個設備上都裝有一個分離的 DMA 控制器。單個的 DMA 控制器可用於向多個設備傳輸,這種傳輸每每同時進行。

DMA 工做原理
首先 CPU 經過設置 DMA 控制器的寄存器對它進行編程,因此 DMA 控制器知道將什麼數據傳送到什麼地方。DMA 控制器還要向磁盤控制器發出一個命令,通知它從磁盤讀數據到其內部的緩衝區並檢驗校驗和。當有效數據位於磁盤控制器的緩衝區中時,DMA 就能夠開始了。

DMA 控制器經過在總線上發出一個讀請求到磁盤控制器而發起 DMA 傳送,這是第二步。這個讀請求就像其餘讀請求同樣,磁盤控制器並不知道或者並不關心它是來自 CPU 仍是來自 DMA 控制器。一般狀況下,要寫的內存地址在總線的地址線上,因此當磁盤控制器去匹配下一個字時,它知道將該字寫到什麼地方。寫到內存就是另一個總線循環了,這是第三步。當寫操做完成時,磁盤控制器在總線上發出一個應答信號到 DMA 控制器,這是第四步。

而後,DMA 控制器會增長內存地址並減小字節數量。若是字節數量仍然大於 0 ,就會循環步驟 2 - 步驟 4 ,直到字節計數變爲 0 。此時,DMA 控制器會打斷 CPU 並告訴它傳輸已經完成了。

重溫中斷
在一臺我的計算機體系結構中,中斷結構會以下所示

5萬字、97 張圖總結操做系統核心知識點

當一個 I/O 設備完成它的工做後,它就會產生一箇中斷(默認操做系統已經開啓中斷),它經過在總線上聲明已分配的信號來實現此目的。主板上的中斷控制器芯片會檢測到這個信號,而後執行中斷操做。

精確中斷和不精確中斷
使機器處於良好狀態的中斷稱爲精確中斷(precise interrupt)。這樣的中斷具備四個屬性:

  • PC (程序計數器)保存在一個已知的地方
  • PC 所指向的指令以前全部的指令已經徹底執行
  • PC 所指向的指令以後全部的指令都沒有執行
  • PC 所指向的指令的執行狀態是已知的
    不知足以上要求的中斷稱爲 不精確中斷(imprecise interrupt),不精確中斷讓人很頭疼。上圖描述了不精確中斷的現象。指令的執行時序和完成度具備不肯定性,並且恢復起來也很是麻煩。

IO 軟件原理
I/O 軟件目標
設備獨立性
I/O 軟件設計一個很重要的目標就是設備獨立性(device independence)。這意味着「咱們可以編寫訪問任何設備的應用程序,而不用事先指定特定的設備」。

5萬字、97 張圖總結操做系統核心知識點

錯誤處理
除了設備獨立性外,I/O 軟件實現的第二個重要的目標就是錯誤處理(error handling)。一般狀況下來講,錯誤應該交給硬件層面去處理。若是設備控制器發現了讀錯誤的話,它會盡量的去修復這個錯誤。若是設備控制器處理不了這個問題,那麼設備驅動程序應該進行處理,設備驅動程序會再次嘗試讀取操做,不少錯誤都是偶然性的,若是設備驅動程序沒法處理這個錯誤,纔會把錯誤向上拋到硬件層面(上層)進行處理,不少時候,上層並不須要知道下層是如何解決錯誤的。

同步和異步傳輸
I/O 軟件實現的第三個目標就是 同步(synchronous) 和 異步(asynchronous,即中斷驅動)傳輸。這裏先說一下同步和異步是怎麼回事吧。

同步傳輸中數據一般以塊或幀的形式發送。發送方和接收方在數據傳輸以前應該具備同步時鐘。而在異步傳輸中,數據一般以字節或者字符的形式發送,異步傳輸則不須要同步時鐘,可是會在傳輸以前向數據添加奇偶校驗位。大部分物理IO(physical I/O) 是異步的。物理 I/O 中的 CPU 是很聰明的,CPU 傳輸完成後會轉而作其餘事情,它和中斷心靈相通,等到中斷髮生後,CPU 纔會回到傳輸這件事情上來。

緩衝
I/O 軟件的最後一個問題是緩衝(buffering)。一般狀況下,從一個設備發出的數據不會直接到達最後的設備。其間會通過一系列的校驗、檢查、緩衝等操做才能到達。

共享和獨佔
I/O 軟件引發的最後一個問題就是共享設備和獨佔設備的問題。有些 I/O 設備可以被許多用戶共同使用。一些設備好比磁盤,讓多個用戶使用通常不會產生什麼問題,可是某些設備必須具備獨佔性,即只容許單個用戶使用完成後才能讓其餘用戶使用。

一共有三種控制 I/O 設備的方法

  • 使用程序控制 I/O
  • 使用中斷驅動 I/O
  • 使用 DMA 驅動 I/O
    I/O 層次結構
    I/O 軟件一般組織成四個層次,它們的大體結構以下圖所示

5萬字、97 張圖總結操做系統核心知識點

下面咱們具體的來探討一下上面的層次結構

中斷處理程序
在計算機系統中,中斷就像女人的脾氣同樣無時無刻都在產生,中斷的出現每每是讓人很不爽的。中斷處理程序又被稱爲中斷服務程序 或者是 ISR(Interrupt Service Routines),它是最靠近硬件的一層。中斷處理程序由硬件中斷、軟件中斷或者是軟件異常啓動產生的中斷,用於實現設備驅動程序或受保護的操做模式(例如系統調用)之間的轉換。

中斷處理程序負責處理中斷髮生時的全部操做,操做完成後阻塞,而後啓動中斷驅動程序來解決阻塞。一般會有三種通知方式,依賴於不一樣的具體實現

  • 信號量實現中:在信號量上使用 up 進行通知;
  • 管程實現:對管程中的條件變量執行 signal 操做
  • 還有一些狀況是發送一些消息
    設備驅動程序
    每一個鏈接到計算機的 I/O 設備都須要有某些特定設備的代碼對其進行控制。這些提供 I/O 設備到設備控制器轉換的過程的代碼稱爲 設備驅動程序(Device driver)。

設備控制器的主要功能有下面這些

  • 接收和識別命令:設備控制器能夠接受來自 CPU 的指令,並進行識別。設備控制器內部也會有寄存器,用來存放指令和參數

  • 進行數據交換:CPU、控制器和設備之間會進行數據的交換,CPU 經過總線把指令發送給控制器,或從控制器中並行地讀出數據;控制器將數據寫入指定設備。

  • 地址識別:每一個硬件設備都有本身的地址,設備控制器可以識別這些不一樣的地址,來達到控制硬件的目的,此外,爲使 CPU 能向寄存器中寫入或者讀取數據,這些寄存器都應具備惟一的地址。

  • 差錯檢測:設備控制器還具備對設備傳遞過來的數據進行檢測的功能。

在這種狀況下,設備控制器會阻塞,直到中斷來解除阻塞狀態。還有一種狀況是操做是能夠無延遲的完成,因此驅動程序不須要阻塞。在第一種狀況下,操做系統可能被中斷喚醒;第二種狀況下操做系統不會被休眠。

設備驅動程序必須是可重入的,由於設備驅動程序會阻塞和喚醒而後再次阻塞。驅動程序不容許進行系統調用,可是它們一般須要與內核的其他部分進行交互。

與設備無關的 I/O 軟件
I/O 軟件有兩種,一種是咱們上面介紹過的基於特定設備的,還有一種是設備無關性的,設備無關性也就是不須要特定的設備。設備驅動程序與設備無關的軟件之間的界限取決於具體的系統。下面顯示的功能由設備無關的軟件實現

5萬字、97 張圖總結操做系統核心知識點

與設備無關的軟件的基本功能是對全部設備執行公共的 I/O 功能,而且向用戶層軟件提供一個統一的接口。

緩衝
不管是對於塊設備仍是字符設備來講,緩衝都是一個很是重要的考量標準。緩衝技術應用普遍,但它也有缺點。若是數據被緩衝次數太多,會影響性能。

錯誤處理
在 I/O 中,出錯是一種再正常不過的狀況了。當出錯發生時,操做系統必須儘量處理這些錯誤。有一些錯誤是隻有特定的設備才能處理,有一些是由框架進行處理,這些錯誤和特定的設備無關。

I/O 錯誤的一類是程序員編程錯誤,好比尚未打開文件前就讀流,或者不關閉流致使內存溢出等等。這類問題由程序員處理;另一類是實際的 I/O 錯誤,例如向一個磁盤壞塊寫入數據,不管怎麼寫都寫入不了。這類問題由驅動程序處理,驅動程序處理不了交給硬件處理,這個咱們上面也說過。

設備驅動程序統一接口
咱們在操做系統概述中說到,操做系統一個很是重要的功能就是屏蔽了硬件和軟件的差別性,爲硬件和軟件提供了統一的標準,這個標準還體如今爲設備驅動程序提供統一的接口,由於不一樣的硬件和廠商編寫的設備驅動程序不一樣,因此若是爲每一個驅動程序都單獨提供接口的話,這樣無法搞,因此必須統一。

分配和釋放
一些設備例如打印機,它只能由一個進程來使用,這就須要操做系統根據實際狀況判斷是否可以對設備的請求進行檢查,判斷是否可以接受其餘請求,一種比較簡單直接的方式是在特殊文件上執行 open操做。若是設備不可用,那麼直接 open 會致使失敗。還有一種方式是不直接致使失敗,而是讓其阻塞,等到另一個進程釋放資源後,在進行 open 打開操做。這種方式就把選擇權交給了用戶,由用戶判斷是否應該等待。

設備無關的塊
不一樣的磁盤會具備不一樣的扇區大小,可是軟件不會關心扇區大小,只管存儲就是了。一些字符設備能夠一次一個字節的交付數據,而其餘的設備則以較大的單位交付數據,這些差別也能夠隱藏起來。

用戶空間的 I/O 軟件
雖然大部分 I/O 軟件都在內核結構中,可是還有一些在用戶空間實現的 I/O 軟件,凡事沒有絕對。一些 I/O 軟件和庫過程在用戶空間存在,而後以提供系統調用的方式實現。


盤能夠說是硬件裏面比較簡單的構造了,同時也是最重要的。下面咱們從盤談起,聊聊它的物理構造

盤硬件
盤會有不少種類型。其中最簡單的構造就是磁盤(magnetic hard disks), 也被稱爲 hard disk,HDD等。磁盤一般與安裝在磁臂上的磁頭配對,磁頭可將數據讀取或者將數據寫入磁盤,所以磁盤的讀寫速度都一樣快。在磁盤中,數據是隨機訪問的,這也就說明能夠經過任意的順序來存儲和檢索單個數據塊,因此你能夠在任意位置放置磁盤來讓磁頭讀取,磁盤是一種非易失性的設備,即便斷電也能永久保留。

磁盤
爲了組織和檢索數據,會將磁盤組織成特定的結構,這些特定的結構就是「磁道、扇區和柱面」

5萬字、97 張圖總結操做系統核心知識點

磁盤被組織成柱面形式,每一個盤用軸相連,每個柱麪包含若干磁道,每一個磁道由若干扇區組成。軟盤上大約每一個磁道有 8 - 32 個扇區,硬盤上每條磁道上扇區的數量可達幾百個,磁頭大約是 1 - 16 個。

對於磁盤驅動程序來講,一個很是重要的特性就是控制器是否可以同時控制兩個或者多個驅動器進行磁道尋址,這就是重疊尋道(overlapped seek)。對於控制器來講,它可以控制一個磁盤驅動程序完成尋道操做,同時讓其餘驅動程序等待尋道結束。控制器也能夠在一個驅動程序上進行讀寫草哦作,與此同時讓另外的驅動器進行尋道操做,可是軟盤控制器不能在兩個驅動器上進行讀寫操做。

RAID
RAID 稱爲 磁盤冗餘陣列,簡稱 磁盤陣列。利用虛擬化技術把多個硬盤結合在一塊兒,成爲一個或多個磁盤陣列組,目的是提高性能或數據冗餘。

RAID 有不一樣的級別

  • RAID 0 - 無容錯的條帶化磁盤陣列
  • RAID 1 - 鏡像和雙工
  • RAID 2 - 內存式糾錯碼
  • RAID 3 - 比特交錯奇偶校驗
  • RAID 4 - 塊交錯奇偶校驗
  • RAID 5 - 塊交錯分佈式奇偶校驗
  • RAID 6 - P + Q冗餘
    磁盤格式化
    磁盤由一堆鋁的、合金或玻璃的盤片組成,磁盤剛被建立出來後,沒有任何信息。磁盤在使用前必須通過低級格式化(low-levvel format),下面是一個扇區的格式

5萬字、97 張圖總結操做系統核心知識點

前導碼至關因而標示扇區的開始位置,一般以位模式開始,前導碼還包括柱面號、扇區號等一些其餘信息。緊隨前導碼後面的是數據區,數據部分的大小由低級格式化程序來肯定。大部分磁盤使用 512 字節的扇區。數據區後面是 ECC,ECC 的全稱是 「error correction code」 ,數據糾錯碼,它與普通的錯誤檢測不一樣,ECC 還能夠用於恢復讀錯誤。ECC 階段的大小由不一樣的磁盤製造商實現。ECC 大小的設計標準取決於「設計者願意犧牲多少磁盤空間來提升可靠性」,以及程序能夠處理的 ECC 的複雜程度。一般狀況下 ECC 是 16 位,除此以外,硬盤通常具備必定數量的備用扇區,用於替換製造缺陷的扇區。

磁盤臂調度算法
下面咱們來探討一下關於影響磁盤讀寫的算法,通常狀況下,影響磁盤快讀寫的時間由下面幾個因素決定

  • 尋道時間 - 尋道時間指的就是將磁盤臂移動到須要讀取磁盤塊上的時間
  • 旋轉延遲 - 等待合適的扇區旋轉到磁頭下所需的時間
  • 實際數據的讀取或者寫入時間
    這三種時間參數也是磁盤尋道的過程。通常狀況下,尋道時間對總時間的影響最大,因此,有效的下降尋道時間可以提升磁盤的讀取速度。

若是磁盤驅動程序每次接收一個請求並按照接收順序完成請求,這種處理方式也就是 先來先服務(First-Come, First-served, FCFS) ,這種方式很難優化尋道時間。由於每次都會按照順序處理,無論順序如何,有可能此次讀完後須要等待一個磁盤旋轉一週才能繼續讀取,而其餘柱面可以立刻進行讀取,這種狀況下每次請求也會排隊。

一般狀況下,磁盤在進行尋道時,其餘進程會產生其餘的磁盤請求。磁盤驅動程序會維護一張表,表中會記錄着柱面號看成索引,每一個柱面未完成的請求會造成鏈表,鏈表頭存放在表的相應表項中。

一種對先來先服務的算法改良的方案是使用 最短路徑優先(SSF) 算法,下面描述了這個算法。

假如咱們在對磁道 6 號進行尋址時,同時發生了對 11 , 2 , 4, 14, 8, 15, 3 的請求,若是採用先來先服務的原則,以下圖所示
5萬字、97 張圖總結操做系統核心知識點

咱們能夠計算一下磁盤臂所跨越的磁盤數量爲 5 + 9 + 2 + 10 + 6 + 7 + 12 = 51,至關因而跨越了 51 次盤面,若是使用最短路徑優先,咱們來計算一下跨越的盤面

5萬字、97 張圖總結操做系統核心知識點

跨越的磁盤數量爲 4 + 1 + 1 + 4 + 3 + 3 + 1 = 17 ,相比 51 足足省了兩倍的時間。

可是,最短路徑優先的算法也不是天衣無縫的,這種算法照樣存在問題,那就是優先級 問題,

這裏有一個原型能夠參考就是咱們平常生活中的電梯,電梯使用一種電梯算法(elevator algorithm) 來進行調度,從而知足協調效率和公平性這兩個相互衝突的目標。電梯通常會保持向一個方向移動,直到在那個方向上沒有請求爲止,而後改變方向。

電梯算法須要維護一個二進制位,也就是當前的方向位:UP(向上)或者是 DOWN(向下)。當一個請求處理完成後,磁盤或電梯的驅動程序會檢查該位,若是此位是 UP 位,磁盤臂或者電梯倉移到下一個更高跌未完成的請求。若是高位沒有未完成的請求,則取相反方向。當方向位是 DOWN時,同時存在一個低位的請求,磁盤臂會轉向該點。若是不存在的話,那麼它只是中止並等待。

咱們舉個例子來描述一下電梯算法,好比各個柱面獲得服務的順序是 4,7,10,14,9,6,3,1 ,那麼它的流程圖以下
5萬字、97 張圖總結操做系統核心知識點

因此電梯算法須要跨越的盤面數量是 3 + 3 + 4 + 5 + 3 + 3 + 1 = 22

電梯算法一般狀況下不如 SSF 算法。

錯誤處理
通常壞塊有兩種處理辦法,一種是在控制器中進行處理;一種是在操做系統層面進行處理。

這兩種方法常常替換使用,好比一個具備 30 個數據扇區和兩個備用扇區的磁盤,其中扇區 4 是有瑕疵的。

5萬字、97 張圖總結操做系統核心知識點

控制器能作的事情就是將備用扇區之一從新映射。

5萬字、97 張圖總結操做系統核心知識點
還有一種處理方式是將全部的扇區都向上移動一個扇區

5萬字、97 張圖總結操做系統核心知識點

上面這這兩種狀況下控制器都必須知道哪一個扇區,能夠經過內部的表來跟蹤這一信息,或者經過重寫前導碼來給出從新映射的扇區號。若是是重寫前導碼,那麼涉及移動的方式必須重寫後面全部的前導碼,可是最終會提供良好的性能。

穩定存儲器
磁盤常常會出現錯誤,致使好的扇區會變成壞扇區,驅動程序也有可能掛掉。RAID 能夠對扇區出錯或者是驅動器崩潰提出保護,然而 RAID 卻不能對壞數據中的寫錯誤提供保護,也不能對寫操做期間的崩潰提供保護,這樣就會破壞原始數據。

咱們指望磁盤可以準確無誤的工做,可是事實狀況是不可能的,可是咱們可以知道的是,一個磁盤子系統具備以下特性:當一個寫命令發給它時,磁盤要麼正確地寫數據,要麼什麼也不作,讓現有的數據完整無誤的保留。這樣的系統稱爲 穩定存儲器(stable storage)。 穩定存儲器的目標就是不惜一切代價保證磁盤的一致性。

穩定存儲器使用兩個一對相同的磁盤,對應的塊一同工做造成一個無差異的塊。穩定存儲器爲了實現這個目的,定義了下面三種操做:

  • 穩定寫(stable write)
  • 穩定讀(stable read)
  • 崩潰恢復(crash recovery)
    時鐘
    時鐘(Clocks) 也被稱爲定時器(timers),時鐘/定時器對任何程序系統來講都是必不可少的。時鐘負責維護時間、防止一個進程長期佔用 CPU 時間等其餘功能。時鐘軟件(clock software) 也是一種設備驅動的方式。下面咱們就來對時鐘進行介紹,通常都是先討論硬件再介紹軟件,採用由下到上的方式,也是告訴你,底層是最重要的。

時鐘硬件
在計算機中有兩種類型的時鐘,這些時鐘與現實生活中使用的時鐘徹底不同。

  • 比較簡單的一種時鐘被鏈接到 110 V 或 220 V 的電源線上,這樣每一個電壓週期會產生一箇中斷,大概是 50 - 60 HZ。這些時鐘過去一直佔據支配地位。
  • 另外的一種時鐘由晶體振盪器、計數器和寄存器組成,示意圖以下所示

5萬字、97 張圖總結操做系統核心知識點
這種時鐘稱爲可編程時鐘 ,可編程時鐘有兩種模式,一種是 一鍵式(one-shot mode),當時鍾啓動時,會把存儲器中的值複製到計數器中,而後,每次晶體的振盪器的脈衝都會使計數器 -1。當計數器變爲 0 時,會產生一箇中斷,並中止工做,直到軟件再一次顯示啓動。還有一種模式時 方波(square-wave mode) 模式,在這種模式下,當計數器變爲 0 併產生中斷後,存儲寄存器的值會自動複製到計數器中,這種週期性的中斷稱爲一個時鐘週期。

時鐘軟件
時鐘硬件所作的工做只是根據已知的時間間隔產生中斷,而其餘的工做都是由時鐘軟件來完成,通常操做系統的不一樣,時鐘軟件的具體實現也不一樣,可是通常都會包括如下這幾點

  • 維護一天的時間
  • 阻止進程運行的時間超過其指定時間
  • 統計 CPU 的使用狀況
  • 處理用戶進程的警告系統調用
  • 爲系統各個部分提供看門狗定時器
  • 完成概要剖析,監視和信息收集
    軟定時器
    時鐘軟件也被稱爲可編程時鐘,能夠設置它以程序須要的任何速率引起中斷。時鐘軟件觸發的中斷是一種硬中斷,可是某些應用程序對於硬中斷來講是不可接受的。

這時候就須要一種軟定時器(soft timer) 避免了中斷,不管什麼時候當內核由於某種緣由呢在運行時,它返回用戶態以前都會檢查時鐘來了解軟定時器是否到期。若是軟定時器到期,則執行被調度的事件也無需切換到內核態,由於自己已經處於內核態中。這種方式避免了頻繁的內核態和用戶態以前的切換,提升了程序運行效率。

軟定時器由於不一樣的緣由切換進入內核態的速率不一樣,緣由主要有

  • 系統調用
  • TLB 未命中
  • 缺頁異常
  • I/O 中斷
  • CPU 變得空閒
    死鎖問題也是操做系統很是重要的一類問題

5萬字、97 張圖總結操做系統核心知識點

資源
大部分的死鎖都和資源有關,在進程對設備、文件具備獨佔性(排他性)時會產生死鎖。咱們把這類須要排他性使用的對象稱爲資源(resource)。資源主要分爲 「可搶佔資源和不可搶佔資源」

可搶佔資源和不可搶佔資源

資源主要有可搶佔資源和不可搶佔資源。可搶佔資源(preemptable resource) 能夠從擁有它的進程中搶佔而不會形成其餘影響,內存就是一種可搶佔性資源,任何進程都可以搶先得到內存的使用權。

不可搶佔資源(nonpreemtable resource) 指的是除非引發錯誤或者異常,不然進程沒法搶佔指定資源,這種不可搶佔的資源好比有光盤,在進程執行調度的過程當中,其餘進程是不能獲得該資源的。

死鎖
若是要對死鎖進行一個定義的話,下面的定義比較貼切

「若是一組進程中的每一個進程都在等待一個事件,而這個事件只能由該組中的另外一個進程觸發,這種狀況會致使死鎖」。

資源死鎖的條件
針對咱們上面的描述,資源死鎖可能出現的狀況主要有

  • 互斥條件:每一個資源都被分配給了一個進程或者資源是可用的
  • 保持和等待條件:已經獲取資源的進程被認爲可以獲取新的資源
  • 不可搶佔條件:分配給一個進程的資源不能強制的從其餘進程搶佔資源,它只能由佔有它的進程顯示釋放
  • 循環等待:死鎖發生時,系統中必定有兩個或者兩個以上的進程組成一個循環,循環中的每一個進程都在等待下一個進程釋放的資源。
    發生死鎖時,上面的狀況必須同時會發生。若是其中任意一個條件不會成立,死鎖就不會發生。能夠經過破壞其中任意一個條件來破壞死鎖,下面這些破壞條件就是咱們探討的重點

死鎖模型
Holt 在 1972 年提出對死鎖進行建模,建模的標準以下:

  • 圓形表示進程
  • 方形表示資源
    從資源節點到進程節點表示資源已經被進程佔用,以下圖所示
    5萬字、97 張圖總結操做系統核心知識點

在上圖中表示當前資源 R 正在被 A 進程所佔用

由進程節點到資源節點的有向圖表示當前進程正在請求資源,而且該進程已經被阻塞,處於等待這個資源的狀態

5萬字、97 張圖總結操做系統核心知識點

在上圖中,表示的含義是進程 B 正在請求資源 S 。Holt 認爲,死鎖的描述應該以下

5萬字、97 張圖總結操做系統核心知識點

這是一個死鎖的過程,進程 C 等待資源 T 的釋放,資源 T 卻已經被進程 D 佔用,進程 D 等待請求佔用資源 U ,資源 U 卻已經被線程 C 佔用,從而造成環。

有四種處理死鎖的策略:

  • 忽略死鎖帶來的影響(驚呆了)
  • 檢測死鎖並回復死鎖,死鎖發生時對其進行檢測,一旦發生死鎖後,採起行動解決問題
  • 經過仔細分配資源來避免死鎖
  • 經過破壞死鎖產生的四個條件之一來避免死鎖
    下面咱們分別介紹一下這四種方法

鴕鳥算法
最簡單的解決辦法就是使用鴕鳥算法(ostrich algorithm),把頭埋在沙子裏,僞裝問題根本沒有發生。每一個人看待這個問題的反應都不一樣。數學家認爲死鎖是不可接受的,必須經過有效的策略來防止死鎖的產生。工程師想要知道問題發生的頻次,系統由於其餘緣由崩潰的次數和死鎖帶來的嚴重後果。若是死鎖發生的頻次很低,而常常會因爲硬件故障、編譯器錯誤等其餘操做系統問題致使系統崩潰,那麼大多數工程師不會修復死鎖。

死鎖檢測和恢復
第二種技術是死鎖的檢測和恢復。這種解決方式不會嘗試去阻止死鎖的出現。相反,這種解決方案會但願死鎖儘量的出現,在監測到死鎖出現後,對其進行恢復。下面咱們就來探討一下死鎖的檢測和恢復的幾種方式

每種類型一個資源的死鎖檢測方式
每種資源類型都有一個資源是什麼意思?咱們常常提到的打印機就是這樣的,資源只有打印機,可是設備都不會超過一個。

能夠經過構造一張資源分配表來檢測這種錯誤,好比咱們上面提到的

5萬字、97 張圖總結操做系統核心知識點
若是這張圖包含了一個或一個以上的環,那麼死鎖就存在,處於這個環中任意一個進程都是死鎖的進程。

每種類型多個資源的死鎖檢測方式
若是有多種相同的資源存在,就須要採用另外一種方法來檢測死鎖。能夠經過構造一個矩陣來檢測從 P1 -> Pn 這 n 個進程中的死鎖。

如今咱們提供一種基於矩陣的算法來檢測從 P1 到 Pn 這 n 個進程中的死鎖。假設資源類型爲 m,E1 表明資源類型1,E2 表示資源類型 2 ,Ei 表明資源類型 i (1 <= i <= m)。E 表示的是 現有資源向量(existing resource vector),表明每種已存在的資源總數。

如今咱們就須要構造兩個數組:C 表示的是當前分配矩陣(current allocation matrix) ,R 表示的是 請求矩陣(request matrix)。Ci 表示的是 Pi 持有每一種類型資源的資源數。因此,Cij 表示 Pi 持有資源 j 的數量。Rij 表示 Pi 所須要得到的資源 j 的數量

5萬字、97 張圖總結操做系統核心知識點

通常來講,已分配資源 j 的數量加起來再和全部可供使用的資源數相加 = 該類資源的總數。

死鎖的檢測就是基於向量的比較。每一個進程起初都是沒有被標記過的,算法會開始對進程作標記,進程被標記後說明進程被執行了,不會進入死鎖,當算法結束時,任何沒有被標記過的進程都會被斷定爲死鎖進程。

上面咱們探討了兩種檢測死鎖的方式,那麼如今你知道怎麼檢測後,你什麼時候去作死鎖檢測呢?通常來講,有兩個考量標準:

  • 每當有資源請求時就去檢測,這種方式會佔用昂貴的 CPU 時間。
  • 每隔 k 分鐘檢測一次,或者當 CPU 使用率下降到某個標準下去檢測。考慮到 CPU 效率的緣由,若是死鎖進程達到必定數量,就沒有多少進程能夠運行,因此 CPU 會常常空閒。
    從死鎖中恢復
    上面咱們探討了如何檢測進程死鎖,咱們最終的目的確定是想讓程序可以正常的運行下去,因此針對檢測出來的死鎖,咱們要對其進行恢復,下面咱們會探討幾種死鎖的恢復方式

經過搶佔進行恢復
在某些狀況下,可能會臨時將某個資源從它的持有者轉移到另外一個進程。好比在不通知原進程的狀況下,將某個資源從進程中強制取走給其餘進程使用,使用完後又送回。這種恢復方式通常比較困難並且有些簡單粗暴,並不可取。

經過回滾進行恢復
若是系統設計者和機器操做員知道有可能發生死鎖,那麼就能夠按期檢查流程。進程的檢測點意味着進程的狀態能夠被寫入到文件以便後面進行恢復。檢測點不只包含存儲映像(memory image),還包含資源狀態(resource state)。一種更有效的解決方式是不要覆蓋原有的檢測點,而是每出現一個檢測點都要把它寫入到文件中,這樣當進程執行時,就會有一系列的檢查點文件被累積起來。

爲了進行恢復,要從上一個較早的檢查點上開始,這樣所須要資源的進程會回滾到上一個時間點,在這個時間點上,死鎖進程尚未獲取所須要的資源,能夠在此時對其進行資源分配。

殺死進程恢復
最簡單有效的解決方案是直接殺死一個死鎖進程。可是殺死一個進程可能照樣行不通,這時候就須要殺死別的資源進行恢復。

另一種方式是選擇一個環外的進程做爲犧牲品來釋放進程資源。

死鎖避免
咱們上面討論的是如何檢測出現死鎖和如何恢復死鎖,下面咱們探討幾種規避死鎖的方式

單個資源的銀行家算法
銀行家算法是 Dijkstra 在 1965 年提出的一種調度算法,它自己是一種死鎖的調度算法。它的模型是基於一個城鎮中的銀行家,銀行家向城鎮中的客戶承諾了必定數量的貸款額度。算法要作的就是判斷請求是否會進入一種不安全的狀態。若是是,就拒絕請求,若是請求後系統是安全的,就接受該請求。

相似的,還有多個資源的銀行家算法,讀者能夠自行了解。

破壞死鎖
死鎖本質上是沒法避免的,由於它須要得到未知的資源和請求,可是死鎖是知足四個條件後纔出現的,它們分別是

  • 互斥
  • 保持和等待
  • 不可搶佔
  • 循環等待
    咱們分別對這四個條件進行討論,按理說破壞其中的任意一個條件就可以破壞死鎖

破壞互斥條件
咱們首先考慮的就是「破壞互斥使用條件」。若是資源不被一個進程獨佔,那麼死鎖確定不會產生。若是兩個打印機同時使用一個資源會形成混亂,打印機的解決方式是使用 假脫機打印機(spooling printer) ,這項技術能夠容許多個進程同時產生輸出,在這種模型中,實際請求打印機的惟一進程是打印機守護進程,也稱爲後臺進程。後臺進程不會請求其餘資源。咱們能夠消除打印機的死鎖。

後臺進程一般被編寫爲可以輸出完整的文件後才能打印,假如兩個進程都佔用了假脫機空間的一半,而這兩個進程都沒有完成所有的輸出,就會致使死鎖。

所以,儘可能作到儘量少的進程能夠請求資源。

破壞保持等待的條件
第二種方式是若是咱們能阻止持有資源的進程請求其餘資源,咱們就可以消除死鎖。一種實現方式是讓全部的進程開始執行前請求所有的資源。若是所需的資源可用,進程會完成資源的分配並運行到結束。若是有任何一個資源處於頻繁分配的狀況,那麼沒有分配到資源的進程就會等待。

不少進程「沒法在執行完成前就知道到底須要多少資源」,若是知道的話,就能夠使用銀行家算法;還有一個問題是這樣「沒法合理有效利用資源」。

還有一種方式是進程在請求其餘資源時,先釋放所佔用的資源,而後再嘗試一次獲取所有的資源。

破壞不可搶佔條件
破壞不可搶佔條件也是能夠的。能夠經過虛擬化的方式來避免這種狀況。

破壞循環等待條件
如今就剩最後一個條件了,循環等待條件能夠經過多種方法來破壞。一種方式是制定一個標準,一個進程在任什麼時候候只能使用一種資源。若是須要另一種資源,必須釋放當前資源。對於須要將大文件從磁帶複製到打印機的過程,此限制是不可接受的。

另外一種方式是將全部的資源統一編號,以下圖所示

5萬字、97 張圖總結操做系統核心知識點

進程能夠在任什麼時候間提出請求,可是全部的請求都必須按照資源的順序提出。若是按照此分配規則的話,那麼資源分配之間不會出現環。

5萬字、97 張圖總結操做系統核心知識點

儘管經過這種方式來消除死鎖,可是編號的順序不可能讓每一個進程都會接受。

其餘問題
下面咱們來探討一下其餘問題,包括 「通訊死鎖、活鎖是什麼、飢餓問題和兩階段加鎖」

兩階段加鎖
雖然不少狀況下死鎖的避免和預防都能處理,可是效果並很差。隨着時間的推移,提出了不少優秀的算法用來處理死鎖。例如在數據庫系統中,一個常常發生的操做是請求鎖住一些記錄,而後更新全部鎖定的記錄。當同時有多個進程運行時,就會有死鎖的風險。

一種解決方式是使用 兩階段提交(two-phase locking)。顧名思義分爲兩個階段,一階段是進程嘗試一次鎖定它須要的全部記錄。若是成功後,纔會開始第二階段,第二階段是執行更新並釋放鎖。第一階段並不作真正有意義的工做。

若是在第一階段某個進程所須要的記錄已經被加鎖,那麼該進程會釋放全部鎖定的記錄並從新開始第一階段。從某種意義上來講,這種方法相似於預先請求全部必需的資源或者是在進行一些不可逆的操做以前請求全部的資源。

不過在通常的應用場景中,兩階段加鎖的策略並不通用。若是一個進程缺乏資源就會半途中斷並從新開始的方式是不可接受的。

通訊死鎖
咱們上面一直討論的是資源死鎖,資源死鎖是一種死鎖類型,但並非惟一類型,還有通訊死鎖,也就是兩個或多個進程在發送消息時出現的死鎖。進程 A 給進程 B 發了一條消息,而後進程 A 阻塞直到進程 B 返回響應。假設請求消息丟失了,那麼進程 A 在一直等着回覆,進程 B 也會阻塞等待請求消息到來,這時候就產生死鎖。

儘管會產生死鎖,可是這並非一個資源死鎖,由於 A 並無佔據 B 的資源。事實上,通訊死鎖並無徹底可見的資源。根據死鎖的定義來講:每一個進程由於等待其餘進程引發的事件而產生阻塞,這就是一種死鎖。相較於最多見的通訊死鎖,咱們把上面這種狀況稱爲通訊死鎖(communication deadlock)。

通訊死鎖不能經過調度的方式來避免,可是能夠使用通訊中一個很是重要的概念來避免:超時(timeout)。在通訊過程當中,只要一個信息被髮出後,發送者就會啓動一個定時器,定時器會記錄消息的超時時間,若是超時時間到了可是消息尚未返回,就會認爲消息已經丟失並從新發送,經過這種方式,能夠避免通訊死鎖。

可是並不是全部網絡通訊發生的死鎖都是通訊死鎖,也存在資源死鎖,下面就是一個典型的資源死鎖。

當一個數據包從主機進入路由器時,會被放入一個緩衝區,而後再傳輸到另一個路由器,再到另外一個,以此類推直到目的地。緩衝區都是資源而且數量有限。以下圖所示,每一個路由器都有 10 個緩衝區(實際上有不少)。

5萬字、97 張圖總結操做系統核心知識點

假如路由器 A 的全部數據須要發送到 B ,B 的全部數據包須要發送到 D,而後 D 的全部數據包須要發送到 A 。沒有數據包能夠移動,由於在另外一端沒有緩衝區可用,這就是一個典型的資源死鎖。

活鎖
某些狀況下,當進程意識到它不能獲取所須要的下一個鎖時,就會嘗試禮貌的釋放已經得到的鎖,而後等待很是短的時間再次嘗試獲取。能夠想像一下這個場景:當兩我的在狹路相逢的時候,都想給對方讓路,相同的步調會致使雙方都沒法前進。

如今假想有一對並行的進程用到了兩個資源。它們分別嘗試獲取另外一個鎖失敗後,兩個進程都會釋放本身持有的鎖,再次進行嘗試,這個過程會一直進行重複。很明顯,這個過程當中沒有進程阻塞,可是進程仍然不會向下執行,這種情況咱們稱之爲 活鎖(livelock)。

飢餓
與死鎖和活鎖的一個很是類似的問題是 飢餓(starvvation)。想象一下你何時會餓?一段時間不吃東西是否是會餓?對於進程來說,最重要的就是資源,若是一段時間沒有得到資源,那麼進程會產生飢餓,這些進程會永遠得不到服務。

咱們假設打印機的分配方案是每次都會分配給最小文件的進程,那麼要打印大文件的進程會永遠得不到服務,致使進程飢餓,進程會無限制的推後,雖然它沒有阻塞。

卻是畫蛇添足,不只沒有提升性能,反而磨損了固態硬盤。因此碎片整理只會縮短固態硬盤的壽命。

相關文章
相關標籤/搜索