进程的虚拟地址空间
再讲进程的虚拟地址空间之前,我们先来了解一下程序的地址空间是什么?
程序的地址空间:
这是程序地址空间。而程序是一个个死的代码,所以叫做程序地址空间不合适。程序运行起来之后,才会占用内存。所以我们称为进程地址空间。
那么我们思考一下,进程地址空间每一个进程都有一个,凭什么每一个进程都会有4G的大小,那么是内存不够用的。
父子进程中的数据独有,代码共享,但是通过代码实现可以在父子进程中实现对全局变量进行修改,也可以通过打印的方式看出父子进程中的同一变量的地址相同,那为什么一个一块空间可以有两个值 ?
其实,在程序中所获得的变量地址都是假地址。因为这个地址空间都是假的。所以我们叫它 进程的虚拟地址空间。
进程虚拟地址空间的含义:
进程虚拟地址空间:是操作系统为进程描述的一个假的地址空间。目的是为了让进程认为自己拥有一块连续的线性的完整的地址空间。但是实际上一个进程使用的内存并非连续存储的,而是通过页表映射了虚拟地址与物理地址之间的关系。为了让进程通过页表获取物理地址,进而实现数据的离散式存储。
进程的虚拟地址空间的作用:
我们知道数据的对应关系,就可以离散式存储,利用内存空间。但是程序运行起来还是需要连续的地址,操作系统为一个程序提供一个线性虚拟的地址,在虚拟的地址中,程序的所有数据都是线性连续存储,但是在物理内存中,程序运行时所要的数据都是离散式存储的,所以操作系统在物理内存与虚拟内存之间建立了一个映射关系,提高了内存的利用率。
这个映射关系就是页表。所以虚拟地址空间可以通过页表直接映射到物理地址空间,这样就可以寻找程序运行时所要的数据变量。
直接在物理内存上修改,不仅内存利用率低,而且会缺乏内存的访问控制。大家都可以对同一块内存上修改,这样操作不安全,那么就会缺乏访问控制。
-
页表可以进行内存的访问控制,页表可以对每个虚拟地址进行权限标记。
- 页表中可以规定物理内存上的数据是否可以读写,页表中若查到你要修改只读数据,那么就是内存访问错误。
- 当对nullptr空指针访问时,程序发生段错误。物理地址上0号位置实际上是有空间的。 其实nullptr是一个虚拟地址,当你访问nullptr虚拟地址时,不能读写,是虚拟地址空间规定。
进程的虚拟地址空间都是通过页表来访问自己映射的那块内存,其它的程序不能访问,这样就保证进程之间相互独立,独立就代表着稳定。
我们接下来看父子进程中如何通过虚拟地址空间实现数据独有?
虚拟地址空间其实也是我们描述出来的,通过mm_struct结构体(PCB),记录每一个分区段的位置和大小,这个mm_struct * 也就是进程的内存指针 。因为虚拟地址空间是靠很多区域的位置信息描述出来的,所以有时候也叫做内存描述符
进程之间为什么能通过虚拟地址空间空间实现数据独有?
一开始的时候,通过PCB描述的进程1和进程2上的变量val都是100,在进程1和进程2的虚拟地址空间上都是相同的位置,所以我们在程序中看到的变量val的地址相同,其实是虚拟地址,假地址。此时通过页表映射到物理内存中的相同位置,但是当进程1改变val值的时候,就不一样了。
注意:这里的进程1和进程2也可以是父子进程
当进程1通过页表映射要修改val值的时候,页表会根据权限,知道进程1要修改数据,所以会先在物理内存上重新开一块空间,并且将val值拷贝到新空间上,此时更新进程2的页表映射,使进程2上的val值映射到物理内存上新的位置。在此过程中,进程1和进程2的虚拟地址空间上val的空间是不会改变的。所以进程间就实现了数据独有。
写时拷贝技术:等到需要更改数据时,再去申请内存,重新拷贝一份,并且更改页表的对应关系。
写时拷贝的原因是:提高进程创建的效率,有可能多个进程只是读数据,并不修改这个数据。所以用的时候再去开辟新的内存,拷贝数据。提高效率。
我们再来捋一下如何创建子进程,使得数据独有?
创建一个子进程的流程: 写时拷贝技术
- 创建pcb
- 拷贝父进程中的pcb的数据(拥有相同的虚拟地址空间,相同的页表)
- 父子进程一开始映射同一块物理内存
- 等到物理内存修改时,才为子进程重新开辟内存,拷贝数据。
为什么代码共享而数据独有 ?
因为进程的虚拟地址空间中代码段是相同的,不会修改,所以一直映射同一块物理内存中。
虚拟地址的构成:页号 + 页内偏移
页面: 4096 物理内存:4G
所以一共有1024*1024个页表项,页表项也是结构体数组,如果一个页表要开这么大一个空间,那就太浪费了。因为用不到这么多的映射关系,所以就消耗资源。
二级页表的引入:
所以有了二级页表,这块页表用到多少页表项,这块页表的内存通过另一块页表来映射,用到了才为页表分配这块内存,用不到就不分配了,提高内存利用率。
三种内存管理方式
内存管理:
分段式:对程序比较友好,但是内存利用率不高
分页式:提高内存利用率
段页式:目前采用的方案
分段式:
分页式:
段页式:
物理地址的计算方法: 虚拟地址/页面大小 = 页号。 通过页表得到块号,块号*块大小 + 页内偏移(虚拟地址%页面大小)