05.内存管理.md
文章目錄
- 5. 內(nèi)存
- 5.1 什么是內(nèi)存
- 5.2 存儲器體系結(jié)構(gòu)
- 5.3 存儲管理
- 5.3.1 內(nèi)存管理的內(nèi)容
- 5.3.2 內(nèi)存管理的需求
- 5.3.3 內(nèi)存管理的術(shù)語
- 5.3.4 內(nèi)存管理的發(fā)展
- 5.3.5 簡單分頁的內(nèi)存管理
- 5.3 什么是虛擬內(nèi)存
- 5.3.1 虛擬內(nèi)存術(shù)語
- 5.3.2 地址翻譯
- 5.3.3 一個進程的虛擬地址空間分布
- 6.鏈接
- 6.1 程序編譯鏈接的一般過程
- 6.2 鏈接的要求
5. 內(nèi)存
5.1 什么是內(nèi)存
在第一課的學(xué)習(xí)中已經(jīng)有過對內(nèi)存的介紹,我們在這里不再贅述,同時也知道內(nèi)存是計算機中非常重要的一部分,計算機運行時的數(shù)據(jù)指令等等都需要在內(nèi)存中存儲。
5.2 存儲器體系結(jié)構(gòu)
計算機的存儲體系結(jié)構(gòu)具體到不同的具體的計算機設(shè)計當(dāng)中又是各不相同的,這里給出一個大體的層次結(jié)構(gòu),緩存有效的原因是源自于程序執(zhí)行的局部性原理。
5.3 存儲管理
??這里聊的存儲管理主要是指沒有基于虛擬內(nèi)存的存儲管理。
5.3.1 內(nèi)存管理的內(nèi)容
??想一下存儲管理主要要解決問題,存儲管理主要還是管理各個進程在運行時的程序和數(shù)據(jù)的訪問。
當(dāng)要啟動一個應(yīng)用程序的時候,首先需要將該進程的可執(zhí)行程序加載到內(nèi)存當(dāng)中,創(chuàng)建一個進程,這個進程有自己特定的內(nèi)存區(qū)域,存放剛才加載進來的代碼指令,數(shù)據(jù)塊,以及一些進程的描述信息,控制信息等。
同時,在進程運行的過程中可能還要訪問文件等外部存儲,這個時候還需要將這些東西加載到內(nèi)存當(dāng)中才行。內(nèi)存管理就是為了解決這些問題而存在的。
下面的圖也顯示了一個進程在內(nèi)存中的需求:
5.3.2 內(nèi)存管理的需求
為了內(nèi)存使用效率的提升,內(nèi)存被換出內(nèi)存后再換進內(nèi)存的時候不一定能夠換到原來的位置,所以換到新的位置也要能夠支持,稱為重定位技術(shù)。
每個進程的內(nèi)部內(nèi)存都要受到保護,保護必須由處理器來實現(xiàn),而不是操作系統(tǒng)來滿足,因為操作系統(tǒng)不能預(yù)測程序可能產(chǎn)生的所有內(nèi)存訪問。
要支持對部分共享區(qū)域的受控訪問,比如一些程序共享庫文件等
就是對一個進程使用的內(nèi)存如何劃分,分段是比較符合實際程序情況的,數(shù)據(jù)段,代碼段等等這樣的。
計算機存儲器至少要組織成兩級,即內(nèi)存和外存。內(nèi)存提供快速的訪問,成本也相對較高。此外,內(nèi)存是易失性的,即它不能提供永久性存儲。外存比內(nèi)存慢而且便宜,且通常是非易失性的。因此,大容量的外存可用于長期存儲程序和數(shù)據(jù),而較小的內(nèi)存則用于保存當(dāng)前使用的程序和數(shù)據(jù)。在這種兩級方案中,系統(tǒng)主要關(guān)注的是內(nèi)存和外存之間信息流的組織交換等工作,比如在內(nèi)存不夠用的時候?qū)⒁徊糠謨?nèi)存換到外存當(dāng)中的操作。
5.3.3 內(nèi)存管理的術(shù)語
5.3.4 內(nèi)存管理的發(fā)展
| 固定分區(qū) | 主存被分為很多大小固定的分區(qū),進程可以裝載到大于等于自身大小的分區(qū)。 | 實現(xiàn)簡單 | 1.有內(nèi)部碎片,2.活動進程的數(shù)目是固定的 |
| 動態(tài)分區(qū) | 分區(qū)是被動態(tài)創(chuàng)建的,進程可以裝載到正好等于自身大小的分區(qū)。 | 沒有內(nèi)部碎片,內(nèi)存使用更完全 | ,有外部碎片,需要壓縮外部碎片 |
| 簡單分頁 | 主存被分為很多大小相同的幀,進程被分為很多與幀大小相同的頁。要裝入一個進程,需要將進程所有的頁裝入主存,可以是不連續(xù)的幀中。 | 沒有外部碎片有很少的內(nèi)部碎片(僅出現(xiàn)在進程的最后一頁) | |
| 簡單分段 | 進程被分為很多的段,要裝入一個進程,需要將進程所有的段裝入主存中不一定連續(xù)的動態(tài)分區(qū)。 | 沒有內(nèi)部碎片,比較與動態(tài)分區(qū),內(nèi)存利用率更高,開銷小 | 有外部碎片,需要壓縮外部碎片 |
| 虛擬內(nèi)存分頁 | 與簡單分頁相比,不需要將進程的所有頁裝入主存 | 沒有外部碎片巨大的虛擬內(nèi)存空間 更高程度的多到程序設(shè)計 | 復(fù)雜的內(nèi)存管理開銷 |
| 虛擬內(nèi)存分段 | 與簡單分段相比,不需要將進程所有的段都裝入主存 | 具有虛擬內(nèi)存分頁的三個優(yōu)點,并且支持保護和共享 | 復(fù)雜的內(nèi)存管理開銷 |
5.3.5 簡單分頁的內(nèi)存管理
??內(nèi)存管理的發(fā)展經(jīng)歷了很多迭代,從上面也可以看出來。分區(qū)技術(shù)曾經(jīng)用在過許多過時的系統(tǒng)當(dāng)中。簡單分頁和簡單分段并沒有在實際中使用過,只是為了增強對物理內(nèi)存的管理理解,有助于后面對虛擬內(nèi)存的分頁,分段管理增強理解。
??在簡單分頁中,主存被分為很多大小相同的幀(頁框),進程被分為很多與幀大小相同的頁。要裝入一個進程,需要將進程所有的頁裝入主存,可以是不連續(xù)的幀中。每個進程維護一個頁表,頁表項有頁號和對應(yīng)的物理頁框。
??簡單分頁要求進程運行時當(dāng)前進程的代碼段數(shù)據(jù)段等都要加載進內(nèi)存中,內(nèi)存才能夠運行。
下面這幅圖展示了ABCD 4個進程的載入和換出過程。主存儲中的每個格子代表了一個頁框。
同時下面的第二幅圖顯示了每個進程需要維護一個頁表來保存他使用了主存儲中的哪些頁框。
介紹一種邏輯地址和物理地址的轉(zhuǎn)換方案
為了使分頁方案更加方便,規(guī)定頁和頁框的大小都必須是2的冪。以便于容易地標(biāo)識出相對地址。
相對地址一般由程序的起點和邏輯地址定義,可以用頁號和偏移量表示。
比如這里使用16位的地址,頁的大小是1kb,2的10次方,尋址范圍是64kb,就可以用地址的高6位標(biāo)識頁號,低10位標(biāo)識頁內(nèi)偏移量。
00000101 11011110
對應(yīng)的頁號就是1,偏移量就是1502
提取完頁號以后,以頁號為索引在進程頁表中找到這個頁號對應(yīng)的頁框
把頁框地址和上一步中的偏移量疊加起來就構(gòu)成了物理地址。
5.3 什么是虛擬內(nèi)存
5.3.1 虛擬內(nèi)存術(shù)語
在存儲分配機制中,盡管備用內(nèi)存是主存的一部分,但它也可被尋址。程序引用內(nèi)存使用的地址與內(nèi)存系統(tǒng)用于識別物理存儲站點的地址是不同的,程序生成的地址會自動轉(zhuǎn)換為機器地址。虛擬存儲的大小受計算機系統(tǒng)尋址機制和可用的備用內(nèi)存量的限制,而不受主存儲位置實際數(shù)量的限制
??通過上面的介紹也可以看到,早期計算機使用物理尋址方式,但是到了現(xiàn)在的多任務(wù)計算機時代,普遍使用的是虛擬尋址(virtual addressing);虛擬內(nèi)存,就是對于每個進程來說,他能夠訪問的地址空間都是整個cpu能夠訪問的地址空間的最大值。
??舉例,對于x86系統(tǒng),32位的虛擬內(nèi)存大小是4g,也就是每個進程的虛擬地址空間都有4G大小,64位系統(tǒng)每個進程的虛擬地址空間都是4g*4g的虛擬地址空間(簡直可以認(rèn)為無限大了)如果每個進程的可訪問空間都要裝入內(nèi)存中,那計算機就是有再大的內(nèi)存也是扛不住的。所以對于虛擬內(nèi)存來說,只是有一部分會在物理內(nèi)存當(dāng)中,很多都是需要的時候才會加載,同時可能在物理內(nèi)存不夠的時候又會被換出去。又是局部性決定了可以使用虛擬內(nèi)存。
5.3.2 地址翻譯
在運行的過程中,CPU 通過一個虛擬地址(virtual address,VA)來訪問主存,這個虛擬地址在被送到主存之前會先轉(zhuǎn)換成一個物理地址。將虛擬地址轉(zhuǎn)換成物理地址的任務(wù)叫做地址翻譯(address translation)。
地址翻譯需要 CPU 硬件和操作系統(tǒng)之間的配合。 CPU 芯片上叫做內(nèi)存管理單元(Menory Management Unit, MMU)的專用硬件,利用存放在主存中的查詢表來動態(tài)翻譯虛擬地址,該表的內(nèi)容由操作系統(tǒng)管理。
5.3.3 一個進程的虛擬地址空間分布
x86 平臺 Linux 進程內(nèi)存布局
6.鏈接
??鏈接(linking)是將各種代碼和數(shù)據(jù)部分收集起來并組合成為一個單一文件的過程,這個文件可被加載(或被拷貝)到存儲器并執(zhí)行。鏈接可以執(zhí)行于編譯時(compile time),也就是在源代碼被翻譯成機器代碼時;也可以執(zhí)行于加載時(load time),也就是在程序被加載器(loader)加載到存儲器并執(zhí)行時;甚至執(zhí)行于運行時(runtime),由應(yīng)用程序來執(zhí)行。在早期的計算機系統(tǒng)中,鏈接是手動執(zhí)行的。在現(xiàn)代系統(tǒng)中,鏈接是由叫做鏈接器(linker)的程序自動執(zhí)行的。
?&emsp鏈接器在軟件開發(fā)中扮演著一個關(guān)鍵的角色,因為它們使得分離編譯(separate compilation)成為可能。我們不用將一個大型的應(yīng)用程序組織為一個巨大的源文件,而是可以把它分解為更小、更好管理的模塊,可以獨立地修改和編譯這些模塊。當(dāng)我們改變這些模塊中的一個時,只需簡單地重新編譯它,并重新鏈接應(yīng)用,而不必重新編譯其他文件。
6.1 程序編譯鏈接的一般過程
c源程序到執(zhí)行的的一般過程,假如源程序是main.c
以上步驟可以得到可執(zhí)行文件,也就是可運行程序
可重定位目標(biāo)文件,可執(zhí)行目標(biāo)文件,共享目標(biāo)文件,都被稱為目標(biāo)文件,因為他們有相似的文件格式,都被稱為ELF文件。
這里主要想要學(xué)習(xí)的就是鏈接是如何將可重定位目標(biāo)文件轉(zhuǎn)換為可執(zhí)行目標(biāo)文件的。
其實我們在平時生成的目標(biāo)文件中,具備以下幾個特征:
1. 各個段沒有具體的起始地址,只有段大小信息; 2. 各個標(biāo)識符沒有實際地址,只有段中的相對地址; 3. 段和標(biāo)識符的實際地址需要鏈接器具體確定。鏈接就是的主要過程是
ELF文件的結(jié)構(gòu)格式
6.2 鏈接的要求
1、各個段的鏈接地址必須符合具體平臺的規(guī)范;
2、鏈接腳本中能夠直接定義標(biāo)識符并指定存儲地址;
3、鏈接腳本中能夠指定源代碼中標(biāo)識符的存儲地址;
在 Linux 中,進程代碼段(.text)的合法起始地址為[0x08048000, 0x08049000]。
在鏈接器中默認(rèn)指定了text的起始地址為0x08048000(可以自己設(shè)置),然后鏈接器會基于這個位置對其他的代碼進行虛擬地址的重定位。默認(rèn)的入口函數(shù)是_start函數(shù),這個函數(shù)就放在入口地址位置處。
1.32位進程的虛擬地址空間
這里對鏈接器講的很詳細(xì)
參考:
https://juejin.im/post/59f8691b51882534af254317
https://segmentfault.com/a/1190000016433897
總結(jié)
以上是生活随笔為你收集整理的05.内存管理.md的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 04.并发和互斥.md
- 下一篇: 01.java内存模型