您的位置:首页 > 其它

malloc 从哪里得到的内存空间

2014-08-02 00:49 169 查看
在计算机高级编程语言中,C 语言相对来说是一种低级语言,从某种意义上讲,C 语言就是现代的“汇编语言”。说 C 语言低级很大程度上是因为 C 程序员需要手动管理存储,具体反应在公认最难最容易出错的指针上。比如编写 C 程序时,经常会出现莫名奇妙的段错误,并且内存泄漏会在不知不觉的情况下发生,直到耗尽你的计算机内存资源为止。更危险的则是缓冲区溢出,使程序非常容易受到攻击。

发明 C++ 的一个目的是为了提升 C 语言的抽象能力,还有一个目的就是为了消除指针,但 C++ 显然没有做到这一点。Java 则继承了 C++ 的遗愿,彻底的消灭了指针。Java 等高级语言采用严格的内存管理,动态的垃圾回收等机制使得程序员不用去手动管理内存,不用和底层打交道。但 C 语言的地位仍是无法取代的。在必须和底层打交道的时候,就得使用 C 语言(有时候甚至要用汇编语言),比如现代操作系统都是用 C 语言编写的。另外,高级语言在引入高级特征的同时,效率上就会有所损失,在非常强调执行效率的地方,C 语言通常是首选。


动态存储器分配


大多是 C 程序在运行时会需要额外的存储,并且不能事先知道需要的存储大小,这时候使用一种动态存储分配器(dynamic memory allocator)。C 标准库提供了一个称为 malloc 的程序进行显式存储器分配,使用 free 函数来释放已分配内存,另外还有 calloc 和 realloc 两个函数。

malloc 返回指向 size 个字节的存储块的指针;calloc 返回指向 num * size 个存储器块的指针,即分配 num 个 size 大小的连续存储块,并且存储器初始化为 0. 注意: malloc 并不保证得到存储块初始化为 0;realloc 用在当 malloc 分配的存储块大小不够时,分配更大的块,并将数据复制到新的块。以上三个函数在没有多余的存储可以分配时则都返回
NULL 指针。free 函数则释放 ptr 指针指向的存储器块。

虚拟存储器


虚拟存储器是现代计算机系统中对内存的一个抽象概念,它是由硬件和软件协同工作,提供给每个进程一个大的、一致的、私有的地址空间。简单的说,对一个 n 位的计算机系统,虚拟存储器被组织成存放在磁盘上的,2 ** n 个连续字节大小的数组的连续的地址空间,使用内存作为高速缓存。它为每个进程提供了一个一致的地址空间,从而简化了存储器的管理,并且它保护了每个进程的地址空间不被其他进程破坏(注意这里存储器与内存概念上的区别)。

比如对于 32 位的 Linux 系统,虚拟存储器空间为 2 ** 32 即 4G, 即进程的寻址空间位 4G, 其中前 3G 划分给用户使用,后 1G 留给操作系统使用。操作系统将用户 3G 的空间划分成了数个存储器区域,一个区域就是已经分配过的虚拟存储器上连续的一段空间。比如对于进程来说,进程的代码数据区域总是开始与 0×08048000 处向上增长;堆则在接着代码和数据上面;共享库总是从
0×40000000 处开始向上增长;进程桟则总是从 0xbfffffff 处开始向下递减。



这样链接器在生成可执行文件时,不需要知道数据运行时存放的地址,只需按照约定的方式生成虚拟地址,大大的简化了链接的过程。另外,虚拟地址机制还简化了内存共享、存储器分配和程序加载过程。虚拟地址则在运行时,由 CPU 中的内存管理单元(MMU)翻译为物理地址,即数据的实际地址。

malloc 从哪里得到的内存空间


现在我们可以讨论 malloc 是从哪得到的内存空间了。

在写这篇文章时,笔者犯了一个错误,就是讨论的是 linux, 却在 windows 平台上做实验,cygwin 虽然是 linux 的模拟器,但最终调用的还是 windows API. 在同样的配置下,linux (32 位)平台上的到的结果都远大于 windows 平台,但所得到的最终结果并没错,即:glibc 实现的 malloc 同时使用 brk 和 mmap 两个系统调用获取内存,对于大块内存优先使用 mmap.其他 C 库则取决与其 malloc 函数的具体实现。之前看过一篇文章说,windows
的底层是符合 POSIX 的,求高人证实。

2012/3/18

常见的一种说法是,malloc 分配的空间是堆(heap)中的空间,即上图 brk 处开始的地址空间。堆的大小可以使用系统函数 sbrk 和 brk 进行扩展,我们做个实验看看堆最大能都达到多大。

在我的电脑(2G 内存,Windows 系统,cygwin 编译运行)上运行得到的结果为:

显然这远远少于我们的预期,如果是这样的话那内存利用效率也太低了。我们再用 malloc 函数进行一次实验。

在我电脑上运行的结果为:

这次 malloc 分配到了将近 2G 的存储空间,比较符合预期。那么 malloc 的存储空间还从哪里得到的呢?事实上,malloc 还使用 mmap 和 munmap 函数显式的分配存储器空间。我们做类似的实验。

运行结果为:

mmap 的最大空间和 sbrk 最大地址空间相加和 malloc 最大空间很接近,考虑到内存大小的限制,说明 malloc 是同时使用两者的。

结合上次实验和虚拟存储器区域地址分布,我们发现对于 glibc
malloc 的实现,其首先分配的空间是从高地址向低地址发展,分布在共享存储区域和进程桟区域之间。这说明 malloc 首先使用的是 mmap 分配的存储器区域(最起码对于大小为 1MB 的块是如此),并且 mmap 分配的存储器区域是先从高处后从低处的。malloc 后分配的空间地址是从低地址,大约是堆开始的地方,向上增长。

在 glibc 源文件 malloc.c 中,有下面一段话:


mmap 函数


mmap 函数是 UNIX 的一个系统函数,其功能是要求内核创建一个新的虚拟存储器区域(想想我们前面讨论过的存储器区域概念),最好是从地址 start 开始的一个区域,并将文件描述符 fd 指定的文件对象的一个连续的组块映射到这个新的区域。munmap 则删除虚拟存储器的区域。上面使用的 mmap(NULL, 1 << 20, PROT_READ, MAP_PRIVATE|MAP_ANON, 0, 0) 调用,fd 为 0,将创建一个新的包含 1MB 的只读、私有、请求二进制零的虚拟存储器区域。

mmap 内存映射文件


mmap 的另一个作用就是把一个文件的一部分映射到内存中,这样可以想操作内存一样操作文件中的内容,方便随机读和改写文件中的内容。当flag 为 MAP_SHARED 时调用 mmap 可以将内存中的更改写回到磁盘。我写了一个简单的使用
mmap 打开文件的程序包,放在了 github 上,在这里

总结


虚拟存储器是现代计算机系统非常重要的概念之一,它总是默默的、自动的工作着。对于大多数程序员,特别是高级语言的程序员,并不十分需要了解这些概念。但对于每天和存储器打交道的 C 程序员,明白虚拟存储器的概念,知道它是怎么工作的将有非常大的帮助。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐