向下之旅(二十一):进程地址空间(二)

  操作内存区域

  内核时常需要判断进程地址空间中的内存区域是否满足某些条件,比如某个指定地址是否包含在某个内存区域中。为了方便执行这类对内存区域的操作,内核定义了许多的辅助函数。

  1. find_vma()

  该函数在指定的地址空间中搜索第一个vm_end大于addr的内存区域,即该函数寻找第一个包含addr或首地址大于addr的内存区域,如果没有发现这样的区域,该函数返回NULL,否则返回指向匹配的内存区域的vm_area_struct结构体指针。

  2. find_vma_prev()

  该函数与find_vma()工作方式相同,但是它返回第一个小于addr的VMA。

  3. find_vma_intersection()

  find_vma_intersection()函数返回第一个和指定地址区间相交的VMA。

  显然如果find_vma()返回NULL,那么find_vma_intersection()也返回NULL,但是如果find_vma()返回有效的VMA,find_vma_intersection()只有在该VMA的起始位置于给定的地址区间结束位置之前,才将其返回。如果VMA的起始位置大于指定地址范围的结束位置,则该函数返回NULL.

  mmap()和do_mmap():创建地址区间

  内核使用do_mmap()函数创建一个新的线性地址空间。但是不一定会创建一个VMA,因为如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并成一个。如果不能合并,则需要创建一个新的VMA。无论哪种情况,do_mmap()函数都会讲一个地址区间加入到进程的地址空间中——无论是扩展在已存在的内存区域还是创建一个新的区域。

  unsigned long do_mmap(struct  file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag, unsigned long offest)

  该函数映射由file指定的文件,具体映射的是文件从偏移offset处开始,长度为len字节的范围内的数据。如果file参数是NULL并且offest参数是0,则这次映射称为匿名映射,反之称为文件映射。

  addr是可选参数,它指定搜索空闲区域的起始位置。

  prot参数指定内存区域中页面的访问权限。flag参数指定了VMA标志,表格如下:

  

  如果系统调用do_mmap()的参数中有无效参数,那么它返回一个负值;负责,它会在虚拟内存那种分配一个合适的新内存区域。如果有可能的话,将新区域和邻近区域进行合并,否则内核从vm_area_cachep长字节(slab)缓存中分配一个vm_area_struct结构体,并且使用vma_link()函数将新分配的内存区域添加到地址空间的内存区域链表和红-黑树中,随后还要更新内存描述符中的total_vm域,然后才返回新分配的地址区间的初始地址。

  mmap()系统调用

  在用户空间可以通过mmap()系统调用获取内核函数do_mmap()的功能,mmap()系统调用定义如下:

  void *mmap2(void *start, size_t length, int prot, int flags, int fd, off_t pgoff)

  因该系统调用是mmap()调用的第二种变种,所以起名为mmap2()。最原始的mmap()调用中最后一个参数是字节偏移量,而目前这个mmap2()使用页面偏移做最后一个参数。使用页面偏移可以映射更大的文件和更大的偏移位置。

  munmap()和do_munmap():删除地址空间

  do_munmap()函数从特定的进程地址空间中删除指定地址空间:

  int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)

  第一个参数指定了要删除区域所在的空间,从start开始,删除len字节的地址区间。

  munmap()系统调用

  系统调用munmap()给用户空间程序提供了一种从自身地址空间中删除指定地址区间的方法,它与系统调动mmap()作用相反。

  int munap(void *start, size_t length)

  页表

  应用程序操作的对象是映射到物理内存之上的虚拟内存,但是处理器直接操作的却是物理内存。即当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。地址的转换工作需要通过查询页表才能完成。大概流程是,地址转换需要将虚拟地址分段,使每段虚地址都作为一个索引指向页表,而页表项则指向下一级别的页表或指向最终的物理页面。

  Linux中使用三级页表完成地址转换,利用多级页表能够节约地址转换需占用的存放空间。

  顶级页表是页全局目录(PGD),其包含了一个pgd_t类型的数组,多数体系结构中pgd_t类型等同于无符号长整数类型。PGD中的表项指向二级页目录中的表项:PMD。

  二级页表是中间页目录(PMD),PMD是个pmd_t类型数组,其中的表项指向PTE的表项。

  最后一级的页表简称页表,其中包含了pte_t类型的页表项,该页表项指向物理页面。

  多数体系结构中,搜索页表的工作是由硬件完成的,但是只有在内核正确设置页表的前提下,硬件才能方便的操作它们。

  每个进程都有自己的页表,内存描述符的pgd域指向的就是进程的页全局目录。操作和检索页表时必须使用page_table_lock锁,该锁在相应的进程的内存描述符中,以防止竞争条件。

  虚拟地址通过页表找到物理地址的过程如下:

 

  页表对应的结构体依赖于具体的体系结构。

  由于几乎每次对虚拟内存中的页面访问都必须先解析它,从而得到物理内存中对应的地址,但是,搜索内存中的物理地址速度很有限,因此多数体系结构都实现了一个翻译后缓冲器,TLB。当请求一个虚拟地址是,处理器将首先检查TLB中是否缓存了该虚拟地址到物理地址的映射。

  虽然硬件完成了有关页表的部分工作,但是页表的管理仍然是内核的关键部分——而且在不断的改进。

 

  参考自:《Linux Kernel Development》.

posted on 2016-03-30 16:33  画家丶  阅读(579)  评论(0编辑  收藏  举报