用户空间程序如何在free()之后将内存传递回内核?

时间:2016-03-31 03:03:44

标签: c memory-management kernel

我一直在阅读很多关于memory allocation on the heap以及某些堆管理分配器如何做的事情。

说我有以下程序:

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

int main(int argc, char *argv[]) {
    // allocate 4 gigabytes of RAM
    void *much_mems = malloc(4294967296);
    // sleep for 10 minutes
    sleep(600);
    // free teh ram
    free(*much_mems);
    // sleep some moar
    sleep(600);

    return 0;
}

让我们说为了论证,我的编译器没有优化上面的任何内容,我实际上可以分配4GiB的RAM,malloc()调用返回一个实际的指针,而不是NULLsize_t可以在我的给定平台上保存一个与4294967296一样大的整数,malloc调用实现的分配器实际上会在堆中分配该数量的RAM 。假设上面的代码完全符合它的样子。

在调用free之后,内核如何知道那些4 GiB的RAM现在是否有资格用于其他进程和内核本身?我不认为内核是Linux,但那将是一个很好的例子。在调用free之前,这个进程的堆大小至少为4GiB,之后它还有堆大小吗?

现代操作系统如何允许用户空间程序将内存返回内核空间? free实现是否对内核(或许多系统调用)执行系统调用,以告诉它现在哪些内存区域可用?我的4 GiB分配是否可能是非连续的?

5 个答案:

答案 0 :(得分:1)

在带有Glibc的GNU / Linux上,通过调用mmap来处理大于几百千字节的大内存分配。当对此调用free函数时,库知道内存是以这种方式分配的(感谢存储在头文件中的元数据)。它只需在其上调用unmap即可释放它。这就是内核知道的方式;正在使用其mmapunmap API。

如果您在程序上运行strace,则可以看到这些调用。

内核使用红黑树跟踪所有mmap个ed区域。给定一个任意虚拟地址,它可以通过执行树步行来快速确定它是否落在mmap区域以及哪个映射。

答案 1 :(得分:1)

  

自由实现是否对内核(或许多系统调用)执行系统调用,以告诉它现在哪些内存区域可用?

Linux上malloc的现代实现将调用mmap来分配大量内存。内核将找到一个未使用的虚拟地址,将其标记为已分配,然后将其返回。 (如果没有足够的可用内存,内核也可能会返回错误)

free然后调用munmap来释放内存,传递分配的地址和大小。

在Windows上,malloc将致电VirtualAllocfree将致电VirtualFree

答案 2 :(得分:1)

  

在调用free之前,此进程的堆大小至少为4GiB ...

C语言没有定义“堆”或“堆栈”。在调用free之前,这个进程有一块4 GB 动态分配的内存...

  

之后,它还有那个堆大小吗?

...在free()之后,对该内存的访问将是未定义的行为,因此出于实际目的,动态分配的内存不再是“那里”。

做什么“幕后”(例如缓存,见下文)取决于图书馆,如有更改,恕不另行通知。这可能会随着可用物理内存量,系统负载,运行时参数......而改变。

  

现代操作系统如何允许用户空间程序将内存返回内核空间?

由标准库的实现决定(当然,必须与操作系统进行实际,物理分配/释放内存)。

其他人已经指出某些现有的实现是如何做到的。存在其他库,操作系统和环境。

  

自由实现是否对内核(或许多系统调用)执行系统调用,以告诉它现在哪些内存区域可用?

可能。库实现完成的常见优化是“缓存”free()d内存,因此后续的malloc()调用可以在没有与内核通信的情况下提供(这是一项代价高昂的操作)。通过这种方式缓存内存的时间,数量和时长是,你猜对了,实现定义了。

  

我的4 GiB分配是否可能是非连续的?

该过程将始终“看到”连续的内存。在支持虚拟内存的系统中(即“现代”桌面操作系统,如Linux或Windows),物理内存可能是非连续的,但虚拟地址会使您的进程进入看连续(或者如果无法满足此要求,则malloc()将失败。)

同样,存在其他系统。您可能正在查看不虚拟化地址的系统(即为进程提供物理地址)。您可能正在查看一个系统,该系统在启动时为进程分配给定数量的内存,从中提供任何malloc()请求,并且不支持额外内存的分配。等等。

答案 3 :(得分:0)

如果我们使用Linux作为示例,它使用mmap来分配大块内存。这意味着当你释放它时它会被umap,即内核被告知它现在可以取消映射这个内存。阅读brksbrk系统调用。一个好的起点就在这里......

What does brk( ) system call do?

在这里。以下文章讨论了如何实现malloc,这将使您了解幕后发生的事情......

How is malloc() implemented internally?

可以在这里找到Doug Lea的malloc。它评论很好,公共领域......

ftp://g.oswego.edu/pub/misc/malloc.c

答案 4 :(得分:-1)

malloc()和free()是内核函数(系统调用)。应用程序调用它来分配和释放堆上的内存。 应用程序本身不分配/释放内存。

整个机制在内核级别执行。

请参阅下面的堆实现代码

void *heap_alloc(uint32_t nbytes) {

    heap_header *p, *prev_p;   // used to keep track of the current unit
    unsigned int nunits;      // this is the number of "allocation units" needed by nbytes of memory

    nunits = (nbytes + sizeof(heap_header) - 1) / sizeof(heap_header) + 1;      // see how much we will need to allocate for this call

    // check to see if the list has been created yet; start it if not
    if ((prev_p = _heap_free) == NULL) {

        _heap_base.s.next = _heap_free = prev_p = &_heap_base;      // point at the base of the memory
        _heap_base.s.alloc_sz = 0;                              // and set it's allocation size to zero
    }

    // now enter a for loop to find a block fo memory
    for (p = prev_p->s.next;; prev_p = p, p = p->s.next) {

        // did we find a big enough block?
        if (p->s.alloc_sz >= nunits) {

            // the block is exact length
            if (p->s.alloc_sz == nunits)
                prev_p->s.next = p->s.next;

            // the block needs to be cut
            else {

                p->s.alloc_sz -= nunits;
                p += p->s.alloc_sz;
                p->s.alloc_sz = nunits;
            }

            _heap_free = prev_p;
            return (void *)(p + 1);
        }

        // not enough space!! Try to get more from the kernel
        if (p == _heap_free) {

            // if the kernel has no more memory, return error!
            if ((p = morecore()) == NULL)
                return NULL;
        }
    }
}

此heap_alloc函数使用morecore函数,其实现如下:

heap_header *morecore() {

    char *cp;
    heap_header *up;

    cp = (char *)pmmngr_alloc_block();      // allocate more memory for the heap

    // if cp is null we have no memory left
    if (cp == NULL)
        return NULL;

    //vmmngr_mapPhysicalAddress(cp, (void *)_virt_addr);      // and map it's virtual address to it's physical address
    vmmngr_mapPhysicalAddress(vmmngr_get_directory(), _virt_addr, (uint32_t)cp, I86_PTE_PRESENT | I86_PTE_WRITABLE);
    _virt_addr += BLOCK_SIZE;      // tack on nu bytes to the virtual address; this will be our next allocation address

    up = (heap_header *)cp;
    up->s.alloc_sz = BLOCK_SIZE;
    heap_free((void *)(up + 1));

    return _heap_free;
}

你可以看到这个函数要求物理内存管理器分配一个块:

cp = (char *)pmmngr_alloc_block();

然后将分配的块映射到虚拟内存中:

vmmngr_mapPhysicalAddress(vmmngr_get_directory(), _virt_addr, (uint32_t)cp, I86_PTE_PRESENT | I86_PTE_WRITABLE);

正如您所看到的,整个故事由内核级别的堆管理器控制。