[include /asm-i386/page.h: 39]node
39 #if CONFIG_X86_PAE
40 typedef struct { unsigned long pte_low, pte_high; } pte_t;
41 typedef struct { unsigned long long pmd; } pmd_t;
42 typedef struct { unsigned long long pgd; } pgd_t;
43 #define pte_val(x) ((x).pte_low | ((unsigned long long)(x).pte_high << 32))
44 #else
45 typedef struct { unsigned long pte_low; } pte_t;
46 typedef struct { unsigned long pmd; } pmd_t;
47 typedef struct { unsigned long pgd; } pgd_t;
48 #define pte_val(x) ((x).pte_low)
49 #endif
當中pte_t爲頁表項,在i386當中。一個頁的大小爲4K。這意味着也表項的低12位是0,高20位是物理頁的起始地址。linux
因此低12位可以用來保存頁面保護和訪問權限信息。頁面保護和訪問權限信息被定義在pgprot_t中
[include/asm-i386/page.h : 52]git
52 typedef struct { unsigned long pgprot; } pgprot_t;
pgprot_t的低12位定義例如如下:
[include/asm-i386/pgtable.h : 152]github
152 #define _PAGE_BIT_PRESENT 0
153 #define _PAGE_BIT_RW 1
154 #define _PAGE_BIT_USER 2
155 #define _PAGE_BIT_PWT 3
156 #define _PAGE_BIT_PCD 4
157 #define _PAGE_BIT_ACCESSED 5
158 #define _PAGE_BIT_DIRTY 6
159 #define _PAGE_BIT_PSE 7 /* 4 MB (or 2MB) page, Pentium+, if present.. */
160 #define _PAGE_BIT_GLOBAL 8 /* Global TLB entry PPro+ */
161
162 #define _PAGE_PRESENT 0x001
163 #define _PAGE_RW 0x002
164 #define _PAGE_USER 0x004
165 #define _PAGE_PWT 0x008
166 #define _PAGE_PCD 0x010
167 #define _PAGE_ACCESSED 0x020
168 #define _PAGE_DIRTY 0x040
169 #define _PAGE_PSE 0x080 /* 4 MB (or 2MB) page, Pentium+, if present.. */
170 #define _PAGE_GLOBAL 0x100 /* Global TLB entry PPro+ */
將pgprot_t和pte_t的高20位合併就能夠獲得真正的頁表項,該操做有宏__make_pte來完畢
[include/asm-i386/pgtable-2level.h : 61]數組
61 #define __mk_pte(page_nr,pgprot) __pte(((page_nr) << PAGE_SHIFT) | pgprot_val(pgprot))
當MMU進行映射的時候,會首先檢查P位,也就是_PAGE_BIT_PRESENT(BIT0)位,假設該位爲1,則進行映射,不然就產生一個缺頁中斷。緩存
內核維護一個全局的mem_page的page數組,每個page表明着一個物理頁面,整個數組內的page就表明了所有物理頁面。在內核定義中,可以用pte_t高20位當成索引去訪問mem_page數組,從而獲得該物理頁的page結構。page結構定義例如如下:
[include/linux/mm.h : 134]markdown
126 /* 127 * Try to keep the most commonly accessed fields in single cache lines 128 * here (16 bytes or greater). This ordering should be particularly 129 * beneficial on 32-bit processors. 130 * 131 * The first line is data used in page cache lookup, the second line 132 * is used for linear searches (eg. clock algorithm scans). 133 */
134 typedef struct page {
135 struct list_head list;
136 struct address_space *mapping;
137 unsigned long index;
138 struct page *next_hash;
139 atomic_t count;
140 unsigned long flags; /* atomic flags, some possibly updated asynchronously */
141 struct list_head lru;
142 unsigned long age;
143 wait_queue_head_t wait;
144 struct page **pprev_hash;
145 struct buffer_head * buffers;
146 void *virtual; /* non-NULL if kmapped */
147 struct zone_struct *zone;
148 } mem_map_t;
假設頁面的內容來自一個文件,index表明該頁面在文件裏的序號。當頁面被交換出內存。但還保留內容做爲緩衝時,index指名了頁面的去向。
系統中的每個物理頁面都相應了一個page結構。也就是說一個page結構是物理頁面的「登記信息」,或者說「管理信息」。在系統初始化時。所有page結構被創建,並且page做爲一個物理頁面的管理結構,當一個page結構被分配出去的時候,就表示了一個物理頁面被分配使用。數據結構
系統內存被分爲兩個區:app
(依據系統配置。還可以添加第三個區給ZONE_HIGHMEN,內核訪問超過1G的物理空間)
這意味着mem_page數組中的page也被相應分爲ZONE_DMA和ZONE_NORMAL兩組,而既然已經分組了,就會有分組管理信息,故而每個內存區域具備一個區域管理結構:zone_struct。定義例如如下:
[include/linux/mmzone.h : 24]async
24 typedef struct zone_struct {
25 /* 26 * Commonly accessed fields: 27 */
28 spinlock_t lock;
29 unsigned long offset;
30 unsigned long free_pages;
31 unsigned long inactive_clean_pages;
32 unsigned long inactive_dirty_pages;
33 unsigned long pages_min, pages_low, pages_high;
34
35 /* 36 * free areas of different sizes 37 */
38 struct list_head inactive_clean_list;
39 free_area_t free_area[MAX_ORDER];
40
41 /* 42 * rarely used fields: 43 */
44 char *name;
45 unsigned long size;
46 /* 47 * Discontig memory support fields. 48 */
49 struct pglist_data *zone_pgdat;
50 unsigned long zone_start_paddr;
51 unsigned long zone_start_mapnr;
52 struct page *zone_mem_map;
當中39行的free_area爲一組空暇區塊隊列,組內有「連續的空暇物理頁面」和「離散的空暇物理頁面」兩種隊列。因爲分配內存的時候有可能要求分配連續的物理頁,因此將連續的物理頁和離散的物理頁分開管理。而offset則表示該分區在mem_map中的起始號。
但隨着NUMA*(非均質儲存結構)的引入,分區發生了變化。NUMA指的是,在當下多處理器結構中,每個CPU都具備本地儲存,稱之爲內存節點,而多個CPU之間又有共享的內存。
CPU訪問本地儲存的速度要快於訪問共享儲存的速度。也就是說,在一個連續的物理地址上,儲存器的訪問速度不一致,這就叫作NUMA。在NUMA結構中。假設要分配幾個連續的物理頁,通常要求分配在質地一樣的儲存器內。爲此,內核定義了一個pglist_data結構,每個pglist_data表明這一個內存節點。每個內存節點都可以擁有ZONE_DMA和ZONE_NORMAL(依據配置還可能有ZONE_HIGHMEN)兩個區,也就意味這每個內存節點都有一個page數組和一個zone_t數組,用於管理該節點上兩個區的所有page。總的來講。原來將整個物理空間分爲三個區來管理的模式,現在變成了將整個物理空間分爲若干內存節點。各個內存節點將節點上的所有物理空間進行分區管理*。pglist_data定義例如如下:
[include/linux/mmzone.h : 79]
79 typedef struct pglist_data {
80 zone_t node_zones[MAX_NR_ZONES];
81 zonelist_t node_zonelists[NR_GFPINDEX];
82 struct page *node_mem_map;
83 unsigned long *valid_addr_bitmap;
84 struct bootmem_data *bdata;
85 unsigned long node_start_paddr;
86 unsigned long node_start_mapnr;
87 unsigned long node_size;
88 int node_id;
89 struct pglist_data *node_next;
90 } pg_data_t;
80: node_zones爲該內存節點上的三個分區管理結構。
81: node_zonelists爲一個指向zone_t指針數組鏈表(鏈表的每個節點上都掛着一個數組),鏈表上的每個節點包括一個指針數組。該數組的元素依照待定的次序指向每個儲存節點的node_zones的數組。前面說過。在NUMA中分配物理頁,每每要求在同一內存節點上分配,假設這時當前節點的空暇連續物理頁沒法知足分配,則可以經過這個指針數組依照0、一、二、三、4……的次序查找其餘儲存節點上的node_zones,直到找到一個可以知足分配的儲存節點。指針數組中的元素排列次序,就稱爲一種分配策略。比方說有儲存節點A、B、C、D、E。而node_zoneslist某節點上的指針數組元素排列次序爲pA、pC、pD、pB。這個策略就規定了:要分配物理頁的時候首先嚐試從A分配。假設A不能知足,就查找B,假設B不能知足就查找C….而假設該點的指針數組爲pC、pA、pD、pB。則這個策略規定:要分配物理頁的時候首先嚐試從C分配,假設C不能知足,就查找A。假設A不能知足就查找D….
因此把node_zonelists稱爲分配策略鏈表也不爲過。
82: node_mem_map數組包括了該內存節點上的所有page結構。
每個進程都擁有本身的3G進程空間和1G共享的內核空間。
很是少會有進程佔用到3G的空間,每每是佔用多個離的虛存區域。虛存區域的抽象數據結構定義例如如下:
[include/linux/mm.h : 41]
41 struct vm_area_struct {
42 struct mm_struct * vm_mm; /* VM area parameters */
43 unsigned long vm_start;
44 unsigned long vm_end;
45
46 /* linked list of VM areas per task, sorted by address */
47 struct vm_area_struct *vm_next;
48
49 pgprot_t vm_page_prot;
50 unsigned long vm_flags;
51
52 /* AVL tree of VM areas per task, sorted by address */
53 short vm_avl_height;
54 struct vm_area_struct * vm_avl_left;
55 struct vm_area_struct * vm_avl_right;
56
57 /* For areas with an address space and backing store, 58 * one of the address_space->i_mmap{,shared} lists, 59 * for shm areas, the list of attaches, otherwise unused. 60 */
61 struct vm_area_struct *vm_next_share;
62 struct vm_area_struct **vm_pprev_share;
63
64 struct vm_operations_struct * vm_ops;
65 unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */
66 struct file * vm_file;
67 unsigned long vm_raend;
68 void * vm_private_data; /* was vm_pte (shared mem) */
69 };
43: vm_start 該虛存區域的開始地址
44: vm_end 該虛存區域的結束地址
47: vm_next 用於將進程空間內所有的內存區域組成一個單項鍊表進行管理
49: pgprot_t 頁面保護權限,因爲一個內存區域中僅僅有一個用於描寫敘述頁面訪問權限pgprot_t這意味這在容許個區域中的所有頁面訪問權限是一致的。
54~55:
53 short vm_avl_height;
54 struct vm_area_struct * vm_avl_left;
55 struct vm_area_struct * vm_avl_right;
這三個成員用於將內存區域組成一棵AVL樹,因爲常常需要在進程空間中查找某個內存區域。假設用線性鏈表查找的方式,會影響效率。
61: vm_next_share ????
62 vm_pprev_share ???
64 vm_ops指向一個struct ,其實是一個跳轉表:
120 struct vm_operations_struct {
121 void (*open)(struct vm_area_struct * area);
122 void (*close)(struct vm_area_struct * area);
123 struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int write_access);
124 };
該跳轉表用於虛存區間的打開,關閉和創建,當中nopage在發生缺頁中斷的時候會被調用。
在vm_area_struct結構中有一個成員vm_mm指向了mm_struct結構。這個結構是整個進程虛存空間的數據抽象。它管理這進程中的所有vm_area_struct抽象,換句話mm_struct是一個更高層的數據結構。定義例如如下:
[include/linux/mm.h : 203]
203 struct mm_struct {
204 struct vm_area_struct * mmap; /* list of VMAs */
205 struct vm_area_struct * mmap_avl; /* tree of VMAs */
206 struct vm_area_struct * mmap_cache; /* last find_vma result */
207 pgd_t * pgd;
208 atomic_t mm_users; /* How many users with user space? */
209 atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */
210 int map_count; /* number of VMAs */
211 struct semaphore mmap_sem;
212 spinlock_t page_table_lock;
213
214 struct list_head mmlist; /* List of all active mm's */
215
216 unsigned long start_code, end_code, start_data, end_data;
217 unsigned long start_brk, brk, start_stack;
218 unsigned long arg_start, arg_end, env_start, env_end;
219 unsigned long rss, total_vm, locked_vm;
220 unsigned long def_flags;
221 unsigned long cpu_vm_mask;
222 unsigned long swap_cnt; /* number of pages to swap on next pass */
223 unsigned long swap_address;
224
225 /* Architecture-specific MM context */
226 mm_context_t context;
227 };
204: mmap做爲進程的所虛存區域構成的鏈表的鏈表頭。
205: mmap_avl指向進程所有虛存區域構成的AVL樹的根節點。
206: mmap_cache指向近期一次使用的虛存空間。因爲程序具備局部性。常常連續訪問同一片區域的空間。因此這裏記錄了近期一次使用的虛存區域。是一種緩存機制。
207: pgd指向了進程的頁文件夾
208~212:
儘管每個進程僅僅能擁有一個mm_struct,但是一個mm_struct卻能爲多個進程所用。最顯著的樣例就是父進程使用vfork建立子進程。這樣就會涉及到資源進程和引用計數的問題,當中mm_users、mm_count、map_count做爲引用計數,而mmap_sem、page_table_lock則用來保證訪問的相互排斥。
216~219:用於說明整個進程地址空間中各個段的起始和結束地址,比方代碼段。數據段,棧段。環境變量段等等。(注,這裏的段是指鏡像段,不能與段頁管理中的段相混淆)
每個進程都擁有一份mm_struct,它是整個3G進程空間的數據抽象,而因爲進程每每不會整個3G空間都使用,而是使用不一樣的離散虛存區域。並且每個離散區域的使用都不同,比方說棧所在的區域和代碼段所在的區域的使用就不同,從而致使各個區域的屬性、需要的管理操做都不一致,因此就將各個虛存區域提煉出來單獨管理,而後就再將所有虛存區域組成一顆AVL樹交由mm_struct統一管理。這中設計思想屬於提煉差別,或者說提煉子類的思想。