第一層理解
1. 每個進程都有自己獨立的4G内存空間,各個進程的内存空間具有類似的結構
2. 一個新進程建立的時候,将會建立起自己的内存空間,此進程的數據,代碼等從磁盤拷貝到自己的進程空間,哪些數據在哪裡,都由進程控制表中的task_struct記錄,task_struct中記錄中一條鍊表,記錄中内存空間的分配情況,哪些地址有數據,哪些地址無數據,哪些可讀,哪些可寫,都可以通過這個鍊表記錄
3. 每個進程已經分配的内存空間,都與對應的磁盤空間映射
問題:
計算機明明沒有那麼多内存(n個進程的話就需要n*4G)内存
建立一個進程,就要把磁盤上的程序文件拷貝到進程對應的内存中去,對于一個程序對應的多個進程這種情況,浪費内存!
第二層理解
1. 每個進程的4G内存空間隻是虛拟内存空間,每次訪問内存空間的某個地址,都需要把地址翻譯為實際物理内存地址
2. 所有進程共享同一物理内存,每個進程隻把自己目前需要的虛拟内存空間映射并存儲到物理内存上。
3. 進程要知道哪些内存地址上的數據在物理内存上,哪些不在,還有在物理内存上的哪裡,需要用頁表來記錄
4. 頁表的每一個表項分兩部分,第一部分記錄此頁是否在物理内存上,第二部分記錄物理内存頁的地址(如果在的話)
5. 當進程訪問某個虛拟地址,去看頁表,如果發現對應的數據不在物理内存中,則缺頁異常
6. 缺頁異常的處理過程,就是把進程需要的數據從磁盤上拷貝到物理内存中,如果内存已經滿了,沒有空地方了,那就找一個頁覆蓋,當然如果被覆蓋的頁曾經被修改過,需要将此頁寫回磁盤
總結:
優點:
1.既然每個進程的内存空間都是一緻而且固定的,所以鍊接器在鍊接可執行文件時,可以設定内存地址,而不用去管這些數據最終實際的内存地址,這是有獨立内存空間的好處
2.當不同的進程使用同樣的代碼時,比如庫文件中的代碼,物理内存中可以隻存儲一份這樣的代碼,不同的進程隻需要把自己的虛拟内存映射過去就可以了,節省内存
3.在程序需要分配連續的内存空間的時候,隻需要在虛拟内存空間分配連續空間,而不需要實際物理内存的連續空間,可以利用碎片。
另外,事實上,在每個進程創建加載時,内核隻是為進程“創建”了虛拟内存的布局,具體就是初始化進程控制表中内存相關的鍊表,實際上并不立即就把虛拟内存對應位置的程序數據和代碼(比如.text .data段)拷貝到物理内存中,隻是建立好虛拟内存和磁盤文件之間的映射就好(叫做存儲器映射),等到運行到對應的程序時,才會通過缺頁異常,來拷貝數據。還有進程運行過程中,要動态分配内存,比如malloc時,也隻是分配了虛拟内存,即為這塊虛拟内存對應的頁表項做相應設置,當進程真正訪問到此數據時,才引發缺頁異常。
補充理解:
虛拟存儲器涉及三個概念: 虛拟存儲空間,磁盤空間,内存空間
可以認為虛拟空間都被映射到了磁盤空間中,(事實上也是按需要映射到磁盤空間上,通過mmap),并且由頁表記錄映射位置,當訪問到某個地址的時候,通過頁表中的有效位,可以得知此數據是否在内存中,如果不是,則通過缺頁異常,将磁盤對應的數據拷貝到内存中,如果沒有空閑内存,則選擇犧牲頁面,替換其他頁面。
mmap是用來建立從虛拟空間到磁盤空間的映射的,可以将一個虛拟空間地址映射到一個磁盤文件上,當不設置這個地址時,則由系統自動設置,函數返回對應的内存地址(虛拟地址),當訪問這個地址的時候,就需要把磁盤上的内容拷貝到内存了,然後就可以讀或者寫,最後通過manmap可以将内存上的數據換回到磁盤,也就是解除虛拟空間和内存空間的映射,這也是一種讀寫磁盤文件的方法,也是一種進程共享數據的方法 共享内存
物理内存:在内核态申請内存比在用戶态申請内存要更為直接,它沒有采用用戶态那種延遲分配内存技術。内核認為一旦有内核函數申請内存,那麼就必須立刻滿足該申請内存的請求,并且這個請求一定是正确合理的。相反,對于用戶态申請内存的請求,内核總是盡量延後分配物理内存,用戶進程總是先獲得一個虛拟内存區的使用權,最終通過缺頁異常獲得一塊真正的物理内存。
1.物理内存的内核映射
IA32架構中内核虛拟地址空間隻有1GB大小(從3GB到4GB),因此可以直接将1GB大小的物理内存(即常規内存)映射到内核地址空間,但超出1GB大小的物理内存(即高端内存)就不能映射到内核空間。為此,内核采取了下面的方法使得内核可以使用所有的物理内存。
1).高端内存不能全部映射到内核空間,也就是說這些物理内存沒有對應的線性地址。不過,内核為每個物理頁框都分配了對應的頁框描述符,所有的頁框描述符都保存在mem_map數組中,因此每個頁框描述符的線性地址都是固定存在的。内核此時可以使用alloc_pages()和alloc_page()來分配高端内存,因為這些函數返回頁框描述符的線性地址。
2).内核地址空間的後128MB專門用于映射高端内存,否則,沒有線性地址的高端内存不能被内核所訪問。這些高端内存的内核映射顯然是暫時映射的,否則也隻能映射128MB的高端内存。當内核需要訪問高端内存時就臨時在這個區域進行地址映射,使用完畢之後再用來進行其他高端内存的映射。
由于要進行高端内存的内核映射,因此直接能夠映射的物理内存大小隻有896MB,該值保存在high_memory中。内核地址空間的線性地址區間如下圖所示:
從圖中可以看出,内核采用了三種機制将高端内存映射到内核空間:永久内核映射,固定映射和vmalloc機制。
2.物理内存管理機制
基于物理内存在内核空間中的映射原理,物理内存的管理方式也有所不同。内核中物理内存的管理機制主要有夥伴算法,slab高速緩存和vmalloc機制。其中夥伴算法和slab高速緩存都在物理内存映射區分配物理内存,而vmalloc機制則在高端内存映射區分配物理内存。
夥伴算法
夥伴算法負責大塊連續物理内存的分配和釋放,以頁框為基本單位。該機制可以避免外部碎片。
per-CPU頁框高速緩存
内核經常請求和釋放單個頁框,該緩存包含預先分配的頁框,用于滿足本地CPU發出的單一頁框請求。
slab緩存
slab緩存負責小塊物理内存的分配,并且它也作為高速緩存,主要針對内核中經常分配并釋放的對象。
vmalloc機制
vmalloc機制使得内核通過連續的線性地址來訪問非連續的物理頁框,這樣可以最大限度的使用高端物理内存。
3.物理内存的分配
内核發出内存申請的請求時,根據内核函數調用接口将啟用不同的内存分配器。
3.1 分區頁框分配器
分區頁框分配器 (zoned page frame allocator) ,處理對連續頁框的内存分配請求。分區頁框管理器分為兩大部分:前端的管理區分配器和夥伴系統,如下圖:
管理區分配器負責搜索一個能滿足請求頁框塊大小的管理區。在每個管理區中,具體的頁框分配工作由夥伴系統負責。為了達到更好的系統性能,單個頁框的申請工作直接通過per-CPU頁框高速緩存完成。
該分配器通過幾個函數和宏來請求頁框,它們之間的封裝關系如下圖所示。
這些函數和宏将核心的分配函數__alloc_pages_nodemask()封裝,形成滿足不同分配需求的分配函數。其中,alloc_pages()系列函數返回物理内存首頁框描述符,__get_free_pages()系列函數返回内存的線性地址。
3.2 slab分配器
slab 分配器最初是為了解決物理内存的内部碎片而提出的,它将内核中常用的數據結構看做對象。slab分配器為每一種對象建立高速緩存。内核對該對象的分配和釋放均是在這塊高速緩存中操作。一種對象的slab分配器結構圖如下:
可以看到每種對象的高速緩存是由若幹個slab組成,每個slab是由若幹個頁框組成的。雖然slab分配器可以分配比單個頁框更小的内存塊,但它所需的所有内存都是通過夥伴算法分配的。
slab高速緩存分專用緩存和通用緩存。專用緩存是對特定的對象,比如為内存描述符創建高速緩存。通用緩存則是針對一般情況,适合分配任意大小的物理内存,其接口即為kmalloc()。
3.3 非連續内存區内存的分配
内核通過vmalloc()來申請非連續的物理内存,若申請成功,該函數返回連續内存區的起始地址,否則,返回NULL。vmalloc()和kmalloc()申請的内存有所不同,kmalloc()所申請内存的線性地址與物理地址都是連續的,而vmalloc()所申請的内存線性地址連續而物理地址則是離散的,兩個地址之間通過内核頁表進行映射。
vmalloc()的工作方式理解起來很簡單:
1).尋找一個新的連續線性地址空間;
2).依次分配一組非連續的頁框;
3).為線性地址空間和非連續頁框建立映射關系,即修改内核頁表;
vmalloc()的内存分配原理與用戶态的内存分配相似,都是通過連續的虛拟内存來訪問離散的物理内存,并且虛拟地址和物理地址之間是通過頁表進行連接的,通過這種方式可以有效的使用物理内存。但是應該注意的是,vmalloc()申請物理内存時是立即分配的,因為内核認為這種内存分配請求是正當而且緊急的;相反,用戶态有内存請求時,内核總是盡可能的延後,畢竟用戶态跟内核态不在一個特權級。
需要C/C Linux服務器開發學習資料私信“資料”(資料包括C/C ,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!