一、地址空间
1、进程地址空间由进程可寻址的虚拟地址组成,而且内核允许进程使用这种虚拟内存中的地址。
1)、每个进程都有一个32位或64位的平坦地址空间,空间的具体大小取决于体系结构。术语平坦指的是地址空间范围是一个独立的连续空间。
2)、通常情况下,每个进程都有唯一的这种平坦地址空间。
3)、一个进程的地址空间与另一个进程的地址空间即使有相同的内存地址,实际上也彼此互不相干,称这样的进程为线程。
2、内存地址是一个给定的值,它要在地址空间范围内。
1)、尽管一个进程可以访问可以寻址4GB的虚拟内存,但这并不代表它就有权访问所有的虚拟地址。
2)、在地址空间中,一些虚拟内存的地址区间更值得关心,它们可被内存访问。这些可被访问的合法地址空间称为内存区域。
3、进程只能访问有效内存区域内的内存地址。每个内存区域也具有相关权限如对相关进程有可读、可写、可执行属性。
4、内存区域也包含个各种内存对象。
5、进程地址空间中的任何有效地址都只能位于唯一的区域,这些内存区域不能相互覆盖。每个不同的内存片段都对应一个独立的内存区域:栈、对象代码、全局变量、被映射
的 文件等。
二、内存描述符
一)、相关简介
1、内核使用内存描述符结构体表示进程的地址空间,该结构包含了和进程控制有关的全部信息。内存描述符由mm_struct结构体表示,定义在文件<linux/sched.h>中。
2、所有的mm_struct结构体都通过自身的mmlist域链接在一个双向链表中,该链表的首元素是init_mm内存描述符,它表示init进程的地址空间。操作该链表的时候需要使用
mmlist_lock锁来防止并发访问,该锁定义在文件kernel/fork.h中。
二)、分配内存描述符
1、fork()函数利用copy_mm()函数复制父进程的内存描述符,也就是current->mm域给其子进程,而子进程中的mm_struct结构体是通过文件kernel/fork.c中的allocate_mm()
宏从mm_cachep_slab缓存中分配得到的。
2、如果父进程希望和其子进程共享空间,可以在调用clone()时,设置CLONE_VM标志。把这养的进程称之为线程。
三)撤销内存描述符
1、当进程退出时,内核会调用定义在kernel/exit.c中的exit_mm()函数,该函数执行一些常规的撤销工作,同时更新一些统计量。
四)、mm_struct与内核线程
1、内核线程没有进程地址空间,也没有相关的内存描述符,所有内核线程对应的进程描述符中的mm域为空。
2、为了避免内核线程为内存描述符和页表浪费内存,也为了当新内核线程运行时,避免浪费处理器周期向新地址空间进行转换,内核线程将直接使用前一个进程的内存描述
符。
三、虚拟内存地址
一)、相关简介
1、内存区域由vm_area_struct结构体描述,定义在文件<linux/mm_types.h>中。内存区域在Linux中也经常称作虚拟地址空间。
1)、vm_area_struct结构体描述了指定地址空间内连续区间上的一个独立内存范围。
2)、vm_area_struct结构体定义和各个域描述符。
二)、VMA标志
1、VMA标志是一种位标志,其定义见<linux/mm.h>中。它包含在mm_flags域内,标志了内存区域所包含的页面的行为和信息。
2、和物理页的访问不同权限不同,VMA标志反映了内核处理页面所需要遵守的行为准则,而不是硬件要求。而且,am_flags同时也包含了内存区域中每个页面的信息,或内
存区域的整体信息,而不是具体的独立页面。
3、VNA标志可能取值(P252 表15-1)
三)、VMA操作
1、vm_area_struct结构体中的am_ops域指向与指定内存区域相关的操作函数表,内核使用表中的方法操作VMA。vm_area_struct作为通用对象代表了任何类类型的内存区
域,而操作表描述针对特定的对象实例的特定方法。
2、操作函数由vm_oprations_struct结构体表示,定义在<linux/mm.h>中。
四)、内存区域的树型结构和内存区域的链表结构
1、可以通过内存描述符中的mmap和mm_rb域之一访问内存区域。这两个域各自独立地指向与内存描述符相关的全体内存区域对象。其实,它们包含完全相同的
am_area_struct结构体的指针,仅仅组织方法不同。
2、mmap域使用单独链表链接所有的内存区域对象。每一个vm_area_struct结构体通过自身的vm_struct域被连入链表,所有的区域按地址增长的方向排序,mmap域指向链
表中第一个内存区域,链表中最后一个结构体指针指向空。
3、mm_rb域使用红-黑数链接所有的内存区域对象。mm_rb域指向红-黑数的根节点,地址空间中每一个vm_area_struct结构体通过自身的vm_rb连接到树中。
五)、实际使用中的内存区域
四、操作内存区域
一)、find_vma()
1、为了找到一个给定的内存地址属于哪一个内存区域,内核提供了find_vma()函数。该函数定义在文件<mm/mmap.c>中。
struct vm_area_struct * find_vma(struct mm_struct *mm, unsigned long addr);
2、如果指定的地址不再缓存中,那么必须搜索和内存描述符相关的所有内存区域。这种搜索通过红-黑数进行。
二)、find_vma_prev()
1、find_vma_prev()函数和find_vma()函数工作方式相同,但是它返回一个小于addr的AMV。该函数定义和声明分别在文件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);
三)、find_vma_intersection()
1、find_vma_intersection()函数返回第一个和指定地址区间相交的VMA。
五、mmap()和do_mmap():创建地址区间
1、do_mmap()函数会将一个地址空间加入到进程的地址空间中——无论是扩展已存在的内存区域还是创建一个新的区域。其定义在文件<linux/mm.h>中。
struct vm_area_struct * find_vma(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flag, unsigned long offset);
2、在用户空间可以通过mmap()系统调用获取内核函数do_mmap()的功能。mmap()系统调用定义如下:
void * mmap2(void *start, size_t length, int prot
int flags, int fd, off_t pgoff);
六、munmap()和do_munmap():删除地址空间
1、do_munmap()函数从特定的进程空间中删除地址空间,该函数定义在文件<linux/mm.h>中;
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len);
2、系统调用munmap()给用户空间程序提供了一种自身地址空间中删除指定地址区间的方法,其与系统调用mmap()的作用相反:
int munmap(void *start, size_t length);
七、页表
1、当程序访问一个虚拟地址时,首先必须将虚拟地址转换为物理地址,然后处理器才能解析地址访问请求。地址的转换需要通过查询页表才能完成,概括的讲,地址转换需要
将虚拟地址分段,使每段虚拟地址都作为一个索引指向页表,而页表则指向下一级别的页表或者指向最终的物理页面。
2、Linux中使用三级页表完成转换。
1)、顶级页表是页全局目录(PGD),它包含一个pgd_t类型数组,多个体系结构中pgd_t类型等同于无符号长整型。PGD中的表项指向二级目录中的表项:PMD。
2)二级页表是中间页目录(PMD),它是个pmd_t类型数组,其中的表项指向PTE中表项。
3)、最后一级的页表简称页表,其中包含了pte_t类型的页表项,该页表项指向物理页面。