Linux系統內存管理


                                       

                 <linux 內存管理模型>

下面這個圖將Linux內存管理基本上描述完了,但是顯得有點復雜,接下來一部分一部分的解析。

 

內存管理系統可以分為兩部分,分別是內核空間內存管理和用戶空間內存管理:

————內存管理子系統的職責是:進程請求內存時分配可用內存,進程釋放內存后回收內存,以及跟蹤系統內存使用情況。現代操作系統要求能夠使多個程序共享系統資源,同時要求內存限制對於開發者是透明的。在這種情況下,虛擬內存應運而生。虛擬內存可以使得進程可以訪問比實際內存大得多的空間,並且使得多個程序共享內存顯得更加有效。

————當程序從內存中取得數據的時候,需要使用地址指出需要訪問的內存位置(注意:這個地址是虛擬地址,他們組成的進程的虛擬地址空間)。每個進程都有自己的虛擬地址空間,這樣做的好處是可以防止非法讀取或覆蓋其他進程的數據(虛擬地址允許進程使用超過物理內存的內存空間,因此操作系統可以給每個進程提供獨立的虛擬線性地址空間。)

 

                       <頁>

a:作為內存管理的基本單元,頁的許多狀態需要被記錄下來(比如,內核需要知道什么時候可以被回收),因此內核為內核中的每個頁都准備了頁描述符struct page{}.系統在初始化時根據物理內存的大小建立起一個page結構數組mem_map,作為物理頁面的“倉庫”。

b:struct page 

{

    unsigned long flags;//32位的位圖,每一位表示頁面的一個屬性

    atomic_t count ;//統計頁面正在被都少個進程使用,為0時表示可以回收。

    struct list_head list;//頁表的雙向鏈表

    struct address_space *mapping;

    unsigned long index;

    struct list_head lru;//鏈接最少使用的頁表,可能會被回收

    union{

        struct pte_chain  *chain;

        pte_addr_t direct

   }pte;

    unsigned long private;

    #if definde (WANT_PAGE_VIRTUAL)

    void *virtual;//指向頁面的虛擬地址

    #endif

}

          <系統在內存中的分布示意圖>

 

 

a:4G進程地址空間解析

b:虛擬地址空間分配及其與物理內存對應圖

其中kmalloc和vmalloc函數申請的空間對應着不同的區域,同時又不同的含義。

c:物理內存分布圖
------如下圖所示,Linux系統啟動后,整個系統在物理內存中的結構如圖所示,和虛擬內存結構圖相比還是有很大的區別的,圖中的ZONE_HIMEN也就是系統的用戶空間了。
 
物理地址有896M直接映射到虛擬地址的內存空間,這是一一對應的映射,只有起始地址不一樣,偏移是一樣的。個大小大多是固定的,哪怕你的內存超過一個G,太小了就另外說了,當你內存很大的時候,超過896M時,剩余的那些內存怎么辦呢?這多出來的叫做高端內存,如果你使用vmalloc申請空間,就會在高端內存中分配,如果你使用kmalloc申請空間,就會在小於896的內存中分配。所以還是很講究的。  

 

                               <內核空間內存管理>

a:操作系統的生命周期可以分為兩個階段:
————自舉階段:

自舉階段使用臨時內存(系統剛剛啟動的時候)

————正常運行階段:

即熊啟動完成后,系統正常運行的階段

 

b:正常運行階段又分為兩個部分:

————固定分配部分:

這部分是有固定的內存分配給內核代碼和數據。

————動態請求部分:

為動態內存請求分配內存,動態請求源自於進程的創建和空間的擴張。

 

c:內存管理區

————並非所有的內核空間的內存區域都會被公平對待,對內核中的不同內存的使用是有限制的。內存管理的區是由頁面組成的,Linux內核將內核空間分為3個內存管理區:
ZONE_DMA:用於分配DMA頁面請求

ZONE_NORMAL:具有虛擬映射的非DMA頁面區間

ZONE_HIHGMEN:高端內存區間

 

c:內存空間管理區描述符

————與內核管理的的所有對象一樣,每個內存管理區都有一個叫做zone的結構體,其中存放內存管理區的所有信息,記錄這內存管理區的使用情況.

struct zone {

unsigned long watermark[NR_WMARK];

unsigned long percpu_drift_mark;

unsigned longlowmem_reserve[MAX_NR_ZONES];

#ifdef CONFIG_NUMA

int node;

unsigned longmin_unmapped_pages;

unsigned longmin_slab_pages;

#endif

struct per_cpu_pageset __percpu *pageset;

spinlock_tlock;

int                     all_unreclaimable; 

#ifdef CONFIG_MEMORY_HOTPLUG

seqlock_tspan_seqlock;

#endif

struct free_areafree_area[MAX_ORDER];

#ifndef CONFIG_SPARSEMEM

unsigned long*pageblock_flags;

#endif /* CONFIG_SPARSEMEM */

#ifdef CONFIG_COMPACTION

unsigned intcompact_considered;

unsigned intcompact_defer_shift;

#endif

ZONE_PADDING(_pad1_)

spinlock_tlru_lock;

struct zone_lru {

struct list_head list;

} lru[NR_LRU_LISTS];

struct zone_reclaim_stat reclaim_stat;

unsigned longpages_scanned;   

unsigned longflags;   

atomic_long_tvm_stat[NR_VM_ZONE_STAT_ITEMS];

unsigned int inactive_ratio;

ZONE_PADDING(_pad2_)

wait_queue_head_t* wait_table;

unsigned longwait_table_hash_nr_entries;

unsigned longwait_table_bits;

struct pglist_data*zone_pgdat;

unsigned longzone_start_pfn;

unsigned longspanned_pages;

unsigned longpresent_pages;

const char*name;

 

} ____cacheline_internodealigned_in_smp;

 

d:內存管理區操作輔助函數

(2)for_each_zone()

遍歷系統中的所有內存管理區

(2)is_highmem()和is_normal()
連個函數檢測內存是否位於該內存管理區
 

e:頁面請求函數

------頁面是存放頁的基本內存單元(其實就是很多的頁組成了頁面),只要進程請求內存,內核只要滿足要求就會給其分配頁面。同理,只要進程不在需要頁面,內核就會將其回收。

(1)返回指向pages結構體的指針,(返回void* 類型)(該結構體對應分配的請求頁面)

alloc_page()//該函數用於請求單頁

alloc_pages()//該函數用於請求4個頁面

(2)返回32為虛擬地址,該地址是分配頁面的首地址

__get_free_page()/__get_dma_pages()

 

f:釋放請求頁面

__free_page()/__free_pages()

 

g:伙伴系統(伙伴算法)

-------每當頁面被分配和回收的時候,系統都會遇到外部碎片或內存碎片的問題(即頁面散布在內存中,即使可用頁面足夠多,但是無法分配大塊的連續頁面)。為了解決這個問題,Linux系統提供了伙伴算法。

 

h:伙伴算法原理

伙伴系統把內存中空閑塊組成鏈表,將不同大小的空閑內存塊組織起來(我猜測是將相同大小的組織在一起),雖然大小不一樣,但是都是2的冪次方。當系統中有進程釋放沒存的時候,伙伴系統就會搜索與所釋放塊大小相等的可用空閑內存塊,如果找到相鄰的空閑塊,就將其合並成兩倍於自身大小的塊。這種合並的塊稱為伙伴。

 

i:分配與釋放頁面源代碼

(1)分配頁面

 
 
 
 (2)釋放頁面
注意:代碼細節詳見《Linux內核編程》P130.
 
 k:slab分配器
--------我們知道頁是Linux內存管理的基本單元。但是進程往往會以字節的為單位請求內存,如果照樣給其分配一個頁面,這樣顯得浪費內存,為了解決這個問題並實這種小塊內存請求。內核開發者們實現了slab分配器。
        slab分配器為了減少內存分配,初始化,銷毀和釋放的代價,通常會把經常使用的內存區以緩存的方式對待,並加以維護(即比如系統經常會使用task_struct ,這就將該結構體以緩存方式常駐內存),當進程不在需要該內存區時,就會把該內存放入緩沖區。

        由此可見,slab時間上由許多緩存組成。緩存分為"專用"和"通用"。專用緩存保存特定對象的內存區,比如各種描述符,比如進程描述符"struct task_structs".

 

注意:關於slab的詳細信息,見《Linux內核編程》P130

 

                                                                    <用戶空間/進程內存管理>

-------以上討論了內核如何管理自己的內存空間,接下來討論用戶空間如何讓管理自己的內存空間。用戶進程創建后需要分配一個虛擬地址空間,並且可用通過增加或刪除地址間隔得以擴大或縮小。(地址間隔(一段地址空間):是一種內存單元,也被稱作內存范圍或內存區,把進程地址空間划分為不同的區域是有用的,不同的區域具有不同的保護方案和訪問屬性,比如".text"".data"".bss""棧""棧")。

 

a:task_struct 

(1)每一個任務都有一個

b:mm_struct 

(1)每個人物都有一個mm_struct 結構,內核用該結構表示內存地址范圍(所有的mm_struct 描述符統一放在雙向鏈表中,鏈表頭對應於0進程的mm_struct ,可以通過全局變量ini_mm來訪問該描述符)

(2)該結構部分代碼

 (3)結構體分析
1)mmap :進程對應的內存區描述符(vm_area_struct),被分配給鏈接於一個鏈表中的進程,通過mm_struct 中的mmap訪問該鏈表。同時vm_area_struct 中的vm_next 將 各個內存區描述符連接起來。
2)pgd:這是一個指針,指向存放該內存區中頁的全局目錄,通過該目錄加上虛擬地址可以訪問到物理地址。
3)mm_users:存放方位該內存進程的數量
4)mm_conut :是對mm_struct 的使用計數,如果為0,這可以回收頁面。
5)map_count:存放進程地址空間中內存區數量,或者說是vm_area_struct描述符的數量。
6)start_code和end_code:用於存放進程代碼段開始和結束地址(個人認為是虛擬地址)
7)start_data和end_data:存放初始化數據開始和結束地址(個人認為是虛擬地址)
8)start_brk和end_brk:堆棧的開始和結束地址
9)start_stack:進程棧的開始地址
10)arg_start和arg_end:指向傳遞給進程的參數的開始和結束地址
11)env_start和env_end:存放環境的開始和結束地址
 

c:vm_area_struct 

----------該結構體定義了虛擬內存區域,因為對於一個進程來將,進程存在於不同的內存區,每個內存區都有對應的vm_area_struct。通常進程所使用到的虛存空間不連續,且各部分虛存空間的訪問屬性也可能不同。所以一個進程的虛存空間需要多個vm_area_struct結構來描述。在vm_area_struct結構的數目較少的時候,各個vm_area_struct按照升序排序,以單鏈表的形式組織數據(通過vm_next指針指向下一個vm_area_struct結構)。但是當vm_area_struct結構的數據較多的時候,仍然采用鏈表組織的化,勢必會影響到它的搜索速度。針對這個問題,vm_area_struct還添加了vm_avl_hight(樹高)、vm_avl_left(左子節點)、vm_avl_right(右子節點)三個成員來實現AVL樹,以提高vm_area_struct的搜索速度。

(2)結構體部分代碼

 (3)結構體分析
1)vm_mm:所有的內存區間都屬於進程地址空間,而地址空間由mm_struct表示,該指針指向所屬進程的mm_struct
2)vm_start和vm_end:該內存區間的起始地址和結束地址
3)vm_next:指向所屬於該進程空間的下一個內存區間
4)vm_ops:指向操作vm_area_struct 的函數集合,比如發生缺頁異常的時候會調用該函數
 
<圖解以上幾個數據結構的關系>
 
 
(4)mmap()函數與vm_area_struct 之間的關系

1) mmap調用實際上就是一個內存對象vma的創建過程, mmap的調用格式是:

void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);

 

2)參數詳解

其中start是映射地址, length是映射長度如果flagsMAP_FIXED不被置位則該參數通常被忽略而查找進程地址空間中第一個長度符合的空閑區域;Fd是映射文件的文件句柄, offset是映射文件中的偏移地址;prot是映射保護權限可以是PROT_EXEC, PROT_READ, PROT_WRITE, PROT_NONE, flags則是指映射類型可以是MAP_FIXED, MAP_PRIVATE, MAP_SHARED, 該參數必須被指定為MAP_PRIVATEMAP_SHARED其中之一,MAP_PRIVATE是創建一個寫時拷貝映射(copy-on-write), 也就是說如果有多個進程同時映射到一個文件上,映射建立時只是共享同樣的存儲頁面但是某進程企圖修改頁面內容則復制一個副本給該進程私用它的任何修改對其它進程都不可見. MAP_SHARED則無論修改與否都使用同一副本任何進程對頁面的修改對其它進程都是可見的.

3)mmap系統調用的實現過程是
1.
先通過文件系統定位要映射的文件; 
2.
權限檢查映射的權限不會超過文件打開的方式也就是說如果文件是以只讀方式打開那么則不允許建立一個可寫映射; 
3.
創建一個vma對象並對之進行初始化; 
4.
調用映射文件的mmap函數其主要工作是給vm_ops向量表賦值; 
5.
把該vma鏈入該進程的vma鏈表中如果可以和前后的vma合並則合並; 
6.
如果是要求VM_LOCKED(映射區不被換出)方式映射則發出缺頁請求把映射頁面讀入內存中.

 

5)munmap(void * start, size_t length):

該調用可以看作是mmap的一個逆過程它將進程中從start開始length長度的一段區域的映射關閉如果該區域不是恰好對應一個vma, 則有可能會分割幾個或幾個vma.

 
 
                                                                    <進程在內存空間的分布>
------當用戶空間程序被載入內存后,其擁有了自己的線性地址空間,該空間被划分為各中內存區間,不同的區間擁有不同的功能。
a:text段
該段被稱作代碼段,存放的是程序的執行指令,擁有execute和read屬性,mm_struct 中start_code和end_code保存了text段的起始地址。
b:data段
存放所有已經初始化的數據,包括靜態分配的數據(static)和初始化的全局數據
c:gvar
存放未初始化的全局變量。
d:bss
存放未初始化的數據
e:堆
用於擴展進程的線性地址空間,當程序調用函數malloc()獲得的內存置於該區間。mm_struct中的start_brk和end_brk用於記錄該區間的起始和結束地址。
f:棧
該段包含已經分配內存的局部變量,當作函數調用的時候,函數的局部變量被壓入棧,函數返回的時候,於函數相關的變量被彈出。mm_struct 中的start_stack記錄棧的起始位置。
g:相關關系圖
 

                          <虛擬地址轉化到物理地址>

————處理器只能操作物理地址,虛擬地址和對應的物理地址之間的轉換需要借助內核中的頁表來維護。頁表對內存中的頁面走向進行記錄,在內核運行的整個生命期間,頁表都放在內存中。Linux采用的是三級頁表的分頁機制,分別為:

a:PGD(page globle directory)

由mm_struct 中的pgd_t指定

b:PMD(page middle directory)

由數據類型pmd_t指定

c:PTE(page table)

由數據類型pte_t指定

d:三者之間的關系圖如下

 

e:x86體系的轉化過程詳細分析

 

      首先將32位的虛擬地址的高10位取出來作為偏移,這個偏移加上CR3寄存器里面的一級也表基地址,就是存儲二級頁表基地址的單元的地址,根據該單元存儲的二級頁表的基地址找到頁表,然后取出32位虛擬地址的中間10位作為偏移,將二級頁表的基地址和偏移相加得到物理頁表的基地址的存儲單元的基地址,從該單元取出物理也表達的基地址加上32位虛擬地址的低12位就是物理頁表的物理地址。

 

                              <物理內存分配>

 

 

只有實實在在的去訪問虛擬地址所對應的內存時,才會分配內存,如果不訪問,則拿到的只是一個虛擬地址。

 

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM