ILD

malloc memory fragmentation and madvise system call
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2024-5-24 站点:Inside Linux Development

    最近在写一个程序,涉及到文件上传和下载,它大量用到动态内存分配,下载的时候会预缓存文件内容,因此下载的时候,内存占用可以达到20MB。但是空闲的时候,内存占用并没有降到和程序刚启动的时候一样低,而是有9M的样子。


    我们知道,malloc在分配小块的时候,是使用的data segment区域的内存,它通过brk/sbrk来改变大小,这是一个连续的内存区域。这让我想到一个碎片化的问题:不断分配小块内存,直到100M,然后释放所有之前分配的,留最后一个块不释放,那么之前的也不能释放,因为释放需要改变数据段的大小,而低部被占用了,显然不能改变。那不就导致碎片化的问题了吗。


    写一个程序测试:分配N个块,然后等待按键,然后释放前N-1个,并且调用malloc_trim()手动回收内存,然后暂停。等待按键和暂停是为了方便用meminfo工具读取内存占用情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <malloc.h>
 
int main(int argc, char **argv)
{
    int bs, N;
    int i;
    char **p;
 
    if (argc != 3) {
        fprintf(stderr, "invalid arguments\n");
        return 1;
    }
 
    bs = atoi(argv[1]);
    N = atoi(argv[2]);
 
    printf("malloc %d blocks, block size %d, pid %d\n", N, bs, getpid());
 
    p = malloc(N * sizeof(void *));
    if (!p) {
        fprintf(stderr, "malloc failed\n");
        return 1;
    }
 
    for (i = 0; i < N; i++) {
        p[i] = malloc(bs);
        if (!p[i]) {
            fprintf(stderr, "malloc failed\n");
            return 1;
        }
        memset(p[i], 0, bs);
    }
 
    printf("malloc done, press anykey to continue\n");
    getchar();
 
    for (i = 0; i < N - 1; i++)
        free(p[i]);
 
    malloc_trim(0);
    printf("free done\n");
    pause();
    return 1;
}


执行:

$ ./memfrag 100000 10
malloc 10 blocks, block size 100000, pid 1074320
malloc done, press anykey to continue


然后使用meminfo查看堆的内存情况:

$ sudo ./meminfo 1074320
   free swapped     exc     shr     ave
    0KB     0KB     4KB     0KB     0KB 557ea77dc000-557ea77dd000 r--p     0 /work/test/memfrag
    0KB     0KB     4KB     0KB     0KB 557ea77dd000-557ea77de000 r-xp  1000 /work/test/memfrag
    0KB     0KB     4KB     0KB     0KB 557ea77de000-557ea77df000 r--p  2000 /work/test/memfrag
    0KB     0KB     4KB     0KB     0KB 557ea77df000-557ea77e0000 r--p  2000 /work/test/memfrag
    0KB     0KB     4KB     0KB     0KB 557ea77e0000-557ea77e1000 rw-p  3000 /work/test/memfrag
  128KB     0KB   980KB     0KB     0KB 557ea7ff3000-557ea8108000 rw-p     0 [heap]
    0KB     0KB     0KB   136KB     0KB 7f517e570000-7f517e592000 r--p     0 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  556KB     0KB     0KB   948KB     8KB 7f517e592000-7f517e70a000 r-xp 22000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  164KB     0KB     0KB   148KB     0KB 7f517e70a000-7f517e758000 r--p 19a000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0KB     0KB    16KB     0KB     0KB 7f517e758000-7f517e75c000 r--p 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0KB     0KB     8KB     0KB     0KB 7f517e75c000-7f517e75e000 rw-p 1eb000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
    4KB     0KB    20KB     0KB     0KB 7f517e75e000-7f517e764000 rw-p     0
    0KB     0KB     0KB     4KB     0KB 7f517e77a000-7f517e77b000 r--p     0 /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0KB     0KB     0KB   140KB     0KB 7f517e77b000-7f517e79e000 r-xp  1000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0KB     0KB     0KB    32KB     0KB 7f517e79e000-7f517e7a6000 r--p 24000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0KB     0KB     4KB     0KB     0KB 7f517e7a7000-7f517e7a8000 r--p 2c000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0KB     0KB     4KB     0KB     0KB 7f517e7a8000-7f517e7a9000 rw-p 2d000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0KB     0KB     4KB     0KB     0KB 7f517e7a9000-7f517e7aa000 rw-p     0
  120KB     0KB    12KB     0KB     0KB 7fff7f887000-7fff7f8a8000 rw-p     0 [stack]
   16KB     0KB     0KB     0KB     0KB 7fff7f8fd000-7fff7f901000 r--p     0 [vvar]
    4KB     0KB     0KB     4KB     0KB 7fff7f901000-7fff7f903000 r-xp     0 [vdso]
    0KB     0KB     0KB     0KB     0KB ffffffffff600000-ffffffffff601000 --xp     0 [vsyscall]
VSS  3472KB
RSS  2480KB
PSS  1080KB
USS  1068KB


可以看到有980KB的独占物理内存占用。按任意键释放内存,并调用malloc_trim()回收,然后再看内存占用:

$ sudo ./meminfo 1074320
   free swapped     exc     shr     ave
    0KB     0KB     4KB     0KB     0KB 557ea77dc000-557ea77dd000 r--p     0 /work/test/memfrag
    0KB     0KB     4KB     0KB     0KB 557ea77dd000-557ea77de000 r-xp  1000 /work/test/memfrag
    0KB     0KB     4KB     0KB     0KB 557ea77de000-557ea77df000 r--p  2000 /work/test/memfrag
    0KB     0KB     4KB     0KB     0KB 557ea77df000-557ea77e0000 r--p  2000 /work/test/memfrag
    0KB     0KB     4KB     0KB     0KB 557ea77e0000-557ea77e1000 rw-p  3000 /work/test/memfrag
  876KB     0KB   104KB     0KB     0KB 557ea7ff3000-557ea80e8000 rw-p     0 [heap]
    0KB     0KB     0KB   136KB     0KB 7f517e570000-7f517e592000 r--p     0 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  556KB     0KB     0KB   948KB     8KB 7f517e592000-7f517e70a000 r-xp 22000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  164KB     0KB     0KB   148KB     0KB 7f517e70a000-7f517e758000 r--p 19a000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0KB     0KB    16KB     0KB     0KB 7f517e758000-7f517e75c000 r--p 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0KB     0KB     8KB     0KB     0KB 7f517e75c000-7f517e75e000 rw-p 1eb000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
    4KB     0KB    20KB     0KB     0KB 7f517e75e000-7f517e764000 rw-p     0
    0KB     0KB     0KB     4KB     0KB 7f517e77a000-7f517e77b000 r--p     0 /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0KB     0KB     0KB   140KB     0KB 7f517e77b000-7f517e79e000 r-xp  1000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0KB     0KB     0KB    32KB     0KB 7f517e79e000-7f517e7a6000 r--p 24000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0KB     0KB     4KB     0KB     0KB 7f517e7a7000-7f517e7a8000 r--p 2c000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0KB     0KB     4KB     0KB     0KB 7f517e7a8000-7f517e7a9000 rw-p 2d000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0KB     0KB     4KB     0KB     0KB 7f517e7a9000-7f517e7aa000 rw-p     0
  120KB     0KB    12KB     0KB     0KB 7fff7f887000-7fff7f8a8000 rw-p     0 [stack]
   16KB     0KB     0KB     0KB     0KB 7fff7f8fd000-7fff7f901000 r--p     0 [vvar]
    4KB     0KB     0KB     4KB     0KB 7fff7f901000-7fff7f903000 r-xp     0 [vdso]
    0KB     0KB     0KB     0KB     0KB ffffffffff600000-ffffffffff601000 --xp     0 [vsyscall]
VSS  3344KB
RSS  1604KB
PSS   204KB
USS   192KB


可以看到,独占的内存只有104KB了,大致只有一个块,但是虚拟地址空间范围没变。这是为啥呢?原来是madvise系统调用的功劳。

使用strace分析进程做了啥:

$ strace ./memfrag 100000 10

fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
brk(NULL)                               = 0x557766f4a000
brk(0x557766f6b000)                     = 0x557766f6b000
write(1, "malloc 10 blocks, block size 100"..., 49malloc 10 blocks, block size 100000, pid 1074352
) = 49
brk(0x557766f9c000)                     = 0x557766f9c000
brk(0x557766fcd000)                     = 0x557766fcd000
brk(0x557766ffd000)                     = 0x557766ffd000
brk(0x55776702e000)                     = 0x55776702e000
brk(0x55776705f000)                     = 0x55776705f000
write(1, "malloc done, press anykey to con"..., 38malloc done, press anykey to continue
) = 38
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
read(0,
"\n", 1024)                     = 1
madvise(0x557766f4b000, 897024, MADV_DONTNEED) = 0
brk(0x55776703f000)                     = 0x55776703f000
write(1, "free done\n", 10free done
)             = 10


madvise可以把data segment中间地址的物理内存释放。但是保留虚拟地址空间。


参考:

https://comp.unix.programmer.narkive.com/keelAZ81/memory-fragmentation-with-malloc

https://man7.org/linux/man-pages/man2/madvise.2.html



Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.