內存管理、磁盤和文件拾遺

mr-cup-fabien-barral-o6GEPQXnqMY-unsplash

內存管理、磁盤和文件拾遺

Part1. 內存管理

一個程序的可執行文件在內存中的結果,從大的角度能夠分爲兩個部分:只讀部分和可讀寫部分。只讀部分包括程序代碼(.text)和程序中的常量(.rodata)。
可讀寫部分(變量)大體可分爲下面幾個部分:程序員

  • .data:初始化了的全局變量和靜態變量
  • .bss:即 Block Started by Symbol,未初始化的全局變量和靜態變量
  • heap:堆,使用 mallocreallocfree函數控制的變量,堆在全部的線程,共享庫,和動態加載的模塊中被共享使用。
  • stack:棧,函數調用時使用棧來保存函數現場,自動變量(即生命週期限制在某個 scope 的變量)也存放在棧中。

1. .data.bss

這兩個常常放在一塊兒說,由於他們都是用來存儲全局變量和靜態變量的,區別在於 .data 區存放的初始化過的,.bss區存放的是沒有初始化過的。例如:算法

int val = 3;
char string[] = 'Hello World';

這兩個變量的值會在一開始被存儲在 .text 中,由於值是寫在代碼裏面的,在程序啓動時會拷貝到 .data 區中。
若不初始化,相似:segmentfault

static int i;

這個變量就會被放在 .bss 區中。安全

靜態變量和全局變量

全局變量

在一個代碼文件中,一個變量要麼定義在函數中,要麼定義在函數外。當定義在函數外時,這個變量就有了全局做用域,成爲了全局變量。
全局變量不光意味着這個變量能夠在整個文件中使用,也意味着這個變量能夠在其餘文件中使用(這種叫 external linkage)。
當有以下兩個文件時:
A.c微信

#include <stdio.h>
int a;
int compute(void);
int main()
{
    a = 1;
    printf("%d %d", a, compute());
    return 0;
}

B.c數據結構

int a;
int compute(void)
{
    a = 0;
    return a;
}

在編譯過程當中會產生重複定義的錯誤!由於有兩個全局的 a 變量,編譯器不知道應該使用哪個,爲了不這種問題,就須要引入 static函數

靜態變量

使用 static 關鍵字修飾的變量,static 關鍵字對變量的做用域進行了限制,具體的限制以下:性能

  • 在函數外定義:全局變量,可是隻在當前文件中可見(叫作 internal linkage)。
  • 在函數內定義:全局變量,可是隻在此函數內可見(同時,在屢次函數調用中,變量的值不會丟失)。
  • C++ 在類中定義:全局變量,可是隻在此類中可見

對於全局變量來講,爲了不上面提到的重複定義錯誤,咱們能夠在一個文件中使用 static,另外一個不使用,這樣使用 static 的就會使用本身的 a 變量,而沒有用 static 的會使用全局的 a 變量。編碼

注意:靜態這個中文翻譯有點莫名其妙,給人的感受像是不可改變的,實際上static 跟不可改變沒有關係,不可改變的變量使用 const 關鍵字修飾!!!spa

extern

extern 是 C 語言的另外一個關鍵字,用來指示變量或函數的定義在別的文件中,使用 extern 能夠在多個源文件中共享某個變量。

程序在內存和硬盤上不一樣的存在形式

這裏提到的四個區,是指程序在內存中存在的形式,和程序在硬盤上存儲的格式不是徹底對應的。程序在硬盤上存儲的格式更加複雜,並且是和操做系統有關的,具體能夠參考:wikipedia
一個明顯的例子區分這個差異:
以前提到的未定義的全局變量存儲在 .bss 區,這個區域不會佔用可執行文件的空間(通常只存儲這個區域的長度),可是卻會佔用內存空間。這些變量沒有定義,所以可執行文件中不須要存儲他們的值,在程序啓動過程當中,他們的值會被初始化成 0,存儲在內存中。

2. 棧

棧是用於存放本地變量,內部臨時變量以及有關上下文的內存區域。程序在調用函數時,操做系統會自動經過壓棧和彈棧完成保存函數現場等操做,不須要程序員手動干預。
棧是一塊連續的內存區域,棧頂的地址和棧的最大容量是系統預先規定好的,能從棧得到的空間較小。若是申請的空間超過棧的剩餘空間時,例如遞歸深度過深,將提示:stackoverflow
棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧、出棧都有專門的指令執行,這就決定了棧的效率比較高。

3. 堆

堆是用於存放除了棧裏的東西以外全部其餘東西的內存區域,當使用 mallocfree 時就是在操做堆中的內存。對於堆來講,釋放工做由程序員控制,容易產生 memory leak

堆是向高地址擴展的數據結構,是不連續的內存區域。這裏因爲系統是用鏈表來存儲的空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。因而可知,堆得到的空間比較靈活,也比較大。

對於堆而言,頻繁的 new/delete 勢必會形成內存空間的不連續,從而形成大量的碎片,使程序效率下降。對於棧而言,則不會出現這個問題,由於棧是先進後出的隊列,永遠都不可能有一個內存塊從棧中間彈出。

堆都是動態分配的,沒有靜態分配的堆。棧有兩種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,好比局部變量的分配。動態分配由 alloca 函數進行分配,可是棧的動態分配和堆是不一樣的,他的動態分配是由編譯器進行釋放,無需咱們手工實現。

計算機底層並無對堆的支持,堆則是 C/C++ 函數庫提供的,同時因爲上面提到的碎片問題,都會致使堆的效率比棧要低。

Part.2 內存分配

  • 虛擬地址:用戶編譯時將代碼(或數據)分紅若干個段,每條代碼或每一個數據的地址由段名稱 + 段內相對地址構成,這樣的程序地址稱爲虛擬地址。
  • 邏輯地址:虛擬地址中,段內相對地址部分稱爲邏輯地址。
  • 物理地址:實際物理內存中所看到的存儲地址稱爲物理地址。
  • 邏輯地址空間:在實際應用中,將虛擬地址和邏輯地址常常不加以區分,通稱爲邏輯地址,邏輯地址的幾個稱爲邏輯地址空間。
  • 線性地址空間:CPU 地址總線能夠訪問的全部地址合稱爲線性地址空間。
  • 物理地址空間:實際存在的可訪問的物理內存地址集合稱爲物理地址空間。
  • MMU(Memery Management Unit)內存管理單元:實現將用戶程序的虛擬地址(邏輯地址)-> 物理地址映射的 CPU 中的硬件電路。
  • 基地址:在進行地址映射時,常常以段或頁爲單位並以其最小地址(即起始地址)爲基值來進行計算。
  • 偏移量:在以段或頁爲單位進行地址映射時,相對於基地址的地址值。

虛擬地址先通過分段機制映射到線性地址,而後線性地址經過分頁機制映射到物理地址。

Part.3 虛擬內存

請求調頁

也成爲按需調頁,即對不在內存中的「頁」,當進程執行時才調入,不然有可能到程序結束時也不會調入。

頁面置換算法

  • FIFO 算法
    先入先出,即淘汰最先調入的頁面。
  • OPT(MIN) 算法
    選將來最遠將使用的頁淘汰,是一種最優的方案,能夠證實缺頁數最小。
    惋惜,MIN 須要知道未來發生的事,只能在理論中存在,實際不可應用。
  • LRU(Least-Recently-Used) 算法
    用過去的歷史預測未來,選最近最長時間沒有使用的頁淘汰(也稱最近最少使用)。LRU 準確實現:計數器法,頁碼棧法。因爲代價較高,一般不使用準確實現,而是採用近似實現,例如 Clock 算法。

內存抖動

頁面的頻繁更換,致使整個系統效率急劇降低,這個現象稱爲內存抖動(或顛簸)。
抖動通常是內存分配算法很差,內存過小引發或者程序的算法不佳引發的。

Belady 現象

對有的頁面置換算法,頁錯誤率可能會隨着分配幀數的增長而增長。
FIFO 會產生 Belady 異常。
棧式算法無 Belady 異常,LRU、LFU(最不常用)、OPT 都屬於棧式算法。

Part.4 磁盤調度

磁盤訪問延遲 = 隊列時間 + 控制器時間 + 尋道時間 + 旋轉時間 + 傳輸時間。
磁盤調度的目的是減少延遲,其中前兩項能夠忽略,尋道時間是主要矛盾。

磁盤調度算法

  • FCFS
    先進先出的調度策略,這個策略具備公平的優勢,由於每一個請求都會獲得處理,而且是按照接收到的順序進行處理。
  • SSTF(Shortest-seek-time 最短尋道時間優先)
    選擇使磁頭從當前位置開始移動最少的磁盤 I/O 請求,因此 SSTF 老是選擇致使最小尋道時間的請求。
    老是選擇最小尋找時間並不能保證平均尋找時間最小,可是能提供比 FCFS 算法更好的性能,會存在飢餓現象。
  • SCAN
    SSTF + 中途不回折,每一個請求都有處理機會。
    SCAN 要求磁頭僅僅沿一個方向移動,並在途中知足全部未完成的請求,直到它到達這個方向上的最後一個磁道,或者在這個方向上沒有其餘請求爲止。
    因爲磁頭移動規律與電梯運行類似,SCAN 也被稱爲電梯算法。
    SCAN 算法對最近掃描過的區域不公平,所以,它的訪問局部性方面不如 FCFS 算法和 SSTF 算法好。
  • C-SCAN
    SCAN + 直接移到另外一端,兩端請求都能很快處理。
    把掃描限定在一個方向,當訪問到某個方向的最後一個磁道時,磁道返回磁盤相反方向磁道的末端,並再次開始掃描。
    其中 「C」 是 Circular(環)的意思。
  • LOOK 和 C-LOOK
    採用 SCAN 算法和 C-SCAN 算法時磁頭老是嚴格地遵循從盤面的一端到另外一端,顯然,在實際使用時還能夠改進,即磁頭移動只須要到達最遠端的一個請求便可返回,不須要到達磁盤端點。這種形式的 SCAN 算法和 C-SCAN 算法稱爲 LOOK 和 C-LOOK 調度。這是由於它們在朝一個給定方向移動前會查看是否有請求。

Part5. 文件系統

分區表

  • MBR:支持最大卷爲 2TB(Terabytes),而且每一個磁盤最多有 4 個主分區(或 3 個主分區、1 個擴展分區和無限制的邏輯驅動器)
  • GPT:支持最大卷爲 18EB(Exabytes),而且每磁盤的分區數沒有上限,只受到操做系統限制,因爲分區表自己須要佔用必定空間,最初規劃硬盤分區時,留給分區表的空間決定了最多能夠有多少個分區,IA-64版 Windows 限制最多有 128 個分區,這也是 EFI 標準規定的分區表的最小尺寸。另外 GPT 分區磁盤有備份分區表來提升分區數據結構的完整性。

RAID 技術

獨立硬盤冗餘陣列(RAID, Redundant Array of Independent Disks),舊稱廉價磁盤冗餘陣列(Redundant Array of Inexpensive Disks),簡稱磁盤陣列。利用虛擬化存儲技術把多個硬盤組合起來,成爲一個或多個硬盤陣列組,目的爲提高性能或數據冗餘,或是二者同時提高。

在運做中,取決於 RAID 層級不一樣,數據會以多種模式分散於各個硬盤,RAID 層級的命名會以 RAID 開頭並帶數字,例如:RAID 0、RAID 一、RAID 五、RAID 六、RAID 七、RAID 0一、RAID 十、RAID 50、RAID 60。每種等級都有其理論上的優缺點,不一樣的等級在兩個目標間獲取平衡,分別是增長數據可靠性以及增長存儲器(羣)讀寫性能。

  • RAID 0
    RAID 0 是最先出現的 RAID 模式,須要兩塊以上的硬盤,能夠提升整個磁盤的性能和吞吐量。
    RAID 0 沒有提供冗餘或錯誤修復能力,其中一塊硬盤損壞,全部的數據將遺失。
    -w183
  • RAID 1
    RAID 1 就是鏡像,其原理爲在主硬盤上存放數據的同時也在鏡像硬盤上寫同樣的數據,當主硬盤(物理)損壞時,鏡像硬盤則代替主硬盤的工做。由於有鏡像硬盤作數據備份,因此 RAID 1 的數據安全性在全部 RAID 級別上來講是最好的。
    但不管用多少磁盤作 RAID 1,僅算一個磁盤的容量,是全部 RAID 中磁盤利用率最低的。
    實際容量:Size = min(S1, S2, S3 ... Sn)
    -w175
  • RAID 2
    這是 RAID 0 的改良版,以漢明碼(Hamming Code)的方式將數據進行編碼後分區爲獨立的比特,並將數據分別寫入硬盤中。由於在數據中加入了錯誤修正碼(ECC,Error Correction Code),因此數據總體的容量會比原始數據大一些,RAID 2 至少須要三臺磁盤驅動器方能運做。
    -w348
  • RAID 3
    採用 Bit-interleaving(數據交錯存儲)技術,它須要經過編碼再將數據比特分割後分別存在磁盤中,而將同比特檢查後單獨存在一個硬盤中,但因爲數據內的比特分散在不一樣的硬盤上,所以就算要讀取一小段數據資料均可能須要全部的硬盤進行工做,因此這種規格比較適用於讀取大量數據時使用。
    -w258
  • RAID 4
    它與 RAID 3 不一樣的是它在分區時是以區塊爲單位分別存在硬盤中,但每次的數據訪問都必須從同比特檢查的那個硬盤中取出對應的同比特數據進行覈對,因爲過於頻繁的使用,因此對硬盤的損耗可能會提升。(快交織技術,Block interleaving)。
    -w256

    RAID 二、三、4 在實際應用中不多使用

  • RAID 5
    RAID Level 5 是一種存儲性能、數據安全和存儲成本兼顧的存儲解決方案,他使用的是 Disk Striping(硬盤分區)技術。
    RAID 5 至少須要三塊硬盤,RAID 5 不是對存儲的數據進行備份,而是把數據和相對應的數據分別存儲於不一樣的磁盤上。
    RAID 5 容許一塊硬盤損壞。
    實際容量:Size = (N - 1) * min(S1, S2, S3... SN)
    -w263
  • RAID 6
    與 RAID 5 相比,RAID 6 增長第二個獨立的奇偶校驗信息塊。兩個獨立的奇偶系統使用不一樣的算法,數據的可靠性很是高,即便兩塊磁盤同時失效也不會影響數據的使用。
    RAID 6 至少須要 4 塊硬盤。
    實際容量:Size = (N - 2) * min(S1, S2, S3 ... SN)
    -w304
  • RAID 10/01 (RAID 1 + 0, RAID 0 + 1)
    RAID 10 是先鏡射再分區數據,再將全部硬盤分爲兩組,視爲是 RAID 0 的最低組合,而後將這兩組各自視爲 RAID 1 運做。
    RAID 01 則是跟 RAID 10 的程序相反,是先分區再將數據鏡射到兩組硬盤。它將全部的硬盤分爲兩組,變成 RAID 1 的最低組合,而將兩組硬盤各自視爲 RAID 0 運做。
    當 RAID 10 有一個硬盤受損,其他硬盤會繼續運做,RAID 01 只要有一個硬盤受損,同組 RAID 0 的全部硬盤都會中止運做,只剩下其餘組的硬盤運做,可靠性較低。
    若是以 6 個硬盤建 RAID 01,鏡射再用三個建 RAID 0,那麼壞一個硬盤便會有三個硬盤脫機,所以,RAID 10 遠比 RAID 01 經常使用,零售主板絕大多數支持 RAID 0/1/5/10, 但不支持 RAID 01.
    RAID 10 至少須要 4 塊硬盤,且硬盤數量必須爲偶數。
    -w271

常見的文件系統

  • Windows:FAT,FAT16,FAT32,NTFS
  • Linux:ext2/3/4,btrfs,ZFS
  • Mac OS X:HFS+

更多幹貨文章

博客:www.qiuxuewei.com
微信公衆號:@開發者成長之路
公衆號二維碼

一個沒有雞湯只有乾貨的公衆號

相關文章
相關標籤/搜索