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

目录

4.3 内存映射原理

4.4 数据结构

4.4.1 树和链表

4.4.2 虚拟内存区域VMA的表示

4.4.3 相关数据结构


本专栏文章将有70篇左右,欢迎+关注,查看后续文章。

本节讲VMA结构体struct vm_area_struct和struct address_space。

4.3 内存映射原理

所有进程的虚拟空间总和比物理内存大得多,因此只有最常用的虚拟空间才映射到物理内存。

当访问一个未映射物理内存的虚拟内存时,进行按需调页。

按需调页步骤:

        1. 进程访问用户空间虚拟地址,但无法通过页表找到对应物理地址。

        2. CPU触发缺页异常。

        3. 通过虚拟地址对应address_space结构体,找到磁盘数据。

        4. 分配物理内存页,读取磁盘数据到内存。

        5. 建立正确页表,进程恢复执行。

后续详解。

4.4 数据结构

struct mm_struct:提供了进程在内存布局的所有必要信息。

struct mm_struct {

        ...

        struct vm_area_struct         *mmap;         链表,用于遍历该进程所有vma

        struct rb_root                       mm_rb;         红黑树,用于查找和插入该进程vma

        struct vma_area_struct        *mmap_cache;         缓存上一次find_vma结果

        ...

}

4.4.1 树和链表

每个区域(VMA)都用一个vm_area_struct表示,如代码段,数据段,堆,栈。

一个进程的所有VMA同时有两种排序方式:

        1. 单链表:mm_struct->mmap

                VMA根据起始地址以递增顺序放入mm_struct->mmap链表。

                作用:链表使用于遍历VMA。

        2. 红黑树:根节点为mm_struct->mm_rb

                VMA太多时,查找/插入操作等用红黑树更快。

4.4.2 虚拟内存区域VMA的表示

虚拟内存区域:即VMA。如:

        一个进程空间的代码段,数据段,堆,栈,mmap映射区域等。

每个VMA用struct vm_area_struct实例表示。

应用层哪些操作时会生成一个VMA:

        mmap函数。

        运行可执行文件或共享库。

        堆栈增长。

        shmat创建共享内存。

        malloc函数大内存。

两种映射:

        1. 匿名映射:没有对应映射的文件,如:

                堆,栈,mmap的MAP_ANONYMOUS类型映射。

                创建映射方法:

                        void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE,                                 MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); //fd为-1

        2. 文件映射:有对应映射源文件,如:

                数据段,代码段(对应可执行文件的.data和.text section)。

                创建映射方法:

                        void *addr = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);

struct vm_area_struct {

        struct mm_struct         *vm_mm;         

                所属mm_struct

       

         unsigned long              vm_start,vm_end;         

                该vma起始与结束地址

        

        struct vm_area_struct         *vm_next, *vm_prev;

                mm_struct中vma链表中前/后一个VMA(如上图)

      

          pgprot_t                 vm_page_prot;

        unsigned long         vm_flags;

        struct rb_node         vm_rb;

        union {

                struct {

                        struct rb_node rb;

                        unsigned long rb_subtree_last;

                } linear;

                struct list_head nonlinear;

        } shared;

        struct list_head           anon_vma_chain;

        struct anon_vma         *anon_vma;

        struct vm_operations_struct         *vm_ops;

        

        unsigned long         vm_pgoff;

                映射的文件位置的偏移量。

                若映射整个文件则为0。

        struct file         *vm_file;

                表示被映射的文件。

               若匿名映射,则为NULL。

}

成员解释:

1. pgprot_t vm_page_prot:

        该vma对应物理页的权限。值有:

                PROT_READ:允许读取页面。

                PROT_WRITE:允许写入页面。

                PROT_EXEC:允许执行页的代码。

                PROT_NONE:不允许任何访问。

        用法:entry = mk_pte(page, vma->vm_page_prot); //生成对应权限的页表项。

2. vm_flags:

        该VMA访问权限。值有:

                VM_READ,VM_WRITE,VM_EXEC:可读,可写,可执行

                VM_SHARD:该VMA由多进程共享。

                VM_PRIVATE:该VMA是私有的

                VM_MAYREAD:可设置为可读。

                VM_GROWSUP:该VMA向上扩展,如栈。

                VM_DONTCOPY:fork函数时不复制该VMA。

        根据VMA的访问权限(vm_flags)获取对应页面的保护权限(vm_page_prot)

                vma->vm_page_prot = vm_get_page_prot(vma->vm_flags) ;

3. struct vm_area_struct *vm_next, *vm_prev;

        mm_struct中struct vm_area_struct * mmap:是一个链表头。

                链表作用:连接了该进程的所有vma,便于遍历VMA。

        vm_prev:前一个VMA

        vm_next:后一个VMA

        注意:VMA地址递增排序。

4. struct rb_node vm_rb;

        mm_struct中struct rb_root mm_rb:一个红黑树根节点。

                mm_rb作用:

                        包含该进程的所有VMA到红黑树中,便于高效查询VMA。

        vm_rb:树节点,用于插入到红黑根节点中。

5. union {

        struct {

                struct rb_node rb;                 线性映射

                unsigned long rb_subtree_last;

        } linear;

        struct list_head nonlinear;         非线性映射

} shared;

用于文件映射的VMA:

1. VMA是线性映射

        struct rb_node rb作用:

                将该VMA插入到以address_space->i_mmap为根结点的红黑树。

2. VMA是非线性映射(即该VMA映射到多个非连续的物理内存区域。)

        struct list_head nonlinear作用:

                将该VMA连接到address_space->i_mmap_nonlinear链表中。

address_space作用:

        同一文件被映射到不同进程,有多个VMA。

        address_space通过红黑树或链表管理所有VMA。

        所以通过address_space,可知道一个文件被映射到哪些VMA。

线性映射:

        将虚拟地址简单偏移得到物理地址。如文件和共享库的映射。

非线性映射:

        被映射到多个非连续物理内存。如大文件映射。

6. struct list_head anon_vma_chain

        struct anon_vma *anon_vma

        (后续章节详解)

        刚创建子进程后,父子进程会共享相同地址空间,包括匿名页。

        管理共享同一匿名页的所有VMA。

匿名页时,stuct page->struct address_space *mapping不是指向struct address_space,而是struct anon_vma。

struct address_space管理文件映射的VMA,而struct anon_vma管理匿名映射的VMA。

如何找到映射该匿名页的所有VMA:

page -> struct anon_vma -> struct anon_vma_chain -> VMA

7. struct vm_operations_struct vm_ops:

        该VMA的操作函数。

        struct vm_operations_struct {

                void (*open)(struct vm_area_struct * area);

                void (*close)(struct vm_area_struct * area);

                        创建/删除vma,不常用,设为NULL。

                int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);

        };

fault函数使用场景:

        缺页异常:

                某虚拟地址没有分配对应物理页,触发缺页异常。

                执行fault函数,分配页,并读取数据到页中。

        写时复制COW:

                向只读映射写数据时,触发写时复制。

                执行fault函数,分配页作为副本并写入数据,不影响其他进程共享页面。

        共享页面拷贝:

                进程尝试写共享页时,fault将创建分配页,作为私有副本,并写入数据。

多进程共享同一物理页可节约内存。使用场景:

        执行相同程序:可共享代码段的页。

        动态库:共享库的代码段和数据段。基于写时复制,同一变量在不同进程的值不同。

        文件映射:映射同一文件到多进程,如映射数据库系统。

        共享内存IPC:多进程映射同一内存,进行进程间通信。

4.4.3 相关数据结构

struct file:内核中表示一个打开的文件。

struct file {

        struct inode                         *f_inode;             对应磁盘文件

        struct address_space         *f_mapping;         该文件映射信息

}

struct address_space {

        struct inode                         *host;         缓存的磁盘文件

        struct radix_tree_root         page_tree;

                该树连接了缓存了文件host的所有页page。

        作用:

                1. 通过该树,可查找到缓存文件指定偏移index的页。

                2. 可知某文件指定偏移处是否被缓存。若缓存,直接操作页,不用从磁盘读取。

                        //radix_tree_insert(&mapping->page_tree, index, page);

        struct rb_root                    i_mmap;

                红黑树,连接该文件的线性映射的所有VMA。

        struct list_head                 i_mmap_nonlinear;

                链表,连接该文件的非线性映射的所有VMA。

        struct address_space_operations         *a_ops;

}

address_space作用:

        1. 关联了文件和映射该文件的页(页缓存)。

                即关联struct inode *host和struct radix_tree_root page_tree。

        2. 保存映射该文件所有VMA。

                即struct rb_root i_mmap树和struct list_head i_mmap_nonlinear链表。

struct inode {         inode对应一个磁盘文件

        struct address_space         *i_mapping;

}

struct file {                 file对应一个已打开的文件

        struct inode                         *f_inode;

        struct address_space         *f_mapping;

}

打开文件时:

        file->f_mapping = inode->i_mapping;

一个进程多次打开同一个文件,内核为每次打开创建一个struct file实例。

而struct inode只有一个。

struct inode和struct file都包含struct address_space。

通过address_space的host成员可找到文件inode。

再通过inode中主/次设备号找到块设备文件。

如何将文件1.c的从头偏移100到1000区域映射到内存中?

        fd = open("/tmp/1.c", O_RDONLY);

        mmap(NULL, 900, PROT_READ, MAP_PRIVATE, fd, 100);

除了映射文件,mmap还可直接映射raw设备。
        作用:绕过文件系统接口和文件缓冲区。
        fd = open("/dev/mtdlbock1", O_RDONLY);
        mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, offset);

  • 43
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Linux内核是一个开源的操作系统内核,它是Linux操作系统的基础组件之一。深入研究Linux内核架构,需要对操作系统、计算机体系结构和编程语言等方面有一定的基础和了解。 Linux内核架构包括:进程管理、内存管理、文件系统、设备驱动、网络模块等。进程管理负责创建、调度和终止进程,通常采用分时多任务的方式。内存管理负责管理物理内存和虚拟内存,并提供内存分配和释放的功能。文件系统负责管理文件和目录,提供对文件的读写操作。设备驱动负责与硬件设备进行通信,以实现对硬件的控制和操作。网络模块负责处理网络通信的各种协议和传输。 深入研究Linux内核架构需要了解内核的设计思想、数据结构和算法。例如,进程管理使用了进程控制块(PCB)来描述进程的状态和属性;内存管理使用了页表和分页机制来管理内存的使用和分配;文件系统使用了索引节点(inode)来描述文件的属性和位置;设备驱动使用了设备文件和设备文件操作接口来与硬件设备进行通信;网络模块使用了套接字(socket)来实现网络通信。 通过深入研究Linux内核架构,可以更好地理解和掌握操作系统的原理和机制,能够进行内核调试、优化和定制,提高系统性能和安全性。此外,对于开发和维护Linux操作系统的人员来说,深入研究内核架构也是必不可少的,它可以帮助开发者更好地理解和使用Linux内核,提供更好的技术支持和开发服务。 总之,深入研究Linux内核架构对于理解和掌握操作系统原理、提高系统性能和安全性、以及提供技术支持和开发服务都具有重要意义。对于任何希望从事Linux开发和研究的人员来说,深入研究Linux内核架构是必不可少的学习内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山下小童

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值