内存管理与分页机制

来源:互联网 发布:长春网络推广 编辑:程序博客网 时间:2024/05/07 09:02

一、问题提出:

我们经常会使用malloc()以及free()函数进行堆区内存申请与释放。那么你是否会这样做:

int * p = malloc(0);/*malloc分配了0个字节吗,如果是那么p指向谁呢,是NULL吗*/free(p);/*假如malloc分配了0个字节,p指向了NULL,那么free(NULL)不会出现段错误吗*/

我想很少有人这样做,因为除了喜欢“打破砂锅问到底”,或者经常使用测试一些特例的方法去学习的的人,一般人不会注意到这个问题到底是怎样的结果。

我们可以做一个简单的测试:

/*********************************2016年12月25日16:09:44**测试环境:Redhat 6.4**测试int * p = malloc(0);p是否指向NULL*******************************/#include<stdio.h>#include<stdlib.h>int main(void){    int * p = (int *)malloc(0);    printf("%d,%d\n",*(p),*(p+1024));    free(p);    int * q = NULL;    printf("%d,%d\n",*(q),*(q+1024));    return 0;}

这里写图片描述

在测试中我们可以看到,q指针指向NULL,所以对其取值会发生段错误,而对于p来说,虽然它申请了0字节的空间,但是free()释放以及取值时都不会发生段错误(读者可以拆开测试,否则有人会怀疑是free()引发的的段错误,而不是*q取q值时引发的段错误)。由此我们可以得知,malloc(0)分配的不是0个字节,p也不是指向NULL。那malloc(0)分配了几个字节?并且为什么*(p+1024)也不会越界发生段错误呢?这就是内存的分页机制与内存管理所决定。

二、虚拟内存(Virtual Memory)与物理内存(Physical memory):

1、内存类型细分:

内存由于用途不同,分类也不尽相同,一般我们对于内存的分类也就这几种:栈区(stack area)、堆区(heap area)、全局区(静态区)(存放全局变量与静态变量static)、BSS段(存放未初始化的全局变量,未初始化的全局变量默认值为0)、文字常量区、数据区(data area)、代码区(code area)等。

关于BSS段存储的未初始化全局变量的值,我们可以测试一下,如下(i为未初始化的全局变量,其值为0):
这里写图片描述

而关于这些不同类型的内存地址区域,其所在位置如下图所示:
这里写图片描述

2、Linux内存分配时的maps文件:

关于上面所讲内存划分的各段地址位置关系,我们可以用程序进行测试:

# include<stdio.h># include<stdlib.h>int num1;/*BSS段*/int num2 = 2;/*全局区*/char * str1 = "str1";/*文字常量区*/int main(void){    printf("%d\n",getpid());/*获取当前进程id号*/    int num3 = 3;/*栈区*/    static int num4 = 4;/*全局区*/    const int num5 = 5;/*栈区*/    char * str2 = "str2";/*文字常量区*/    char str3[] = "str3";/*栈区*/    int * p = malloc(sizeof(0));/*&p在栈区,p在堆区*/    printf("num1:%p\nnum2:%p\nnum3:%p\nnum4:%p\nnum5:%p\n",&num1,&num2,&num3,&num4,&num5);    printf("str1:%p\nstr2:%p\nstr3:%p\n",str1,str2,str3);    printf("&p:%p\np:%p\n",&p,p);    while(1){}/*死循环以保证进程不会结束,方便查看/proc/pid/maps文件*/    free(p);    return 0;}

我们可以查看/proc/pid/maps文件(pid表示以进程id号命名的文件名),其中有该pid的内存分配的详细情况。注意:proc下各个进程目录占磁盘大小都是0(读者可自行测试),因为其数据都存在于内存,该文件只是一个映射。实际不存在,如果该进程消亡,pid这个目录及其子目录将会消失。所以可以用循环测试,并且maps文件中的内存地址为已经映射了物理内存的虚拟内存地址。我们先运行程序,如下所示(获得当前进程pid为5052):

这里写图片描述

我们可以”vim /proc/5052/maps”查看该文件下的内存分配情况
cd proc/5052:
这里写图片描述
vim maps:

这里写图片描述

3、内存地址映射关系:

每个进程都先天设定了4G的虚拟内存地址(不是真实的地址,只是一个编号)。虚拟内存开始时不对应任何内存,直接使用会引发段错误,不进入内核就接触不到物理内存地址,只会接触到虚拟内存地址。虚拟内存地址必须映射物理内存(或者硬盘上的文件)以后才能存储数据(数据存储在物理内存上,打印地址为虚拟内存地址)。而内存分配其实就是虚拟内存地址映射物理内存的过程,内存回收则是解除映射关系的过程。
虚拟内存中,0~3G是用户控制,3~4G是内核空间。用户层不能直接访问内核层,可以通过Unix/Linux的系统函数访问内核层。我们通常所讲内存地址,其实都不是真正意义上的物理内存(PC机上内存硬件)的地址,而是虚拟内存地址。两个不同的进程,当其某个变量地址一样(虚拟),但是物理地址并不一样。

映射关系如图所示(A、B进程均已映射物理内存,而C进程未映射物理内存,注意:虚拟内存一般并不会全部映射):

这里写图片描述

对于不同进程的同一地址,是虚拟地址而不是物理地址我们可以做个测试:
这里写图片描述

由于两个不同进程有各自的虚拟内存,打印的进程1的内存地址为虚拟内存地址,而进程2的相同的虚拟内存地址,不能操作进程1的虚拟内存地址已映射的物理内存地址,并且进程2的*p并没有映射物理内存地址,所以进程2运行出现段错误。

三、内存分页机制(Memory Paging Mechanism)与malloc详解:

1、内存管理页机制:

最小存储单位是一个字节(1B),最小管理单位是一页(4KB),虚拟内存地址连续时物理内存地址可以不连续,即使一次分配6000字节(不到两页也分配两页),两个内存页物理地址可能不挨着。多次申请内存时,如果之前分配的页内存没用完,则不再分配,除非之前分配的内存页用完才继续映射新的一页。getpagesize()可以获取当前内存页的大小。硬盘也是如此:即使一个.txt文件中只有一个“a”字母,其大小为1B而其占用大小为4K。

如图所示:test.txt文件中仅仅有14个’a’字符,但是现实其占用磁盘大小仍然是4K(一页)
这里写图片描述

Windows下也有相同的分页机制(文件大小小于实际占用空间大小):
这里写图片描述

2、为什么要有这种机制(一次性最少分配1页(4K))?

一句话:为了方便管理。
不可能进程每次申请一次系统就需要向其分配一次。(就像你和弟弟管妈妈要1块钱买辣条,你妈妈给了你俩十块钱说:“一周内都不要给我再要”,其实就算你一周内再向她要,妈妈也会给你,她只是不想你们俩不停地要而已,这就是管理(只不过我管我妈要1块,她好像给我5毛钱…….))。系统也是这样,它一次分配至少一页,在你(进程)没用完之前它都不会再给你分配,而当你用完分配的内存之后,就需要重新分配了。

就拿malloc来说,第一次malloc(0)时一次性映射33个内存页(Redhat6.4),关于这点我们测试一下:

这里写图片描述

只malloc()了一次,分配了33页,对前33页操作不会出错,但是一超过33页(p相对位置不为0,p+33*1024为虚拟地址的第34页)就产生了段错误,因为超过的虚拟内存地址并没有映射(分配)物理内存。

3、malloc(0)分配了多少内存?

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

就拿我们测试内存划分时的例子来说(仅借用地址划分关系,程序不同):
这里写图片描述

p申请了0字节,但是系统分配了0X08fa9000~0X08fca000(共0X08fca000-0X08fa9000=21000H=(2^17+2^12)Byte=(2^7+2^2)KB=(2^5+1)页=33页)

这里写图片描述

而p指向的地址为0X08fa9008(偏移了8个字节),直接指向高四位的4个字节(共12字节)。
这里写图片描述
如果我们将低八位的数据进行清空或者修改(修改任意个字节),free就有可能失败,测试如下:
这里写图片描述
将p的低四位数据清零之后,附加信息出错,free失败,出错结果如下:
这里写图片描述

0 0
原创粉丝点击