博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux下进程内存管理之malloc和sbrk
阅读量:6178 次
发布时间:2019-06-21

本文共 3521 字,大约阅读时间需要 11 分钟。

之前自己突发兴趣想写一下malloc函数,顺便了解一下进程的内存管理。在写的过程中发现其实malloc只不过是通过调用Linux下的sbrk函数来实现内存的分配,只是在sbrk之上加了一层对所分配的内存的管理罢了,而sbrk以及brk是实现从虚拟内存到内存的映射的。在实际动手写之前先来了解一下Linux下一个进程的内存空间分配。

进程内存空间分配

Linux下每个进程所分配的虚拟内存空间是3G,但实际使用过程中不可能也没有必要为一个进程分配如此大的空间,毕竟内存是很宝贵的资源。当一个进程执行的时候系统为其分配的内存空间主要包括数据段,代码段,栈,堆等等。而malloc所申请的空间就是从堆中分配的。先来看下面这张图:

这就是一个进程的内存空间,其中的Data Segment出要是存放已经初始化的静态数据,而BSS segment则存放为初始化的静态数据,在此之上的堆,然后是栈。值得注意的是,堆和栈的增长方向正好是相反的。现在先通过一段简单的代码来看一下data segment 和BSS segment的分配。

8 #include 
9 #include
10 11 int bssvar; 18 int dataSegmentVar = 1; 19 20 int main() 21 { 22 printf("bssvar:%p, dataSegmentVar:%p,gap:%d", &bssvar, &dataSegmentVar, ((int)&bssvar - (int)&dataSegmentVar)); 23 return 0; 24 }
程序运行的结果是:

bssvar:0x6008c0, dataSegmentVar:0x6008ac,gap:20

可以看到dataSegment在BSS segment之下,他们之间的有20个字节的空间也即data segment的分配空间大小是20字节。但是这个大小并不是固定的,如果程序中的静态未初始化变量大于20个字节,那么data segment的空间会相应地增长。

8 #include 
9 #include
10 11 int bssvar, bssvar1, bssvar2, bssvar3, bssvar4, bssvar5; 12 char c; 13 int dataSegmentVar = 1; 14 15 int main() 16 { 17 printf("bssvar:%p, dataSegmentVar:%p,gap:%d", &bssvar, &dataSegmentVar, ((int)&bssvar - (int)&dataSegmentVar)); 18 return 0; 19 }
程序运行的结果是:

bssvar:0x6008c4, dataSegmentVar:0x6008ac,gap:24

这个时候dataSegment的变量是5个int和一个char,总共的大小是21个字节,而此时dataSegment的大小是24个字节,空间超过20,但是为了对齐,所以不是21而是24。

另外在上图中还有一个值得注意的地方就是program break,这是进程堆的末尾地址。当用户通过malloc函数申请空间的时候,实际就是利用sbrk函数移动program break,使其向上增长,以获得更大的堆空间。所以看起来很神秘的内存申请只不过是移动一个指针而已,哈哈。

不过这只是对简单的原理,里面还有很多细节需要考虑,接下来还是用一段程序来说。

8 #include 
9 #include
10 11 int main() 12 { 13 void* ptr, *ptr1; 14 ptr = sbrk(0); 15 printf("sbrk:%p\n", ptr); 16 ptr1 = malloc(100); 17 ptr = sbrk(0); 18 printf("sbrk:%p, ptr1:%p\n", ptr, ptr1); 19 free(ptr1); 20 ptr = sbrk(0); 21 printf("sbrk:%p\n",ptr); 22 }~
程序中首先用sbrk(0)得到堆部分的末尾地址,然后利用malloc申请了一个100字节长度的空间,这个时候再来看堆空间的末尾地址以及所申请的空间的地址。最后,再释放所申请的空间然后再来看堆空间地址。

程序的运行结果:

sbrk:0x2439000

sbrk:0x245a000, ptr1:0x2439010

sbrk:0x245a000

一开始堆区的末尾地址是0x2439000,但是当利用malloc申请完100字节的空间之后,堆区的末尾地址变为了0x245a000,一下子变大了0x21000。另外还值得注意的就是malloc所申请的空间的起始地址是0x2439010,比一开始的堆末尾地址向后移动了16个字节。这个不难理解,每一段内存空间都需要有一些元数据去管理该空间,所以我猜想这16个字节就是用来记录malloc所分配这100个字节空间的信息,包括大小,状态等等。

那么为什么明明只申请了100个字节的空间,program break却向后移动了这么多?这个也不难理解,总不能每次用户申请一段小的空间都去调用一次sbrk吧,这样的开销太大。所以干脆一次性分配一段大空间出来,除了用户所申请的空间之外,剩下的空间可以用于之后的malloc空间申请。来看下一段程序:

8 #include 
9 #include
10 11 int main() 12 { 13 void* ptr, *ptr1; 14 ptr = sbrk(0); 15 printf("sbrk:%p\n", ptr); 16 ptr1 = malloc(100); 17 ptr = sbrk(0); 18 printf("sbrk:%p, ptr1:%p\n", ptr, ptr1); 19 ptr1 = malloc(100); 20 ptr = sbrk(0); 21 printf("sbrk:%p, ptr1:%p\n",ptr, ptr1); 22 free(ptr1); 23 ptr = sbrk(0); 24 printf("sbrk:%p\n",ptr); 25 }
运行结果:

sbrk:0x933000

sbrk:0x954000, ptr1:0x933010

sbrk:0x954000, ptr1:0x933080

sbrk:0x954000

可以看到,尽管通过malloc函数申请了两块100字节的空间,但是program break并未因此而移动两次。另外,第一块空间和第二块空间的地址相差的不是100个字节而是112个字节,究其原因估计还是因为对齐的问题吧。

通过上文的讲解,我们发现,其实malloc也没有这么神秘了,它只不过就是利用sbrk来申请了一段空间罢了。不过除了申请空间之外,还需要管理这些空间才是malloc真正核心的地方。这些问题将在下一篇博文《自己动手写malloc》中详细讲解。

关于sbrk还有brk的用法网上有一大堆,这里就不细讲了,推荐一篇文章吧:

你可能感兴趣的文章
洛谷 1373 小a和uim之大逃离
查看>>
一不小心把win10的秘钥卸载了解决方法
查看>>
SilverLight之向后台请求数据-WebClient
查看>>
HDU Problem 1260 Tickets 【dp】
查看>>
STL map容器常用API
查看>>
队列的顺序存储---顺序队列
查看>>
Delphi 读取 c# webservice XML的base64编码图片字符串转化图片并显示
查看>>
第三天
查看>>
connector for python
查看>>
等价类划分的应用
查看>>
Web Service(下)
查看>>
trigger()
查看>>
nvm 怎么安装 ?
查看>>
Java VM里的magic
查看>>
[Node.js]Domain模块
查看>>
Linux操作系统文档
查看>>
利用Tensorflow训练自定义数据
查看>>
c++官方文档-枚举-联合体-结构体-typedef-using
查看>>
[题解]UVA11029 Leading and Trailing
查看>>
利用vue-gird-layout 制作可定制桌面 (一)
查看>>