malloc()函数有始有终

1 malloc()

malloc()函数是申请一块堆区内存的常用函数,简单应用如下:

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char *argv[]){
    char *test = (char *)malloc(100);
    test[0] = 'A';
    printf("0x%02x\n", test[4095]);
    return 0;
}

编译执行后的结果如下:
这里写图片描述
问题出现了,明明上述malloc()函数只开辟了100个字节的空间,为什么可以访问第4096个字节呢?
因为malloc()函数执行时,调用的系统调用是sys_brk(),该系统调用分配进程虚拟地址空间的时候是以4KB页为单位进行分配的,而且当出现缺页异常的时候,默认操作的基本单位同样是4KB的页,于是sys_brk()系统调用最小分配4096字节的内容。

2 用户空间的malloc堆区管理

其实,malloc()函数并没有直接调用sys_brk()系统调用,malloc()函数是glibc函数库对于进程堆区内存管理的一种实现方法,类似于内核中的kmalloc函数用于分配和管理内核中需要的内存空间,但是内核中的kmalloc()函数依赖于内核中的slab分配器实现具体的内核虚拟地址空间内存的分配,而malloc()依赖于glibc库的管理机制和sys_brk()系统调用实现具体的用户虚拟地址空间内存的分配,两者的工作原理和工作场景是不一样的。
glibc函数库使用块来划分通过sys_brk()系统调用获得的内存区域,然后使用隐式链表结构来管理划分的这些块,根据块是否被占用给,分为:空闲块和占用块。其中,glibc参照内核中slab分配器的工作原理根据空闲块的大小将空闲块划分为了多个类别,在每个类别中使用链表来管理所有的块,比如:size为1024字节的块放到一个链表中,size为512字节的块放到一个链表中等。因此,块的分配和回收会涉及到隐式链表结构中元素的分割、合并等操作,而常见的堆区溢出的原理也存在于glibc库中对堆区隐式链表操作的代码中,具体详解请见文章“Understanding glibc malloc”。

3 内核空间的sys_brk()系统调用

《奔跑吧,linux内核》将malloc比作是零售商,而把sys_brk比作代理商,个人感觉还是相当贴切的。
sys_brk()负责在进程虚拟地址空间的堆区(start_brk, mmap_base)开辟一段适合的虚拟地址空间,根据sys_brk()接收的参数对该部分虚拟地址空间进行相应的操作。
一个进程的虚拟地址空间使用结构体mm_struct来表示,其中mm->start_brk代表是堆区的起始地址,由于堆是向上(向地址较高处)增长,于是其最大不会超过mm->mmap_base所表示的mmap内存区域的大小,《奔跑吧,Linux内核》给出32位系统中堆区内存长度最大为1GB。另外,进程堆区当前的最高点是mm->brk。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/u011414616/article/details/80930939