为什么在使用`munmap()`之后,页面回收仍然存在?

3

为了一个研究项目,我需要使用mmap()munmap()重新实现malloc()free()的编码。

我在最新版本的Ubuntu上运行。为了进行测试,我使用/usr/bin/time命令的time -v选项,该选项向我显示有关我的程序包括内存的大量信息。以下是一些示例:

因此,我们可以看到次要页面错误,它对应于根据我们的使用而变化的回收页面数量,但特别是如果我们在malloc()之后使用free(),则回收页面的数量将返回到其初始数量,这在我的重新实现中不是这种情况:

以下是我代码的一些部分,以便可视化我所做的事情。

在我的malloc()中:

static t_page *__alloc_page(size_t size)
{
    struct rlimit limit;
    t_page *page;

    getrlimit(RLIMIT_AS, &limit);
    if (size > limit.rlim_max)
        return (NULL);
    page = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (page == MAP_FAILED)
        return (NULL);
    ft_bzero(page, sizeof(t_page));
    page->size = size;
    page->used_size = sizeof(t_page);
    return (page);
}

在我的free()函数里:
static void __free_page(t_page *page)
{
    t_binding *binder = __get_binder(page);

    binder->count--;
    if (binder->pages == page)
        binder->pages = page->next;
    
    if (page->prev != NULL)
        page->prev->next = page->next;
    if (page->next != NULL)
        page->next->prev = page->prev;

    if (munmap(page, page->size) == -1)
        ft_putstr("free(): munmap error\n");
}

关于我的大小信息,它总是getpagesize()的倍数(N * getpagesize())。

这是我如何进行测试的

首先,我将文件malloc.cfree.c等编译成动态库(libmalloc.so)。
然后,我使用以下主函数构建两个二进制文件。一个使用我的malloc,另一个使用libc。

clang main.c -o libc_malloc
clang main.c -D LIBMALLOC libmalloc.so -o my_malloc

#ifdef LIBMALLOC
# include "../includes/malloc.h"
#else
# include <stdlib.h>
#endif

int main(void)
{
    int i;
    char *addr;

    i = 0;
    while (i < 1024) 
    {
        addr = (char*)malloc(1024);
        addr[0] = 42;
        free(addr);
        i++; 
    }
    return (0);
}

我还有一个脚本,可以让我使用名为run.sh的动态库运行我的二进制文件:

#!/bin/sh
export LD_LIBRARY_PATH="."
export LD_PRELOAD="`pwd`/libmalloc.so"
$@

最后我用 time -v 运行了我的两个二进制文件,就像这样:
/usr/bin/time -v ./libc_malloc
./run.sh /usr/bin/time -v ./my_malloc

如何以最简方式进行复现

#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    int i;
    char *addr;

    i = 0;
    #ifdef _MMAP_
        printf("mmap\n");
    #else
        printf("malloc\n");
    #endif
    while (i < 1024) 
    {
        #ifdef _MMAP_
            addr = mmap(NULL, 4 * getpagesize(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        #else
            addr = malloc(4 * getpagesize());
        #endif
        addr[0] = 42;
        #ifdef _MMAP_
            munmap(addr, 4 * getpagesize());
        #else
            free(addr);
        #endif
        i++; 
    }
    return (0);
}

将上述主函数复制到一个文件中(main.c)。
创建两个二进制文件如下:

clang main.c -o using_malloc
clang -D _MMAP_ main.c -o using_mmap

然后使用time -v运行它们:

/usr/bin/time -v ./using_malloc
/usr/bin/time -v ./using_mmap

我尝试过的方法

在搜索互联网时,我发现了这篇文章,它与我的问题完全相同:
使用munmap时页面回收更高
但是提出的解决方案不起作用(而且我不能使用它)。
我也不能使用像posix_madvise()msync()这样的函数...
我尝试了它们,看看它们是否能解决我的问题,但没有成功。
我还运行了别人的项目。他的工作得很好,而我们似乎在做同样的事情。
我错过了什么吗?


请看我的回答:Malloc is using 10x the amount of memory necessary。它讲到了RSS(常驻集大小)。请注意,映射/取消映射并不完全等同于RSS,因此如果没有使用posix_madvise/madvise/msync,您将无法对进程的RSS进行如此多的控制。 - Craig Estey
此外,如需更多信息,请参阅我的其他答案:mmap 如何提高文件读取速度? 在那个答案中,有链接到我其他答案的 [更多] 信息:哪些段会受到写时复制的影响?以最有效的方式 平台特定 按行读取,这些内容提供了有关这些问题的额外细节。 - Craig Estey
首先,您确定mallocfree调用的是的版本而不是标准库中的版本吗?要实现这一点可能需要付出一些努力。 - Nate Eldredge
你能否将一个 [mcve] 放在单一的代码块中,连同精确的构建和运行命令、输出结果以及为什么认为代码有问题的原因?跟踪零散的代码片段很困难,而你提供的 main 函数也没有帮助,因为它们似乎实际上并没有测量任何东西。 - Nate Eldredge
是的,抱歉我更新了@NateEldredge 而且我确信我的malloc和free被正确调用了。 - lucocozz
1个回答

1
我已经找到了问题所在。在我的主函数中,我不断地执行malloc()和free()。当系统执行munmap()时,它试图通过不立即删除由mmap()分配的页面来进行优化,以便稍后重用它。然而,这仍然会导致在后续调用mmap()时创建新页面。
为了解决这个问题,free()需要留下一个剩余页面,以便munmap()不会在循环中被调用。
我的解释可能不是很清楚,请随意提供更多细节。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接