天天看點

對記憶體的思考

開篇

今天,計算機系統結構的真正挑戰不在于記憶體的容量,而是記憶體的速度。如果你的軟體實際上受到磁盤和記憶體的等待時間(通路時間)的限制,那麼就是再好的

晶片也無濟于事。在記憶體和cpu之間存在着一道很深的鴻溝,而且是越來越深。在過去,每隔一兩年,cpu的處理速度就會提升一倍,在相同的時間内,記憶體的容

量倒是擴大了一倍,但它的通路時間提升卻沒有那麼明顯。。

是以我的了解是:記憶體主要受限于容量和速度。

容量問題除了內建工藝的發展擴大實體記憶體容量,還可用用虛拟記憶體的辦法解決。通路速度則可通過Cache技術的發展和程式設計的優化。

記憶體管理的前世今生

最初的計算機使用的記憶體直接對實體記憶體進行通路。這樣的方式很快就被淘汰,原因有二:1)直接通路記憶體不利于程式間、程式和作業系統之間的保護。比如說某個程序可能讀或者寫作業系統的資料,這樣做是很危險的。2)這對程式大小有很大的限制。要運作的程式都要首先加載進入記憶體中,如果程式過大,超過記憶體的大小,這樣的程式要運作是不可能的。

下面先介紹一下之前的記憶體管理模式,然後在介紹現在使用的虛拟記憶體技術。

Intel 80x86記憶體模型及工作原理:

(也就是之前計算機的工作原理)

80x86記憶體模型的基本形式是段。它是一塊64kb的記憶體區域,有一個段寄存器所指向。記憶體位址的形成過程是:取得段寄存器的值,向左移動4位(相當于乘上16)

然後加上偏移位址的值。注意,不同的段位址加上偏移位址可能指向同一個記憶體位址。這裡的通路位址就是實體位址,也就是說在沒有虛拟記憶體之前的計算機對記憶體的通路是直接通路實體位址的。

MS-DOS 640k的限制由來:

在MS-Dos下運作的應用程式都有一個嚴格的記憶體限制,就是可用記憶體隻有640KB.這個限制源于Intel 8086這個最初的DOS機器的最大位址傳回。8086支援20位的位址

,總共是1M的記憶體。之是以可用記憶體隻有640KB是因為某些段必須保留供系統使用。

如:

F0000到FFFF     64KB,永久性的ROM區域BIOS、診斷資訊等 

D0000到EFFF  128KB,用于ROM存儲區域。。。

後面的就不一一列舉。

下面介紹虛拟記憶體技術~

虛拟記憶體

虛拟,也就是不存在的意思,也就是說這裡的記憶體實際上是不存在的。這種技術給每個程序提供了一個完整的位址空間,即好像每個程序都擁有整個記憶體(如果是32位的話每個程序就有4GB的位址空間,也就是說每個程序可使用的記憶體大小是4GB)那麼這是如何實作的呢?

其實實體記憶體還是隻有一個,每個程序雖然有4GB的空間,但是用程序運作的時候那4GB的空間中隻有用得到的那部分映射到了實體位址上,而用不到的則存放在磁盤中,等要用的時候再把它裝入實體記憶體。其實這就是虛拟記憶體的大體思想。

和MS-DOS一樣,讓程式受限于機器的實體記憶體數量是相當不友善的。很早的時候,就有了虛拟記憶體的概念。它的基本思路是用廉價但緩慢的磁盤來擴充記憶體。

在任一時刻,程式實際需要使用的虛拟記憶體區段的内容就被載入到實際的實體記憶體中。當實體記憶體中的資料有一段時間未被使用,就可能被轉移到硬碟中,節省下

來的空間用于載入其他資料。現在的很多計算機系統都使用了虛拟記憶體的技術。

虛拟記憶體應用例子:

SunOS中每個程式都運作于32位的位址空間中,每個程式都以為自己有對整個位址空間的通路權,這正是通過虛拟記憶體實作的。所有程序共享機器的實體記憶體,當記憶體用完

時就用磁盤儲存資料。在運作時,資料在磁盤和記憶體之間來回移動,記憶體管理器把虛拟位址翻譯為實體記憶體位址,并讓一個程序始終運作于真正的實體記憶體中。這些都是由操作

系統完成,應用程式員隻看到虛位址 ,而不知道具體的切換情況。

虛拟記憶體通過“頁”的形式組織。也就是作業系統在磁盤和記憶體之間來回移動進行保護的機關,一般為級K位元組。

交換區:

從潛在的可能性上說,以程序有關的所有記憶體都将被系統調用,但是如果該程序不會馬上運作(可能由于優先級等原因或者是處于睡眠狀态),作業系統可能暫時取回給它 配置設定的記憶體,把該程序相關的資訊備份到磁盤上,這樣這個程序就被換出。在磁盤中有一個特殊的交換區用于儲存從記憶體中換出的程序。交換區的大小一般是記憶體的幾倍。

程序隻能操作位于實體記憶體中的頁面。當一個程序引用一個不存在實體記憶體中的頁面時,MMU就會産生一個錯誤。核心對此作出反應,并判斷是否有效,無效,核心向程序發出一個“Segmentation violation(段違規)”的信号。如果有效,核心從磁盤中取回該頁,換入到記憶體中。一旦頁面進入記憶體,程序便被解鎖,可以重新運作。程序并不知道它曾因為頁面換入時間等待了一會。

Cache存儲器:

首先,Cache存儲器是對多層存儲概念的擴充,可以認為Cache是為解決記憶體和cpu資料交換速度過慢而産出的。

它的特點是容量小,價格高,速度快,它位于記憶體和cpu之間,有兩種組織方式,有的是位于cpu一側,有的則是位于記憶體一側。

catch的操作速度和系統周期時間相同,是以一個50MHz的處理器,他的cache的存取周期為20ns,典型情況下,記憶體的速度僅為cache 的四分之一!速度确實很客觀哈~

簡答的介紹一下cache的工作原理:cache有一個位址清單及他們的内容,随着處理器不斷引用新的記憶體位址,位址清單的内容也一直在變化中。當cpu要從記憶體中取出資料時,先看位址是否存在于cahe位址清單中,如果存在直接讀取,不存在則從記憶體中讀取。記憶體中讀取資料以行為機關,在讀取的同時也裝入cache中。

有關高速緩存可參見博文:

對緩存的思考——提高命中率 對緩存的思考【續】——編寫高速緩存友好代碼

記憶體洩漏

”記憶體洩漏“其實我是不想用這種比較專業的說法來說問題的,好像會給人感覺比較遠。。。但是仔細想下這個翻譯其實還是有道理的。“洩漏”,洩漏的結果是怎樣?洩漏了會變少,記憶體洩漏就是可用的記憶體少了。為什麼會變少呢?。如果你申請的記憶體(比如說位址A)在結束時沒有釋放,記憶體管理程式就會認為位址A的資料是有用的。但實際上使用位址A的程序已經結束,但他申請的記憶體還沒釋放。如果沒有其他程式顯示的釋放的話,這塊位址就會一直被認為是有用的(其他程序用不了)。這就是記憶體洩漏了。

有些程式并不需要管理他們動态記憶體的使用,當需要記憶體時,他們簡單的通過配置設定記憶體獲得,而不必擔心如何回收它。當然,如果有回收機制的語言也不必擔心如何釋放配置設定的記憶體。

對于需要管理動态記憶體配置設定和回收的情況,比如c語言通常不适用垃圾回器(自動回收不用的記憶體塊)那麼這些程式在釋放和配置設定記憶體時,就需要認真對待。

堆經常會出現兩種類型的問題:

釋放或改寫仍在使用的記憶體(稱為記憶體損壞)

未釋放不在使用的記憶體(稱為記憶體洩漏)

這是最難被調試發現的問題之一。如果每次已配置設定的記憶體塊不再使用了卻不是放,程序就會配置設定越來越多的記憶體,導緻可用記憶體越來越少。

避免記憶體洩漏

每次配置設定記憶體時,注意在不用的時候釋放記憶體。如果釋放和先前配置設定的不對應,那很可能就會造成記憶體洩漏!

如果是c語言,一個更簡單的辦法就是用alloca函數配置設定記憶體,當離開調用alloca函數時,他配置設定的記憶體就會被自動釋放。顯然,這不适用于那些建立比函數身命周期長的變量的函數。有些熱不贊成使用alloca,因為它不并是一種可移植的方法。如果處理器在硬體上不支援堆棧,alloca()就很難高效的實作。

如何檢測記憶體洩漏:

1、如果是unix或者linux,首先用swap指令觀察還有多少可用的交換空間。在短時間内連續鍵入該指令兩三下次,看可用的交換區是否在減少。還可以使用其它的一些如netstat、vmstat的工具。如果發現不斷有記憶體配置設定且從不釋放,就可能出現記憶體洩漏。

2、确定可以程序:用ps-lu指令來顯示所有程序的大小。重複這個指令,如果一個程序看上去不斷增長而從不縮小,那麼基本可以确定發生洩漏的程序了。

參考資料:C專家程式設計

出處:

http://www.cnblogs.com/yanlingyin/

繼續閱讀