系统调用brk()和mmap()

系统调用brk()

brk()的可见度不高,但brk()是常用的系统调度,用户进程通过它向内核申请空间。总是通过malloc来间接的用到brk(),如果把malloc想象成零售,那brk就是批发。

由于每个进程的虚存空间都很大(3G),但实际需要使用的空间又很小,内核不可能在创建进程时就为整个虚存空间都分配号相应的物理空间并建立映射,而只能时需要用到多少才分配多少。

在mm_struct结构中有一个成分brk,表示当前分配内存的底部,当一个进程需要分配内存时,将要求的大小与当前的动态分配区底部相加,所得的就是所要求的新边界。当内核能满足要求时,系统调用brk()返回0;当内核发现无法满足要求,或者发现新的边界已经过于逼近设于顶部的堆栈时,就拒绝分配而返回-1.

系统调用brk()在内核中的实现为sys_brk( ),这个函数既可以用来分配空间,即把动态分配区底部的边界往上推;也可以用来释放,即归还空间。

通过do_munmap( )解除一部分区间的映射。

do_munmap() > find_vma_prev( )

它扫描当前进程用户空间的vm_area_struct结构链表或RB树,试图找到高于addr的第一个区间,如果找到,则函数返回该区间的vma_area_struct结构指针。如果返回的指针为0或者该区间的起始地址也高于addr+len,那表示想要解除映射的那部分空间原来就没有映射,所以直接返回0.如果这部分空间落到某个区间的中间,那么解除映射有可能使原来的区间一分为二,所以要分配好一个空白的vm_area_struct结构extra。

另一方面,要解除映射的那部分空间也有可能跨越好几个区间,所以通过for循环把所有涉及的区间都转移到一个临时队列free中,如果建立了RB树,则要把这些区间的vm_area_struct结构从RB树中删除。

zap_page_range( )解除若干连续页面的映射,并且释放所映射的内存页面,或对交换设备上物理页面的引用。

sys_brk > do_munmap( ) > zap_page_range( )

这个函数解除一块虚存区间的页面映射。首先通过pgd_offset()在第一层页面目录中找到起始地址所属的目录项,然后通过一个do-while循环从这个目录项开始处理涉及的所有的目录项。

sys_brk > do_munmap( ) > zap_page_range( ) > zap_pmd_range( )

同样,先通过pmd_offset(),在第二层目录表中找到起始目录项。对于采用二级映射的i386结构,中间目录表这一层是空的。

sys_brk > do_munmap( ) > zap_page_range( ) > zap_pmd_range( )  > zap_pte_range( )

还是通过pte_offset( )找到在给定页面表的起始表项。然后在一个for循环中,对需要解除映射的页面调用ptep_get_and_clear()将页面表项清为0;

最后通过free_pte()解除对内存页面以及盘上页面的使用。

如果页面表项表明在解除映射前页面就已不存在内存上,只需调用swap_free( )(递减盘上页面的使用计数,当页面的使用计数为0时,才真正释放盘上的页面)。否则通过free_page_and_swap_cache()解除对盘上页面和内存页面二者的使用。

sys_brk > do_munmap( ) > zap_page_range( ) > zap_pmd_range( )  > zap_pte_range( ) > free_page_and_swap_cache( ) > delete_from_swap_cache_nolock( )

将页面上的内容“冲刷”到块设备上,(对映射到用用户空间的文件才进行,对于交换设备内容已经没有意义了);然后通过lru_cache_del()将页面从所在的LRU队列中脱离。然后通过_ _delere_from_swap_cache( ),使页面脱离换入换出队列和杂凑队列。递减page结构的使用计数,当计数为0时,让他回到空闲页面队列。


当新边界高于老边界,分配空间 

首先检查对进程的资源限制,检查所要求的那部分空间是否与已经存在的某一个空间冲突。检查系统是否有足够的空闲内存页面。

sys_brk() > do_brk( )

建立新的映射时,看看是否可以跟原有的区间合并,即通过扩展原有区间来覆盖新增的区间。如果不行就得另建一个区间。


系统调用mmap()

一个进程可以通过mmap(),将一个已打开文件的内容映射到它的用户空间

sys_mmap2( ) > do_mmap2( ) 

当调用参数flags中把标志位MAP_ANONYMOUS设成1,表示没有文件,只是用来“圈地”。

sys_mmap2( ) > do_mmap2( ) > do_mmap_pgoff( )

首先对文件和区间两方面都做一些检查,包括起始地址与长度、已经映射的次数等等。

当参数flags的标志位MAP_FIXED为0,就表示指定的映射地址只是个参考值。不满足可以由内核分配一个。

也就是说当给定目标地址为0,内核从(TASK_SIZE/3)即1G处开始向上在当前进程的虚存空间中寻找一块足以容纳给定长度的区间。当给定地址不为0时,函数find_vma()在当前进程已经映射的虚存空间中找到第一个满足vma->vm_end大于给定地址的区间。如果找不到这么一个区间,那就说明给定的地址尚未映射,因而可以使用。

通过kmem_cache_alloc( )为带映射的区间分配一个vm_area_struct数据结构。设置vm_pgoff字段(代表所映射内容在文件中的起点,在发生缺页异常时可以找到在文件中的位置)

==================================================================================================

在内核中常常先分配某项资源,然后检验条件,如果条件不符再将资源释放(而不是先检验条件后分配资源),因为在分配资源的过程中是否发生了调度,以及其它进程或线程的运行是否可能改变这些条件。

当我们建立映射,只是尚未插入到mm_struct中,如果检测到目的地址在当前进程的虚存空间是否已经使用,如果已经使用就把老的映射撤销。如果操作失败,就得转移到free_vma,把已经分配的vm_area_struct撤销。

==================================================================================================

根据映射为专有的或共享的而分别指向数据结构file_private_mmap或file_shared_mmap。检验用于页面读写的函数是否存在。

所谓文件与虚存之间的映射包含两个环节,一是物理页面与文件映射之间的换入/换出,二物理页面与虚存空间的映射。二者都是动态的,只有真正需要的时候才会进行(拖延战术)。

具体的,就是为映射的建立,物理页面的换入换出(以及映射的拆除)分别准备一些函数,就是filemap_nopage()、ext2_readpage()以及ext2_writepage()。

(1)首先,当这个区间的一个页面首次受到访问,发生缺页异常,调用do_no_page()。do-no_page()通过ext2_readpage( )

分配一个空闲内存页面并从文件读入相应的页面,然后建立映射。

(2)建立映射以后,对页面的写操作不会立即写回文件,通过内核线程bdflush()周期性运行时通过page_launder()间接的调用ext2_writepage( ),将页面的内容写入文件。如果长时间没有受到访问,依然会耗尽他的生命,从而变成不活跃状态。











你可能感兴趣的:(Linux内核读书笔记)