點我查看祕籍連載數組
Linux的虛擬地址空間採用「分段+分頁」結合的方式實現。先看分段,以後再介紹分頁。函數
分段是將內存劃分紅各個段落(Segment),每一個段落的長度能夠不一樣,且虛擬地址空間中未使用的空間不會映射到物理內存中,因此操做系統不會爲這段空間分配物理內存。這樣的話,內核爲剛建立的進程分配的物理內存能夠很小,隨着進程運行不斷使用內存,內核再爲進程按需分配物理內存。也就是說,儘管地址空間的範圍和物理內存大小同樣,但不會將所有空間映射到物理內存。佈局
對於Linux進程的虛擬地址空間來講,它的內存佈局以下圖。優化
虛擬空間分了以下幾個段:操作系統
從上面的描述大概也能推測出,除了堆內存外,其它段落空間都是自動填充分配的,用戶沒法控制這些內存的使用。而堆內存段是用戶能使用的自由內存區,絕大多數程序的用戶數據都丟在這裏面,算是一個大雜燴空間。翻譯
例如,下圖中是一段C代碼和內存佈局之間的對應關係。設計
提示:其它語言的內存佈局
上面的佈局C程序的內存佈局,也是Linux下進程的內存佈局。其它語言(好比CPython)編寫的程序運行起來後,只要是在Linux下運行,其進程的佈局也會如此。只不過這些語言的程序中,全局變量、局部變量等可能和C的佈局不同,這和各語言的底層設計有關。好比C編寫的某動態語言,它不要求指定變量的數據類型,那麼在加載到內存的時候天然不知道該變量類型所需的空間大小,當它轉換成C後(儘管不會真的轉換成C代碼),這個變量只能丟進堆內存做爲動態數據。3d
使用分段的好處就是「各段自掃門前雪」,雖然在地址空間中每一個分段的地址都是連續的,但實際上,每一個分段映射到物理內存地址時是獨立的,段與段之間能夠不連續。這是由於CPU爲每一個段都使用一對(即兩個)特殊的寄存器:基址寄存器和界限寄存器。blog
而界限寄存器中的值用來表示該段在物理內存中的大小,即已爲該段分配了多少內存。當準備用虛擬地址加基址計算物理地址時,須要先根據界限寄存器中的值檢查將要訪問的物理內存地址是否超出了這個段的範圍。若是超出了,則表示訪問了不屬於該段的內存,也即內存的越界訪問,而用戶進程是沒有權限訪問其它進程或未分配內存的地址的,這時會收到一個SIGSEGV(segmentation violation)信號並提示:Segmentation Fault,即段錯誤或段異常。收到這個信號後默認狀況下會終止該進程,由於它訪問了非法地址,可是能夠設置該信號的信號處理程序,從而作出其它處理。進程
例如,下圖中的進程訪問了Kernel段或者unallocated memory部分的內存,都會報錯。Kernel段除了內核進程,任何用戶進程都沒法訪問,典型的地址是用戶進程想要訪問0x0地址時,而該地址屬於Kernel,因此報錯。而unallocated memory是還未分配的內存,界限寄存器會保護該段沒法訪問。
再例如,C數組的越界訪問時也會出現該問題。下圖直觀地顯示了在Windows中一樣的內存越界錯誤。
也就是說,基址寄存器是用來轉換地址的,界限寄存器是用來保護進程不越界訪問內存的。CPU藉助基址寄存器和界限寄存器管理並提供地址翻譯和內存保護的功能,一般稱爲內存管理單元(Memory Management Unit,MMU)。
最後再說明一點,內存地址翻譯的任務既能夠由操做系統來作,也能夠由硬件CPU來作。但若是徹底由操做系統來完成,就須要頻繁地陷入到內核態,這樣效率會很是低。因此,這項任務交給CPU硬件來完成,操做系統只需在必要的時候介入,好比分配內存、回收內存等。