目录
一、概念理解
进程地址空间(Process Address Space)是指一个进程在运行时可以访问的内存区域。它是操作系统为每个进程分配的独立内存空间,用于存储代码、数据、堆栈等信息。
进程地址空间的主要作用是为每个进程提供一个独立的内存环境,确保进程之间的内存访问互不干扰。这种隔离机制可以防止一个进程意外或恶意地访问其他进程的内存,从而提高系统的稳定性和安全性。
虚拟地址空间:是一种系统层面的内存管理抽象概念,是对整个系统中内存地址的一种虚拟划分方式,它为每个进程提供了一个看似独立、连续且独占的地址空间,让进程在这个虚拟的地址环境中运行,而不必关心实际物理内存的布局和其他进程的内存使用情况。
虚拟地址空间:主要作用是为操作系统提供一种高效的内存管理机制,通过虚拟地址与物理地址的映射,实现内存的动态分配、回收、保护和共享等功能,方便操作系统对整个系统的内存资源进行统一管理和调度,提高内存的利用率和系统的整体性能,它更像是一个为操作系统管理内存而构建的 "大框架"。
来看一个现象


父子进程的全局变量的地址是一样的, 但是变量内容不一样! 能得出如下结论:
- 变量内容不一样, 所以父子进程输出的变量绝对不是同一个变量
- 但是地址值是一样的, 说明, 该地址绝对不是物理地址!
- 在linux下, 这种地址叫做虚拟地址
- 我们在使用C/C++语言所看到的地址, 全部都是虚拟地址! 物理地址, 用户一概看不到, 有OS统一管理。
OS负责将虚拟地址转化为物理地址。
二、理解进程地址空间

我们在学习C语言的时候想必大家对这张图并不陌生, 这里可以理解为每一个进程都认为自己是独占系统物理内存大小, 进程之间,彼此不知道, 不关心对方的存在, 从而实现一定程度隔离!那么操作系统是如何进行管理的呢? 先描述在组织 。所谓的进程虚拟地址空间, 本质是一个内核数据结构(类似PCB)。

三、理解地址空间上的地址

一个进程一个虚拟地址空间。一个进程一个页表。
页表是操作系统里管内存的关键数据结构。它像本 "地址映射字典",记录着虚拟地址和物理地址的对应关系。
进程用的是虚拟地址,当要访问内存时,内存管理单元依据页表把虚拟地址转成物理地址。页表项里有物理页框号等信息,还能标记地址是否有效、访问权限等,帮系统管理内存,保障访问安全。
进程地址空间是pcb构建的结构体,进程地址空间中划分为了几个区域被pcb统一管理起来。每个进程存放的是虚拟地址,某个进程的虚拟地址通过页表映射为物理地址,进而访问内存中的指定变量。这些操作都是由操作系统来完成的。
创建父进程和进程的时候虚拟地址和物理地址的变化

创建父子进程的时候,父子各有一份虚拟地址。父子进程共享代码和数据,此时虚拟地址和物理地址是一样的,父子进程的页表虚拟地址与物理地址的映射关系也相同。当此时对子进程进行修改,子进程发生写时拷贝。子进程的物理地址发生变化,子进程的虚拟地址不会发生变化。只是子进程页表中虚拟地址和物理的地址的映射关系发生了变化。
四、虚拟内存管理
描述linux下进程的地址空间的所有信息的结构体是mm_struct(内存描述符)。每个进程只有一个mm_struct结构,在每个进程的task_struct结构中, 有一个指向该进程的结构。
mm_struct结构是对整个用户空间的描述。每一个进程都会有自己独立的mm_struct,这样每一个进程都会有自己独立的地址空间才能互不干扰。 先来看看有task_struct到mm_struct,进程的地址空间分布的情况。

那既然每⼀个进程都会有自己独立的mm_struct,操作系统肯定是要将这么多进程的mm_struct组织起来的!虚拟空间的组织方式有两种:
- 当虚拟区较少时采取单链表,由mmap指针指向这个链表;
- 当虚拟区间多时采取红黑树进行管理,由mm_rb指向这棵树
linux内核使用 vm_area_struct 结构来表示⼀个独立的虚拟内存区域(VMA),由于每个不同质的虚拟内存区域功能和内部机制都不同,因此⼀个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。上面提到的两种组织方式使用的就是vm_area_struct结构来连接各个VMA,放便进程快速访问。


虚拟内存的管理方案 = struct mm_struct + 页表, 通过页表像物理地址空间进行映射。物理内存的地址是由操作系统进行管理, 用户是看不到这里的, 未初始化的数据 初始化数据和正文代码都是由OS自主完成, 写时拷贝,所以我们刚刚看到的两个进程所对应的地址是一样的其实是虚拟地址, 如果数据只读, 那么子进程就和父进程是共享的, 如果是写入, 则写入时操作系统会另外在物理内存开辟一块地址, 所以物理地址是不一样的。
而对于堆区,栈区堆区是被操作系统动态创建的, 并没有在物理内存中, 而是在虚拟内存中进行区域的扩大和缩小, 而虚拟内存中的堆区和栈区是怎么知道要划分多少区域的大小呢, 其实在进程创建的时候, 进程中的各个区域大小已经被记录下来了, 可以使用test等指令进行查看。
如果一个很大的进程要被加载进来, 从磁盘到内存中是分批加载的, 通过页表的标记为来标记某段代码是否在内存中存在, 另外还有标记为使用来记录执行权限的, 比如code区域为什么是只读取, 其实是页表的rwx标记为标记为只读, 其实对于物理内存并没有权限的概念, 都是由操作系统进行管理的。
五、为什么要有虚拟地址空间
• 安全风险
每个进程都可以访问任意的内存空间,这也就意味着任意一个进程都能够去读写系统相关内存区域,如果是一个木马病毒,那么他就能随意的修改内存空间,让设备直接瘫痪。
• 地址不确定
众所周知,编译完成后的程序是存放在硬盘上的,当运行的时候,需要将程序搬到内存当中去运行,如果直接使用物理地址的话,我们无法确定内存现在使用到哪里了,也就是说拷贝的实际内存地址每一次运行都是不确定的,比如:第一次执行a.out时候,内存当中一个进程都没有运行,所以搬移到内存地址是0x00000000,但是第二次的时候,内存已经有10个进程在运行了,那执行a.out的时候,内存地址就不一定了
• 效率低下
如果直接使用物理内存的话,一个进程就是作为一个整体(内存块)操作的,如果出现物理内存不够用的时候,我们一般的办法是将不常用的进程拷贝到磁盘的交换分区中,好腾出内存,但是如果是物理地址的话,就需要将整个进程一起拷走,这样,在内存和磁盘之间拷贝时间太长,效率较低。
5.1 存在这么多问题,有了虚拟地址空间和分页机制就能解决了吗?当然!
• 地址空间和页表是OS创建并维护的!是不是也就意味着,凡是想使用地址空间和页表进行映射,
也一定要在OS的监管之下来进行访问!!也顺便保护了物理内存中的所有的合法数据,包括各个进程以及内核的相关有效数据!
• 因为有地址空间的存在和页表的映射的存在,我们的物理内存中可以对未来的数据进行任意位置
的加载!物理内存的分配 和 进程的管理就可以做到没有关系,进程管理模块和内存管理模块就完
成了解耦合。 因为有地址空间的存在,所以我们在C、C++语言上new, malloc空间的时候,其实是在地址
空间上申请的,物理内存可以甚至一个字节都不给你。而当你真正进行对物理地址空间访问
的时候,才执行内存的相关管理算法,帮你申请内存,构建页表映射关系(延迟分配),这
是由操作系统自动完成,用户包括进程完全0感知!!
• 因为页表的映射的存在,程序在物理内存中理论上就可以任意位置加载。它可以将地址空间上的
虚拟地址和物理地址进行映射,在进程视角所有的内存分布都可以是有序的。
本篇完,下篇见!
