05.內存管理.md

5. 內存

5.1 什麼是內存

第一課的學習中已經有過對內存的介紹,我們在這裏不再贅述,同時也知道內存是計算機中非常重要的一部分,計算機運行時的數據指令等等都需要在內存中存儲。

5.2 存儲器體系結構

計算機的存儲體系結構具體到不同的具體的計算機設計當中又是各不相同的,這裏給出一個大體的層次結構,緩存有效的原因是源自於程序執行的局部性原理。
在這裏插入圖片描述

5.3 存儲管理

  這裏聊的存儲管理主要是指沒有基於虛擬內存的存儲管理。

5.3.1 內存管理的內容

  想一下存儲管理主要要解決問題,存儲管理主要還是管理各個進程在運行時的程序和數據的訪問。
當要啓動一個應用程序的時候,首先需要將該進程的可執行程序加載到內存當中,創建一個進程,這個進程有自己特定的內存區域,存放剛纔加載進來的代碼指令,數據塊,以及一些進程的描述信息,控制信息等。
同時,在進程運行的過程中可能還要訪問文件等外部存儲,這個時候還需要將這些東西加載到內存當中才行。內存管理就是爲了解決這些問題而存在的。

下面的圖也顯示了一個進程在內存中的需求:
在這裏插入圖片描述

5.3.2 內存管理的需求

  1. 重定位
    爲了內存使用效率的提升,內存被換出內存後再換進內存的時候不一定能夠換到原來的位置,所以換到新的位置也要能夠支持,稱爲重定位技術。
  2. 保護
    每個進程的內部內存都要受到保護,保護必須由處理器來實現,而不是操作系統來滿足,因爲操作系統不能預測程序可能產生的所有內存訪問。
  3. 共享
    要支持對部分共享區域的受控訪問,比如一些程序共享庫文件等
  4. 邏輯組織
    就是對一個進程使用的內存如何劃分,分段是比較符合實際程序情況的,數據段,代碼段等等這樣的。
  5. 物理組織
    計算機存儲器至少要組織成兩級,即內存和外存。內存提供快速的訪問,成本也相對較高。此外,內存是易失性的,即它不能提供永久性存儲。外存比內存慢而且便宜,且通常是非易失性的。因此,大容量的外存可用於長期存儲程序和數據,而較小的內存則用於保存當前使用的程序和數據。在這種兩級方案中,系統主要關注的是內存和外存之間信息流的組織交換等工作,比如在內存不夠用的時候將一部分內存換到外存當中的操作。

5.3.3 內存管理的術語

  1. 頁框:物理內存中的固定長度塊
  2. 頁: 固定長度的數據塊,存儲在二級存儲中(如磁盤),數據頁可以臨時複製到頁框當中。
  3. 段:變長數據塊,存儲在二級存儲中(如磁盤),數據頁可以臨時複製到內存中的一個可用區域中(分段),或者可以將一個段分爲很多頁,然後將每頁單獨複製到內存中(分段與分頁結合式)
  4. 邏輯地址:指與當前數據在物理內存中分配的地址沒有關係的訪問地址
  5. 相對地址:相對地址是邏輯地址的一個特例,他是相對於某些已知點(通常是程序的開始處)存儲單元。
  6. 物理地址:或者成爲絕對地址,是指物理內存中的一個存儲單元。

5.3.4 內存管理的發展

技術 簡要說明 優點 缺點
固定分區 主存被分爲很多大小固定的分區,進程可以裝載到大於等於自身大小的分區。 實現簡單 1.有內部碎片,2.活動進程的數目是固定的
動態分區 分區是被動態創建的,進程可以裝載到正好等於自身大小的分區。 沒有內部碎片,內存使用更完全 ,有外部碎片,需要壓縮外部碎片
簡單分頁 主存被分爲很多大小相同的幀,進程被分爲很多與幀大小相同的頁。要裝入一個進程,需要將進程所有的頁裝入主存,可以是不連續的幀中。 沒有外部碎片有很少的內部碎片(僅出現在進程的最後一頁)
簡單分段 進程被分爲很多的段,要裝入一個進程,需要將進程所有的段裝入主存中不一定連續的動態分區。 沒有內部碎片,比較與動態分區,內存利用率更高,開銷小 有外部碎片,需要壓縮外部碎片
虛擬內存分頁 與簡單分頁相比,不需要將進程的所有頁裝入主存 沒有外部碎片巨大的虛擬內存空間 更高程度的多到程序設計 複雜的內存管理開銷
虛擬內存分段 與簡單分段相比,不需要將進程所有的段都裝入主存 具有虛擬內存分頁的三個優點,並且支持保護和共享 複雜的內存管理開銷

5.3.5 簡單分頁的內存管理

  內存管理的發展經歷了很多迭代,從上面也可以看出來。分區技術曾經用在過許多過時的系統當中。簡單分頁和簡單分段並沒有在實際中使用過,只是爲了增強對物理內存的管理理解,有助於後面對虛擬內存的分頁,分段管理增強理解。
  在簡單分頁中,主存被分爲很多大小相同的幀(頁框),進程被分爲很多與幀大小相同的頁。要裝入一個進程,需要將進程所有的頁裝入主存,可以是不連續的幀中。每個進程維護一個頁表,頁表項有頁號和對應的物理頁框。
  簡單分頁要求進程運行時當前進程的代碼段數據段等都要加載進內存中,內存才能夠運行。
下面這幅圖展示了ABCD 4個進程的載入和換出過程。主存儲中的每個格子代表了一個頁框。
同時下面的第二幅圖顯示了每個進程需要維護一個頁表來保存他使用了主存儲中的哪些頁框。
在這裏插入圖片描述
在這裏插入圖片描述
介紹一種邏輯地址和物理地址的轉換方案
爲了使分頁方案更加方便,規定頁和頁框的大小都必須是2的冪。以便於容易地標識出相對地址。
相對地址一般由程序的起點和邏輯地址定義,可以用頁號和偏移量表示。
比如這裏使用16位的地址,頁的大小是1kb,2的10次方,尋址範圍是64kb,就可以用地址的高6位標識頁號,低10位標識頁內偏移量。

00000101 11011110
對應的頁號就是1,偏移量就是1502

提取完頁號以後,以頁號爲索引在進程頁表中找到這個頁號對應的頁框
把頁框地址和上一步中的偏移量疊加起來就構成了物理地址。

5.3 什麼是虛擬內存

5.3.1 虛擬內存術語

  1. 虛擬內存:
    在存儲分配機制中,儘管備用內存是主存的一部分,但它也可被尋址。程序引用內存使用的地址與內存系統用於識別物理存儲站點的地址是不同的,程序生成的地址會自動轉換爲機器地址。虛擬存儲的大小受計算機系統尋址機制和可用的備用內存量的限制,而不受主存儲位置實際數量的限制
  2. 虛擬地址: 在虛擬內存中分配給某一位置的地址,它使得該位置可被訪問,就好像是主內的一部分那樣
  3. 虛擬地址空間:配給進程的虛擬存儲
  4. 地址空間:用於某進程的內存地址範圍
  5. 實地址: 物理內存中存儲位置的地址

  通過上面的介紹也可以看到,早期計算機使用物理尋址方式,但是到了現在的多任務計算機時代,普遍使用的是虛擬尋址(virtual addressing);虛擬內存,就是對於每個進程來說,他能夠訪問的地址空間都是整個cpu能夠訪問的地址空間的最大值。
  舉例,對於x86系統,32位的虛擬內存大小是4g,也就是每個進程的虛擬地址空間都有4G大小,64位系統每個進程的虛擬地址空間都是4g*4g的虛擬地址空間(簡直可以認爲無限大了)如果每個進程的可訪問空間都要裝入內存中,那計算機就是有再大的內存也是扛不住的。所以對於虛擬內存來說,只是有一部分會在物理內存當中,很多都是需要的時候纔會加載,同時可能在物理內存不夠的時候又會被換出去。又是局部性決定了可以使用虛擬內存。

5.3.2 地址翻譯

在運行的過程中,CPU 通過一個虛擬地址(virtual address,VA)來訪問主存,這個虛擬地址在被送到主存之前會先轉換成一個物理地址。將虛擬地址轉換成物理地址的任務叫做地址翻譯(address translation)。

地址翻譯需要 CPU 硬件和操作系統之間的配合。 CPU 芯片上叫做內存管理單元(Menory Management Unit, MMU)的專用硬件,利用存放在主存中的查詢表來動態翻譯虛擬地址,該表的內容由操作系統管理。

5.3.3 一個進程的虛擬地址空間分佈

x86 平臺 Linux 進程內存佈局
在這裏插入圖片描述

6.鏈接

  鏈接(linking)是將各種代碼和數據部分收集起來並組合成爲一個單一文件的過程,這個文件可被加載(或被拷貝)到存儲器並執行。鏈接可以執行於編譯時(compile time),也就是在源代碼被翻譯成機器代碼時;也可以執行於加載時(load time),也就是在程序被加載器(loader)加載到存儲器並執行時;甚至執行於運行時(runtime),由應用程序來執行。在早期的計算機系統中,鏈接是手動執行的。在現代系統中,鏈接是由叫做鏈接器(linker)的程序自動執行的。
 &emsp鏈接器在軟件開發中扮演着一個關鍵的角色,因爲它們使得分離編譯(separate compilation)成爲可能。我們不用將一個大型的應用程序組織爲一個巨大的源文件,而是可以把它分解爲更小、更好管理的模塊,可以獨立地修改和編譯這些模塊。當我們改變這些模塊中的一個時,只需簡單地重新編譯它,並重新鏈接應用,而不必重新編譯其他文件。

6.1 程序編譯鏈接的一般過程

c源程序到執行的的一般過程,假如源程序是main.c

  1. 預處理(preprocessing) :預處理階段主要處理#include和#define,它把#include包含進來的.h 文件插入到#include所在的位置,把源程序中使用到的用#define定義的宏用實際的字符串代替,這一步生成 main.i 文件
  2. 編譯(compilation) :把代碼翻譯成彙編語言,該選項只進行編譯而不進行彙編,生成彙編代碼,生成的文件是main.s。
  3. 彙編(assembly):彙編階段把main.s文件翻譯成二進制機器指令文件main.o, 該文件被稱爲可重定位目標文件。
  4. 鏈接(linking) :將可執行目標文件經過一系列處理生成可執行目標文件
    以上步驟可以得到可執行文件,也就是可運行程序
  5. 加載(loader)就可以執行了。

可重定位目標文件,可執行目標文件,共享目標文件,都被稱爲目標文件,因爲他們有相似的文件格式,都被稱爲ELF文件。
這裏主要想要學習的就是鏈接是如何將可重定位目標文件轉換爲可執行目標文件的。

其實我們在平時生成的目標文件中,具備以下幾個特徵:

1. 各個段沒有具體的起始地址,只有段大小信息;
2. 各個標識符沒有實際地址,只有段中的相對地址;
3. 段和標識符的實際地址需要鏈接器具體確定。

鏈接就是的主要過程是

  1. 解析符號
  2. 重定位生成可執行文件
    ELF文件的結構格式
    在這裏插入圖片描述

6.2 鏈接的要求

1、各個段的鏈接地址必須符合具體平臺的規範;
2、鏈接腳本中能夠直接定義標識符並指定存儲地址;
3、鏈接腳本中能夠指定源代碼中標識符的存儲地址;
在 Linux 中,進程代碼段(.text)的合法起始地址爲[0x08048000, 0x08049000]。
在鏈接器中默認指定了text的起始地址爲0x08048000(可以自己設置),然後鏈接器會基於這個位置對其他的代碼進行虛擬地址的重定位。默認的入口函數是_start函數,這個函數就放在入口地址位置處。
1.32位進程的虛擬地址空間
這裏對鏈接器講的很詳細

參考:
https://juejin.im/post/59f8691b51882534af254317
https://segmentfault.com/a/1190000016433897

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章