1. 程式人生 > 實用技巧 >磁碟與IO基礎知識

磁碟與IO基礎知識

iwehdio的部落格園:https://www.cnblogs.com/iwehdio/

學習自:

磁碟排程演算法

  • 磁碟排程演算法的目的很簡單,就是為了提高磁碟的訪問效能,一般是通過優化磁碟的訪問請求順序來做到的。

  • 尋道的時間是磁碟訪問最耗時的部分,如果請求順序優化的得當,必然可以節省一些不必要的尋道時間,從而提高磁碟的訪問效能。

  • 先來先服務:

    • 先到來的請求,先被服務。
    • 比較簡單粗暴,但是如果大量程序競爭使用磁碟,請求訪問的磁軌可能會很分散,那先來先服務演算法在效能上就會顯得很差,因為尋道時間過長。
  • 最短尋道時間優先:

    • 優先選擇從當前磁頭位置所需尋道時間最短的請求。
    • 相比先來先服務效能提高了不少。但這個演算法可能存在某些請求的飢餓
    • 假設是一個動態的請求,如果後續來的請求都是小於 183 磁軌的,那麼 183 磁軌可能永遠不會被響應,於是就產生了飢餓現象,這裡產生飢餓的原因是磁頭在一小塊區域來回移動。
  • 掃描演算法:

    • 最短尋道時間優先演算法會產生飢餓的原因在於:磁頭有可能在一個小區域內來回得移動。
    • 為了防止這個問題,可以規定:磁頭在一個方向上移動,訪問所有未完成的請求,直到磁頭到達該方向上的最後的磁軌,才調換方向,這就是掃描(Scan)演算法。
    • 掃描排程演算法效能較好,不會產生飢餓現象,但是存在這樣的問題,中間部分的磁軌會比較佔便宜,中間部分相比其他部分響應的頻率會比較多,也就是說每個磁軌的響應頻率存在差異。
  • 迴圈掃描演算法:

    • 掃描演算法使得每個磁軌響應的頻率存在差異,那麼要優化這個問題的話,可以總是按相同的方向進行掃描,使得每個磁軌的響應頻率基本一致。
    • 只有磁頭朝某個特定方向移動時,才處理磁軌訪問請求,而返回時直接快速移動至最靠邊緣的磁軌,也就是復位磁頭,這個過程是很快的,並且返回中途不處理任何請求。
    • 迴圈掃描演算法相比於掃描演算法,對於各個位置磁軌響應頻率相對比較平均。
  • LOOK與C-LOOK演算法:

    • 對掃描演算法和迴圈掃描演算法的優化。優化的思路就是磁頭沒必要移動到最後一個磁軌,可以移動到「最遠的請求」位置,然後立即反向移動。

I/O

裝置控制器

  • 裝置管理器:

    • 電腦裝置可以接非常多的輸入輸出裝置。每個裝置的用法和功能都不同,那作業系統是如何把這些輸入輸出裝置統一管理的呢。
    • 為了遮蔽裝置之間的差異,每個裝置都有一個叫裝置控制器(Device Control) 的元件,比如硬碟有硬碟控制器、顯示器有視訊控制器等。

    • 這些控制器都很清楚的知道對應裝置的用法和功能,所以 CPU 是通過裝置控制器來和裝置打交道的。
    • 裝置控制器裡有晶片,它可執行自己的邏輯,也有自己的暫存器,用來與 CPU 進行通訊,比如:
      • 通過寫入這些暫存器,作業系統可以命令裝置傳送資料、接收資料、開啟或關閉,或者執行某些其他操作。
      • 通過讀取這些暫存器,作業系統可以瞭解裝置的狀態,是否準備好接收一個新的命令等。
  • 控制器是有三類暫存器,它們分別是狀態暫存器(Status Register)、 命令暫存器(Command Register)以及資料暫存器(Data Register):

    • 資料暫存器,CPU 向 I/O 裝置寫入需要傳輸的資料,比如要列印的內容是「Hello」,CPU 就要先發送一個 H 字元給到對應的 I/O 裝置。
    • 命令暫存器,CPU 傳送一個命令,告訴 I/O 裝置,要進行輸入/輸出操作,於是就會交給 I/O 裝置去工作,任務完成後,會把狀態暫存器裡面的狀態標記為完成。
    • 狀態暫存器,目的是告訴 CPU ,現在已經在工作或工作已經完成,如果已經在工作狀態,CPU 再發送資料或者命令過來,都是沒有用的,直到前面的工作已經完成,狀態寄存標記成已完成,CPU 才能傳送下一個字元和命令。
    • CPU 通過讀、寫裝置控制器中的暫存器來控制裝置,這比 CPU 直接控制輸入輸出裝置,要方便和標準很多。
  • 輸入輸出裝置可分為兩大類 :塊裝置(Block Device)和字元裝置(Character Device)。

    • 塊裝置,把資料儲存在固定大小的塊中,每個塊有自己的地址,硬碟、USB 是常見的塊裝置。
    • 字元裝置,以字元為單位傳送或接收一個字元流,字元裝置是不可定址的,也沒有任何尋道操作,滑鼠是常見的字元裝置。
  • 塊裝置通常傳輸的資料量會非常大,於是控制器設立了一個可讀寫的資料緩衝區

    • CPU 寫入資料到控制器的緩衝區時,當緩衝區的資料囤夠了一部分,才會發給裝置。
    • CPU 從控制器的緩衝區讀取資料時,也需要緩衝區囤夠了一部分,才拷貝到記憶體。
    • 這樣做是為了,減少對裝置的操作次數。
  • CPU 是如何與裝置的控制暫存器和資料緩衝區進行通訊的?存在兩個方法:

    • 埠 I/O,每個控制暫存器被分配一個 I/O 埠,可以通過特殊的彙編指令操作這些暫存器,比如 in/out 類似的指令。
    • 記憶體對映 I/O,將所有控制暫存器對映到記憶體空間中,這樣就可以像讀寫記憶體一樣讀寫資料緩衝區。

I/O控制方式

  • 每種裝置都有一個裝置控制器,控制器相當於一個小 CPU,它可以自己處理一些事情,但有個問題是,當 CPU 給裝置傳送了一個指令,讓裝置控制器去讀裝置的資料,它讀完的時候,要怎麼通知 CPU 呢?

    • 控制器的暫存器一般會有狀態標記位,用來標識輸入或輸出操作是否完成。於是,第一種是輪詢等待的方法,讓 CPU 一直查暫存器的狀態,直到狀態標記為完成,很明顯,這種方式非常的傻瓜,它會佔用 CPU 的全部時間。
    • 第二種方法 —— 中斷,通知作業系統資料已經準備好了。我們一般會有一個硬體的中斷控制器,當裝置完成任務後觸發中斷到中斷控制器,中斷控制器就通知 CPU,一箇中斷產生了,CPU 需要停下當前手裡的事情來處理中斷。
      • 中斷有兩種,一種軟中斷,例如程式碼呼叫 INT 指令觸發,一種是硬體中斷,就是硬體通過中斷控制器觸發的。
    • 但中斷的方式對於頻繁讀寫資料的磁碟,並不友好,這樣 CPU 容易經常被打斷,會佔用 CPU 大量的時間。對於這一類裝置的問題的解決方法是使用 DMA(Direct Memory Access) 功能,它可以使得裝置在 CPU 不參與的情況下,能夠自行完成把裝置 I/O 資料放入到記憶體。
  • 要實現 DMA 功能要有 「DMA 控制器」硬體的支援。

    • DMA 的工作方式如下:
      • CPU 需對 DMA 控制器下發指令,告訴它想讀取多少資料,讀完的資料放在記憶體的某個地方就可以了;
      • 接下來,DMA 控制器會向磁碟控制器發出指令,通知它從磁碟讀資料到其內部的緩衝區中,接著磁碟控制器將緩衝區的資料傳輸到記憶體;
      • 當磁碟控制器把資料傳輸到記憶體的操作完成後,磁碟控制器在總線上發出一個確認成功的訊號到 DMA 控制器;
      • DMA 控制器收到訊號後,DMA 控制器發中斷通知 CPU 指令完成,CPU 就可以直接用記憶體裡面現成的資料了。
    • CPU 當要讀取磁碟資料的時候,只需給 DMA 控制器傳送指令,然後返回去做其他事情,當磁碟資料拷貝到記憶體後,DMA 控制機器通過中斷的方式,告訴 CPU 資料已經準備好了,可以從記憶體讀資料了。僅僅在傳送開始和結束時需要 CPU 干預。

裝置驅動程式

  • 雖然裝置控制器遮蔽了裝置的眾多細節,但每種裝置的控制器的暫存器、緩衝區等使用模式都是不同的,所以為了遮蔽「裝置控制器」的差異,引入了裝置驅動程式

  • 裝置控制器不屬於作業系統範疇,它是屬於硬體,而裝置驅動程式屬於作業系統的一部分,作業系統的核心程式碼可以像本地呼叫程式碼一樣使用裝置驅動程式的介面,而裝置驅動程式是面向裝置控制器的程式碼,它發出操控裝置控制器的指令後,才可以操作裝置控制器。

  • 不同的裝置控制器雖然功能不同,但是裝置驅動程式會提供統一的介面給作業系統,這樣不同的裝置驅動程式,就可以以相同的方式接入作業系統。

  • 裝置完成了事情,則會發送中斷來通知作業系統。那作業系統就需要有一個地方來處理這個中斷,這個地方也就是在裝置驅動程式裡,它會及時響應控制器發來的中斷請求,並根據這個中斷的型別呼叫響應的中斷處理程式進行處理。

  • 裝置驅動程式初始化的時候,要先註冊一個該裝置的中斷處理函式。

  • 中斷處理程式的處理流程:
    1. 在 I/O 時,裝置控制器如果已經準備好資料,則會通過中斷控制器向 CPU 傳送中斷請求;
    2. 保護被中斷程序的 CPU 上下文;
    3. 轉入相應的裝置中斷處理函式;
    4. 進行中斷處理;
    5. 恢復被中斷程序的上下文。

通用塊層

  • 對於塊裝置,為了減少不同塊裝置的差異帶來的影響,Linux 通過一個統一的通用塊層,來管理不同的塊裝置。
  • 通用塊層是處於檔案系統和磁碟驅動中間的一個塊裝置抽象層,它主要有兩個功能:
    • 第一個功能,向上為檔案系統和應用程式,提供訪問塊裝置的標準介面,向下把各種不同的磁碟裝置抽象為統一的塊裝置,並在核心層面,提供一個框架來管理這些裝置的驅動程式;
    • 第二功能,通用層還會給檔案系統和應用程式發來的 I/O 請求排隊,接著會對佇列重新排序、請求合併等方式,也就是 I/O 排程,主要目的是為了提高磁碟讀寫的效率。
  • Linux 記憶體支援 5 種 I/O 排程演算法:
    • 沒有排程演算法,它不對檔案系統和應用程式的 I/O 做任何處理,這種演算法常用在虛擬機器 I/O 中,此時磁碟 I/O 排程演算法交由物理機系統負責。
    • 先入先出排程演算法,這是最簡單的 I/O 排程演算法,先進入 I/O 排程佇列的 I/O 請求先發生。
    • 完全公平排程演算法,大部分系統都把這個演算法作為預設的 I/O 排程器,它為每個程序維護了一個 I/O 排程佇列,並按照時間片來均勻分佈每個程序的 I/O 請求。
    • 優先順序排程演算法,顧名思義,優先順序高的 I/O 請求先發生, 它適用於執行大量程序的系統,像是桌面環境、多媒體應用等。
    • 最終期限排程演算法,分別為讀、寫請求建立了不同的 I/O 佇列,這樣可以提高機械磁碟的吞吐量,並確保達到最終期限的請求被優先處理,適用於在 I/O 壓力比較大的場景,比如資料庫等。

儲存系統I/O軟體分層

  • 可以把 Linux 儲存系統的 I/O 由上到下可以分為三個層次,分別是檔案系統層、通用塊層、裝置層。

  • 這三個層次的作用是:
    • 檔案系統層,包括虛擬檔案系統和其他檔案系統的具體實現,它向上為應用程式統一提供了標準的檔案訪問介面,向下會通過通用塊層來儲存和管理磁碟資料。
    • 通用塊層,包括塊裝置的 I/O 佇列和 I/O 排程器,它會對檔案系統的 I/O 請求進行排隊,再通過 I/O 排程器,選擇一個 I/O 發給下一層的裝置層。
    • 裝置層,包括硬體裝置、裝置控制器和驅動程式,負責最終物理裝置的 I/O 操作。
  • 有了檔案系統介面之後,不但可以通過檔案系統的命令列操作裝置,也可以通過應用程式,呼叫 readwrite 函式,就像讀寫檔案一樣操作裝置,所以說裝置在 Linux 下,也只是一個特殊的檔案。
  • 除了讀寫操作,還需要有檢查特定於裝置的功能和屬性。於是,需要 ioctl 介面,它表示輸入輸出控制介面,是用於配置和修改特定裝置屬性的通用介面。
  • 儲存系統的 I/O 是整個系統最慢的一個環節,所以 Linux 提供了不少快取機制來提高 I/O 的效率。
    • 為了提高檔案訪問的效率,會使用頁快取、索引節點快取、目錄項快取等多種快取機制,目的是為了減少對塊裝置的直接呼叫。
    • 為了提高塊裝置的訪問效率, 會使用緩衝區,來快取塊裝置的資料。

鍵盤敲入字母時,期間發生了什麼?

  • CPU 的硬體架構圖:

    • CPU 裡面的記憶體介面,直接和系統匯流排通訊,然後系統匯流排再接入一個 I/O 橋接器,這個 I/O 橋接器,另一邊接入了記憶體匯流排,使得 CPU 和記憶體通訊。再另一邊,又接入了一個 I/O 匯流排,用來連線 I/O 裝置,比如鍵盤、顯示器等。
  • 當用戶輸入了鍵盤字元,鍵盤控制器就會產生掃描碼資料,並將其緩衝在鍵盤控制器的暫存器中,緊接著鍵盤控制器通過匯流排給 CPU 傳送中斷請求

  • CPU 收到中斷請求後,作業系統會儲存被中斷程序的 CPU 上下文,然後呼叫鍵盤的中斷處理程式

  • 鍵盤的中斷處理程式是在鍵盤驅動程式初始化時註冊的,那鍵盤中斷處理函式的功能就是從鍵盤控制器的暫存器的緩衝區讀取掃描碼,再根據掃描碼找到使用者在鍵盤輸入的字元,如果輸入的字元是顯示字元,那就會把掃描碼翻譯成對應顯示字元的 ASCII 碼。

  • 得到了顯示字元的 ASCII 碼後,就會把 ASCII 碼放到「讀緩衝區佇列」,接下來就是要把顯示字元顯示螢幕了,顯示裝置的驅動程式會定時從「讀緩衝區佇列」讀取資料放到「寫緩衝區佇列」,最後把「寫緩衝區佇列」的資料一個一個寫入到顯示裝置的控制器的暫存器中的資料緩衝區,最後將這些資料顯示在螢幕裡。

  • 顯示出結果後,恢復被中斷程序的上下文


iwehdio的部落格園:https://www.cnblogs.com/iwehdio/