一.內存地址重定位
在匯編指令中,我們有時會看到如下指令:
.text .entry: 代碼入口 call 40 - main: .......
那么這里的40指向的是內存中的哪個位置呢?是內存的實際地址嗎?
顯然,如果是實際地址的話我們的程序必須被裝載在內存0地址處,但這樣做肯定是存在問題的,一方面,如果這樣的話每個程序都要放到0地址處;另一方面,內存0地址
處已經被操作系統占用。
因此,這里的40必然是一個邏輯地址(或相對地址)
那么,程序在內存中需要修改源代碼中的邏輯地址,改為實際物理地址(如上圖程序被加載到內存1000處時40和300都被修改了),因為程序並不是始終存在於內存的同一位置的,在被換出(swap out)內存並再次換入(swap in)時很可能並不會被加載到同一位置,所以編譯時確定實際物理地址是不可取的;在這里,更可行的方式是運行時動態加上一個基地址(base)。
(如圖中所示,操作系統進行內存調度,將部分程序換出,將其他程序換入)
那么這個基地址被存放在哪里呢?在Linux中,基地址會被存到PCB中。在操作系統里每個進程都有一個數據結構與之對應,被稱為PCB(進程控制塊),當我們fork()一個進程的時候實際上是創建了一個PCB並從其父進程的PCB哪里繼承一些數據,並將這個數據結構插入到系統的進程樹中。
所以,整體大概是如下流程:
編譯程序->fork出PCB(進程)->在內存中找到空閑的內存空間->將這段空間的基地址賦值給PCB中的base字段->載入程序->執行每行程序時將源碼中的地址與PCB中的基地址相加。
當然為了提高效率當操作系統切換到當前進程時base字段會被放入寄存器中,所以相加是在寄存器中進行,而且CPU本身為這種操作提供了硬件支持--MMU(內存管理單元)。
二.內存的分段機制
我們在平時玩游戲或寫代碼時,認為我們所使用的進程(比如游戲程序或eclipse)是整個存在於內存中的,而事實上呢?
如果您學過一些匯編或C/C++的話,可能會很自然地認為進程是如下組成的:
進程分為幾個段,每個段都有自己的特點,有不同的用途,而事實上就像上圖所示,我們前文所講的0地址並不是整個程序從0開始,而是程序中的每個段都從0開始,
程序的這幾個段有的只讀(如代碼段),有的可寫(如數據段),如果同一處理的話明顯會造成混亂,比如錯誤地寫了程序段等。
因此程序在加載時並非整個一起加載進內存,通常是分段加載。
所以,在加載段進內存時,地址重定位就與之前加載整個程序進內存有所不同:
這種情況下,定位一個地址就變為段的基地址+段內偏移量,如果使用之前那種加載整個進程的方式,PCB中只需要存放一個程序基地址,而分段加載則需要
存放每個段的基地址,這樣僅僅使用一個base字段是滿足不了的,在PCB中必須保存一個表用來保存每個段的基地址:
如上圖,根據進程段表,匯編指令運行時會進行重定位,過程如下:比如執行圖中第一條mov指令,會在段表中找到DS段的基地址360k,再於100相加,這才是
實際地址。
每個Linux中的進程都會使用一個進程段表(LDT表),而操作系統本身也是一個進程,它也同樣使用的進程段表(GDT表)。
因此,我們進程地址重定向的一個整體流程變為:
編譯程序-->創建PCB-->程序中的某個段(如代碼段)找到一個空閑的內存空間-->將內存基地址存入PCB中的LDT表里-->其它段載入過程類似
-->執行程序,將邏輯地址和LDT表中該段的基地址相加。
三.內存分區和分頁
1.內存分區
前文提到我們需要將首先我們的程序需要載入內存,在載入時需要在內存中找到空閑區域,因此必須將內存事先分割成不同區域,那么如何分割內存才合理呢?
首先,我們可能會想到將內存等分為n個區域,但這明顯是不合理的,因為我們的程序占用空間大小不一,每個段需要的空間亦不相同。
一種很自然地想法是,根據每個段實際需要的大小進行分配,並記錄已經占用的空間和剩余空間:
這樣,當一個段請求內存時,就到空閑分區中申請一段內存,並在已分配分區表中記錄這一過程。
內存適配:
當一個段請求內存時,如果有內存中有很多大小不一的空閑位置,那么選擇哪個最合理?
1. 首先適配:空閑分區表中選擇第一個位置(優點:查表速度快)
2. 最差適配:選擇一個最大的空閑區域
3. 最佳適配:選擇一個空閑位置大小和申請內存大小最接近的位置,比如申請一個40k內存,而恰巧內存中有一個50k的空閑位置(這里最佳並不意味着最好,
因為最佳適配可能導致大量內存碎片)
2.內存分頁
盡管分區的方式解決了申請內存的問題,但很明顯的是其會帶來大量的內存碎片,意思是盡管我們內存中仍然存在很大空間,但全部都是一些零散的空間,當申請
大塊內存時會出現申請失敗。如圖中所示,灰色區域為內存碎片。
為了不使這些零散的空間浪費,操作系統會做內存緊縮,即將內存中的段移動到另一位置。但明顯移動進程是一個低效的操作。
這就引入了內存分頁的機制:
如圖中所示,如果操作系統實現將內存分割成多個頁框,而段申請內存時按頁分配,這樣的話,一個段最多浪費一個頁框。也就不需要做耗時的內存緊縮操作了。
但如上圖所示,一個段會被分割到多個頁框中,顯然,我們的內存重定位方式需要進一步改變。
待續。。。。
參考資料:
哈工大操作系統公開課:http://mooc.study.163.com/course/HIT-1000003007
Linux原理圖和PCB:http://blog.csdn.net/kangear/article/details/41940091