引言:現代操作系統提供了一種對內存的抽象概念,叫做虛擬存儲器,它為每個進程提供了一個大的,一致的,和私有的地址空間。通過一個很清晰的機制,虛擬存儲器提供了3個重要的能力:
1)它將主存看成是一個存儲在磁盤上的地址空間的高速緩存,在主存中只保存活動區域,並根據需要在磁盤和主存之間來回傳送數據,通過這種方式,它高效的使用了主存。
2)它為每個進程提供了一致的地址空間,從而簡化了存儲器管理。
3)它保護了每個進程的地址空間不被其他進程破壞。
Linux操作系統同樣也采用了虛擬內存技術,對一個進程而言,它好像可以訪問整個系統的所有物理內存,更重要的是,即使單獨一個進程,它擁有的地址空間也可以遠遠大於系統物理內存。
一:Linux虛擬內存區域及地址空間
進程地址空間由進程可尋址的虛擬內存組成,對於某個虛擬內存地址,它要在地址空間范圍內,例如: 0421f000,這個值表示的是進程32位地址空間中的一個特定的字節。盡管一個進程可以尋址4GB的虛擬內存(在32位的地址空間中),但是這並不代表它有權訪問所有的虛擬地址。在地址空間中,我們更常用或者關心的是某個虛擬內存地址空間,比如 0848000-084c000,它們可以被進程訪問。我們稱這些可被訪問的合法地址空間稱為 虛擬內存區域。通過內核,進程可以給自己的地址空間動態的增加或減少虛擬內存區域。
Linux進程的虛擬內存區域一般有:代碼段、數據段、堆、用戶棧、共享段。每個存在的虛擬頁面都保存在某個區域中,而不屬於某個區域的虛擬頁是不存在的,並且不能被進程訪問。內核不用記錄那些不存在的虛擬頁,而這樣的頁也不占用存儲器、磁盤或者內核本身的其他任何資源。
進程只能訪問有效內存區域的內存地址,每個內存區域也具有相關權限,如可讀、可寫、可執行性質。如果一個進程訪問了無效范圍中的內存區域或者以不正確的方式訪問了有效地址,那么內核就會終止該進程,並返回 “段錯誤”信息。
二:內存描述符
task_struct 中的一個條目 mm 指向mm_struct 即內存描述符,它描述了進程的虛擬內存當前狀態。mm_struct 定義在linux/sched.h中:
1 struct mm_struct { 2 struct vm_area_struct * mmap; /* list of VMAs */
3 struct rb_root mm_rb; 4 struct vm_area_struct * mmap_cache; /* last find_vma result */
5 unsigned long free_area_cache; /* first hole */
6 pgd_t * pgd; 7 atomic_t mm_users; /* How many users with user space? */
8 atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */
9 int map_count; /* number of VMAs */
10 struct rw_semaphore mmap_sem; 11 spinlock_t page_table_lock; /* Protects task page tables and mm->rss */
12
13 struct list_head mmlist; /* List of all active mm's. These are globally strung 14 * together off init_mm.mmlist, and are protected 15 * by mmlist_lock 16 */
17
18 unsigned long start_code, end_code, start_data, end_data; 19 unsigned long start_brk, brk, start_stack; 20 unsigned long arg_start, arg_end, env_start, env_end; 21 unsigned long rss, total_vm, locked_vm; 22 unsigned long def_flags; 23
24 unsigned long saved_auxv[40]; /* for /proc/PID/auxv */
25
26 unsigned dumpable:1; 27 cpumask_t cpu_vm_mask; 28
29 /* Architecture-specific MM context */
30 mm_context_t context; 31
32 /* coredumping support */
33 int core_waiters; 34 struct completion *core_startup_done, core_done; 35
36 /* aio bits */
37 rwlock_t ioctx_list_lock; 38 struct kioctx *ioctx_list; 39
40 struct kioctx default_kioctx; 41 };
這里我們主要看3個字段:1:struct vm_area_struct * mmap; 2:struct rb_root mm_rb 3: pgd_t * pgd;
其中 pgd 指向第一級頁表即頁全局目錄的基址,當內核運行這個進程時,它就將pgd存放在CR3寄存器內,根據它來進行地址轉換工作。
mmap 和 mm_rb 這兩個不同數據結構體描述的對象是相同的:該地址空間中的所有內存區域。
mmap 指向一個 vm_area_struct 結構的鏈表,利於簡單、高效地遍歷所有元素。
mm_rb 指向的是一個紅-黑樹結構節點,適合搜索指定元素。
其中每個 vm_area_struct 描述了當前虛擬地址空間的一個內存區域。這個結構定義在
文件 linux/mm.h中,如下所示:
1 struct vm_area_struct { 2 struct mm_struct * vm_mm; /* The address space we belong to. */
3 unsigned long vm_start; /* Our start address within vm_mm. */
4 unsigned long vm_end; /* The first byte after our end address 5 within vm_mm. */
6
7 /* linked list of VM areas per task, sorted by address */
8 struct vm_area_struct *vm_next; 9
10 pgprot_t vm_page_prot; /* Access permissions of this VMA. */
11 unsigned long vm_flags; /* Flags, listed below. */
12
13 struct rb_node vm_rb; 14
15 /*
16 * For areas with an address space and backing store, 17 * linkage into the address_space->i_mmap prio tree, or 18 * linkage to the list of like vmas hanging off its node, or 19 * linkage of vma in the address_space->i_mmap_nonlinear list. 20 */
21 union { 22 struct { 23 struct list_head list; 24 void *parent; /* aligns with prio_tree_node parent */
25 struct vm_area_struct *head; 26 } vm_set; 27
28 struct prio_tree_node prio_tree_node; 29 } shared; 30
31 /*
32 * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma 33 * list, after a COW of one of the file pages. A MAP_SHARED vma 34 * can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack 35 * or brk vma (with NULL file) can only be in an anon_vma list. 36 */
37 struct list_head anon_vma_node; /* Serialized by anon_vma->lock */
38 struct anon_vma *anon_vma; /* Serialized by page_table_lock */
39
40 /* Function pointers to deal with this struct. */
41 struct vm_operations_struct * vm_ops; 42
43 /* Information about our backing store: */
44 unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE 45 units, *not* PAGE_CACHE_SIZE */
46 struct file * vm_file; /* File we map to (can be NULL). */
47 void * vm_private_data; /* was vm_pte (shared mem) */
48
49 #ifdef CONFIG_NUMA 50 struct mempolicy *vm_policy; /* NUMA policy for the VMA */
51 #endif
52 };
每個內存區域描述符都對應於進程地址空間中的唯一地址空間:
vm_start 域:指向區域的首地址(最低地址),它本身在區間內。
vm_end 域:指向區域的尾地址(最高地址)之后的第一個字節,它本身在區間外。
vm_end - vm_start 的大小就是這個內存區域的長度,另外注意,在同一個地址空間內的不同內存區域不能重疊。
vm_next : 指向鏈表中下一個區域結構。
vm_flags:描述這個區域內的頁面是共享的還是私有的。
vm_page_prot : 描述這個區域內包含的所有頁的讀寫許可權限。
三:實際使用中的內存區域
下面我們分別用/proc 文件系統和 pmap工具查看下給定進程的內存空間及所包含的內存區域。
hello.c
1 #include<stdio.h>
2 int main() 3 { 4
5 printf("Hello\n"); 6 return 0; 7 }
下面列出該進程地址空間中的內存區域:
先通過/proc文件系統查看:
每行數據格式為:
起始地址 - 尾部地址 訪問權限 偏移 主設備號:次設備號 i節點 文件名
下面是用pmap工具查看的結果:
從上面可以看出:
1:可執行對象hello的 代碼段、數據段 、bss段的虛擬內存區間
2:C庫中libc.so的代碼段、數據段、bss段
3:動態鏈接程序ld.so的代碼段、數據段、bss段
4:分配的內存段即相當於堆段 [anon] ,分配的棧段 [stack]
5: mappded: 2004K 表示該進程的全部地址空間大約為2004K
6:writeable/private 172k 表示可讀寫的內存空間大小,即消耗的物理內存空間大小。
進程訪問了2003KB的數據和代碼空間,而僅僅消耗了172KB的物理內存,如果一片內存區域是共享的或者不可寫的,那么內核只需要在內存中為此保留一份映射。可以看出利用這種共享不可寫內存的方法節約了大量內存空間。