【操作系统】深入理解Linux虚拟内存

什么是虚拟内存地址?

 

为什么使用虚拟内存地址访问内存?

1.进程隔离:每个进程的虚拟地址空间是独立的,进程之间无法彼此访问。

2.简化内存管理:操作系统负责将虚拟地址映射到物理地址,程序员无需关心物理内存的分配细节。

3.支持更大的地址空间:虚拟内存允许程序使用比实际物理内存更大的地址空间。

4.内存共享:虚拟内存机制允许多个进程共享同一块物理内存。

5.按需分页:虚拟内存支持按需分页机制,即只有在程序访问某块内存时,操作系统才会将其加载到物理内存中。

6.内存映射文件:虚拟内存机制支持将文件映射到内存中,程序可以通过访问内存来读写文件。

Linux 进程虚拟内存空间

32 位机器上进程虚拟内存空间分布

64 位机器上进程虚拟内存空间分布

进程虚拟内存空间的管理

在进程描述符 task_struct 结构中,有一个专门描述进程虚拟地址空间的内存描述符 mm_struct 结
构,这个结构体中包含了进程虚拟内存空间的全部信息。 

当调用fork()创建进程的时候,会创建task_struct结构,并拷贝父进程相关资源到新进程的task_struct结构,其中就包括拷贝父进程的虚拟内存空间mm_struct结构。可以看到,子进程在新创建出来之后它的虚拟内存空间和父进程虚拟内存空间是一样的,直接拷贝过来

  • 通过fork()函数创建出的子进程,它的虚拟内存空间以及相关页表相当于父进程虚拟内存空间的一份拷贝,直接从父进程拷贝到子进程中。
  • 而通过vfork或clone创建出的子进程,会增加父进程虚拟地址空间的引用计数,直接将父进程虚拟内存空间以及相关页表赋值给子进程,这样父进程和子进程虚拟内存空间就变成共享的了(线程共享其所属进程的虚拟内存空间)。也就是说,父子进程之间使用同一个虚拟内存空间,并不是一份拷贝。
  • 是否共享地址空间是进程与线程的主要区别,线程对于内核而言仅仅是共享特定资源的进程。

内核如何划分用户态和内核态虚拟内存空间

进程的内存描述符 mm_struct 结构体中的 task_size 变量,定义了用户态地址空间与内核态地址空间之间的分界线:

 

对于32位系统,用户态虚拟地址范围:0x0000 0000 - 0xC000 000,内核态虚拟地址范围:0xC000 000 - 0xFFFF FFFF,所以task_size为0xC000 000。

对于64位系统,用户态虚拟地址范围:0x0000 0000 0000 0000 - 0x0000 7FFF FFFF F000,内核态虚拟地址范围:0xFFFF 8000 0000 0000 - 0xFFFF FFFF FFFF FFFF,所以task_size为0x0000 7FFF FFFF F000。

内核如何布局进程虚拟内存空间

内核如何管理虚拟内存区域

我们知道,内核是通过一个mm_struct结构的内存描述符来表示进程的虚拟内存空间,并通过task_size域来划分用户态虚拟内存空间和内核态虚拟内存空间。对于划分出的虚拟内存空间,其中包括许多特定区域,如:代码段、数据段......那么这些虚拟内存区域在内核中是如何表示的?

这就需要一个结构体vm_area_struct,正是这个结构体描述了这些这些虚拟内存区域 VMA(virtual memory area)。

定义虚拟内存区域的访问权限和行为规范

常用到的vm_flags:

关联内存映射中的映射关系

针对虚拟内存区域的相关操作

虚拟内存区域在内核中是如何被组织的

在 struct vm_area_struct 结构中,有与组织结构相关的一些属性:

在内核中,内存区域 vm_area_struct 会有两种组织形式,一种是双向链表用于高效的遍历,另一种是红黑树用于高效的查找。

双向链表形式:

我们可以通过 cat /proc/pid/maps 或者 pmap pid 查看进程的虚拟内存空间布局以及其中包含的所有内存区域。这两个命令背后的实现原理就是通过遍历内核中的这个 vm_area_struct 双向链表获取的。 

红黑树形式:

内核虚拟内存空间

32 位体系内核虚拟内存空间布局

直接映射区

这里记住内核态虚拟内存空间的前 896M 区域是直接映射到物理内存中的前 896M 区域,直接映射区中映射关系式一比一映射,映射关系是固定的不会改变。  

直接映射区在物理内存中存的是什么内容?

直接映射区从功能划分的角度,分为 ZONE_DMA 和 ZONE_NORMAL

ZONE_HIGHMEM 高端内存

剩余的 128M 大小的内核虚拟内存空间是如何布局的? 

内核虚拟内存空间中的 3G + 896M 这块地址在内核中定义为 high_memory,high_memory 往上
有一段 8M 大小的内存空洞。空洞范围为:high_memory 到 VMALLOC_START 。 

vmalloc 动态映射区

VMALLOC_START 到 VMALLOC_END 之间的这块区域成为动态映射区。采用动态映射的方式映射物理内存中的高端内存。

和用户态进程使用 malloc 申请内存一样,在这块动态映射区内核是使用 vmalloc 进行内存分配。
vmalloc 分配的内存在虚拟内存上是连续的,但是物理内存是不连续的。通过页表来建立物理内存与虚拟内存之间的映射关系,从而可以将不连续的物理内存映射到连续的虚拟内存上。

由于 vmalloc 获得的物理内存页是不连续的,因此它只能将这些物理内存页一个一个地进行映射,在性能开销上会比直接映射大得多。 

永久映射区

在 PKMAP_BASE 到 FIXADDR_START 之间的这段空间称为永久映射区。在内核的这段虚拟地址
空间中允许建立与物理高端内存的长期映射关系。比如内核通过 alloc_pages() 函数在物理内存的高端内存中申请获取到的物理内存页,这些物理内存页可以通过调用 kmap 映射到永久映射区中。

#define LAST_PKMAP 1024

LAST_PKMAP 表示永久映射区可以映射的页数限制。

固定映射区

 在 FIXADDR_START 和 FIXADDR_TOP 之间的这段空间称为固定映射区。

临时映射区

在内核虚拟内存空间中的最后一块区域为临时映射区,那么临时映射区是用来干什么的呢?

进行文件写入的时候,将用户缓冲区数据拷贝到 page cache 中时,内核不能直接拷贝,因为此时从 page cache 中取出的缓存页 page 是物理地址,而在内核中是不能够直接操作物理地址的,只能操作虚拟地址。

因此就需要使用 kmap_atomic 将缓存页临时映射到内核空间的一段虚拟地址上,这段虚拟地址就位于内核虚拟内存空间中的临时映射区上,然后将用户缓冲区中的待写入数据通过这段映射的虚拟地址拷贝到 page cache 中的相应缓存页中。这时文件的写入操作就已经完成了。由于是临时映射,所以在拷贝完成之后,调用 kunmap_atomic 将这段映射再解除掉。

32位体系结构下 Linux 虚拟内存空间整体布局

64位体系结构下 Linux 虚拟内存空间整体布局

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值