2025年Linux内存管理宏观篇(七)虚拟内存

Linux内存管理宏观篇(七)虚拟内存Linux 内存管理宏观篇 七 虚拟内存 前面知道了物理内存 物理内存是实打实的 我只有这么多 用的时候你只能用这么多 为了解决一些问题 产生虚拟内存 通过虚拟内存可以让我们每个进程都能拥有虚拟的 3GB 用户态地址空间 同时与硬件层屏蔽后还可以增加我们程序的移植性

大家好,我是讯享网,很高兴认识大家。

Linux内存管理宏观篇(七)虚拟内存

前面知道了物理内存,物理内存是实打实的,我只有这么多,用的时候你只能用这么多。

为了解决一些问题,产生虚拟内存,通过虚拟内存可以让我们每个进程都能拥有虚拟的3GB用户态地址空间,同时与硬件层屏蔽后还可以增加我们程序的移植性。以及众多好处,这里在前面认识内存的时候讲过,移植性,保护内存安全等等。

我觉得最重要的是我们可以通过地址虚拟赋予每个进程更大的内存使用空间,当然这就涉及到不断地映射与释放。而我们的进程基本上不会实实在在的得到3GB的物理内存。

咱们常用的malloc()是内存分配的接口

而mmap()是用户态用来建立文件映射或者匿名映射的函数。

知道了

  • 虚拟内存是什么?
  • 虚拟内存为什么?

因为进程的地址空间肯定是进程的元素,因此在进程的管理结构体 struct mm_struct也会有进程地址空间属性,这个属性就是管理VMA。

下面就来看看进程地址空间。

1、进程地址空间

1、是什么?

是进程可以寻址的虚拟空间地址

2、有多大?

32位的是4GB

3、都能操作?

不得行,只有3GB的用户地址可以,内核地址需要通过系统调用。而用户空间的进程地址空间则可以被合法访问,地址空间称为内存区域(memory area)。

6、内存区域有什么?

代码段映射,可执行文件中包含只读并可执行的程序头,如代码段和init段等。

数据段映射,可执行文件中包含可读可写的程序头,如数据段和bss段等。

用户进程的栈。通常是在用户空间的最高地址,从上往下延伸。它包含栈帧,里面包含了局部变量和函数调用参数等。注意不要和内核栈混淆,进程的内核栈独立存在并有内核维护,主要用于上下文切换。

MMAP 映射区域。位于用户进程栈下面,主要用于 mmap 系统调用,比如映射一个文件的内容到进程地址空间等。

堆映射区域。malloc()函数分配的进程虚拟地址就是这段区域。

7、虚拟内存大家都是3GB,要是小红和小绿搞到一起怎么办?

不可能的,因为虚拟地址分配的值一样,但是每个进程都有自己的页表,页表是独一份,因此每个进程能访问的区域都是相互隔离的。各有不同。(你以为给了你全世界,实际上只是一个角)

下面来看看内存描述符是什么?

2、内存描述符 mm_struct

对于进程的内存区域和对应的页表映射,内核采用抽象一个数据结构来管理。然后在进程控制块(PCB)结构task_struct中有一个指针mm是指向这个mm_struct数据结构的。

task_struct(mm)–>mm_struct–>VMA

mm_struct数据结构定义在include/linux/mm_types.h文件中,下面是它的主要成员。

在这里插入图片描述
讯享网
在这里插入图片描述

3、VMA管理

VMA数据结构定义在mm_types.h文件中。[include/linux/mm_types.h]
在这里插入图片描述

进一步看看mm_struct,struct mm_struct数据结构是描述进程内存管理的核心数据结构,该数据结构也提供了管理VMA所需要的信息,这些信息概况如下。

[include/linux/mm_types.h] struct mm_struct { struct vm_area_struct *mmap; struct rb_root mm_rb; … }; 

讯享网

每个VMA都要连接到mm_struct中的链表和红黑树中,以方便查找。mmap 形成一个单链表,进程中所有的 VMA 都链接到这个链表中,链表头是mm_struct->mmap。

mm_rb是红黑树的根节点,每个进程有一棵VMA的红黑树。

VMA按照起始地址以递增的方式插入mm_struct->mmap链表。当进程拥有大量的VMA时,扫描链表和查找特定的VMA是非常低效的操作,例如在云计算的机器中,所以内核中通常要靠红黑树来协助,以便提高查找速度。从VMA的角度来观察进程的内存管理,如图7.20所示。(当年特么的学的算法终于用上了)
在这里插入图片描述

1.查找VMA

通过虚拟地址addr来查找VMA是内核中常用的操作。内核提供一个API函数来实现这个查找操作。

讯享网struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr) struct vm_area_struct *find_vma_prev(struct mm_struct *mm, unsigned long addr,struct vm_area_struct pprev) static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm,unsigned long start_addr, unsigned long end_addr) 

find_vma()函数根据给定地址addr查找满足如下条件之一的VMA,如图7.21所示。addr在VMA空间范围内,即 vma->vm_start <= addr < vma->vm_end。距离addr最近,并且VMA的结束地址大于addr的一个VMA。(说明了什么?)
在这里插入图片描述

2.插入VMA

insert_vm_struct()是内核提供的插入VMA的核心API函数。

int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma) 

insert_vm_struct()函数向VMA链表和红黑树插入一个新的VMA。参数mm是进程的内存描述符,vma是要插入的线性区VMA。(这个链表和红黑树的关系是什么?)

3.合并VMA

在新的 VMA 被加入进程的地址空间时,内核会检查它是否可以与一个或多个现存的VMA进行合并。vma_merge()函数实现将一个新的VMA和附近的VMA合并。变成一个更大的连续的空间。

讯享网struct vm_area_struct *vma_merge(struct mm_struct *mm,struct vm_area_struct *prev, unsigned long addr,unsigned long end, unsigned long vm_flags,struct anon_vma *anon_vma, struct file *file,pgoff_t pgoff, struct mempolicy *policy) 

vma_merge()函数参数多达9个。其中,mm是相关进程的struct mm_struct数据结构。prev是紧接着新VMA前继节点的VMA,一般通过find_vma_links()函数来获取。add和end是新VMA的起始地址和结束地址。vm_flags是新VMA的标志位。如果新VMA属于一个文件映射,则参数file指向该文件struct file数据结构。参数proff指定文件映射偏移量。参数anon_vma是匿名映射的struct anon_vma数据结构。

4、malloc背后的男人

malloc()函数是C语言中的内存分配函数。假设系统中有进程A和进程B,分别使用testA和testB函数分配内存。

void testA(void){ char * bufA = malloc(100); … *bufA = 100; … } 
讯享网//进程B分配内存 void testB(void){ char * bufB = malloc(100); mlock(bufB, 100); … } 

其实在内存这方面的函数,经常会问到的就是这个是否会立即分配内存?这个分配的内存在哪里呢?

malloc()函数是C语言标准库里封装的一个核心函数。C标准库做一些处理后调用Linux内核系统去调用brk。也许读者并不太熟悉brk的系统调用,原因在于很少有人会直接使用系统调用 brk 向系统申请内存,而总是通过 malloc()之类的 C 标准库的 API 函数。如果把malloc()想象成零售,那么 brk 就是代理商。malloc 函数的实现为用户进程维护一个本地小仓库,当进程需要使用更多的内存时就向这个小仓库“要货”,小仓库存量不足时就通过代理商brk向内核“批发”。

(我不太认可这里的比喻,其实我们分配内存的申请一发生,我的映像是立马就深入到系统调用了,这里零售说明,我能屯点货,但是malloc最多就是个中间人,把你要给我的东西通过他转交给我,而没有这个缓存的属性。我觉得中间人更合适,但是这是因为我对malloc的认识很浅薄,继续保持疑问往下看。)

brk系统调用定义如下。

SYSCALL_DEFINE1(brk, unsigned long, brk) 

在32位Linux内核中,每个用户进程拥有3GB的虚拟空间。内核如何为用户空间划分这3GB的虚拟空间呢?

3GB的用户空间怎么划分?

用户进程的可执行文件由代码段和数据段组成,数据段包括所有的静态分配的数据空间,例如全局变量和静态局部变量等。这些空间在可执行文件装载时,内核就为其分配好这些空间,包括虚拟地址和物理页面,并建立好二者的映射关系。

如图7.22所示,用户进程的用户栈从3GB虚拟空间的顶部开始,由顶向下延伸,而brk分配的空间是从数据段的顶部end_data到用户栈的底部。动态分配空间是从进程的end_data开始,每次分配一块空间,就把这个边界往上推进一段,同时内核和进程都会记录当前的边界的位置。

在这里插入图片描述
malloc函数其实是为用户空间分配进程地址空间的,用内核术语就是分配一块VMA,相当于一个空的纸箱子。因为VMA还没和具体物理地址挂钩呢。相当于一个空的纸箱子。

那什么时候才往纸箱子里装东西呢?
一是到了真正使用箱子时才往里面装东西;(声明未初始化)
二是分配箱子时就装了你想要的东西。(声明初始化)

进程A中的testA函数就是第一种情况。当使用这段内存时,CPU去查询页表,发现页表为空,CPU触发缺页异常,然后在缺页异常里一页一页地分配内存,需要一页给一页。(这个缺页异常据说很精彩)

进程B里面的testB函数是第二种情况,直接分配已装满的纸箱子,你要的虚拟内存都已经分配了物理内存并建立了页表映射。

还有个问题就是malloc相同的虚拟地址会打架吗?

其实每个用户进程有自己的一份页表,mm_struct 数据结构中有一个pgd成员指向这个页表的基地址,在用fork()函数创建新进程时会初始化一份页表。每个进程有一个mm_struct数据结构,包含一个属于进程自己的页表、一个管理VMA的红黑树和链表。进程本身的VMA会挂入属于自己的红黑树和链表,所以即使进程A和进程B使用malloc分配内存返回的相同的虚拟地址,它们也是两个不同的VMA,分别被不同的两套页表来管理。

简言之:数据结构里有页表,页表基地址不同,还有各种不同,进程有很多自己独特的数据结构,因此最后的物理地址是不一样的。

所以即使进程A和进程B使用malloc分配内存返回的相同的虚拟地址,它们也是两个不同的VMA,分别被不同的两套页表来管理。

在这里插入图片描述
(得写写libc这个东西的作用)

6、mmap背后故事

mmap/munmap 接口函数是用户空间最常用的两个系统调用接口,无论是在用户程序中分配内存、读写大文件、链接动态库文件,还是多进程间共享内存,都可以看到mmap/munmap的身影。mmap/munmap函数声明如下。

讯享网#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); int munmap(void *addr, size_t length); 

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后根据文件关联性和映射区域是否共享等属性,mmap又可以分成如下4种情况,如表7.7所示。
在这里插入图片描述
1.私有匿名映射

当参数fd=−1且flags= MAP_ANONYMOUS | MAP_PRIVATE时,创建的mmap映射是私有匿名映射。私有匿名映射最常见的用途是在glibc分配大块的内存中,当需要分配的内存大于MMAP_THREASHOLD(128KB)时,glibc会默认使用mmap代替brk来分配内存。

2.共享匿名映射

当参数fd=−1且flags= MAP_ANONYMOUS | MAP_SHARED时,创建的mmap映射是共享匿名映射。共享匿名映射让相关进程共享一块内存区域,通常用于父子进程之间通信。

2)直接打开“/dev/zero”设备文件,然后使用这个文件句柄来创建mmap。

上述两种方式最终都会调用到shmem模块来创建共享匿名映射。

3.私有文件映射

创建文件映射时,flags的标志位被设置为MAP_PRIVATE,此时就会创建私有文件映射。私有文件映射最常用的场景是加载动态共享库。

4.共享文件映射
创建文件映射时,flags的标志位被设置为MAP_SHARED,此时就会创建共享文件映射。如果prot参数指定了PROT_WRITE,那么打开文件时需要指定O_RDWR标志位。共享文件映射通常有如下两个场景。

1)读写文件。把文件内容映射到进程地址空间,同时对映射的内容做修改,内核的回写机制最终会把修改的内容同步到磁盘中。

2)进程间通信。进程之间的进程地址空间相互隔离,一个进程不能访问到另一个进程的地址空间。如果多个进程都同时映射到一个相同文件时,就实现了多进程间的共享内存通信。如果一个进程对映射内容做了修改,那么另一个进程是可以看到的。

mmap机制在Linux内核中实现的代码框架和brk机制非常类似,mmap机制如图7.24所示,其中有很多关于VMA的操作。另外,mmap机制和缺页异常机制结合在一起会变得复杂很多。

在这里插入图片描述

小结

待写

vma
libc
malloc
mmap

小讯
上一篇 2025-04-09 23:17
下一篇 2025-04-02 16:22

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/43676.html