C语言——操作系统内存分配过程

内存分配机制的发展过程:

第一阶段——程序直接操作物理内存。

某台计算机总的内存大小是128M,现在同时运行两个程序A和B,A需占用内存10M,B需占用内存100。计算机在给程序分配内存时会采取这样的方法:先将内存中的前10M分配给程序A,接着再从内存中剩余的118M中划分出100M分配给程序B

1.进程地址空间不隔离。

进程之间可以互相修改内存,导致不安全,可能会出现一个进程出现bug,导致另一个本来好好的进程挂掉。

2.内存使用效率低。

在A和B都运行的情况下,如果用户又运行了程序C,而程序C需要30M大小的内存才能运行,而此时系统只剩下18M的空间可供使用,所以此时系统必须在已运行的程序中选择一个将该程序的数据暂时拷贝到硬盘上,释放出部分空间来供程序C使用,然后再将程序C的数据全部装入内存中运行。

3.程序运行的地址不确定。

当内存中的剩余空间可以满足程序C的要求后,操作系统会在剩余空间中随机分配一段连续的20M大小的空间给程序C使用,因为是随机分配的,所以程序运行的地址是不确定的。

虚拟内存
虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。目前,大多数操作系统都使用了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。

第二阶段——分段(解决了第一阶段的第1和第3个问题)

在进程和物理内存增加一个中间层,利用一种间接的地址访问方法访问物理内存。程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。这样,只要操作系统处理好虚拟地址到物理内存地址的映射,就可以保证不同的程序最终访问的内存地址位于不同的区域,彼此没有重叠,就可以达到内存地址空间隔离的效果。

第三阶段——分页(解决了第一阶段的第2个问题)

将地址空间分成许多的页。每页的大小由CPU决定,然后由操作系统选择页的大小。目前Inter系列的CPU支持4KB或4MB的页大小,而PC上目前都选择使用4KB。

对比分段

在分段的方法中,每次程序运行时总是把程序全部装入内存,而分页的方法则有所不同。分页的思想是程序运行时用到哪页就为哪页分配内存,没用到的页暂时保留在硬盘上。当用到这些页时再在物理地址空间中为这些页分配内存,然后建立虚拟地址空间中的页和刚分配的物理内存页间的映射。

举例说明加载可执行文件的过程
一个可执行文件(PE文件)其实就是一些编译链接好的数据和指令的集合,它也会被分成很多页,在PE文件执行的过程中,它往内存中装载的单位就是页。当一个PE文件被执行时,操作系统会先为该程序创建一个4GB的进程虚拟地址空间。前面介绍过,虚拟地址空间只是一个中间层而已,它的功能是利用一种映射机制将虚拟地址空间映射到物理地址空间,所以,创建4GB虚拟地址空间其实并不是要真的创建空间,只是要创建那种映射机制所需要的数据结构而已,这种数据结构就是页目和页表。

.当创建完虚拟地址空间所需要的数据结构后,进程开始读取PE文件的第一页。在PE文件的第一页包含了PE文件头和段表等信息,进程根据文件头和段表等信息,将PE文件中所有的段一一映射到虚拟地址空间中相应的页(PE文件中的段的长度都是页长的整数倍)。这时PE文件的真正指令和数据还没有被装入内存中,操作系统只是根据PE文件的头部等信息建立了PE文件和进程虚拟地址空间中页的映射关系而已。当CPU要访问程序中用到的某个虚拟地址时,当CPU发现该地址并没有相相关联的物理地址时,CPU认为该虚拟地址所在的页面是个空页面,CPU会认为这是个页错误(Page Fault),CPU也就知道了操作系统还未给该PE页面分配内存,CPU会将控制权交还给操作系统。操作系统于是为该PE页面在物理空间中分配一个页面,然后再将这个物理页面与虚拟空间中的虚拟页面映射起来,然后将控制权再还给进程,进程从刚才发生页错误的位置重新开始执行。由于此时已为PE文件的那个页面分配了内存,所以就不会发生页错误了。随着程序的执行,页错误会不断地产生,操作系统也会为进程分配相应的物理页面来满足进程执行的需求。

分页方法的核心思想就是当可执行文件执行到第x页时,就为第x页分配一个内存页y,然后再将这个内存页添加到进程虚拟地址空间的映射表中,这个映射表就相当于一个y=f(x)函数。应用程序通过这个映射表就可以访问到x页关联的y页了。

内存管理

最小存储单位是一个字节(1B),最小管理单位是一页(4KB),虚拟内存地址连续时物理内存地址可以不连续,即使一次分配6000字节(不到两页也分配两页),两个内存页物理地址可能不连续。多次申请内存时,如果之前分配的页内存没用完,则不再分配,除非之前分配的内存页用完才继续映射新的一页。硬盘也是如此(硬盘上称为Block块)。

例如test文件里面只有4个字符,却也需要占4K的大小。
C语言——操作系统内存分配过程_第1张图片

C语言中的malloc函数

#include
#include
int main(void){

    char * p = malloc(0);

    printf("%d,%d\n",*(p),*(p+32*4096));//打印第33页的内容,被默认初始化。

    free(p);
}

运行结果如下:
C语言——操作系统内存分配过程_第2张图片

#include
#include
int main(void){

    char * p = malloc(0);

    printf("%d,%d\n",*(p),*(p+33*4096));//打印第4页的内存位置,报段错误

    free(p);
}

运行结果如下:段错误是因为超过的虚拟内存地址并没有映射(分配)物理内存。
C语言——操作系统内存分配过程_第3张图片

free函数

malloc()给变量分配给变量内存时,除了数据区域外,还额外需要保存一些信息。底层有一个双向链表保存额外信息。malloc()给指针了12个字节,其中4个字节存放数据,另外8个存放其他信息或者空闲,如果将12个字节中前(低位)几个字节清空或者进行修改,free就可能出错,因为free只有首地址不能释放,还得需要额外附加信息(如malloc分配的长度)

修改malloc申请的内存的指针位置,free会无法正常释放内存。
C语言——操作系统内存分配过程_第4张图片

上图中的:
这里写图片描述

C语言——操作系统内存分配过程_第5张图片

此处又再次证明了malloc每一次分配的的内存页是33页。

修改malloc申请的内存的指针的前几位的值,free也会无法正常释放内存。

C语言——操作系统内存分配过程_第6张图片

参考文章https://blog.csdn.net/ljabclj/article/details/44155221

你可能感兴趣的:(c语言)