文章目录
进程地址空间:内核除了管理本身的内存外,还必须管理用户空间中进程的内存,这个内存就是进程地址空间,即系统中每个用户空间进程所看到的内存。
ps:注意是用户空间。
Linux操作系统采用虚拟内存技术,系统中的所有进程之间以虚拟方式共享内存。
1.地址空间
进程地址空间由进程可寻址的虚拟内存组成,内核允许进程使用这种虚拟内存中的地址。
一个进程的地址空间与另一个进程的地址空间即使有相同的内存地址,实际上也彼此互不相干–这样的进程称为线程。
内存地址是一个给定的值,要在地址空间范围之内。
进程没有权限访问所有的虚拟地址,可被访问的合法地址空间被称为内存区域。
进程可以通过内核给自己的地址空间动态的添加或减少内存区域。
内存区域(也叫虚拟内存区域)可以包含各种内存对象,如下:
- 可执行文件代码的内存映射,称为代码段(text section);
- 可执行文件的已初始化全局变量的内存映射,称为数据段(data section);
- 包含未初始化全局变量,bss段的零页(页面中的信息全部都为0值,所以可用于映射bss段等目的)的内存映射;
- 用于进程用户空间栈的零页的内存映射;
- 每一个诸如C库或动态链接程序等共享库的代码段、数据段和bs也会被载入进程的地址空间;
- 任何内存映射文件;
- 任何共享内存段;
- 任何匿名的内存映射,如malloc()分配的内存。
2.内存描述符–mm_struct
内核使用内存描述符结构体表示进程的地址空间,该结构包含了和进程地址空间有关的全部信息。
内存描述符由mm_struct结构体表示,定义在文件<linux/mm_types.h>书中是<linux/sched.h>。
struct mm_struct {
struct vm_area_struct * mmap; /* list of VMAs 内存区域链表 */
struct rb_root mm_rb; /* VMA 形成的红黑树 */
struct vm_area_struct * mmap_cache; /* last find_vma result 最近使用的内存区域 */
#ifdef CONFIG_MMU
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags);
#endif
unsigned long mmap_base; /* base of mmap area */
unsigned long mmap_legacy_base; /* base of mmap area in bottom-up allocations */
unsigned long task_size; /* size of task vm space */
unsigned long highest_vm_end; /* highest vma end address */
pgd_t * pgd; /* 页全局目录 */
atomic_t mm_users; /* How many users with user space? 使用地址空间的用户数 */
atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) 主使用计数器 */
int map_count; /* number of VMAs 内存区域的个数 */
spinlock_t page_table_lock; /* Protects page tables and some counters 页表锁 */
struct rw_semaphore mmap_sem; /* 内存区域的信号量*/
struct list_head mmlist; /* 所有mm_struct 形成的链表 List of maybe swapped mm's. These are globally strung
* together off init_mm.mmlist, and are protected
* by mmlist_lock
*/
unsigned long hiwater_rss; /* High-watermark of RSS usage */
unsigned long hiwater_vm; /* High-water virtual memory usage */
unsigned long total_vm; /* Total pages mapped */
unsigned long locked_vm; /* Pages that have PG_mlocked set */
unsigned long pinned_vm; /* Refcount permanently increased */
unsigned long shared_vm; /* Shared pages (files) */
unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE */
unsigned long stack_vm; /* VM_GROWSUP/DOWN */
unsigned long def_flags;
unsigned long nr_ptes; /* Page table pages */
unsigned long start_code, end_code, start_data, end_data; /* 代码段的首、尾 数据的首、尾地址 */
unsigned long start_brk, brk, start_stack; /* 堆的首、尾地址 */
unsigned long arg_start, arg_end, env_start, env_end; /* 命令行参数的首、尾 环境变量的首、尾地址 */
unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
/*
* Special counters, in some configurations protected by the
* page_table_lock, in other configurations by being atomic.
*/
struct mm_rss_stat rss_stat;
struct linux_binfmt *binfmt;
cpumask_var_t cpu_vm_mask_var;
/* Architecture-specific MM context */
mm_context_t context; /* 体系结构特殊数据 */
unsigned long flags; /* Must use atomic bitops to access the bits 状态标志 */
struct core_state *core_state; /* coredumping support 核心转储的支持 */
#ifdef CONFIG_AIO
spinlock_t ioctx_lock; /* AIO I/O 链表锁*/
struct hlist_head ioctx_list; /* AIO I/O 链表 */
#endif
#ifdef CONFIG_MM_OWNER
/*
* "owner" points to a task that is regarded as the canonical
* user/owner of this mm. All of the following must be true in
* order for it to be changed:
*
* current == mm->owner
* current->mm != mm
* new_owner->mm == mm
* new_owner->alloc_lock is held
*/
struct task_struct __rcu *owner;
#endif
/* store ref to file /proc/<pid>/exe symlink points to */
struct file *exe_file;
#ifdef CONFIG_MMU_NOTIFIER
struct mmu_notifier_mm *mmu_notifier_mm;
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
pgtable_t pmd_huge_pte; /* protected by page_table_lock */
#endif
#ifdef CONFIG_CPUMASK_OFFSTACK
struct cpumask cpumask_allocation;
#endif
#ifdef CONFIG_NUMA_BALANCING
/*
* numa_next_scan is the next time that the PTEs will be marked
* pte_numa. NUMA hinting faults will gather statistics and migrate
* pages to new nodes if necessary.
*/
unsigned long numa_next_scan;
/* numa_next_reset is when the PTE scanner period will be reset */
unsigned long numa_next_reset;
/* Restart point for scanning and setting pte_numa */
unsigned long numa_scan_offset;
/* numa_scan_seq prevents two threads setting pte_numa */
int numa_scan_seq;
/*
* The first node a task was scheduled on. If a task runs on
* a different node than Make PTE Scan Go Now.
*/
int first_nid;
#endif
#if defined(CONFIG_NUMA_BALANCING) || defined(CONFIG_COMPACTION)
/*
* An operation with batched TLB flushing is going on. Anything that
* can move process memory needs to flush the TLB when moving a
* PROT_NONE or PROT_NUMA mapped page.
*/
bool tlb_flush_pending;
#endif
struct uprobes_state uprobes_state;
};
mmap 和 mm_rb 这两个不同数据结构体描述的对象是相同的:该地址空间中的全部内存区域。
mmap 是以链表形式存放的,mm_rb以红黑树的形式存放。
所有的mm_struct 结构体都通过自身的mmlist域连接在一个双向链表中,该链表的首元素是init_mm内存描述符,它代表init进程的地址空间。
ps:操作该链表的时候需要使用mmlist_lock锁来防止并发访问,该锁定义在文件kernel/fork.c中。
2.1 分配内存描述符–copy_mm()
在进程的进程描述符(task_struct)中,mm域存放着该进程使用内存描述符(mm_struct),所以current->mm指向当前进程的内存描述符。
fork()函数利用copy_mm()函数复制父进程的内存描述符,也就是current->mm域给其子进程,而子进程中mm_struct结构体实际是通过文件kernel/fork.c中的allocate_mm()宏从mm_cachep slab缓存中分配得到的。因此通常,每个进程都有唯一的mm_struct结构体,即唯一的进程地址空间。
ps:是否共享地址空间几乎是进程和Linux中所谓的线程间本质上的唯一区别。线程对内核来说仅仅是一个共享特定资源的进程而已。
Q:如何让父进程和子进程共享地址空间?
A:在调用clone()时,设置CLONE_VM标志。(这样的子进程称为线程)当CLONE_VM被指定后,内核就不再需要调用allocate_mm()函数,而仅仅需要在调用copy_mm()函数中将mm域指向其父进程的内存描述符就可以了。
2.2 撤销内存描述符–exit_mm->mmput->mmdrop->free_mm->kmem_cache_free
当进程退出时,内核会调用定义在kernel/exit.c中的exit_mm()函数,该函数执行一些常规的撤销工作,同时更新一些统计量。
其中,该函数会调用mmput()函数减少内存描述符中mm_users用户计数,如果用户计数降到零,将调用mmdrop()函数,减少mm_count使用计数。
如果使用计数也等于零了,说明该内存描述符不再有任何使用者了,那么调用free_mm()宏通过kmem_cache_free()函数将mm_struct结构体归还到mm_cachep slab缓存中。
2.3 mm_struct 与内核线程
内核线程没有进程地址空间,也没有相关的内存描述符,所以内核线程对应的进程描述符中的mm域为空,即task_struct里的mm_struct为空。(因为内核线程没有用户上下文)
Q:内核线程对应的进程描述符中mm域为空,使用谁的?
A:内核线程使用前一个进程的内存描述符–active_mm域的作用。
当一个进程被调度时,该进程的mm域指向的地址空间被装载到内存,进程描述符中active_mm域会被更新,指向新的地址空间。内核线程没有地址空间,所有mm域为NULL。于是,当一个内核线程被调度时,内核发现它的mm域为NULL,就会保留前一个进程的地址空间,随后内核更新内核线程对应的进程描述符中active_mm域,使其指向前一个进程的内存描述符。所以在需要的时候,内核线程便可以使用前一个进程的页表。因为内核线程不访问用户空间的内存,所以它们仅仅使用地址空间和内核内存相关的信息。
3.虚拟内存区域–vm_area_struct
内存区域由vm_area_struct结构体描述,定义在文件<linux/mm_types.h>中。
内存区域在Linux内核中页称作虚拟内存区域(VMAs:virtual memortAreas)。
vm_area_struct结构体描述了指定地址空间内连续区间上的一个独立内存范围。
内核将每个内存区域作为一个单独的内存对象管理,每个内存区域都拥有一致的属性。
每一个VMA就可以代表不同类型的内存区域。
struct vm_area_struct {
/* The first cache line has the info for VMA tree walking. */
unsigned long vm_start; /* Our start address within vm_mm. 区间的首地址 */
unsigned long vm_end; /* 区间的尾地址 The first byte after our end address
within vm_mm. */
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next, *vm_prev; /* VMA 链表 */
struct rb_node vm_rb; /* 树上该VMA的节点 */
/*
* Largest free memory gap in bytes to the left of this VMA.
* Either between this VMA and vma->vm_prev, or between one of the
* VMAs below us in the VMA rbtree and its ->vm_prev. This helps
* get_unmapped_area find a free area of the right size.
*/
unsigned long rb_subtree_gap;
/* Second cache line starts here. */
struct mm_struct *vm_mm; /* The address space we belong to. 相关的mm_struct内存描述符结构体 */
pgprot_t vm_page_prot; /* Access permissions of this VMA. 访问控制权限 */
unsigned long vm_flags; /* Flags, see mm.h. 标志 */
/*
* For areas with an address space and backing store,
* linkage into the address_space->i_mmap interval tree, or
* linkage of vma in the address_space->i_mmap_nonlinear list.
*
* For private anonymous mappings, a pointer to a null terminated string
* in the user process containing the name given to the vma, or NULL
* if unnamed.
*/
union {
struct {
struct rb_node rb; /* 树上该VMA的节点 */
unsigned long rb_subtree_last;
} linear;
struct list_head nonlinear;
const char __user *anon_name;
} shared;
/*
* A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
* list, after a COW of one of the file pages. A MAP_SHARED vma
* can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack
* or brk vma (with NULL file) can only be in an anon_vma list.
*/
struct list_head anon_vma_chain; /* Serialized by mmap_sem &
* page_table_lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock 匿名VMA对象 */
/* Function pointers to deal with this struct. */
const struct vm_operations_struct *vm_ops; /* 相关的操作表 */
/* Information about our backing store: */
unsigned long vm_pgoff; /* 文件中的偏移量 Offset (within vm_file) in PAGE_SIZE
units, *not* PAGE_CACHE_SIZE */
struct file * vm_file; /* File we map to (can be NULL). 被映射的文件(如果存在) */
void * vm_private_data; /* was vm_pte (shared mem) 私有数据 */
#ifndef CONFIG_MMU
struct vm_region *vm_region; /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
struct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
};
每个内存描述符都对应域进仓地址空间中的唯一区间。
vm_start域指向区间的首(最低)地址,vm_end域指向区间的尾(最高)地址之后的第一个字节–vm_start在区间内,vm_end在区间外。
在同一个地址空间内的不同内存区间不能重叠。
vm_mm域指向和VMA相关的mm_struct结构体,注意,每个VMA对其相关的mm_struct结构体来说都是唯一的,所以即使两个独立的进程将同一个文件映射到各自的地址空间,它们分别都会有一个vm_area_struct结构体来标志自己的内存区域;
如果两个线程共享一个地址空间,那么它们也同时共享其中的所有vm_area_struct结构体。
3.1 VMA 标志
VMA标志是一种位标志,其定义在<linux/mm/h>。
它包含在vm_flags域内,标志了内存区域所包含的页面的行为和信息。
VMA标志可取值如下表:
标志 | 对VMA及其页面的影响 |
---|---|
VM_READ | 页面可读取 |
VM_WRITE | 页面可写 |
VM_EXEC | 页面可执行 |
VM_SHARED | 页面可共享 |
VM_MAYREAD | VM_READ 标志可被设置 |
VM_MAYWRITE | VM_WRITE 标志可被设置 |
VM_MAYEXEC | VM_EXEC 标志可被设置 |
VM_MAYSHARE | VM_SHARE 标志可被设置 |
VM_GROWSDOWN | 区域可向下增长 |
VM_GROWSUP | 区域可向上增长 |
VM_SHM | 区域可用作共享内存 |
VM_DENYWRITE | 区域映射一个不可写文件 |
VM_EXECUTABLE | 区域映射一个可执行文件 |
VM_LOCKED | 区域中的页面被锁定 |
VM_IO | 区域映射设备 I/O 空间 |
VM_SEQ_READ | 页面可能被连续访问 |
VM_RAND_READ | 页面可能被随机访问 |
VM_DONTCOPY | 区域不能再fork()时被拷贝 |
VM_DONTEXPAND | 区域不能通过mremap()增加 |
VM_RESERVED | 区域不能被换出 |
VM_ACCOUNT | 该区域是一个记账 VM 对象 |
VM_HUGETLB | 区域使用了hugetlb 页面 |
VM_NONLINEAR | 该区域是非线性映射的 |
3.2 VMA 操作–vm_ops -> vm_operations_struct
vm_area_struct 结构体中的vm_ops 域指向与指定内存区域相关的操作函数表,内核使用表中的方法操作VMA。
vm_area_struct 作为通用对象代表了任何类型的内存区域,而操作表描述符对特定的对象实例的特定方法。
操作函数表由vm_operations_struct 结构体表示,定义在文件 <linux/mm.h>中:
/*
* These are the virtual MM functions - opening of an area, closing and
* unmapping it (needed to keep files on disk up-to-date etc), pointer
* to the functions called when a no-page or a wp-page exception occurs.
*/
struct vm_operations_struct {
void (*open)(struct vm_area_struct * area);
void (*close)(struct vm_area_struct * area);
int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);
/* notification that a previously read-only page is about to become
* writable, if an error is returned it will cause a SIGBUS */
int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);
/* called by access_process_vm when get_user_pages() fails, typically
* for use by special VMAs that can switch between memory and hardware
*/
int (*access)(struct vm_area_struct *vma, unsigned long addr,
void *buf, int len, int write);
#ifdef CONFIG_NUMA
/*
* set_policy() op must add a reference to any non-NULL @new mempolicy
* to hold the policy upon return. Caller should pass NULL @new to
* remove a policy and fall back to surrounding context--i.e. do not
* install a MPOL_DEFAULT policy, nor the task or system default
* mempolicy.
*/
int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);
/*
* get_policy() op must add reference [mpol_get()] to any policy at
* (vma,addr) marked as MPOL_SHARED. The shared policy infrastructure
* in mm/mempolicy.c will do this automatically.
* get_policy() must NOT add a ref if the policy at (vma,addr) is not
* marked as MPOL_SHARED. vma policies are protected by the mmap_sem.
* If no [shared/vma] mempolicy exists at the addr, get_policy() op
* must return NULL--i.e., do not "fallback" to task or system default
* policy.
*/
struct mempolicy *(*get_policy)(struct vm_area_struct *vma,
unsigned long addr);
int (*migrate)(struct vm_area_struct *vma, const nodemask_t *from,
const nodemask_t *to, unsigned long flags);
#endif
/* called by sys_remap_file_pages() to populate non-linear mapping */
int (*remap_pages)(struct vm_area_struct *vma, unsigned long addr,
unsigned long size, pgoff_t pgoff);
};
函数 | 描述 |
---|---|
*void open(struct vm_are_struct *area) | 当制定的内存区域被加入到一个地址空间时,该函数被调用 |
*void close(struct vm_area_struct *area) | 当指定的内存区域从地址空间删除时,该函数被调用 |
*void fault(struct vm_area_struct *area,struct vm_fault *vmf) | 当没有出现在物理内存中的页面被访问时,该函数被页面故障处理调用 |
*int page_mkwrite(struct vm_area_struct *area, struct vm_fault *vmf) | 当某个页面为只读页面时,该函数被页面故障处理调用 |
*int access(struct vm_area_struct *vma, unsigned long address, void *buf, int int write) | 当get_uset_pages()函数调用失败时,该函数被access_process_vm()函数调用 |
3.3 内存区域的树型结构和内存区域的链表结构–mm_struct之*mmap和mm_rb
mmap域使用单独链表连接所有的内存区域对象。每一个vm_area_struct结构体通过自身的vm_next域被连入链表,所有的区域按地址增长的方法排序,mmap域指向链表中第一个内存区域,链中最后一个结构体指针指向空。
mm_rb域使用红黑树连接所有的内存区域对象。mm_rb域指向红黑树的根节点,地址空间中每一个vm_area_struct结构体通过自身的vm_rb域连接到树中。
链表用于需要遍历全部节点的时候,而红黑树适用于在地址空间中定位特定内存区域的时候。
内核为了内存区域上的各种不同操作都能获得高性能,所以同时使用了这两种数据结构。
3.4 实际使用中的内存区域–/proc文件系统或pmap(1)工具
可以使用/proc 文件系统和pmap(1)工具查看给定进程的内存空间和其中所含的内存区域。
eg:
可以使用cat /proc/<pid>/maps或pmap <pid>列出该进程地址空间中包含的内存区域。
其中由代码段、数据段和bss段等,假设该进程与C库动态链接,那么地址空间还将分别包括libc.so和ld.so对应的上述三种内存区域。瓷碗,地址空间还要包含进程栈对应的内存区域。
每行数据格式如下:
开始-结束 访问权限 偏移 主设备号:次设备号 i 节点 文件
前三行分别对应C库中的libc.so的代码段、数据段和bss段;
接着两行是可执行对象的代码段和数据段;
接下来三行是动态链接程序ld.so的代码段、数据段和bss段;
最后一行是进程的栈。
ps:
代码段具有可读且可执行权限,数据段和bss具有可读、可写但不可执行权限。而堆栈则可读、可写,甚至还可执行。
共享不可写内存的方法节约了大量的内存空间。
没有映射文件的内存区域的设备标志位00:00,索引节点标志也为0,这个区域就是零页–零页映射的内容全为零。
如果将零页映射到可写的内存区域,那么该区域将全初始化为0(bss段需要的就是全0的内存区域)。
由于内存未被共享,所以只要一有进程写该处数据,那么该处数据就将被拷贝出来(写时拷贝-COW),然后才被更新。
每个和进程相关的内存区域都对应一个vm_area_struct结构体,进程不同于线程,进程结构体stask_struct包含唯一的mm_struct结构体引用,而线程则是共享了地址空间,即copy_mm。
4.操作内存区域
4.1 find_vma()
内核提供了find_vma()函数用来找到一个给定的内存地址属于哪一个内存区域(在指定的地址空间中搜索第一个vm_end大于addr的内存区域,也就是寻找第一个包含addr或者首地址大于addr的内存区域)。
定义在文件<mm/mmap.c>中:
struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr);
4.2 find_vma_prev()
find_vma_prev()函数和find_vma()工作方式相同,但它返回第一个小于addr的VMA。
该函数定义和声明分别在文件mm/mmap.c和文件<linux/mm.h>中:
struct vm_area_struct *find_vma_prev(struct mm_struct *mm,unsigned long addr,struct vm_area_struct **pprev);
pprev参数存放指向先于addr的VMA指针。
4.3 find_vma_intersection()
find_vma_intersection()函数返回第一个和指定地址区间相交的VMA。
该函数是内联函数,定义在文件<linux/mm.h>中:
/* Look up the first VMA which intersects the interval start_addr..end_addr-1,
NULL if none. Assume start_addr < end_addr. */
static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
{
struct vm_area_struct * vma = find_vma(mm,start_addr);
if (vma && end_addr <= vma->vm_start)
vma = NULL;
return vma;
}
参数mm是要搜索的地址空间;
start_addr是区间的开始首地址;
end_addr是区间的尾地址。
如果find_vma()返回NULL,那么find_vma_interesection()也会返回NULL。
如果find_vma()返回有效的VMA,find_vma_intersection()只有在该VMA的起始位置于给定的地址区间结束位置之前,才将其返回。如果VMA的起始位置大于指定位置范围的结束位置,则该函数返回NULL。
5.mmap()和do_mmap():创建地址区间
mmap()->do_mmap()
内核使用do_mmap()函数创建一个新的线性地址区间。但是说该函数创建了一个新的VMA并不非常准确,因为如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限,两个区间合并为一个。如果不能合并,则确实需要创建一个新的VMA。
无论哪种情况,do_mmap()函数都会将一个地址区间加入到进程的地址空间中–无论是扩展已存在的内存区域还是创建一个新的区域。
do_mmap()函数定义在文件kernel/arch/powerpc/kernel/syscalls.c,以内联形式展示,书中<linux/mm.h>中。 ps:比较旧的内核版本是do_mmap,后面更新成do_mmap2,区别仅仅在于最后一个参数,后者使用页面偏移作为最后一个参数,使用页面偏移量可以映射更大的文件和更大的偏移位置:
unsigned long sys_mmap2(unsigned long addr, size_t len,
unsigned long prot, unsigned long flags,
unsigned long fd, unsigned long pgoff)
{
return do_mmap2(addr, len, prot, flags, fd, pgoff, PAGE_SHIFT-12);
}
unsigned long sys_mmap(unsigned long addr, size_t len,
unsigned long prot, unsigned long flags,
unsigned long fd, off_t offset)
{
return do_mmap2(addr, len, prot, flags, fd, offset, PAGE_SHIFT);
}
static inline unsigned long do_mmap2(unsigned long addr, size_t len,
unsigned long prot, unsigned long flags,
unsigned long fd, unsigned long off, int shift)
{
unsigned long ret = -EINVAL;
if (!arch_validate_prot(prot))
goto out;
if (shift) {
if (off & ((1 << shift) - 1))
goto out;
off >>= shift;
}
ret = sys_mmap_pgoff(addr, len, prot, flags, fd, off);
out:
return ret;
}
ps:
映射没有和文件相关,该情况称作匿名映射(anonymous mapping);
如果指定了文件名和偏移量,则该映射称为文件映射(file-backed mapping);
虽然C库仍然可以使用原始版本的映射方法(mmap),但它其实还是基于函数mmap2()进行的,因为对原始mmap()方法的调用是通过将字节偏移转化为页面偏移,从而转化为对mmap2()函数的调用来实现的。
6.munmap()和do_munmap():删除地址区间
munmap()(kernel/include/linux/syscalls.h) ->do_munmap()
do_munmap()函数从特定的进程地址空间中删除指定地址区间,该函数定义在文件kernel/mm/nommu.c,书中是<linux/mm.h>。
int do_munmap(struct mm_struct *mm,unsigned long start, size_t len);
第一个参数指定要删除区域所在的地址空间,删除从地址start开始,长度为len字节的地址区间;
成功返回零,失败返回负数错误码。
系统调用munmap()给用户空间程序提供了一种从自身地址空间删除指定地址区间的方法,和系统调用mmap()作用相反:
asmlinkage long sys_munmap(unsigned long addr, size_t len);
7.页表–转换虚拟地址和物理地址的工作
虽然应用程序操作的对象是映射到物理内存上的虚拟内存,但是处理器直接操作的却是物理内存。
Q:页表为何存在?
A:当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。地址的转换工作需要通过查询页表才能完成,即地址转换需要将虚拟地址分段,使每段虚拟地址都作为一个索引指向页表,而页表项则指向下一级别或指向最终的物理页面。
Linux中使用三级页表完成地址转换。利用多级页表能够节约地址转换需占用的存放空间。
顶级页表是页全局目录(PGD),包含了一个pgd_t类型数组,多数体系结构图中pgd_t类型等同于无符号长整型类型。PGD中的表项指向二级页目录中的表项:PMD;
二级页表是中间页目录(PMD),是个pmd_t类型数组,其中的表项指向PTE中的表项;
最后一级的页表简称页表(PTE),包含了pte_t类型的页表项,该页表项指向物理页面。
每个进程都有自己的页表(线程会共享页表)。
内存描述符的pgd域指向的就是进程的页全局目录(PGD)。
ps:操作和检索页表时,必须使用page_table_lock锁,该锁在相应的进程的内存描述符中,以防止竞争条件。
页表对应的结构体依赖于具体的体系结构,定义在文件<asm/oage.h>。
Q:页表机制的性能?
A:由于几乎每次对虚拟内存中的页面访问都必须先解析页表,从而得到物理内存中的对应地址,所以页表操作的性能非常关键。但不幸的是,搜索内存的物理地址速度很有限,因此为了加可搜索,多数体系结构图都实现了一个翻译后缓冲器(translate lookaside buffer,TLB)。
TLB作为一个将虚拟地址映射到物理地址的硬件缓存,当请求访问一个虚拟地址时,处理器将首先检查TLB中是否缓存了该虚拟地址到物理地址的映射,如果在缓存中直接命中,物理地址立刻返回;否则,就需要再通过页表搜索需要的物理地址。