1.背景
虚拟内存的一大优势就是每个进程有自己的虚拟地址空间,OS 负责将其虚拟地址空间映射到物理内存中。
内核处理用户部分的地址空间的方式,与内核部分不同:
- 内核部分内存的分配是立刻满足的,并对全局可见:
1)__get_free_pages()或alloc_pages() 从page frame 分配器获取page frame;
2)kmem_cache_alloc() 或kmalloc() 使用专用或通用的slab 分配器分配objects;
3)vmalloc() 获取非连续的内存。
如此简单的分配方式的两个原因是:
1)内核是整个OS中优先级最高的部分;
2)内核可靠安全,所有内核函数都是无error的,不需要加入保护以防止错误 - 用户进程情况,则不同
1)进程内存申请的请求,通常内核会推迟分配内存给用户进程;
i. 每个进程通常实际使用的地址空间,就只有整个虚拟地址空间的其中的几个区域;并且每个区域相隔比较远。 内核需要高效管理这些分散的区域。
ii. 进程可执行文件加载时,进程不可能在不远的将来会访问所有的code page;
iii. 进程通过malloc()分配内存时,并不意味着进程会很快访问所有分配的内存
2)用户进程并不可靠,内核必须捕捉所有用户态的错误以确保用户进程会破坏系统稳定性和安全性。
2. 进程虚拟地址空间
2.1 进程虚拟地址空间布局
(1)进程虚拟地址空间由很多虚拟内存区域(VMA)构成:
-
stack
用于存放局部变量,函数参数和函数调用 -
mmap
主要用于映射文件内容到虚拟地址空间的内存映射,也用于映射共享object 和动态库 -
heap
动态分配的内存存放的地方,允许进程存放运行时的数据。malloc()分配的内存来自此区域 -
BSS
存放未初始化的静态变量 -
data
存放全局变量,静态初始化的变量 -
text
映射程序二进制文件到内存的区域
(2)每个VMA, 物理上映射到一到多个内存块,进程的page table 做相应的地址映射。
2.2 进程地址空间描述符
- 进程可用的地址空间通过进程task_struct 中的struct mm_struct 结构管理
- 每个进程有一个mm_struct ,用户线程共享同一个地址空间;
- 通过寻找所有task_structs 中指向相同的mm_struct 可以识别出task list 中的线程;
- 内核线程不需要mm_struct,通常内核线程的task_struct->mm为NULL;
- mmap
是进程地址空间中所有VMA 区域的链表头 - mmap_cache
缓存上次访问的VMA; - mm_rb
所有的VMA 排放在链表mm_struct->mmap中,并为了快速查询,也放在红黑树中,这个是树的root
- start_code, end_code
对应程序二进制文件映射的code VMA 区域的起始和结束虚拟地址 - start_data, end_data
对应data VMA 区域的起始和结束虚拟地址 - start_brk, brk
对应heap VMA 区域的起始和当前结束的虚拟地址 - start_stack
对应stack VMA 区域的起始和当前结束的虚拟地址 - arg_start, arg_end
对应命令行参数列表的 VMA 区域的起始和当前结束的虚拟地址 -
env_start, env_end
对应环境参数的 VMA 区域的起始和当前结束的虚拟地址
2.3 虚拟内存区域VMA
-
每个虚拟内存区域由vm_area_struct 管理
- VMA 区域之间不会重叠,每个VMA区域代表着相同保护访问方式和目的的的区域
- 进程的完整的VMA区域可通过/proc/PID/maps 查看
- vm_start, vm_end
对应这个该vma 区域的起始虚拟地址和结束地址 - vm_next
VMA直接通过vm_next 链接到一起,通过地址排序 - vm_rb
VMA 通过vm_rb 链接到红黑树中 - vm_mm
指向所属的进程的mm_struct 结构 - vm_file
对于基于文件的VMA 区域(code 区,共享库,共享内存映射区等等),vm_file 指向对应文件的struct file 指针,该文件struct file ->f_dentry->d_inode->i_mapping 的struct address_space 拥有所有关于文件的信息,包含指向文件系统操作的文件系统函数 - anon_vma_node
heap,stack 和mmap 的虚拟地址空间的分配都通过匿名内存映射分配的, 内核通过struct anon_vma 将代表匿名内存的VMA区域链接到一起,便于快速访问所有的映射匿名页的进程VMA -
vm_ops
VMA 操作指针,open 和close 通常为NULL,nopage 用于page fault
-
vm_operations_struct
2.4 基于文件的VMA区域
-
基于文件的VMA 区域 通过struct address_space 描述
-
每个address_space结构作为一个文件inode 对应的pages 的抽象
i. 文件打开的时候,kernel 设置file->f_mapping为inode->i_mapping
ii. inode 是per-file 数据结构,而file 是per-process 数据结构;这样让多个进程能直接访问同样的文件,而不需要与其他进程交互
host
指向拥有对于pages的owner inode;i_mmap
为了高效管理cache 中的文件pages, 需要跟踪所有映射到同一个address_space的VMA。 i_mmap 是所有包含当前映射到该address_space的VMA的红黑树。-
page_tree
i. 处于address_space对象的所有包含文件数据的物理page 都通过radix tree 数据结构组织管理起来以高效访问,page_tree 是radix tree 的root,是struct radix_tree_root 类型的实例;
ii. 一方面一个进程的所有VMA 被同时组织管理在一个链表和红黑树数据结构中
iii. 另一方面radix 树数据结构用于反向查找一个给定文件的所有VMA;
-
struct radix_tree_root
radix tree 的每个node 是struct radix_tree_node 类型的,struct radix_tree_root 中的*rnode 作为radix tree的 第一个node;
2.6 文件的虚拟地址空间与文件系统之间的联系
文件的内存映射是两个不同地址空间的映射
i. 一个地址空间是用户进程的虚拟内存地址空间;
ii. 另一个是文件系统覆盖的地址空间;内核以read, write 请求的方式建立两个地址空间的联系
-
vm_operations_struct 结构体用于建立联系,提供方法读内容到物理内容
-
而具体的文件系统的操作是通过address_space 结构的address_space_operation
i. readpage,readpages 读块设备的单个和多个页内容到内存中;
ii.writepage,writepages 写内存中的单个页和多个页到块设备中对应的位置
iii. set_page_dirty 标记页内容改变,不在与块设备中的内容一致
vm_operation_struct 与 address_space 之间的联系
i. 通过kernel 的标准实现vm_operations_struct 实例,几乎所有文件系统在使用 generic_file_vm_ops.-
filemap_faul的实现使用底层文件系统对应的address_space提供的readpage 操作