《深入Linux内核架构》第4章 进程虚拟内存(3) | 您所在的位置:网站首页 › 深入linux内核架构epub › 《深入Linux内核架构》第4章 进程虚拟内存(3) |
目录 4.5 VMA相关操作 4.5.1 查找VMA 4.5.2 VMA合并 4.5.3 插入VMA 4.5.4 创建VMA 4.6 地址空间 (address_space) 本专栏文章将有70篇左右,欢迎+关注,查看后续文章。 4.5 VMA相关操作查找: 从mm_struct中查找包含指定地址的VMA。 合并: 合并两个相邻VMA。 插入: 将VMA插入到mm_struct中。 删除: 从mm_struct中删除VMA。 4.5.1 查找VMA即从mm_struct中查找包含addr地址的VMA。 struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr) { struct vm_area_struct *vma = mm->mmap_cache; //缓存的VMA if (vma && vma->vm_end > addr && vma->vm_start struct vm_area_struct *vma_tmp; vma_tmp = rb_entry(rb_node, struct vm_area_struct, vm_rb); if (vma_tmp->vm_end > addr) { vma = vma_tmp; if (vma_tmp->vm_start rb_left; } else rb_node = rb_node->rb_right; } if (vma) mm->mmap_cache = vma; //找到VMA后,缓存起来。 } //while return vma; } find_vma_intersection: 作用:确定start_addr, end_addr是否完全被一个VMA包含。 基于find_vma实现。 struct vm_area_struct *find_vma_intersection(struct mm_struct *, start_addr, end_addr) { struct vm_area_struct *vma = find_vma(mm,start_addr); if (vma && end_addr vm_start) vma = NULL;
return vma; } 4.5.2 VMA合并将新VMA加入进程地址空间(mm_struct)时,会检查是否可以和现存VMA合并。 使用vma_merge函数尝试合并。 可合并的条件: 1. 两个VMA的地址紧邻。 2. VMA标志相同。 3. 映射同一文件,映射的文件部分连续。 4. 都不是匿名映射。 vma_merge -> can_vma_merge_after:是否可以和前一个VMA合并。 -> can_vma_merge_after:是否可以和后一个VMA合并。 -> vma_adjust:执行合并VMA操作。 将修改address_space->i_mmap树。 释放多余VMA。 4.5.3 插入VMA将一个VMA插入一个进程地址空间(mm_struct)中。 int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma) insert_vm_struct的内部实现: 1. find_vma_links: 遍历VMA红黑树(树根节点:mm->mm_rb),比较地址,寻找插入位置。 2. vma_link:执行插入操作。 2.1 __vma_link: 2.1.1 插入到VMA链表中。 若本VMA是mm中第一个MVA,则mm_struct->mmap链表中。 (因为mm_struct->mmap是进程VMA链表头) 若mm中已有VMA,则插入到prev->vm_next = vma; 2.1.2 插入到VMA红黑树(mm->mm_rb)。 2.2 __vma_link_file: 2.2.1 若是线性文件映射: 插入到address_space的i_mmap间隔树。 2.2.2 若是非线性文件映射: 插入到address_space的i_mmap_nonlinear链表。 4.5.4 创建VMA插入新VMA之前,使用get_unmapped_area函数(mm/mmap.c)在mm_struct中寻找一个未映射区域。 get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags) { get_area = current->mm->get_unmapped_area; //当加载运行进程时调用了arch_pick_mmap_layout函数, 将mm->get_unmapped_area设为了arch_get_unmapped_area; 不同体系架构定义不同。 if (file && file->f_op && file->f_op->get_unmapped_area) { get_area = file->f_op->get_unmapped_area; //通常不定义struct file_operations f_op->get_unmapped_area方式。 //通常使用mm->get_unmapped_area(即arch_get_unmapped_area) } addr = get_area(file, addr, len, pgoff, flags); return addr; } arch_get_unmapped_area: 作用: 在指定进程的地址空间(mm_struct)中查找可映射的区域。 工作流程: 1. 检查是否设置MAP_FIXED标志,(若设置,在固定位置创建映射) 2. 未设置,则继续查找可用区域直到TASK_SIZE。 使用场景: 动态内存分配。 文件映射mmap。 加载可执行文件,创建进程。 4.6 地址空间 (address_space)本节简单讲解,第16章详解。 不管有多少个进程或使用多少次mmap内存映射,只要映射的是同一个文件,它们都共享同一个address_space实例。 即一个文件对应一个address_space实例。 struct address_space: 作用: 1. 管理该文件映射的所有VMA。 所以关联了inode与VMA。 2. 用radix树组织所有缓存该文件的page。 缓存文件的偏移确定了在树中位置。 所以通过 address_space可以快速了解一个文件的某个部分有没有被缓存。 并且可调用address_space_operations读写该缓存文件的页。并定期或手动同步到磁盘。 struct address_space { struct inode *host; //所属文件 struct radix_tree_root page_tree; //连接该文件的缓存页 struct rb_root i_mmap; //组织线性映射VMA struct list_head i_mmap_nonlinear; //连接非线性映射VMA struct address_space_operations *a_ops; //该address_space的操作函数 } struct address_space_operations { int (*writepages)(struct address_space *mapping, struct writeback_control *wbc) //回写内存脏页到磁盘。 int (*readpage)(struct file *, struct page *); //从磁盘读取数据到内存页。 int (*set_page_dirty)(struct page *page); //页数据修改后,用该函数标记该页为脏。 int (*migratepage) (struct address_space *,page *, page *, enum migrate_mode); } 如何通过VMA调用struct address_space_operations? 答:vm_operations_struct的fault函数指针。 struct vm_area_struct { struct vm_operations_struct *vm_ops; } struct vm_operations_struct generic_file_vm_ops = { .fault = filemap_fault, }; int filemap_fault(struct vm_area_struct *vma, struct vm_fault *vmf) { struct file *file = vma->vm_file; struct address_space *mapping = file->f_mapping; page = page_cache_alloc_cold(mapping); //分配页 mapping-> a_ops->readpage(file, page); } VMA和address_space之间: 1. vm得到成员struct file *vm_file。 2. struct file得到成员struct address_space *f_mapping。 inode也可得到address_space。 inode-> struct address_space i_mapping 最后可调用address_space中a_ops->readpage(file, page); 从而读取文件系统的文件到内存页。 文件系统创建inode时: alloc_inode()进行inode->i_mapping初始化。不同文件系统实现不同。 以jffs2为例: inode->i_mapping->a_ops = &jffs2_file_address_operations; 打开一个文件时: struct file *filp->f_mapping = struct inode->i_mapping; |
CopyRight 2018-2019 实验室设备网 版权所有 |