虚拟地址与物理地址
目录
页表
虚拟地址(struct mm_struct)
写时拷贝详解
虚拟地址是笼罩在物理地址的一层地址,我们先来看看虚拟地址长什么样的。
我们创建了子父两个进程,然后各自打印gval地址并只让子进程自增。看效果。
可以看到子进程的gval都加到106了,父进程还是原先的100,这很正常,因为进程是独立的并行的,每一个进程都调用的是自己的gval,但是为什么两个进程的gval值不一样,物理地址肯定不一样呀,为什么这里gval的地址一样呢,或者是说两个gval既然不在一个进程为什么地址是一样的。
这个就是虚拟地址,我们也叫他线性地址。虚拟地址使得每个进程认为自己独占整个系统物理内存的大小,使其高度隔离,我们的操作系统是不可能让进程直接接触物理地址的,因为这样对自己的内存不好。
我们来看一下进程的地址空间(虚拟地址),所谓的虚拟地址空间本质上就是一个内核数据结构对象,类似于PCB,我们成为结构体,struct mm_struct我们可以看到虚拟地址被分为很多块内存空间,栈区,堆区什么的,这些是怎么划分的呢,区域空间划分就是分割出区域快,然后保存区域分割的头尾的标识符,int start1 int end1.。。。
地址的本质就是一个数字,可以被保存在字节里面,空间范围内的地址我们可以随便用,暂时不需要记录它的地址的。
那我们知道了虚拟地址集合是在物理地址的前面的,但是一个进程总得加载到内存吧,这时就需要触碰到物理地址,那这两个地址是如何关联起来的呢,我们是通过一张页表来实现的。
页表
页表是完成虚拟地址和物理地址相互映射的中间体,一个虚拟地址假设为0x1111,那对应在页表中可能就映射在物理地址的0x1243上了,所以虚拟内存管理方案=struct mm_struct + 页表,那是什么虚拟地址都会有对应的页表来进行映射对应的物理地址吗,这不可能的,页表会从进程是否存在,和此进程是否有rwx权限来判断,如果该进程没有w权限缺强制要在内存中写入,页表就不会允许这种行为,就不会有物理地址了,如果这个进程不存在或者没有在用或者没在内存中,页表也不会创建物理地址,终归一句话,页表是在你进程有在使用的时候才创建物理地址,这样更省空间。
对常量区的只读确写入操作会报错是系统层面的报错,在代码层面由于代码本身没有问题所以不会有事,所以加上const指示是为了让代码层面也感知到错误的发生。
我们再回到之前的那个子父进程的程序,为什么子进程和父进程打印的gval的地址是一样的,可以看出子进程和父进程的物理地址都指向了gval,指向了同一块,说明子进程一定是父进程的拷贝,子进程是依附于父进程而生的,所以子进程在父进程之后创建然后拷贝了父进程的虚拟地址结构体,页表,这个拷贝页表的动作相当于浅拷贝了,这样父子映射到了同样的代码内存区域,子父代码共享,但是这是在子进程不修改变量的时候,数据是共享的,如果子进程修改了gval的值直接触发操作系统的写时拷贝,进程数据被挂起并分批加载到内存。这些可执行程序的代码和数据可以加载到内存的任意位置处,极少部分被加载到特点位置处,最后页表创建物理内存不需要内存管理知道。
上面这个图很重要!!!
虚拟地址(struct mm_struct)
首先这个结构体是被OS动态创建的,所以上面对应的栈区等的空间都是可以原地自增的,在可执行程序在编译的时候,各个区域的大小信息就已经有了,你的进程所开辟的是虚拟空间,不会直接接触内存的,所以虚拟地址这个结构体里面的元素从可执行程序里面来,有分段,也有属性其中包括操作系统的进程管理和编译器,可执行程序也有关系的。
为什么要有虚拟地址:
1。虚拟地址+页表是为了保护内存的
2。进程管理和内存管理在系统层面进行了耦合让进程以统一的视角看待物理内存,使内存管理从无序变成有序。配置虚拟地址空间都是OS系统自动完成的。
所以进程最终可以认为是由上面这4部分构成的。
写时拷贝详解
在操作系统中,写时复制(Copy-on-Write,COW) 是一种优化内存管理的策略,常见于进程创建时。COW 允许父进程和子进程共享内存空间,直到某一进程尝试修改共享内存时,操作系统才会将这些内存页复制到新的位置,从而使得父进程和子进程各自拥有独立的内存副本。
子进程和父进程原本是共用一块物理内存空间的,这块空间刚开始是标记为只读的,当子进程试图修改这个空间的数据时,由于这个空间原本是只读的,就会触发页面错误,当某个进程试图修改一个共享的内存页时,会发生页面错误。操作系统此时检查该页是否已经被标记为只读,并发现该页正处于共享状态。为了保持写时复制的语义,操作系统会分配一块新的物理内存页,并将原始页的内容复制到新的内存页中,这个新的物理内存页会被映射到修改该页的进程的虚拟内存地址空间,并且该页会被标记为可读写。原来的共享页则仍然对另一个进程保持只读。
经过复制和映射之后,修改过的进程就拥有了该页面的一个独立副本,可以自由修改,而不会影响另一个进程。此时,父进程和子进程就拥有各自独立的内存副本,而这种副本创建是惰性且延迟的,只有在真正需要修改时才发生。
反正详细过程就是: