码迷,mamicode.com
首页 > 其他好文 > 详细

调用malloc 之后发生了什么????

时间:2018-08-15 13:32:33      阅读:263      评论:0      收藏:0      [点我收藏+]

标签:包含   增加   一段   str   不同   nmap   记录   移动   并且   

4.5.1 堆管理的相关库函数

在ISO C中规定了三个动态分配内存的函数,分别是: 
        void *malloc(size_t size); 
        void *calloc(size_t nmemb, size_t size); 
        void *realloc(void *ptr, size_t size); 
在这三个库函数中,大家最常用的就是malloc。调用malloc函数可以分配长度为size的内存空间,内存空间的数据没有初始化。其返回值就是指向这段被分配空间的指针。calloc和malloc相似,只不过返回的是一个有nmemb个元素的数组,每个元素的大小是size bytes。也就是分配了nmemb*size大小的内存空间,并将空间内的数据都初始化为0。


4.5.2 堆管理的相关系统调用

malloc系列函数的实现与Linux中提供的两个基本调用是分不开的: 
    int brk(void *end_data_segment); 
    void *sbrk(intptr_t increment); 
brk: brk()的作用和它的名字一样用于打破系统给进程设置的访存限制,用于设定进程的内存边界。如前文所述,堆是从虚存低地址向高地址增长。brk()用于设定堆访存的上限,也就是堆顶。就像是一个盖子,随着堆的分配释放而上下移动。在这个盖子之下的内存空间,操作系统都认为是合法的。与brk()相关的还有一个sbrk()函数,sbrk()不是系统调用,而是一个库函数。sbrk(+/-n)意味着将当前访存的上限增加/减少n个字节。 
    
    void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); 
    int munmap(void *addr, size_t len); 
mmap: mmap()的使用较brk()更为灵活,用途也更为广泛。可以将虚拟内存地址映射到文件,共享内存等,方便用户以访存的方式读写文件,完成进程间通信。当然映射后虚存地址就变为合法的了。所以在堆分配的时候,常常借用mmap能向进程添加可访问虚存空间的能力,加之并不需要读写文件等别的要求,所以一般用匿名映射(MAP_ANONYMOUS)来完成。munmap与之所做的事情相反,常用以释放mmap分配的虚存。

4.5.3 堆的内部管理

当malloc()分配内存的时候,首先会先调用上面提到的brk()或者mmap()来向操作系统申请一块内存。其实也就是让操作系统知道这块内存的虚存地址是有效的。在使用这些虚存地址的时候为其分配相应的物理内存,而不是报Segmentation fault. 
    ... 
    int *l = sbrk(0); 
    k=l+1023; 
    printf("k=%d,at %p\n",*k,k); 
    ... 
    运行程序将会抛出: 
    Segmentation fault 
    
    如果改为: 
    ... 
    int *l = sbrk(0); 
    sbrk(1); 
    k=l+1023; 
    printf("k=%d,at %p\n",*k,k); 
    ... 
    程序将正常运行,并输出: 
    k=100,at 0x804affc 
    
第一段代码出错是因为程序访问了还没分配的内存,超过了当前堆的上限。第二段代码使用了sbrk(1)动态分配了内存,所以访问就成功了。注意虽然这里sbrk(1),表面上只把当前堆增加了1个字节。但是因为系统的内存分配是以页为单位的,当前堆实际增加了4KB, 因此对k = l+1023的访问也是合法的。 
    
brk()和mmap()虽然在内存分配的时候用途一样,但是各有各的优点,每次brk()的虚存空间是连续的,便于合并,重用,并更为节省页对齐浪费的空间,但是可能形成内存空洞(见下文),适合较小的内存分配。mmap()不会像brk()那样形成空洞,但不能复用,合并。且开销和具体的平台相关,并会把分配的内存初始化为0,所以适合大空间的分配。在dlmalloc中,如果malloc分配的内存小于128KB, 使用brk()来增加进程使用的内存。如果分配的内存大于等于128KB,则使用mmap()来分配内存(128KB这个值在不同的平台上是可调的)。 
    下面来看一个例子: 
    ... 
    int *heap_var = malloc(sizeof(int)); //较小的内存块分配请求 
    int *large_var = malloc(256*1024);    //较大的内存块分配请求 
    printf("Address of heap_var (Heap):%p\n",heap_var); 
    printf("Address of large_var (Heap):%p\n",large_var); 
    ... 
    输出结果为: 
    Address of heap_var  (Heap):0x804a008 
Address of large_var (Heap):0xb7db2008 
    
    如果用strace命令跟踪,可以发现这段代码执行了如下的系统调用: 
    brk(0x806b000)                          = 0x806b000 
    mmap2(NULL, 266240, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7db2000 
我们可以清楚地看到,对于较小的内存分配,使用了brk()系统调用,对于较大的内存块分配请求,使用了mmap系统调用。并且我们发现这两个地址相差较远,所以堆区常又被分为两个部分,一个是brk分配的内存,通常位于低地址。另一个是mmap分配的内存,也叫地址映射区,通常位于高地址。当然用不同系统调用分配的内存,也可以混合管理,这取决于具体的实现。

4.5.3.2 堆空间的内存管理

接下来就是要对用brk和mmap分配好内存进行管理了。因为brk(),mmap()是系统调用,如果每次调用malloc动态分配内存都执行一次系统调用,那开销是比较大的。再者,如果每次申请的内存较小,但是系统分配的内存都是固定大小的倍数(一般是4KB,一页),这样就会有大量的浪费。所以malloc一般会实现一个内存堆来管理这些内存,malloc分配的内存都会以若干chunk的方式放到内存堆中。每次用户调用malloc动态分配内存的时候,malloc会先到内存堆里进行查找,如果内存堆里没有合适的空闲chunk,再利用brk/malloc系统调用分配一大块内存,然后把新分配的大块内存放到内存堆中,并生成一块合适的chunk块返回给用户。当用户用free释放chunk的时候,可能并不立即使用系统调用释放内存,而是将释放的chunk作为空闲chunk加入内存堆中,和其他的空闲chunk合并,便于下次分配的时候再次使用。 
    
一般说来,释放的chunk如果标记为mmap申请的,则使用munmap释放。如果是brk申请的,进一步判断堆顶之下的空闲chunk是否大于128KB,如果是,则使用brk()释放。如果小于128KB,仍由内存堆维护。这样对brk()的使用就会有个问题,当brk()释放的内存块在堆顶之下,且内存块到堆顶之间还有未释放的内存。那么这块内存的释放将不会成功,从而形成内存空洞。 
    
malloc中为每块chunk都会分配一个数据结构用于管理,也就是chunk head。chunk head有多大?我们来看看malloc(0)时的情况。 
    ... 
    int *heap_var = malloc(0); 
    int *heap_var1 = malloc(0); 
    printf("Address of heap_var: %p\n",heap_var); 
    printf("Address of heap_var1: %p\n", heap_var1); 
    ... 
    这段代码的输出为: 
    Address of heap_var: 0x804a008 
    Address of heap_var1: 0x804a018 
两者指向的位置相差了16个字节,可以看出,对于malloc(0),也会分配16个字节供chunk head使用,即便这个chunk内包含的内存大小为0。而在c99标准中则对malloc(0)的返回未定义。chunk head中记录的一个很重要的信息就是当前chunk的大小。当malloc一块chunk的时候,malloc的内存大小就存放在chunk head中,释放的时候通过地址指针,找到相应块的chunk_head,从而知道要释放的chunk大小。这也是为什么我们在malloc的时候需要指定分配内存的大小,而释放的时候只需要给出释放内存的地址指针就行了。如果free(p)时的指针不是malloc时得到的,那么malloc就会报Segmentation fault,或者./chunk: free(): invalid pointer。

小结: 
1. 无论是堆,还是栈都是对虚存的操作和管理。 
2. 系统调用brk()和mmap()用来动态分配虚存空间,也就是表明这些虚存地址是合法的,访问的时候,系统应为其分配物理内存,而不是报错。 
3. 堆的本质是动态申请的虚存空间。理论上可以用任何方式去管理这块空间。但数据结构--"堆"是最常用的一种,所以这块分配的空间常称为被堆。 
4. 和栈不一样,堆的管理是在用户函数库中进行,malloc/free等函数是堆的入口。 
5. 每次分配的内存块大小都会被记录下来,释放的时候只需要指定要释放的内存地址就行了。这就是为什么malloc的时候要指定大小,free的时候不用。 
6. 堆和栈一样,仍然使用了物理内存的延迟分配策略。

补充:

kmalloc特殊之处在于它分配的内存是物理上连续的,这对于要进行DMA的设备十分重要. 而用vmalloc分配的内存只是线性地址连续,物理地址不一定连续,不能直接用于DMA.

调用malloc 之后发生了什么????

标签:包含   增加   一段   str   不同   nmap   记录   移动   并且   

原文地址:https://www.cnblogs.com/lucelujiaming/p/9480854.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!