從內存來看,早期的機器並無提供多少抽象給用戶。基本上,機器的物理內存看起來如圖所示。程序員
操做系統曾經是一組函數(其實是一個庫),在內存中(在本例中,從物理地址0開始),而後有一個正在運行的程序(進程),目前在物理內存中(在本例中,從物理地址64KB開始),並使用剩餘的內存。函數
過了一段時間,因爲機器昂貴,人們開始更有效地共享機器。所以,多道程序系統和分時系統分別開啓了。oop
在下圖中,有3個進程(A、B、C),每一個進程擁有從512KB物理內存中切出來給它們的一小部份內存。假定只有一個CPU,操做系統選擇運行其中一個進程(好比A),同時其餘進程(B和C)則在隊列中等待運行。spa
隨着時分共享變得流行,人們對操做系統又有了新的要求。特別是多個程序同時駐留在內存中,使保護(protection)成爲重要問題。操作系統
爲了解決這些問題,操做系統須要提供一個易用(easy to use)的物理內存抽象。這個抽象叫做地址空間(address space),是運行的程序看到的系統中的內存。指針
一個進程的地址空間包含運行的程序的全部內存狀態。好比:程序的代碼(code,指令)必須在內存中,所以它們在地址空間裏。當程序在運行的時候,利用棧(stack)來保存當前的函數調用信息,分配空間給局部變量,傳遞參數和函數返回值。最後,堆(heap)用於管理動態分配的、用戶管理的內存。固然,還有其餘的東西(例如,靜態初始化的變量),但如今假設只有這3個部分:代碼、棧和堆。code
在下圖的例子中,咱們有一個很小的地址空間(只有16KB)。程序代碼位於地址空間的頂部(在本例中從0開始,而且裝入到地址空間的前1KB)。代碼是靜態的(所以很容易放在內存中),因此能夠將它放在地址空間的頂部,咱們知道程序運行時再也不須要新的空間。隊列
當咱們描述地址空間時,所描述的是操做系統提供給運行程序的抽象。程序不在物理地址0~16KB的內存中,而是加載在任意的物理地址。可是運行的程序意識不到這點,它認爲本身被加載到特定地址(例如0)的內存中,而且具備很是大的地址空間。這就是虛擬內存系統須要作的事情。進程
虛擬內存(VM)系統的一個主要目標是透明(transparency)。操做系統實現虛擬內存的方式,應該讓運行的程序看不見。所以,程序不該該感知到內存被虛擬化的事實,相反,程序的行爲就好像它擁有本身的私有物理內存。內存
虛擬內存的另外一個目標是效率(efficiency)。操做系統應該追求虛擬化儘量高效(efficient),包括時間上(即不會使程序運行得更慢)和空間上(即不須要太多額外的內存來支持虛擬化)。在實現高效率虛擬化時,操做系統將不得不依靠硬件支持,包括TLB這樣的硬件功能。
最後,虛擬內存第三個目標是保護(protection)。操做系統應確保進程受到保護(protect),不會受其餘進程影響,操做系統自己也不會受進程影響。當一個進程執行加載、存儲或指令提取時,它不該該以任何方式訪問或影響任何其餘進程或操做系統自己的內存內容(即在它的地址空間以外的任何內容)。
在運行一個C程序的時候,會分配兩種類型的內存。第一種稱爲棧內存,它的申請和釋放操做是編譯器來隱式管理的,因此有時也稱爲自動(automatic)內存。第二種類型的內存,即所謂的堆(heap)內存,其中全部的申請和釋放操做都由程序員顯式地完成。
malloc函數很是簡單:傳入要申請的堆空間的大小,它成功就返回一個指向新申請空間的指針,失敗就返回NULL。
#include <stdlib.h> ... void *malloc(size_t size);
要釋放再也不使用的堆內存,程序員只需調用free():
int *x = malloc(10 * sizeof(int)); ... free(x);
該函數接受一個參數,即一個由malloc()返回的指針。分配區域的大小不會被用戶傳入,必須由內存分配庫自己記錄追蹤。
在使用malloc()和free()時會出現一些常見的錯誤。
許多例程在調用以前,都但願你爲它們分配內存。例如,例程strcpy(dst, src)將源字符串中的字符串複製到目標指針。可是,若是不當心,你可能會這樣作:
char *src = "hello"; char *dst; // oops! unallocated strcpy(dst, src); // segfault and die
另外一個相關的錯誤是沒有分配足夠的內存,有時稱爲緩衝區溢出(buffer overflow)。一個常見的錯誤是爲目標緩衝區留出「幾乎」足夠的空間。
char *src = "hello"; char *dst = (char *) malloc(strlen(src)); // too small! strcpy(dst, src); // work properly
在這個錯誤中,程序員正確地調用malloc(),但忘記在新分配的數據類型中填寫一些值。這樣的話程序最終會遇到未初始化的讀取(uninitialized read),它從堆中讀取了一些未知值的數據。
另外一個常見錯誤稱爲內存泄露(memory leak),若是忘記釋放內存,就會發生。在長時間運行的應用程序或系統(如操做系統自己)中,這是一個巨大的問題,由於緩慢泄露的內存會致使內存不足,此時須要從新啓動。
有時候程序會在用完以前釋放內存,這種錯誤稱爲懸掛指針(dangling pointer)。隨後的使用可能會致使程序崩潰或覆蓋有效的內存(例如,你調用了free(),但隨後再次調用malloc()來分配其餘內容,這從新利用了錯誤釋放的內存)。
程序有時還會不止一次地釋放內存,這被稱爲重複釋放(double free)。這樣作的結果是未定義的。
注:系統中實際存在兩級內存管理。第一級是由操做系統執行的內存管理,操做系統在進程運行時將內存交給進程,並在進程退出(或以其餘方式結束)時將其回收。第二級管理在每一個進程中,例如在調用malloc()和free()時,在堆內管理。即便你沒有調用free(),操做系統也會在程序結束運行時,收回進程的全部內存。