當(dāng)前位置:首頁 > 公眾號精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]每日英語,每天進步一點點 前言 之前有不少讀者跟我反饋,能不能寫圖解操作系統(tǒng)? 既然那么多讀者想看,我最近就在瘋狂的復(fù)習(xí)操作系統(tǒng)的知識。 操作系統(tǒng)確實是比較難啃的一門課,至少我認(rèn)為比計算機網(wǎng)絡(luò)難太多了,但它的重要性就不用我多說了。 學(xué)操作系統(tǒng)的

每日英語,每天進步一點點
真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗

前言

之前有不少讀者跟我反饋,能不能寫圖解操作系統(tǒng)?

既然那么多讀者想看,我最近就在瘋狂的復(fù)習(xí)操作系統(tǒng)的知識。

操作系統(tǒng)確實是比較難啃的一門課,至少我認(rèn)為比計算機網(wǎng)絡(luò)難太多了,但它的重要性就不用我多說了。

學(xué)操作系統(tǒng)的時候,主要痛苦的地方,有太多的抽象難以理解的詞語或概念,非常容易被勸退。

即使懷著滿腔熱血的心情開始學(xué)操作系統(tǒng),不過 3 分鐘睡意就突然襲來。。。

該啃的還是得啃的,該圖解的還是得圖解的,萬眾期待的「圖解操作系統(tǒng)」的系列來了。

本篇跟大家說說內(nèi)存管理,內(nèi)存管理還是比較重要的一個環(huán)節(jié),理解了它,至少對整個操作系統(tǒng)的工作會有一個初步的輪廓,這也難怪面試的時候常問內(nèi)存管理。

干就完事,本文的提綱:

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
本文提綱

正文

虛擬內(nèi)存

如果你是電子相關(guān)專業(yè)的,肯定在大學(xué)里搗鼓過單片機。

單片機是沒有操作系統(tǒng)的,所以每次寫完代碼,都需要借助工具把程序燒錄進去,這樣程序才能跑起來。

另外,單片機的 CPU 是直接操作內(nèi)存的「物理地址」。

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗

在這種情況下,要想在內(nèi)存中同時運行兩個程序是不可能的。如果第一個程序在 2000 的位置寫入一個新的值,將會擦掉第二個程序存放在相同位置上的所有內(nèi)容,所以同時運行兩個程序是根本行不通的,這兩個程序會立刻崩潰。

操作系統(tǒng)是如何解決這個問題呢?

這里關(guān)鍵的問題是這兩個程序都引用了絕對物理地址,而這正是我們最需要避免的。

我們可以把進程所使用的地址「隔離」開來,即讓操作系統(tǒng)為每個進程分配獨立的一套「虛擬地址」,人人都有,大家自己玩自己的地址就行,互不干涉。但是有個前提每個進程都不能訪問物理地址,至于虛擬地址最終怎么落到物理內(nèi)存里,對進程來說是透明的,操作系統(tǒng)已經(jīng)把這些都安排的明明白白了。

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
進程的中間層

操作系統(tǒng)會提供一種機制,將不同進程的虛擬地址和不同內(nèi)存的物理地址映射起來。

如果程序要訪問虛擬地址的時候,由操作系統(tǒng)轉(zhuǎn)換成不同的物理地址,這樣不同的進程運行的時候,寫入的是不同的物理地址,這樣就不會沖突了。

于是,這里就引出了兩種地址的概念:

  • 我們程序所使用的內(nèi)存地址叫做虛擬內(nèi)存地址Virtual Memory Address

  • 實際存在硬件里面的空間地址叫物理內(nèi)存地址Physical Memory Address)。

操作系統(tǒng)引入了虛擬內(nèi)存,進程持有的虛擬地址會通過 CPU 芯片中的內(nèi)存管理單元(MMU)的映射關(guān)系,來轉(zhuǎn)換變成物理地址,然后再通過物理地址訪問內(nèi)存,如下圖所示:

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
虛擬地址尋址

操作系統(tǒng)是如何管理虛擬地址與物理地址之間的關(guān)系?

主要有兩種方式,分別是內(nèi)存分段和內(nèi)存分頁,分段是比較早提出的,我們先來看看內(nèi)存分段。


內(nèi)存分段

程序是由若干個邏輯分段組成的,如可由代碼分段、數(shù)據(jù)分段、棧段、堆段組成。不同的段是有不同的屬性的,所以就用分段(Segmentation)的形式把這些段分離出來。

分段機制下,虛擬地址和物理地址是如何映射的?

分段機制下的虛擬地址由兩部分組成,段選擇子段內(nèi)偏移量。

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
內(nèi)存分段-尋址的方式
  • 段選擇子就保存在段寄存器里面。段選擇子里面最重要的是段號,用作段表的索引。段表里面保存的是這個段的基地址、段的界限和特權(quán)等級等。

  • 虛擬地址中的段內(nèi)偏移量應(yīng)該位于 0 和段界限之間,如果段內(nèi)偏移量是合法的,就將段基地址加上段內(nèi)偏移量得到物理內(nèi)存地址。

在上面了,知道了虛擬地址是通過段表與物理地址進行映射的,分段機制會把程序的虛擬地址分成 4 個段,每個段在段表中有一個項,在這一項找到段的基地址,再加上偏移量,于是就能找到物理內(nèi)存中的地址,如下圖:

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
內(nèi)存分段-虛擬地址與物理地址

如果要訪問段 3 中偏移量 500 的虛擬地址,我們可以計算出物理地址為,段 3 基地址 7000 + 偏移量 500 = 7500。

分段的辦法很好,解決了程序本身不需要關(guān)心具體的物理內(nèi)存地址的問題,但它也有一些不足之處:

  • 第一個就是內(nèi)存碎片的問題。

  • 第二個就是內(nèi)存交換的效率低的問題。

接下來,說說為什么會有這兩個問題。

我們先來看看,分段為什么會產(chǎn)生內(nèi)存碎片的問題?

我們來看看這樣一個例子。假設(shè)有 1G 的物理內(nèi)存,用戶執(zhí)行了多個程序,其中:

  • 游戲占用了 512MB 內(nèi)存

  • 瀏覽器占用了 128MB 內(nèi)存

  • 音樂占用了 256 MB 內(nèi)存。

這個時候,如果我們關(guān)閉了瀏覽器,則空閑內(nèi)存還有 1024 - 512 - 256 = 256MB。

如果這個 256MB 不是連續(xù)的,被分成了兩段 128 MB 內(nèi)存,這就會導(dǎo)致沒有空間再打開一個 200MB 的程序。

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
內(nèi)存碎片的問題

這里的內(nèi)存碎片的問題共有兩處地方:

  • 外部內(nèi)存碎片,也就是產(chǎn)生了多個不連續(xù)的小物理內(nèi)存,導(dǎo)致新的程序無法被裝載;

  • 內(nèi)部內(nèi)存碎片,程序所有的內(nèi)存都被裝載到了物理內(nèi)存,但是這個程序有部分的內(nèi)存可能并不是很常使用,這也會導(dǎo)致內(nèi)存的浪費;

針對上面兩種內(nèi)存碎片的問題,解決的方式會有所不同。

解決外部內(nèi)存碎片的問題就是內(nèi)存交換

可以把音樂程序占用的那 256MB 內(nèi)存寫到硬盤上,然后再從硬盤上讀回來到內(nèi)存里。不過再讀回的時候,我們不能裝載回原來的位置,而是緊緊跟著那已經(jīng)被占用了的 512MB 內(nèi)存后面。這樣就能空缺出連續(xù)的 256MB 空間,于是新的 200MB 程序就可以裝載進來。

這個內(nèi)存交換空間,在 Linux 系統(tǒng)里,也就是我們??吹降?Swap 空間,這塊空間是從硬盤劃分出來的,用于內(nèi)存與硬盤的空間交換。

再來看看,分段為什么會導(dǎo)致內(nèi)存交換效率低的問題?

對于多進程的系統(tǒng)來說,用分段的方式,內(nèi)存碎片是很容易產(chǎn)生的,產(chǎn)生了內(nèi)存碎片,那不得不重新 Swap 內(nèi)存區(qū)域,這個過程會產(chǎn)生性能瓶頸。

因為硬盤的訪問速度要比內(nèi)存慢太多了,每一次內(nèi)存交換,我們都需要把一大段連續(xù)的內(nèi)存數(shù)據(jù)寫到硬盤上。

所以,如果內(nèi)存交換的時候,交換的是一個占內(nèi)存空間很大的程序,這樣整個機器都會顯得卡頓。

為了解決內(nèi)存分段的內(nèi)存碎片和內(nèi)存交換效率低的問題,就出現(xiàn)了內(nèi)存分頁。


內(nèi)存分頁

分段的好處就是能產(chǎn)生連續(xù)的內(nèi)存空間,但是會出現(xiàn)內(nèi)存碎片和內(nèi)存交換的空間太大的問題。

要解決這些問題,那么就要想出能少出現(xiàn)一些內(nèi)存碎片的辦法。另外,當(dāng)需要進行內(nèi)存交換的時候,讓需要交換寫入或者從磁盤裝載的數(shù)據(jù)更少一點,這樣就可以解決問題了。這個辦法,也就是內(nèi)存分頁Paging)。

分頁是把整個虛擬和物理內(nèi)存空間切成一段段固定尺寸的大小。這樣一個連續(xù)并且尺寸固定的內(nèi)存空間,我們叫Page)。在 Linux 下,每一頁的大小為 4KB。

虛擬地址與物理地址之間通過頁表來映射,如下圖:

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
內(nèi)存映射

頁表實際上存儲在 CPU 的內(nèi)存管理單元MMU) 中,于是 CPU 就可以直接通過 MMU,找出要實際要訪問的物理內(nèi)存地址。

而當(dāng)進程訪問的虛擬地址在頁表中查不到時,系統(tǒng)會產(chǎn)生一個缺頁異常,進入系統(tǒng)內(nèi)核空間分配物理內(nèi)存、更新進程頁表,最后再返回用戶空間,恢復(fù)進程的運行。

分頁是怎么解決分段的內(nèi)存碎片、內(nèi)存交換效率低的問題?

由于內(nèi)存空間都是預(yù)先劃分好的,也就不會像分段會產(chǎn)生間隙非常小的內(nèi)存,這正是分段會產(chǎn)生內(nèi)存碎片的原因。而采用了分頁,那么釋放的內(nèi)存都是以頁為單位釋放的,也就不會產(chǎn)生無法給進程使用的小內(nèi)存。

如果內(nèi)存空間不夠,操作系統(tǒng)會把其他正在運行的進程中的「最近沒被使用」的內(nèi)存頁面給釋放掉,也就是暫時寫在硬盤上,稱為換出Swap Out)。一旦需要的時候,再加載進來,稱為換入Swap In)。所以,一次性寫入磁盤的也只有少數(shù)的一個頁或者幾個頁,不會花太多時間,內(nèi)存交換的效率就相對比較高。

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
換入換出

更進一步地,分頁的方式使得我們在加載程序的時候,不再需要一次性都把程序加載到物理內(nèi)存中。我們完全可以在進行虛擬內(nèi)存和物理內(nèi)存的頁之間的映射之后,并不真的把頁加載到物理內(nèi)存里,而是只有在程序運行中,需要用到對應(yīng)虛擬內(nèi)存頁里面的指令和數(shù)據(jù)時,再加載到物理內(nèi)存里面去。

分頁機制下,虛擬地址和物理地址是如何映射的?

在分頁機制下,虛擬地址分為兩部分,頁號頁內(nèi)偏移。頁號作為頁表的索引,頁表包含物理頁每頁所在物理內(nèi)存的基地址,這個基地址與頁內(nèi)偏移的組合就形成了物理內(nèi)存地址,見下圖。

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
內(nèi)存分頁尋址

總結(jié)一下,對于一個內(nèi)存地址轉(zhuǎn)換,其實就是這樣三個步驟:

  • 把虛擬內(nèi)存地址,切分成頁號和偏移量;

  • 根據(jù)頁號,從頁表里面,查詢對應(yīng)的物理頁號;

  • 直接拿物理頁號,加上前面的偏移量,就得到了物理內(nèi)存地址。

下面舉個例子,虛擬內(nèi)存中的頁通過頁表映射為了物理內(nèi)存中的頁,如下圖:

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
虛擬頁與物理頁的映射

這看起來似乎沒什么毛病,但是放到實際中操作系統(tǒng),這種簡單的分頁是肯定是會有問題的。

簡單的分頁有什么缺陷嗎?

有空間上的缺陷。

因為操作系統(tǒng)是可以同時運行非常多的進程的,那這不就意味著頁表會非常的龐大。

在 32 位的環(huán)境下,虛擬地址空間共有 4GB,假設(shè)一個頁的大小是 4KB(2^12),那么就需要大約 100 萬 (2^20) 個頁,每個「頁表項」需要 4 個字節(jié)大小來存儲,那么整個 4GB 空間的映射就需要有 4MB 的內(nèi)存來存儲頁表。

這 4MB 大小的頁表,看起來也不是很大。但是要知道每個進程都是有自己的虛擬地址空間的,也就說都有自己的頁表。

那么,100 個進程的話,就需要 400MB 的內(nèi)存來存儲頁表,這是非常大的內(nèi)存了,更別說 64 位的環(huán)境了。

多級頁表

要解決上面的問題,就需要采用的是一種叫作多級頁表Multi-Level Page Table)的解決方案。

在前面我們知道了,對于單頁表的實現(xiàn)方式,在 32 位和頁大小 4KB 的環(huán)境下,一個進程的頁表需要裝下 100 多萬個「頁表項」,并且每個頁表項是占用 4 字節(jié)大小的,于是相當(dāng)于每個頁表需占用 4MB 大小的空間。

我們把這個 100 多萬個「頁表項」的單級頁表再分頁,將頁表(一級頁表)分為 1024 個頁表(二級頁表),每個表(二級頁表)中包含 1024 個「頁表項」,形成二級分頁。如下圖所示:

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
二級分頁

你可能會問,分了二級表,映射 4GB 地址空間就需要 4KB(一級頁表)+ 4MB(二級頁表)的內(nèi)存,這樣占用空間不是更大了嗎?

當(dāng)然如果 4GB 的虛擬地址全部都映射到了物理內(nèi)上的,二級分頁占用空間確實是更大了,但是,我們往往不會為一個進程分配那么多內(nèi)存。

其實我們應(yīng)該換個角度來看問題,還記得計算機組成原理里面無處不在的局部性原理么?

每個進程都有 4GB 的虛擬地址空間,而顯然對于大多數(shù)程序來說,其使用到的空間遠未達到 4GB,因為會存在部分對應(yīng)的頁表項都是空的,根本沒有分配,對于已分配的頁表項,如果存在最近一定時間未訪問的頁表,在物理內(nèi)存緊張的情況下,操作系統(tǒng)會將頁面換出到硬盤,也就是說不會占用物理內(nèi)存。

如果使用了二級分頁,一級頁表就可以覆蓋整個 4GB 虛擬地址空間,但如果某個一級頁表的頁表項沒有被用到,也就不需要創(chuàng)建這個頁表項對應(yīng)的二級頁表了,即可以在需要時才創(chuàng)建二級頁表。做個簡單的計算,假設(shè)只有 20% 的一級頁表項被用到了,那么頁表占用的內(nèi)存空間就只有 4KB(一級頁表) + 20% * 4MB(二級頁表)= 0.804MB
,這對比單級頁表的 4MB 是不是一個巨大的節(jié)約?

那么為什么不分級的頁表就做不到這樣節(jié)約內(nèi)存呢?我們從頁表的性質(zhì)來看,保存在內(nèi)存中的頁表承擔(dān)的職責(zé)是將虛擬地址翻譯成物理地址。假如虛擬地址在頁表中找不到對應(yīng)的頁表項,計算機系統(tǒng)就不能工作了。所以頁表一定要覆蓋全部虛擬地址空間,不分級的頁表就需要有 100 多萬個頁表項來映射,而二級分頁則只需要 1024 個頁表項(此時一級頁表覆蓋到了全部虛擬地址空間,二級頁表在需要時創(chuàng)建)。

我們把二級分頁再推廣到多級頁表,就會發(fā)現(xiàn)頁表占用的內(nèi)存空間更少了,這一切都要歸功于對局部性原理的充分應(yīng)用。

對于 64 位的系統(tǒng),兩級分頁肯定不夠了,就變成了四級目錄,分別是:

  • 全局頁目錄項 PGD(Page Global Directory);

  • 上層頁目錄項 PUD(Page Upper Directory);

  • 中間頁目錄項 PMD(Page Middle Directory);

  • 頁表項 PTE(Page Table Entry);


真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
四級目錄

TLB

多級頁表雖然解決了空間上的問題,但是虛擬地址到物理地址的轉(zhuǎn)換就多了幾道轉(zhuǎn)換的工序,這顯然就降低了這倆地址轉(zhuǎn)換的速度,也就是帶來了時間上的開銷。

程序是有局部性的,即在一段時間內(nèi),整個程序的執(zhí)行僅限于程序中的某一部分。相應(yīng)地,執(zhí)行所訪問的存儲空間也局限于某個內(nèi)存區(qū)域。

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
程序的局部性

我們就可以利用這一特性,把最常訪問的幾個頁表項存儲到訪問速度更快的硬件,于是計算機科學(xué)家們,就在 CPU 芯片中,加入了一個專門存放程序最常訪問的頁表項的 Cache,這個 Cache 就是 TLB(Translation Lookaside Buffer) ,通常稱為頁表緩存、轉(zhuǎn)址旁路緩存、快表等。

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
地址轉(zhuǎn)換

在 CPU 芯片里面,封裝了內(nèi)存管理單元(Memory Management Unit)芯片,它用來完成地址轉(zhuǎn)換和 TLB 的訪問與交互。

有了 TLB 后,那么 CPU 在尋址時,會先查 TLB,如果沒找到,才會繼續(xù)查常規(guī)的頁表。

TLB 的命中率其實是很高的,因為程序最常訪問的頁就那么幾個。


段頁式內(nèi)存管理

內(nèi)存分段和內(nèi)存分頁并不是對立的,它們是可以組合起來在同一個系統(tǒng)中使用的,那么組合起來后,通常稱為段頁式內(nèi)存管理

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
段頁式地址空間

段頁式內(nèi)存管理實現(xiàn)的方式:

  • 先將程序劃分為多個有邏輯意義的段,也就是前面提到的分段機制;

  • 接著再把每個段劃分為多個頁,也就是對分段劃分出來的連續(xù)空間,再劃分固定大小的頁;

這樣,地址結(jié)構(gòu)就由段號、段內(nèi)頁號和頁內(nèi)位移三部分組成。

用于段頁式地址變換的數(shù)據(jù)結(jié)構(gòu)是每一個程序一張段表,每個段又建立一張頁表,段表中的地址是頁表的起始地址,而頁表中的地址則為某頁的物理頁號,如圖所示:

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
段頁式管理中的段表、頁表與內(nèi)存的關(guān)系

段頁式地址變換中要得到物理地址須經(jīng)過三次內(nèi)存訪問:

  • 第一次訪問段表,得到頁表起始地址;

  • 第二次訪問頁表,得到物理頁號;

  • 第三次將物理頁號與頁內(nèi)位移組合,得到物理地址。

可用軟、硬件相結(jié)合的方法實現(xiàn)段頁式地址變換,這樣雖然增加了硬件成本和系統(tǒng)開銷,但提高了內(nèi)存的利用率。


Linux 內(nèi)存管理

那么,Linux 操作系統(tǒng)采用了哪種方式來管理內(nèi)存呢?

在回答這個問題前,我們得先看看 Intel 處理器的發(fā)展歷史。

早期 Intel 的處理器從 80286 開始使用的是段式內(nèi)存管理。但是很快發(fā)現(xiàn),光有段式內(nèi)存管理而沒有頁式內(nèi)存管理是不夠的,這會使它的 X86 系列會失去市場的競爭力。因此,在不久以后的 80386 中就實現(xiàn)了對頁式內(nèi)存管理。也就是說,80386 除了完成并完善從 80286 開始的段式內(nèi)存管理的同時還實現(xiàn)了頁式內(nèi)存管理。

但是這個 80386 的頁式內(nèi)存管理設(shè)計時,沒有繞開段式內(nèi)存管理,而是建立在段式內(nèi)存管理的基礎(chǔ)上,這就意味著,頁式內(nèi)存管理的作用是在由段式內(nèi)存管理所映射而成的的地址上再加上一層地址映射。

由于此時段式內(nèi)存管理映射而成的地址不再是“物理地址”了,Intel 就稱之為“線性地址”(也稱虛擬地址)。于是,段式內(nèi)存管理先將邏輯地址映射成線性地址,然后再由頁式內(nèi)存管理將線性地址映射成物理地址。

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
Intel X86 邏輯地址解析過程

這里說明下邏輯地址和線性地址:

  • 程序所使用的地址,通常是沒被段式內(nèi)存管理映射的地址,稱為邏輯地址;

  • 通過段式內(nèi)存管理映射的地址,稱為線性地址,也叫虛擬地址;

邏輯地址是「段式內(nèi)存管理」轉(zhuǎn)換前的地址,線性地址則是「頁式內(nèi)存管理」轉(zhuǎn)換前的地址。

了解完 Intel 處理器的發(fā)展歷史后,我們再來說說 Linux 采用了什么方式管理內(nèi)存?

Linux 內(nèi)存主要采用的是頁式內(nèi)存管理,但同時也不可避免地涉及了段機制。

這主要是上面 Intel 處理器發(fā)展歷史導(dǎo)致的,因為 Intel X86 CPU 一律對程序中使用的地址先進行段式映射,然后才能進行頁式映射。既然 CPU 的硬件結(jié)構(gòu)是這樣,Linux 內(nèi)核也只好服從 Intel 的選擇。

但是事實上,Linux 內(nèi)核所采取的辦法是使段式映射的過程實際上不起什么作用。也就是說,“上有政策,下有對策”,若惹不起就躲著走。

Linux 系統(tǒng)中的每個段都是從 0 地址開始的整個 4GB 虛擬空間(32 位環(huán)境下),也就是所有的段的起始地址都是一樣的。這意味著,Linux 系統(tǒng)中的代碼,包括操作系統(tǒng)本身的代碼和應(yīng)用程序代碼,所面對的地址空間都是線性地址空間(虛擬地址),這種做法相當(dāng)于屏蔽了處理器中的邏輯地址概念,段只被用于訪問控制和內(nèi)存保護。

我們再來瞧一瞧,Linux 的虛擬地址空間是如何分布的?

在 Linux 操作系統(tǒng)中,虛擬地址空間的內(nèi)部又被分為內(nèi)核空間和用戶空間兩部分,不同位數(shù)的系統(tǒng),地址空間的范圍也不同。比如最常見的 32 位和 64 位系統(tǒng),如下所示:

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
用戶空間與內(nèi)存空間

通過這里可以看出:

  • 32 位系統(tǒng)的內(nèi)核空間占用 1G,位于最高處,剩下的 3G 是用戶空間;

  • 64 位系統(tǒng)的內(nèi)核空間和用戶空間都是 128T,分別占據(jù)整個內(nèi)存空間的最高和最低處,剩下的中間部分是未定義的。

再來說說,內(nèi)核空間與用戶空間的區(qū)別:

  • 進程在用戶態(tài)時,只能訪問用戶空間內(nèi)存;

  • 只有進入內(nèi)核態(tài)后,才可以訪問內(nèi)核空間的內(nèi)存;

雖然每個進程都各自有獨立的虛擬內(nèi)存,但是每個虛擬內(nèi)存中的內(nèi)核地址,其實關(guān)聯(lián)的都是相同的物理內(nèi)存。這樣,進程切換到內(nèi)核態(tài)后,就可以很方便地訪問內(nèi)核空間內(nèi)存。

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
每個進程的內(nèi)核空間都是一致的

接下來,進一步了解虛擬空間的劃分情況,用戶空間和內(nèi)核空間劃分的方式是不同的,內(nèi)核空間的分布情況就不多說了。

我們看看用戶空間分布的情況,以 32 位系統(tǒng)為例,我畫了一張圖來表示它們的關(guān)系:

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗
虛擬內(nèi)存空間劃分

通過這張圖你可以看到,用戶空間內(nèi)存,從低到高分別是 7 種不同的內(nèi)存段:

  • 程序文件段,包括二進制可執(zhí)行代碼;

  • 已初始化數(shù)據(jù)段,包括靜態(tài)常量;

  • 未初始化數(shù)據(jù)段,包括未初始化的靜態(tài)變量;

  • 堆段,包括動態(tài)分配的內(nèi)存,從低地址開始向上增長;

  • 文件映射段,包括動態(tài)庫、共享內(nèi)存等,從低地址開始向上增長(跟硬件和內(nèi)核版本有關(guān))

  • 棧段,包括局部變量和函數(shù)調(diào)用的上下文等。棧的大小是固定的,一般是 8 MB。當(dāng)然系統(tǒng)也提供了參數(shù),以便我們自定義大??;

在這 7 個內(nèi)存段中,堆和文件映射段的內(nèi)存是動態(tài)分配的。比如說,使用 C 標(biāo)準(zhǔn)庫的 malloc() 或者 mmap() ,就可以分別在堆和文件映射段動態(tài)分配內(nèi)存。


總總結(jié)結(jié)

為了在多進程環(huán)境下,使得進程之間的內(nèi)存地址不受影響,相互隔離,于是操作系統(tǒng)就為每個進程獨立分配一套的虛擬地址空間,每個程序只關(guān)心自己的虛擬地址就可以,實際上大家的虛擬地址都是一樣的,但分布到物理地址內(nèi)存是不一樣的。作為程序,也不用關(guān)心物理地址的事情。

每個進程都有自己的虛擬空間,而物理內(nèi)存只有一個,所以當(dāng)啟用了大量的進程,物理內(nèi)存必然會很緊張,于是操作系統(tǒng)會通過內(nèi)存交換技術(shù),把不常使用的內(nèi)存暫時存放到硬盤(換出),在需要的時候再裝載回物理內(nèi)存(換入)。

那既然有了虛擬地址空間,那必然要把虛擬地址「映射」到物理地址,這個事情通常由操作系統(tǒng)來維護。

那么對于虛擬地址與物理地址的映射關(guān)系,可以有分段分頁的方式,同時兩者結(jié)合都是可以的。

內(nèi)存分段是根據(jù)程序的邏輯角度,分成了棧段、堆段、數(shù)據(jù)段、代碼段等,這樣可以分離出不同屬性的段,同時是一塊連續(xù)的空間。但是每個段的大小都不是統(tǒng)一的,這就會導(dǎo)致內(nèi)存碎片和內(nèi)存交換效率低的問題。

于是,就出現(xiàn)了內(nèi)存分頁,把虛擬空間和物理空間分成大小固定的頁,如在 Linux 系統(tǒng)中,每一頁的大小為 4KB。由于分了頁后,就不會產(chǎn)生細(xì)小的內(nèi)存碎片。同時在內(nèi)存交換的時候,寫入硬盤也就一個頁或幾個頁,這就大大提高了內(nèi)存交換的效率。

再來,為了解決簡單分頁產(chǎn)生的頁表過大的問題,就有了多級頁表,它解決了空間上的問題,但這就會導(dǎo)致 CPU 在尋址的過程中,需要有很多層表參與,加大了時間上的開銷。于是根據(jù)程序的局部性原理,在 CPU 芯片中加入了 TLB,負(fù)責(zé)緩存最近常被訪問的頁表項,大大提高了地址的轉(zhuǎn)換速度。

Linux 系統(tǒng)主要采用了分頁管理,但是由于 Intel 處理器的發(fā)展史,Linux 系統(tǒng)無法避免分段管理。于是 Linux 就把所有段的基地址設(shè)為 0,也就意味著所有程序的地址空間都是線性地址空間(虛擬地址),相當(dāng)于屏蔽了 CPU 邏輯地址的概念,所以段只被用于訪問控制和內(nèi)存保護。

另外,Linxu 系統(tǒng)中虛擬空間分布可分為用戶態(tài)內(nèi)核態(tài)兩部分,其中用戶態(tài)的分布:代碼段、全局變量、BSS、函數(shù)棧、堆內(nèi)存、映射區(qū)。


特別推薦一個分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗

長按訂閱更多精彩▼

真棒! 20 張圖揭開內(nèi)存管理的迷霧,瞬間豁然開朗

如有收獲,點個在看,誠摯感謝


免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉