乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      20張圖直接把操作系統(tǒng)內(nèi)存管理部分安排了

       liqualife 2020-06-30

      前言

      之前就有不少小伙伴反饋說(shuō),有沒(méi)有圖解操作系統(tǒng)相關(guān)的文章。

      的確,操作系統(tǒng)確實(shí)是比較難啃的一門(mén)課,而且它的重要性也不言而喻。學(xué)操作系統(tǒng)的時(shí)候,主要痛苦的地方,有太多的抽象難以理解的詞語(yǔ)或概念,非常容易被勸退。

      即使懷著滿腔熱血的心情開(kāi)始學(xué)操作系統(tǒng),不過(guò) 3 分鐘睡意就突然襲來(lái)。。。不過(guò)該啃的還是得啃

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

      干就完事,本文的提綱:

      本文提綱

      正文

      虛擬內(nèi)存

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

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

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

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

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

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

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

      進(jìn)程的中間層

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

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

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

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

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

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

      虛擬地址尋址

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

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


      內(nèi)存分段

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

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

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

      內(nèi)存分段-尋址的方式
      • 段選擇子就保存在段寄存器里面。段選擇子里面最重要的是段號(hào),用作段表的索引。段表里面保存的是這個(gè)段的基地址、段的界限和特權(quán)等級(jí)等。

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

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

      內(nèi)存分段-虛擬地址與物理地址

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

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

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

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

      接下來(lái),說(shuō)說(shuō)為什么會(huì)有這兩個(gè)問(wèn)題。

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

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

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

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

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

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

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

      內(nèi)存碎片的問(wèn)題

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

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

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

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

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

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

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

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

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

      因?yàn)橛脖P(pán)的訪問(wèn)速度要比內(nèi)存慢太多了,每一次內(nèi)存交換,我們都需要把一大段連續(xù)的內(nèi)存數(shù)據(jù)寫(xiě)到硬盤(pán)上。

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

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


      內(nèi)存分頁(yè)

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

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

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

      虛擬地址與物理地址之間通過(guò)頁(yè)表來(lái)映射,如下圖:

      內(nèi)存映射

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

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

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

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

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

      換入換出

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

      分頁(yè)機(jī)制下,虛擬地址和物理地址是如何映射的?

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

      內(nèi)存分頁(yè)尋址

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

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

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

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

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

      虛擬頁(yè)與物理頁(yè)的映射

      這看起來(lái)似乎沒(méi)什么毛病,但是放到實(shí)際中操作系統(tǒng),這種簡(jiǎn)單的分頁(yè)是肯定是會(huì)有問(wèn)題的。

      簡(jiǎn)單的分頁(yè)有什么缺陷嗎?

      有空間上的缺陷。

      因?yàn)椴僮飨到y(tǒng)是可以同時(shí)運(yùn)行非常多的進(jìn)程的,那這不就意味著頁(yè)表會(huì)非常的龐大。

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

      這 4MB 大小的頁(yè)表,看起來(lái)也不是很大。但是要知道每個(gè)進(jìn)程都是有自己的虛擬地址空間的,也就說(shuō)都有自己的頁(yè)表。

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

      多級(jí)頁(yè)表

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

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

      我們把這個(gè) 100 多萬(wàn)個(gè)「頁(yè)表項(xiàng)」的單級(jí)頁(yè)表再分頁(yè),將頁(yè)表(一級(jí)頁(yè)表)分為 1024 個(gè)頁(yè)表(二級(jí)頁(yè)表),每個(gè)表(二級(jí)頁(yè)表)中包含 1024 個(gè)「頁(yè)表項(xiàng)」,形成二級(jí)分頁(yè)。如下圖所示:

      二級(jí)分頁(yè)

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

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

      其實(shí)我們應(yīng)該換個(gè)角度來(lái)看問(wèn)題,還記得計(jì)算機(jī)組成原理里面無(wú)處不在的局部性原理么?

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

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

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

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

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

      • 全局頁(yè)目錄項(xiàng) PGD(Page Global Directory);

      • 上層頁(yè)目錄項(xiàng) PUD(Page Upper Directory);

      • 中間頁(yè)目錄項(xiàng) PMD(Page Middle Directory);

      • 頁(yè)表項(xiàng) PTE(Page Table Entry);

      四級(jí)目錄

      TLB

      多級(jí)頁(yè)表雖然解決了空間上的問(wèn)題,但是虛擬地址到物理地址的轉(zhuǎn)換就多了幾道轉(zhuǎn)換的工序,這顯然就降低了這倆地址轉(zhuǎn)換的速度,也就是帶來(lái)了時(shí)間上的開(kāi)銷(xiāo)。

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

      程序的局部性

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

      地址轉(zhuǎn)換

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

      有了 TLB 后,那么 CPU 在尋址時(shí),會(huì)先查 TLB,如果沒(méi)找到,才會(huì)繼續(xù)查常規(guī)的頁(yè)表。

      TLB 的命中率其實(shí)是很高的,因?yàn)槌绦蜃畛TL問(wèn)的頁(yè)就那么幾個(gè)。


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

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

      段頁(yè)式地址空間

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

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

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

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

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

      段頁(yè)式管理中的段表、頁(yè)表與內(nèi)存的關(guān)系

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

      • 第一次訪問(wèn)段表,得到頁(yè)表起始地址;

      • 第二次訪問(wèn)頁(yè)表,得到物理頁(yè)號(hào);

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

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


      Linux 內(nèi)存管理

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

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

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

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

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

      Intel X86 邏輯地址解析過(guò)程

      這里說(shuō)明下邏輯地址和線性地址:

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

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

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

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

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

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

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

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

      我們?cè)賮?lái)瞧一瞧,Linux 的虛擬地址空間是如何分布的?

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

      用戶空間與內(nèi)存空間

      通過(guò)這里可以看出:

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

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

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

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

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

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

      每個(gè)進(jìn)程的內(nèi)核空間都是一致的

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

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

      虛擬內(nèi)存空間劃分

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

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

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

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

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

      • 文件映射段,包括動(dòng)態(tài)庫(kù)、共享內(nèi)存等,從高地址開(kāi)始向下增長(zhǎng);

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

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


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

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

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

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

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

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

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

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

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

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

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類(lèi)似文章 更多