上一节讲了内核如何管理物理内存,其实内核除了管理本身的内存外,还必须管理用户空间中进程的内存,这就是进程地址空间,也就是系统中每个用户空间进程所看到的内存。
Linux采用虚拟内存技术,系统中所有进程之间以虚拟方式共享内存,对一个进程而言,可以访问整个系统的所有物理内存,其拥有地址空间也可以远远大于系统物理内存。
1.地址空间
进程地址空间由进程可寻址的虚拟内存组成。每个进程都有一个32bit或64bit的平坦地址空间,空间具体大小取决于体系结构。平坦(flat)指的是地址空间范围是一个独立的连续空间(比如,地址从0~232-1).一些操作系统提供了段地址空间,这种地址空间并不是一个独立的线性区域,而是被分段的。现代采用虚拟内存的操作系统都使用平坦地址空间。
每个进程都有唯一的这种平坦地址空间,并且不同进程之间,彼此互不相干,地址空间完全独立。
尽管一个进程可以寻址4GB虚拟内存(32bit),但这并不代表它就有权访问所有虚拟内存,在地址空间中,更关心的是一些可以合法访问的虚拟内存的地址空间,这个空间称为“内存区域(memory areas)”,进程只能访问有效内存区域内的内存地址,每个内存区域也具有相关权限,如可读、可写,可执行属性。如果一个进程访问了不在有效范围的内存区域,或以不正确的方式访问了有效地址,内核就会终止该进程,并返回“段错误”。
内存区域可以包含各种内存对象,比如:
代码段(text section):可执行文件代码的内存映射
数据段(data section): 可执行文件的已初始化全局变量的内存映射
bss段:包含未初始化全局变量,也就是bss段的零页 (页面中的信息全部为0,可用于映射bss段等目的) 的内存映射
栈:用户进程用户空间的栈(不要和进程内核栈混淆,进程的内核栈独立存在并且由内核维护)每个诸如C库或动态链接程序等共享库的代码段、数据段和BSS也会被载入进程的地址空间。
任何内存映射文件;
任何共享内存段;
任何匿名的内存映射,比如malloc()分配的内存;
进程地址空间中的任何有效地址都只能位于唯一的区域,这些内存区域不能相互覆盖;在执行的进程中,每个不同的内存片段都对应一个独立的内存区域:栈、对象代码、全局变量、被映射的文件等。
2.内存描述符
内核使用内存描述符来表示进程的地址空间,该结构体包含了和进程地址空间有关的官不信息,mm_struct结构体,定义在
此处)折叠或打开
1. struct mm_struct {
2. struct vm_area_struct * mmap; /* 内存区域链表list of VMAs */
3. struct rb_root mm_rb; /* VMA形成的红黑树*/
4. struct vm_area_struct * mmap_cache; /* 最近使用的内存区域last find_vma result */
5. #ifdef CONFIG_MMU
6. unsigned long (*get_unmapped_area) (struct file *filp,
7. unsigned long addr, unsigned long len,
8. unsigned long pgoff, unsigned long flags);
9. void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
10. #endif
11. unsigned long mmap_base; /* base of mmap area */
12. unsigned long task_size; /* size of task vm space */
13. unsigned long cached_hole_size; /* if non-zero, the largest hole below free_area_cache */
14. unsigned long free_area_cache; /* 地址空间第一个空洞first hole of size cached_hole_size or larger */
15. pgd_t * pgd; /*页全局目录*/
16. atomic_t mm_users; /* 使用地址空间的用户数How many users with user space? */
17. atomic_t mm_count; /* 主使用计数器How many references to "struct mm_struct" (users count as 1) */
18. int map_count; /* number of VMAs */
19. struct rw_semaphore mmap_sem; /*内存区域的信号量*/
20. spinlock_t page_table_lock; /* 页表锁Protects page tables and some counters */
21.
22. struct list_head mmlist; /*所有mm_struct形成的双向链表 List of maybe swapped mm's. These are globally strung
23. * together off init_mm.mmlist, and are protected
24. * by mmlist_lock
25. */
26.
27.
28. unsigned long hiwater_rss; /* High-watermark of RSS usage */
29. unsigned long hiwater_vm; /* High-water virtual memory usage */
30.
31. /*全部页面数目,上锁的页面数目*/
32. unsigned long total_vm, locked_vm, shared_vm, exec_vm;
33. unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
34. /*代码段的开始地址,结束地址;数据段的首地址和结束地址*/
35. unsigned long start_code, end_code, start_data, end_data;
36. /*堆的首地址,堆的尾地址*/
37. unsigned long start_brk, brk, start_stack;
38. /*命令行参数的首地址和结束地址,环境变量的首地址和结束地址*/
39. unsigned long arg_start, arg_end, env_start, env_end;
40.
41. unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
42.
43. /*
44. * Special counters, in some configurations protected by the
45. * page_table_lock, in other configurations by being atomic.
46. */
47. struct mm_rss_stat rss_stat;
48.
49. struct linux_binfmt *binfmt;
50.
51. cpumask_t cpu_vm_mask;
52.
53. /* Architecture-specific MM context */
54. mm_context_t context;
55.
56. /* Swap token stuff */
57. /*
58. * Last value of global fault stamp as seen by this process.
59. * In other words, this value gives an indication of how long
60. * it has been since this task got the token.
61. * Look at mm/thrash.c
62. */
63. unsigned int faultstamp;
64. unsigned int token_priority;
65. unsigned int last_interval;
66.
67. unsigned long flags; /* Must use atomic bitops to access the bits */
68.
69. struct core_state *core_state; /* coredumping support */
70. #ifdef CONFIG_AIO
71. spinlock_t ioctx_lock;
72. struct hlist_head ioctx_list;
73. #endif
74. #ifdef CONFIG_MM_OWNER
75. /*
76. * "owner" points to a task that is regarded as the canonical
77. * user/owner of this mm. All of the following must be true in
78. * order for it to be changed:
79. *
80. * current == mm->owner
81. * current->mm != mm
82. * new_owner->mm == mm
83. * new_owner->alloc_lock is held
84. */
85. struct task_struct *owner;
86. #endif
87.
88. #ifdef CONFIG_PROC_FS
89. /* store ref to file /proc/<pid>/exe symlink points to */
90. struct file *exe_file;
91. unsigned long num_exe_file_vmas;
92. #endif
93. #ifdef CONFIG_MMU_NOTIFIER
94. struct mmu_notifier_mm *mmu_notifier_mm;
95. #endif
96. };
mm_users域记录正在使用该地址的进程数目,mm_count表示mm_struct结构体的主引用计数,当mm_users值减少为0时(所有使用该地址空间的线程都退出),mm_count变为0;当mm_count等于0,说明已经咩有人和指向该mm_stuct结构体的引用了,这时该结构体会被撤销。
mmap和mm_rb描述同一个对象:该地址空间中的全部内存区域。Mmap以链表形式存放,mm_rb以红-黑树形式存放。内核通常会避免用两种数据结构组织同一种数据,但此处这种冗余派的上用场,mmap链表,利于简单、高效地遍历所有元素;而mm_rb结构更适合搜索指定的元素。覆盖树上的链表并用这两个结构体同时访问相同的数据集,有时候这种操作称为线索树。
所有mm_stuct都通过自身的mmlist域链接在一个双向链表中,该链表首元素是init_mm内存描述符,它代表init进程的地址空间,另外注意,操作该链表是需要使用mmlist_lock来防止并发访问。
2.1 分配内存描述符
在进程的进程描述符task_struct中,mm域存放着该进程使用的内存描述符,所以current->mm指向当前进程的内存描述符。
fork()函数利用copy_mm()函数复制父进程的内存描述符,而子进程中的mm_struct结构体实际是通过文件kernel/fork.c中的allocate_mm()宏从mm_cachep slab缓存中分配得到的。通常每个进程都有唯一的mm_struct结构体,即唯一的进程地址空间。
如果父进程希望和子进程共享地址空间,可以调用clone()时,设置CLONE_VM标志,这样的进程称作线程,Linux中所谓的线程和进程的本质区别,就是是否共享地址空间。
当CLONE_VM被指定后,内核就不再需要调用allocate_mm()函数,而仅仅需要在调用copy_mm()函数中将mm域指向其父进程的内存描述符就可以了:
此处)折叠或打开
1. if (clone_flags & CLONE_VM) {
2. //current 是父进程,而tsk在fork()指向期间是子进程
3. atomic_inc(¤t->mm->mm_users);
4. tsk->mm = current->mm;
5. }
2.2 撤销内存描述符
进程退出时,内核会调用exit_mm(),该函数执行一些常规撤销工作,同时更新一些统计量。
该函数会调用mmput()减少内存描述符中的mm_users用户基数,如果用户计数降到0,将调用mmdrop()函数,减少mm_count使用计数。如果使用计数也等于零,说明内存描述符不再有任何使用者了,那么调用free_mm()宏通过kmem_cache_free()将mm_struct结构体归还到mm_cachep slab缓存中。
2.3 mm_struct与内核线程
内核线程没有进程地址空间,也没有相关的内存描述符,所以内核线程对应的进程描述符mm域为空,事实上,这也正是内核线程的真实含义—它们没有用户上下文。
为了避免内核线程为内存描述符和页表浪费内存,也为了当新内核线程运行时,避免浪费处理器周期向新地址空间进行切换,内核线程将直接使用前一个进程的内存描述符。
当一个进程被调度时,该进程的mm域指向的地址空间被载入内存,进程描述符中的active_mm域会被更新,指向新的地址空间。内核线程没有地址空间,mm域为NULL,于是,当一个内核线程被调度时,内核发现它的mm域为NULL,就会保留前一个进程的地址空间,随后更新内核线程的进程描述符的active_mm域,使其指向前一个进程的内存描述符。所以需要时,内核线程便可以使用前一个进程的页表。
3.虚拟内存区域
内存区域由vm_area_struct结构体描述,定义在 中,内存区域在Linux内核中经常称作虚拟内存区域(virtual memoryAreas, VMAs).
vm_area_struct描述了指定地址空间内连续区间上的一个独立内存范围,内核将每个内存区域作为一个单独的内存对象管理,每个内存区域都拥有一致的属性,比如访问权限等,相应的操作也应该一致。按这种方式,每个VMA就可以代表不同类型的内存区域(比如内存映射文件或者进程用户空间栈)。
此处)折叠或打开
1. /*
2. * This struct defines a memory VMM memory area. There is one of these
3. * per VM-area/task. A VM area is any part of the process virtual memory
4. * space that has a special rule for the page-fault handlers (ie a shared
5. * library, the executable area etc).
6. */
7. struct vm_area_struct {
8. struct mm_struct * vm_mm; /* 相关的mm_struct结构体The address space we belong to. */
9. unsigned long vm_start; /* 区间首地址Our start address within vm_mm. */
10. unsigned long vm_end; /* 区间尾地址The first byte after our end address
11. within vm_mm. */
12.
13. /* linked list of VM areas per task, sorted by address */
14. struct vm_area_struct *vm_next, *vm_prev; //VMA链表
15.
16. pgprot_t vm_page_prot; /* 访问控制权限Access permissions of this VMA. */
17. unsigned long vm_flags; /* 标志:内存区域标志的信息和行为Flags, see mm.h. */
18. struct rb_node vm_rb; //树上该VMA的节点
19.
20. /*
21. * For areas with an address space and backing store,
22. * linkage into the address_space->i_mmap prio tree, or
23. * linkage to the list of like vmas hanging off its node, or
24. * linkage of vma in the address_space->i_mmap_nonlinear list.
25. */
26. union {
27. struct {
28. struct list_head list;
29. void *parent; /* aligns with prio_tree_node parent */
30. struct vm_area_struct *head;
31. } vm_set;
32.
33. struct raw_prio_tree_node prio_tree_node;
34. } shared;
35.
36. /*
37. * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
38. * list, after a COW of one of the file pages. A MAP_SHARED vma
39. * can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack
40. * or brk vma (with NULL file) can only be in an anon_vma list.
41. */
42. struct list_head anon_vma_chain; /* Serialized by mmap_sem &
43. * page_table_lock */
44. struct anon_vma *anon_vma; /* Serialized by page_table_lock */
45.
46. /* Function pointers to deal with this struct. */
47. const struct vm_operations_struct *vm_ops; //相关的操作链表
48.
49. /* Information about our backing store: */
50. unsigned long vm_pgoff; /*文件中偏移量 Offset (within vm_file) in PAGE_SIZE
51. units, *not* PAGE_CACHE_SIZE */
52. struct file * vm_file; /* 被映射的文件File we map to (can be NULL). */
53. void * vm_private_data; /* 私有数据was vm_pte (shared mem) */
54. unsigned long vm_truncate_count;/* truncate_count or restart_addr */
55.
56. #ifndef CONFIG_MMU
57. struct vm_region *vm_region; /* NOMMU mapping region */
58. #endif
59. #ifdef CONFIG_NUMA
60. struct mempolicy *vm_policy; /* NUMA policy for the VMA */
61. #endif
62. };
每个内存描述符都对应于进程地址空间中的唯一区间,vm_end-vm_start大小就是内存区间的长度。在同一地址空间内的不同内存区间不能重叠。
vm_mm域指向和VMA相关的mm_struct结构体,每个VMA对其相关的mm_struct结构体来说都是唯一的,如果两个线程共享一个地址空间,那么它们也同时共享其中所有的vm_area_struct结构体。
3.1VMA标志
VMA是一种位标志,它包含在vm_flags域内,标志了内存区域所包含的页面行为和信息。VMA标识反映了内核处理页面所需遵守的行为准则,而不是硬件要求。vm_flags包含了内存区域中每个页面的信息或内存区域的整体信息,而不是具体的独立页面。
几个重要的标志(这些标志可以按需求组合):
VM_READ,VM_WRITE和VM_EXEC标志了区域中页面的读、写和执行权限。
VM_SHARD:指明内存区域包含的映射是否可以在多进程间共享,
VM_IO:标志内存区域中按对设备I/O空间的映射,该标志通常在设备驱动程序执行mmap()函数进行I/O空间映射时才被设置。
VM_SEQ_READ:标志内核应用程序对映射内容执行有序的(线性和连续的)读操作,这样内核可以有选择地执行预读文件。
3.2 VMA操作
Vm_area_struct结构体中的vm_ops域指向与指定内存区域相关的操作函数
此处)折叠或打开
1. struct vm_operations_struct {
2. //当指定的内存区域被加入到一个地址空间时,该函数被调用
3. void (*open)(struct vm_area_struct * area);
4. //当指定的内存区域从地址空间删除时,该函数调用
5. void (*close)(struct vm_area_struct * area);
6. //当没有出现在屋里内存中的页面被访问时,该函数被页面故障处理调用
7. int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);
8.
9. /* notification that a previously read-only page is about to become
10. * writable, if an error is returned it will cause a SIGBUS */
11. //当某个页面为只读页面时,该函数被页面故障处理调用
12. int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);
13.
14. /* called by access_process_vm when get_user_pages() fails, typically
15. * for use by special VMAs that can switch between memory and hardware
16. */
17. //当get_user_pages()函数调用失败时,该函数被access_process_vm()函数调用
18. int (*access)(struct vm_area_struct *vma, unsigned long addr,
19. void *buf, int len, int write);
20. …
21. };
3.3 内存区域的树型结构和内存区域的链表结构
mmap和mm_rb,独立地指向与内存描述符相关的全体内存区域对象,它们包含完全相同的vm_area_struct结构体指针,仅仅方法不同。
mmap域使用单独的链表链接所有的内存区域对象,每个vm_area_struct结构体通过自身vm_next域被连入链表,mmap域指向链表中的一个内存区域,链中最后一个结构体指针指向空
mm_rb域使用红-黑树链接所有内存区域对象,mm_rb指向红-黑树根节点,地址空间中每个vm_area_struct通过自身的vm_rb连接到树中。
链表用于需要遍历全部节点的时候,而红黑树适用于在地址空间中定位特定内存区域的时候,内核为了内存区域上的各种不同操作都能获得高性能,所以同时使用了这两种数据结构。
3.4 实际使用中的内存区域
可使用/proc文件系统和pmap工具查看给定进程的内存空间和其中所含的内存区域。
此处)折叠或打开
1. #include <stdio.h>
2. int main(void)
3. {
4. printf("hello,world!\r\n");
5. while (1);
6. return 0;
7. }
该程序执行pid是2874,那么
leon@ubuntu:~$ cat /proc/2874/maps
08048000-08049000 r-xp 00000000 08:01 131477 /home/leon/a.out
08049000-0804a000 r--p 00000000 08:01 131477 /home/leon/a.out
0804a000-0804b000 rw-p 00001000 08:01 131477 /home/leon/a.out
b7589000-b758a000 rw-p 00000000 00:00 0
b758a000-b772e000 r-xp 00000000 08:01 524970 /lib/i386-linux-gnu/libc-2.15.so
b772e000-b7730000 r--p 001a4000 08:01 524970 /lib/i386-linux-gnu/libc-2.15.so
b7730000-b7731000 rw-p 001a6000 08:01 524970 /lib/i386-linux-gnu/libc-2.15.so
b7731000-b7734000 rw-p 00000000 00:00 0
b7744000-b7747000 rw-p 00000000 00:00 0
b7747000-b7748000 r-xp 00000000 00:00 0 [vdso]
b7748000-b7768000 r-xp 00000000 08:01 524935 /lib/i386-linux-gnu/ld-2.15.so
b7768000-b7769000 r--p 0001f000 08:01 524935 /lib/i386-linux-gnu/ld-2.15.so
b7769000-b776a000 rw-p 00020000 08:01 524935 /lib/i386-linux-gnu/ld-2.15.so
bfb5b000-bfb7c000 rw-p 00000000 00:00 0 [stack]
每行数据格式如下:
内存地址开始-结束 访问权限 偏移 主设备号:次设备号 i节点 文件
或者用pmap命令查看
leon@ubuntu:~$ pmap 2874
2874: ./a.out
08048000 4K r-x-- /home/leon/a.out
08049000 4K r---- /home/leon/a.out
0804a000 4K rw--- /home/leon/a.out
b7589000 4K rw--- [ anon ]
b758a000 1680K r-x-- /lib/i386-linux-gnu/libc-2.15.so
b772e000 8K r---- /lib/i386-linux-gnu/libc-2.15.so
b7730000 4K rw--- /lib/i386-linux-gnu/libc-2.15.so
b7731000 12K rw--- [ anon ]
b7744000 12K rw--- [ anon ]
b7747000 4K r-x-- [ anon ]
b7748000 128K r-x-- /lib/i386-linux-gnu/ld-2.15.so
b7768000 4K r---- /lib/i386-linux-gnu/ld-2.15.so
b7769000 4K rw--- /lib/i386-linux-gnu/ld-2.15.so
bfb5b000 132K rw--- [ stack ]
total 2004K
分别表示程序和C库的代码段、数据段、bss段
进程全都地址空间大约2004KB,但只有大概不到200KB的内存区域是可写或私有的。如果一片内存范围是共享的或不可写的,那么内核只需要在内存中为文件保留一份映射,比如C库的代码,只读入一次是安全的。
由于内存未被共享,所以只要一有进程写该处数据,那么该处数据就将被拷贝出来(写时拷贝),然后才被更新。
每个和进程相关的内存区域都对应于一个vm_area_strcut结构体。
4.操作内存区域
内核时常需要在某个内存区域上执行一些操作,这些操作非常频繁,它们也是mmap()例程的基础,为了方便这类对内存区域的操作,内核定义了许多辅助函数声明在
4.1 查找一个给定的内存地址属于哪一个内存区域: find_vma()
此处)折叠或打开
1. /* Look up the first VMA which satisfies addr < vm_end, NULL if none. */
2. struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
3. {
4. struct vm_area_struct *vma = NULL;
5.
6. if (mm) {
7. /* Check the cache first. */
8. /* (Cache hit rate is typically around 35%.) */
9. vma = mm->mmap_cache;
10. if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
11. struct rb_node * rb_node;
12.
13. rb_node = mm->mm_rb.rb_node;
14. vma = NULL;
15.
16. while (rb_node) {
17. struct vm_area_struct * vma_tmp;
18.
19. vma_tmp = rb_entry(rb_node,
20. struct vm_area_struct, vm_rb);
21.
22. if (vma_tmp->vm_end > addr) {
23. vma = vma_tmp;
24. if (vma_tmp->vm_start <= addr)
25. break;
26. rb_node = rb_node->rb_left;
27. } else
28. rb_node = rb_node->rb_right;
29. }
30. if (vma)
31. mm->mmap_cache = vma;
32. }
33. }
34. return vma;
35. }
该函数在指定地址空间中搜索的一个vm_end大于addr的内存区域,这样返回的VMA首地址可能大于addr,所以指定的地址并不一定就包含在返回的VMA中。
因为很有可能在执行某个VMA操作后,其他操作还会对该VMA进行操作,所以find_vma()函数返回的结果被缓存在内存描述符的mmap_cache域中,实践证明,被缓存的VMA有相当好的命中率(30~40%),检查被缓存的VMA速度会很快,如果指定的地址不在缓存中,那么必须搜索和内存描述符相关的所有内存区域,这种搜索通过红黑树进行。
4.2 查找第一个和指定地址区间相交的VMA:find_vma_intersection()
此处)折叠或打开
1. /* Look up the first VMA which intersects the interval start_addr..end_addr-1,
2. NULL if none. Assume start_addr < end_addr. */
3. static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
4. {
5. struct vm_area_struct * vma = find_vma(mm,start_addr);
6.
7. if (vma && end_addr <= vma->vm_start)
8. vma = NULL;
9. return vma;
10. }
mm:要搜索的地址空间
start_addr:区间的起始地址
end_addr:区间尾地址
如果find_vma()返回NULL,那么find_vma_intersection()返回NULL;
如果find_vma()返回有效VMA,find_vma_intersection()只有在该VMA的起始位置于给定的地址区间结束位置之前,才将其返回,否者返回NULL
5.mmap()和do_mmap():创建地址区间
内核使用do_mmap()函数创建一个新的线性地址区间。如果这个新的VMA与相邻地址区间具有相同访问权限的话,将合并为一个VMA,如果不能合并,就确实需要创建一个新的VMA了。
无论如何,do_mmap()函数都会将一个地址区间加入到进程的地址空间中,无论是扩展已存在的内存区域还是创建一个新的区域。
在 中定义
static inline unsigned long do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flag, unsigned long offset)
该函数映射由file指定的文件,具体映射从文件偏移ofset开始,长度为len字节。如果file参数是NULL并且offset是0,那么代表这次映射没有和文件相关,这叫做匿名映射(anonymous mapping)。否则叫文件映射(file-backed mapping)。
addr是可选参数,它指定搜索空闲区域的起始位置。
prot参数指定内存区域中页面的访问权限。
flag参数指定VMA标志,这些标志指定类型并改变映射的行为
如果系统调用do_mmap()的参数中有无效参数,它返回一个负值;否者,就会在虚拟内存中分配额一个合适的新内存区域(有可能从slab中获取)。
在用户空间通过调用mmap()系统调用获取内核do_mmap()的功能。
void *mmap2(void *addr,
size_t length,
int prot,
int flags,
int fd,
off_t pgoffset)
该系统调用是mmap()调用的第二种变种,所以起名为mmap2(),原始的mmap()方法的调用最后一个参数是字节偏移量,而mmap2()使用页面偏移量;mmap()调用由POSIX定义,C库中任然作为mmap()方法使用,但新内核中已经没有对应实现了,mmap()方法的调用是通过将字节偏移转化为页面偏移,从而转化为对mmap2()函数的调用来实现的。
6.mummap()和do_mummap():删除地址区间
do_mummap()从特定的进程地址空间中删除指定地址区间,定义在
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
从mm指定的用户空间,删除从地址start开始,长度为len字节的地址区间。
Itn munmap(void *start, size_t length)
该系统调用定义在文件mm/mmap.c中,它是对do_mummap()函数的一个简单封装:
此处)折叠或打开
1. asmlinkage long sys_munmap(unsigned long addr,size_t len)
2. {
3. int ret;
4. struct mm_struct *mm;
5.
6. mm = current->mm;
7. down_write(&mm->mmap_sem);
8. ret = do_munmap(mm,addr,len);
9. up_write(&mm->mmap_sem);
10.
11. return ret;
12. }
7.页表
虽然应用程序操作的对象是对应虚拟内存,但处理器直接操作的却是物理内存,当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。
Linux使用三级页表管理完成地址转换,可按需求在编译简化使用两级,用三级是利用“最大公约数”的思想--- 一种设计简单的体系结构。
每个进程都有自己的页表(线程会共享页表),内存描述符的pgd域指向的就是进程的页全局目录,注意,操作和检索页表时必须使用page_table_lock锁,该锁在相应进程的内存描述符中,防止竞争条件。
页表对应的结构体依赖于具体的体系结构,定义在
由于几乎每次对虚拟内存的页面访问都必须先解析它,从而得到物理地址,所以页表操作的性能非常关键。但不幸的是搜索内存中的物理地址速度很有限,为了加快搜索,多数体系结构实现了一个翻译后缓存器(translate lookaside buffer, TLB)。TLB缓存虚拟地址到物理地址的映射,如果访问的虚拟地址在缓存中命中,物理地址立刻返回;否者就需要再通过页表搜索需要的物理地址。