进程地址空间 -- 分页式,分段式,段页式内存管理与缺页中断

平台

32位操作系统

进程地址空间

计算机物理内存的大小是固定的,就是计算机主板内存槽上的实际物理内存,cpu可以直接进行寻址,物理内存的容量是固定的,但是寻址的空间取决于cpu地址线的数量。在32位系统上,线性地址空间可达4G(2^32);这4G一般是按照3:1的比例进行分配,用户进程享有3G的空间,而内核独自享有剩下的1G内存
在这里插入图片描述
但是我们所看到的这个空间并不是真是存在的而是计算机操作系统为我们描述出来的一个虚拟的空间。
有兴趣验证的朋友可以通过以下代码进行验证

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 0;
}
else if(id == 0){ //child
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
}else{ //parent
printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}

改动

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 0;
}
else if(id == 0){ //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取
g_val=100;
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
}else{ //parent
sleep(3);
printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}

我们会发现父子进程输出的不同地址中却存有相同的变量。这就可以说明父子进程各有各的虚拟地址空间。
如图:
在这里插入图片描述
我们知道,虚拟空间是对一个内存空间的描述(mm_struct),操作系统通过对mm_struct为进程描述了一个完成的连续的线性的地址空间,有效的防止进程直接对物理地址的访问。

为什么要杜绝进程直接对物理空间进行访问?

  1. 进程的代码数据都是连续的内存地址,直接对物理空间访问会造成内存的浪费。
  2. 直接访问内存会因为缺乏内存访问控制而导致不安全。

在这里插入图片描述

内存管理

操作系统通过虚拟地址与物理地址的映射实现了内存管理。

内存管理主要包括虚地址、地址变换、内存分配和回收、内存扩充、内存共享和保护等功能。

我们在这里
下面罗列出的几个经典内存管理方式

分页式内存管理

将程序的逻辑地址空间划分为固定大小的页(page),而物理内存划分为同样大小的页框(page frame)。程序加载时,可将任意一页放人内存中任意一个页框,这些页框不必连续,从而实现了离散分配。在页式存储管理方式中地址结构由两部构成,前一部分是页号,后一部分为页内地址w(位移量);
在这里插入图片描述
页号:页表中页表项的编号
页内偏移:变量首地址相对于页表项起始地址的偏移量
通过页号与物理块号对应,以及页内偏移大小,我们就可以计算出某一虚拟地址所对应的物理地址了
一般情况下 以4096个字节为一页,以4G内存为例子,内存中就有4G/4096页,也就是2^20页,
这时候我们发现4G对应的是2^32次方,所以一个32内存地址中高20位是页号,底12位为页内偏移量。
优点:

  • 没有外碎片,每个内碎片不超过页大比前面所讨论的几种管理方式的最大进步是,

  • 一个程序不必连续存放。

  • 便于改变程序占用空间的大小(主要指随着程序运行,动态生成的数据增多,所要求的地址空间相应增长)。

缺点:

  • 要求程序全部装入内存,没有足够的内存,程序就不能执行;
分段式内存管理

在段式存储管理中,将程序的地址空间划分为若干个段(segment),这样每个进程有一个二维的地址空间。在前面所介绍的动态分区分配方式中,系统为整个进程分配一个连续的内存空间。而在段式存储管理系统中,则为每个段分配一个连续的分区,而进程中的各个段可以不连续地存放在内存的不同分区中。程序加载时,操作系统为所有段分配其所需内存,这些段不必连续,物理内存的管理采用动态分区的管理方法。

简单来说,对于一个进程而言,其中不同的数据可能在栈上,可能在堆上,于是基于这种特点引进了分段式内存管理。
当然, 虚拟地址和物理地址之间是通过段表来进行映射的。可以参考分页式内存管理。

分段分页的异同
  • 需求 是信息的物理单位,分页是为了实现离散分配方式,以减少内存的碎片,提高内存的利用率。或者说,分页仅仅是由于系统管理的需要,而不是用户的需要。段是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了更好地满足用户的需要。

  • 指令 一条指令或一个操作数可能会跨越两个页的分界处,而不会跨越两个段的分界处。

  • 大小 页大小固定且由系统决定,把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的。段的长度不固定,且决定于用户所编写的程序,通常由编译系统在对源程序进行编译时根据信息的性质来划分。

  • 逻辑地址表示 页式系统地址空间是一维的,即单一的线性地址空间,程序员只需利用一个标识符,即可表示一个地址。分段的作业地址空间是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。

  • 查找 比页大,因而段表比页表短,可以缩短查找时间,提高访问速度。

段页式内存管理

段页式内存管理,结合了分页分段的优点,在进行分段的基础上又进行了分页式的内存管理。
唯一缺点就是要求程序全部装入内存,没有足够的内存,程序就不能执行

虚拟地址空间是一个mm_struct结构体,是操作系统为进程描述的一个完整线性以及连续的内存空间,实现了进程在物理地址的离散式存储,提高了内存访问率。通过页表段表对内存进行控制。

缺页中断

什么是缺页中断:
进程线性地址空间里的页面不必常驻内存,在执行一条指令时,如果发现他要访问的页没有在内存中(存在位为0),那么停止该指令的执行,并产生一个页不存在异常,对应的故障处理程序可通过从外存加载加载该页到内存的方法来排除故障,之后,原先引起的异常的指令就可以继续执行,而不再产生异常。
例子:
一般情况下,一个进程想要运行,数据就要加载到内存中,但是内存的额大小有限,当使用的进程过多的时候,内存不够用了,操作系统就会将某些进程先转移到磁盘的交换分区上(磁盘分为交换分区和文件系统分区–存储文件),当我们使用的时候,重新加载到内存上,这就是一种缺页中断。

页面调度算法:
页式虚拟存储器实现的一个难点是设计页面调度(置换)算法,即将新页面调入内存时,如果内存中所有的物理页都已经分配出去,就要按某种策略来废弃某个页面,将其所占据的物理页释放出来,好的算法,让缺页率降低。常见的有先进先出调度算法,最近最少调度算法,最近最不常用调度算法。

猜你喜欢

转载自blog.csdn.net/ifwecande/article/details/106330299