對內存的思考
開篇
今天,計算機系統結構的真正挑戰不在于內存的容量,而是內存的速度。如果你的軟件實際上受到磁盤和內存的等待時間(訪問時間)的限制,那么就是再好的
芯片也無濟于事。在內存和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專家編程

浙公網安備 33010602011771號