《深入Linux内核架构》第4章 进程虚拟内存(3) 您所在的位置:网站首页 深入linux内核架构epub 《深入Linux内核架构》第4章 进程虚拟内存(3)

《深入Linux内核架构》第4章 进程虚拟内存(3)

2024-07-12 07:21| 来源: 网络整理| 查看: 265

目录

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 实验室设备网 版权所有