前言

呵呵 在 c 语言中 malloc 应该是初学者必须了解的一个函数了吧 

但凡 涉及到堆内存分配的相关, 必定会使用到 malloc, realloc, calloc 这几个函数 

其中 malloc 最常见, 也是最 实用 

在 HotspotVM 中也经常会看到 malloc 的身影 

我们这里 来调试一下 malloc 的相关的一些场景 

本文主要的主题有四个

1. malloc(20) 申请内存, 然后 brk 传递的参数是 132K
2. malloc 第一次内存分配 和 第二次内存分配
3. malloc 分配的内存的 header
4. malloc 20, 但实际可用的空间只有 16

测试用例

测试用例如下, 很简单的一个 case, 主要的目的在于使用 malloc, 以及观察 malloc 分配的内存的虚拟地址信息 

root@ubuntu:~/ClionWorkStations/HelloWorld# cat Test01Sum.c 
#include "stdio.h"

int main(int argc, char** argv) {

int x = 2;
int y = 3;
int z = x + y;

void *p1 = malloc(20);
void *p2 = malloc(20);
void *p3 = malloc(20);
printf("p1 : 0x%x\n", p1);
printf("p2 : 0x%x\n", p2);
printf("p3 : 0x%x\n", p3);

printf(" x + y = %d\n ", z);

}

编译使用我们自己手动编译的 glibc 库

export mainClass=Test01Sum

gcc $mainClass.c -o $mainClass -L /root/Desktop/linux/glibc-2.23/install/lib -Wl,--rpath=/root/Desktop/linux/glibc-2.23/install/lib -Wl,-I /root/Desktop/linux/glibc-2.23/install/lib/ld-linux-x86-64.so.2

程序输出结果如下 

p1 : 0x602010
p2 : 0x602030
p3 : 0x602050
 x + y = 5

1. malloc(20) 申请内存, 然后 brk 传递的参数是 132K

如上测试用例, malloc 20 申请内存的时候, 会使用系统调用 brk 来进行虚拟内存分配 

但是 奇怪的是分配的内存是 132k, 我命名只需要 20字节, 为什么 malloc 调用 brk 却申请了 132k 的虚拟内存, 这是为什么呢? 

如下图, 查看 brk 系统调用, 申请的 135168 字节, 合计 132k 的虚拟内存 

16 malloc 虚拟内存分配的调试(1)_brk

接下来我们看一下 malloc 是怎么做的? 这个 132k 又是怎么来的呢?

大致看一下 上下文, 是在 main 中的第一个 malloc(20) 的地方调用了 brk 系统调用 

16 malloc 虚拟内存分配的调试(1)_系统调用_02

看一下 increment, 果然是 135168 

16 malloc 虚拟内存分配的调试(1)_c++_03

然后我们再来看一下 这个 135168 是怎么计算出来的 

这里有两个过程, 计算加对齐, 计算阶段为 32 + 131072 + 32 = 131136

然后和 4096 对齐一下, 之后的结果为 135168 

16 malloc 虚拟内存分配的调试(1)_brk_04

然后另外一个问题是, 用户程序 申请的是 20 字节, 为什么实际申请的是 32 字节呢 

如下 checked_request2size 就是做的这个转换 

16 malloc 虚拟内存分配的调试(1)_内存分配_05

checked_request2size 的计算方式如下 

代入 req 为 20, (20 + 8 + 15) & (0xfff0) = 32 

整个表达式的意思是找到比 (20 + 8) 大的最小的 16 的整数倍 

注意这里的 8, 额外只是加了一个 SIZE_SZ 

16 malloc 虚拟内存分配的调试(1)_内存分配_06

 2. malloc 第一次内存分配 和 第二次内存分配

malloc 的分配流程为 fastbin, small_bin, unsorted_chunks, 迭代 bins, top 

在 Test01Sum 进入第一个 malloc 的时候均无可用空间, 直接到 use_top 

此时 av->top 无可用空间, 调用 sysmalloc 申请空间 

16 malloc 虚拟内存分配的调试(1)_内存分配_07

 sysmalloc 中调用 brk 申请 所需要的 135168 字节的空间

16 malloc 虚拟内存分配的调试(1)_c++_08

然后之后是 申请需要的 32 字节 空间 

p / av->top 初始的值为 0x602000, 分配了 32 字节之后 remainder / av-> top 为 0x602020 

分别更新申请的 chunk 块的 size, 和 remainder 的 size 

然后 chunk2mem 的转换, 增加了 2 * SIZE_SZ = 16, 值为 0x602010

16 malloc 虚拟内存分配的调试(1)_系统调用_09

分配的 chunk 的地址 + 两个头的大小[prev_size, size] 即为响应给用户的地址 

16 malloc 虚拟内存分配的调试(1)_glibc_10

第二次 malloc 的内存分配, 同上面 其他 fast_bin, small_bin 均无可用空间, 到 use_top 

第一次内存分配之后, av->top 为 0x602020, 用户需要空间为 32 字节 

因此 切割一个 32 byte 的 chunk 块 p 之后, av->top 为 0x602040

分别更新申请的 chunk 块的 size, 和 remainder 的 size 

p 的值为 0x602020, 然后 chunk2mem 的转换, 增加了 2 * SIZE_SZ = 16, 值为 0x602030

16 malloc 虚拟内存分配的调试(1)_brk_11

至于第三次内存分配, 就和 第二次差不多了, 然后 我们再来回顾一下 程序的输出 

p1 : 0x602010
p2 : 0x602030
p3 : 0x602050
 x + y = 5

3. malloc 分配的内存的 header

看一下我们的 malloc_chunk, 头部有 2 * 8 个字节, 分别的逻辑意义为 prev_size, size 

在上面的流程中我们没有看到 prev_size 的使用, 呵呵 这个我们后续再来记录 

如果 前一个 chunk 空闲, 则 prev_size 记录的事该 chunk 的空间大小, 如果前一个 chunk 在使用, 则 prev_size 可以被前一个 chunk 使用 

16 malloc 虚拟内存分配的调试(1)_系统调用_12

size 记录的数据如下 

其中 PREV_INUSE 为 1, NON_MAIN_AREA 为 4, 这里可以知道最后 1bit 标记的是 PREV_INUSE, 倒数第三bit 标记的是 NON_MAIN_ARENA 

倒数第二 bit 标记的是, 内存块是使用的 brk 还是 mmap 来分配的空间 

然后 mask 掉后面 3bit, 的大小即为当前 chunk 的空间大小 

16 malloc 虚拟内存分配的调试(1)_brk_13

第一次 malloc 分配的 chunk, chunk 的起始地址为 0x602000, 给用户的起始地址为 0x602010 

0x602000 起始的的 long 值为 0, 默认第一个 chunk 的前一个 chunk 在使用, prev_size 没有作用 

0x602008 起始的的 long 值为 chunk 的 size, 为 0x21, 表示了当前 chunk 空间是 32byte, MAIN_ARENA, 系统调用 brk 分配的内存

16 malloc 虚拟内存分配的调试(1)_glibc_14

第二次 malloc 分配的 chunk, chunk 的起始地址为 0x602020, 给用户的起始地址为 0x602030 

0x602020 起始的的 long 值为 0, 前一个 chunk 在使用, prev_size 没有作用 

0x602028 起始的的 long 值为 chunk 的 size, 为 0x21, 表示了当前 chunk 空间是 32byte, MAIN_ARENA, 系统调用 brk 分配的内存

当然 这里也可以看到 上面 第一个 chunk 的情况 

16 malloc 虚拟内存分配的调试(1)_brk_15

4. malloc 20, 但实际可用的空间只有 16

如果 您够仔细的话, 你会发现 用户申请的是 20 字节 

然后 malloc 头, 占用的空间为 16 字节, 那么 至少应该申请 48 字节才对 

但是 为什么只申请了 32 字节? 

去掉头的 16 字节, 只有 16 字节了?? 这是 怎么回事 

如果 前一个 chunk 空闲, 则 prev_size 记录的事该 chunk 的空间大小, 如果前一个 chunk 在使用, 则 prev_size 可以被前一个 chunk 使用 

这个需要理解 prev_size 的使用了, 按照上面的反向推导一下 

如果当前 chunk 正在使用, 那么 他是可以使用下一个 chunk 的 prev_size 的 

因此 可以回顾一下上面这一段, 根据 用户申请的字节数, 计算需要申请的字节数 这一段, 计算的时候 只是额外增加了一个 SIZE_SZ, 因为当前 chunk 正在使用, 可以额外使用下一个 chunk 的 prev_size, 因此 这里的理解 和 上面的 checked_request2size 

注意这里的 8, 额外只是加了一个 SIZE_SZ 

5. malloc 0, 会怎么处理?

如果 bytes 为 0, 在 checked_request2size 的时候, malloc 会有最小分配空间的需求, 按照 MINSIZE 来进行分配空间, MINSIZE 为 32 字节 

所以 得到的还是一块 32 字节的空间, 但是 至于你如何使用 并没有具体强制约束 

16 malloc 虚拟内存分配的调试(1)_brk_16

checked_request2size 如下, 调整了 sz, 如果 小于 MINSIZE 设置 sz 为 MINSIZE 

16 malloc 虚拟内存分配的调试(1)_c++_17

完