珠海h5建站,江苏网站建设网络公司,郑州网站建设到诺然,辽宁建设工程信息网新点进程地址空间1 内存描述符分配内存描述符销毁内存描述符mm_struct与内核线程2 内存区域VMA标志VMA操作内存区域的树形结构和内存区域的链表结构3 操作内存区域find_vma()find_vma_prev()find_vma_intersection()4 mmap()和do_mmap()#xff1a;创建地址空间mmap#xff08;创建地址空间mmap 系统调用5 munmap()和do_munmap()删除地址空间munmap()系统调用6 页表内核除了管理本身的内存外还必须管理进程的地址空间也就是系统中每个用户空间地址所对应的内存。Linux操作系统采用虚拟内存技术因此系统中的所有进程之间以虚拟方法共享内存对每个进程来说它们好像都可以访问整个系统的所有物理内存。
进程地址空间由每个进程中的线性地址区组成每个进程都有一个32位或64位的平坦地址空间空间的具体大小取决于体系结构平坦地址空间是指地址空间范围是一个独立的连续空间比如地址从0扩展到429496729位地址空间。一些操作系统提供了段地址空间这种地址空间并非是一个独立的线性区域而是被分段的但现代采用虚拟内存的操作系统通常都使用平坦地址空间而不是分段式的内存模式。通常情况下每个进程都有唯一的这种平坦地址空间而进程地址空间之间彼此互不相干两个不同的进程可以在它们各自地址空间的相同地址内存放不同的数据。进程之间也可以选择共享地址空间我们称这样的进程为线程。
1 内存描述符
内核使用内存描述符结构体表示进程的地址空间该结构包含了和进程地址空间有关的全部信息。内存描述符由mm_struct结构体表示定义在文件linux/sched.h中。
struct mm_struct {struct vm_area_struct * mmap; /* list of VMAs */struct rb_root mm_rb;struct vm_area_struct * mmap_cache; /* last find_vma result */unsigned long (*get_unmapped_area) (struct file *filp,unsigned long addr, unsigned long len,unsigned long pgoff, unsigned long flags);void (*unmap_area) (struct vm_area_struct *area);unsigned long mmap_base; /* base of mmap area */unsigned long free_area_cache; /* first hole */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 */struct rw_semaphore mmap_sem;spinlock_t page_table_lock; /* Protects page tables, mm-rss, mm-anon_rss */struct list_head mmlist; /* List of maybe swapped mms. These are globally strung* together off init_mm.mmlist, and are protected* by mmlist_lock*/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 rss, anon_rss, total_vm, locked_vm, shared_vm;unsigned long exec_vm, stack_vm, reserved_vm, def_flags, nr_ptes;unsigned long saved_auxv[42]; /* for /proc/PID/auxv */unsigned dumpable:1;cpumask_t cpu_vm_mask;/* Architecture-specific MM context */mm_context_t context;/* Token based thrashing protection. */unsigned long swap_token_time;char recent_pagein;/* coredumping support */int core_waiters;struct completion *core_startup_done, core_done;/* aio bits */rwlock_t ioctx_list_lock;struct kioctx *ioctx_list;struct kioctx default_kioctx;
};mm_users域记录正在使用该地址的进程数目。比如有两个进程共享该地址空间那么mm_users的值便等于2mm_count是mm_struct的主引用计数只要mm_users不为0那么mm_count值就等于1。当mm_users的值减为0时mm_count域的值才为0如果mm_count的值等于0说明已经没有任何指向该mm_struct结构体的引用了这个时候该结构体会被销毁。 mmap和mm_rb这两个数据结构描述的对象是相同的该地址空间中的全部内存区域。但是mmap是以链表形式存放而后者以红-黑树形式存放。mmap结构体作为链表利于简单、高效地遍历所有元素而mm_rb结构体更适合搜索指定元素。 所有的mm_struct结构体都通过自身的mmlist域连接在一个双向链表中该链表的首元素是init_mm内存描述符它代表init进程的地址空间内存描述符的总数存放在mmlist_nr全局变量中该变量定义在kernel/fork.c中。
分配内存描述符
在进程的struct task_struct进程描述符中mm域存放着该进程使用的内存描述符所以current-mm便指向当前进程的内存描述符。fork函数利用copy_mm()函数复制父进程的内存描述符也就是current-mm域给其子进程而子进程中的mm_struct结构体实际是通过文件kernel/fork.c中的allocate_mm()宏从mm_cachep slab缓存中分配得到的。
如果父进程希望和其子进程共享地址空间可以在调用clone()时设置CLONE_VM标志。我们把这样的进程称作线程。当CLONE_VM被指定后内核就不再需要调用allocate_mm()函数了而仅仅需要在调用copy_mm()函数中将mm域指向其父进程的内存描述符就可以了。
销毁内存描述符
当进程退出时内核会调用exit_mm函数该函数执行一些常规的销毁工作同时更新一些统计量。其中该函数会调用mmput()函数减少内存描述符的mm_users用户计数如果mm_users降到0继续调用mmdrop()函数减少mm_count如果mm_count也等于0了说明该内存描述符不再有任何使用者了那么调用free_mm宏通过kmem_cache_free()将mm_struct结构体归还到mm_cachep_slab缓存中。
mm_struct与内核线程
内核线程没有进程地址空间也没有相关的内存描述符所以内核线程对应的进程描述符中mm域为空。
当一个进程被调度时该进程的mm域指向的地址空间被装载到内存进程描述符中的active_mm域会被更新指向新的地址空间。内核线程没有地址空间所以mm域为NULL于是当一个内核线程被调用时就会保留前一个进程的地址空间随后内核更新内核线程对应的进程描述符中的active_mm域使其指向前一个进程的内存描述符。
2 内存区域
内存区域由vm_area_struct结构体描述定义在文件linux/mm.h中内存区域在内核中也经常被称作虚拟内存区域或VMA。 vm_area_struct结构体描述了指定地址空间内连续区间上的一个独立内存范围。内核将每个内存区域作为一个单独的内存对象管理每个内存区域都拥有一致的属性
struct vm_area_struct {struct mm_struct * vm_mm; /* The address space we belong to. */unsigned long vm_start; /* Our start address within vm_mm. */unsigned long vm_end; /* The first byte after our end addresswithin vm_mm. *//* linked list of VM areas per task, sorted by address */struct vm_area_struct *vm_next;pgprot_t vm_page_prot; /* Access permissions of this VMA. */unsigned long vm_flags; /* Flags, listed below. */struct rb_node vm_rb;/** For areas with an address space and backing store,* linkage into the address_space-i_mmap prio tree, or* linkage to the list of like vmas hanging off its node, or* linkage of vma in the address_space-i_mmap_nonlinear list.*/union {struct {struct list_head list;void *parent; /* aligns with prio_tree_node parent */struct vm_area_struct *head;} vm_set;struct prio_tree_node prio_tree_node;} shared;/** A files 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_node; /* Serialized by anon_vma-lock */struct anon_vma *anon_vma; /* Serialized by page_table_lock *//* Function pointers to deal with this struct. */struct vm_operations_struct * vm_ops;/* Information about our backing store: */unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZEunits, *not* PAGE_CACHE_SIZE */struct file * vm_file; /* File we map to (can be NULL). */void * vm_private_data; /* was vm_pte (shared mem) */#ifdef CONFIG_NUMAstruct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
};每个内存描述符都对应于进程地址空间的唯一区间vm_start域指向区间的首地址vm_end域指向区间的尾地址之后的第一个字节vm_end~vm_start的大小便是内存区间的长度内存区域的位置就在[vm_start,vm_end]之中注意在同一个地址空间内的不同内存区间不能重叠。
vm_mm域指向和VMA相关的mm_struct结构体注意每个VMA对其相关的mm_struct来说都是唯一的所以即使两个独立的进程将同一个文件映射到各自的地址空间它们分别都会有一个vm_area_struct结构体标志自己的内存区域但是如果两个线程共享一个地址空间那么它们也同时共享其中所有的vm_area_struct结构体。
VMA标志
VMA标志是一种位标志其定义在linux/mm.h中它包含在vm_flags域内标志了内存区域所包含的页面的行为和信息和物理页的访问权限不同VMA标志反映了内核处理页面所需要遵守的行为准则而不是硬件要求。
VMA操作
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);struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int *type);int (*populate)(struct vm_area_struct * area, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock);
#ifdef CONFIG_NUMAint (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);struct mempolicy *(*get_policy)(struct vm_area_struct *vma,unsigned long addr);
#endif
};内存区域的树形结构和内存区域的链表结构
上面说过可以通过内存描述符中的mmap和mm_rb域之一访问内存区域这两个域各自独立地指向与内存描述符相关的全部内存区域对象vm_area_struct。
mmap使用单独链表连接所有的内存区域对象vm_area_struct每一个vm_area_struct结构体通过自身的vm_next域被连入链表所有的区域按地址增长的方向排序mmap域指向链表中第一个内存区域链中最后一个VMA结构体指针指向空。
mm_rb域使用红-黑树连接所有内存区域对象mm_rb域指向红-黑树的根结点地址空间中每一个vm_area_struct结构体通过自身的vm_rb域连接到树中。
链表用于需要遍历全部结点的时候而红-黑树适用于在地址空间中定位特定内存区域的时候。内核为了内存区域上的各种不同操作都能获得高性能所以同时使用了这两种数据结构。
3 操作内存区域
内核定义了许多内存区域操作函数它们都声明在文件linux/mm.h中
find_vma()
find_vma()函数定义在mm/mmap.c中。
/* Look up the first VMA which satisfies addr vm_end, NULL if none. */
extern struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr);该函数在指定的地址空间中搜索第一个vm_end大于addr的内存区域。换句话说该函数寻找第一个包含addr或首地址大于addr的内存区域如果没有发现这样的区域该函数返回NULL。否则返回指向匹配的内存区域的vm_area_struct结构体指针返回的结构会被缓存在内存描述符的mmap_cache域中所以find_vma会先在缓存中查找如果指定的地址不在缓存中那么必须搜搜和内存描述符相关的所有内存区域这种搜索通过红-黑树进行。
find_vma_prev()
find_vma_prev()函数和find_vma()工作方式相同但是它返回第一个小于addr的VMA。该函数定义和声明分别在文件mm/mmap.c中和文件linux/mm.h中
extern struct vm_area_struct * find_vma_prev(struct mm_struct * mm, unsigned long addr,struct vm_area_struct **pprev)pprev参数存放指向先于addr的VMA指针。
find_vma_intersection()
find_vma_intersection()返回第一个和指定地址区间相交的VMA。因为该函数和内联函数所以定义在文件linux/mm.h中
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;
}第一个参数是要搜索的地址空间start_addr是区间的开始首位置end_addr是区间的尾位置
4 mmap()和do_mmap()创建地址空间
内核使用do_mmap()函数创建一个新的线性地址区间。如果创建的地址区间和一个已经存在的地址区间相邻并且它们具有相同的访问权限的话那么两个区间将合并为一个。do_mmap()函数会将一个地址区间加入到进程的地址空间中。
do_mmap()函数定义在linux/mm.h中
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指定的文件具体映射的是文件中从偏移量offset处开始长度为len字节的范围内的数据。如果file参数是NULL并且offset参数也是0那么就代码这次映射没有和文件相关该情况被称作匿名映射如果指定了文件名和偏移量那么该映射被称为文件映射。
addr是可选参数它指定搜索空闲区域的起始位置。 prot参数指定内存区域中页面的访问权限。访问权限标志定义在文件asm/mman.h中。 flag参数指定了VMA标志这些标志也定义在文件asm/mman.h中
mmap 系统调用
在用户空间可以通过mmap系统调用获取内核函数do_mmap()的功能。
5 munmap()和do_munmap()删除地址空间
do_munmap()函数从特定的进程地址空间中删除指定地址区间该函数定义在文件linux/mm.h中
extern int do_munmap(struct mm_struct *mm, unsigned long start, size_t len);第一个参数指定要删除区域所在的地址空间删除从地址start开始长度为len字节的地址区间如果成功返回0.
munmap()系统调用
系统调用munmap()给用户空间程序提供了一种从自身地址空间删除指定区间的方法。
int munmap(void *start ,size_t length)该系统调用定义在mm/mmap.c中它是对do_munmap的一个简单封装
6 页表
虽然应用程序操作的对象是映射到物理内存之上的虚拟内存但是处理器直接操作的却是物理内存所以当应用程序访问一个虚拟地址时首先必须将虚拟地址转化为物理地址然后处理器才能解析地址访问请求。地址的转换工作是通过查询页表完成的。 页表对应的结构体依赖具体的体系结构所以定义在文件asm/page.h中