2

我不确定我是否在这里问一个菜鸟问题,但我走了。我也搜索了很多类似的问题,但我一无所获。

因此,我知道如何工作,mmap并且brk无论您输入的长度如何,它都会将其四舍五入到最近的页面边界。我也知道malloc使用brk/sbrkmmap(至少在 Linux/Unix 系统上)但这提出了一个问题:是否malloc也四舍五入到最接近的页面大小?对我来说,页面大小是 4096 字节,所以如果我想分配 16 字节malloc,4096 字节...比我要求的要多得多。

4

2 回答 2

3
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <unistd.h>


int main(void) {
    void *a = malloc(1);
    void *b = malloc(1);
    uintptr_t ua = (uintptr_t)a;
    uintptr_t ub = (uintptr_t)b;
    size_t page_size = getpagesize();

    printf("page size: %zu\n", page_size);
    printf("difference: %zd\n", (ssize_t)(ub - ua));
    printf("offsets from start of page: %zu, %zu\n",
        (size_t)ua % page_size, (size_t)ub % page_size);
}

印刷

page_size: 4096
difference: 32
offsets from start of page: 672, 704

所以很明显,在这种情况下它没有四舍五入到页面大小,这证明它并不总是四舍五入到页面大小。


mmap如果您将分配更改为任意大的大小,它将命中。例如:

void *a = malloc(10000001);
void *b = malloc(10000003);

我得到:

page size: 4096
difference: -10002432
offsets from start of page: 16, 16

很明显,起始地址仍然不是页面对齐的;簿记必须存储在指针下方,并且指针需要充分对齐以获得通常需要的最大对齐 - 你可以用free- 如果free只是给出一个指针但它需要弄清楚分配的大小,它可以在哪里寻找它,只有两种选择是可行的:在一个单独的数据结构中列出所有基指针及其分配大小,或者在当前指针下方的某个偏移量处。而其中只有一个是理智的。

于 2021-01-28T06:09:58.627 回答
3

malloc 和朋友的基本工作是管理这样一个事实,即操作系统通常只能(有效地)处理大型分配(整个页面和页面范围),而程序通常需要更小的块和更细粒度的管理。

所以 malloc (通常)所做的是,第一次调用它时,它会从系统分配大量内存(通过 mmap 或 sbrk——可能是一页或多页),并使用其中的一小部分对于一些数据结构来跟踪堆使用(堆在哪里,哪些部分正在使用以及哪些部分是空闲的),然后将该空间的其余部分标记为空闲。然后,它会分配您从该可用空间中请求的内存,并将其余内存用于后续 malloc 调用。

因此,当您第一次调用 malloc 时,例如 16 个字节,它将使用 mmap 或 sbrk 分配一个大块(可能是 4K 或可能 64K 或可能 16MB 甚至更多)并将其初始化为大部分空闲并返回一个指向 16 个字节的指针某处。第二次调用 malloc 获取另外 16 个字节只会从该池中返回另外 16 个字节——无需返回操作系统获取更多信息。

随着您的程序继续分配更多内存,它将仅来自该池,并且空闲调用会将内存返回到空闲池。如果它通常分配的内存比它释放的多,最终该空闲池将用完,此时,malloc 将调用系统(mmap 或 sbrk)以获取更多内存以添加到空闲池中。

这就是为什么如果您使用某种进程监视器来监视一个使用 malloc/free 分配和释放内存的进程,您通常只会看到内存使用量上升(因为空闲池用完并且系统请求了更多内存),并且通常不会看到它关闭——即使内存正在被释放,它通常只是回到空闲池并且不会取消映射或返回到系统。有一些例外——特别是在涉及非常大的块时——但通常在进程退出之前,您不能依赖任何内存返回给系统。

于 2021-01-28T07:10:17.493 回答