简体   繁体   English

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

[英]How do userspace programs pass memory back to the kernel after free()?

I've been reading a lot about memory allocation on the heap and how certain heap management allocators do it. 我已经阅读了很多有关堆上的内存分配以及某些堆管理分配器如何执行分配的知识。

Say I have the following program: 说我有以下程序:

#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;
}

Let's say for sake of argument that my compiler doesn't optimize out anything above, that I can actually allocate 4GiB of RAM, that the malloc() call returns an actual pointer and not NULL , that size_t can hold an integer as big as 4294967296 on my given platform, that the allocater implemented by the malloc call actually does allocate that amount of RAM in the heap . 假设出于争论的目的,我的编译器没有对上面的内容进行优化,实际上我可以分配4GiB的RAM, malloc()调用返回一个实际的指针而不是NULLsize_t可以容纳一个最大为4294967296的整数在我给定的平台上,由malloc调用实现的malloc 实际上确实在堆中分配了一定数量的RAM Pretend that the above code does exactly what it looks like it will do. 假设上面的代码完全按照预期的方式工作。

After the call to free executes, how does the kernel know that those 4 GiB of RAM are now eligible for use for other processes and for the kernel itself? 调用free执行之后,内核如何知道这4 GiB的RAM现在可以用于其他进程和内核本身了? I'm not assuming the kernel is Linux, but that would be a good example. 我没有假设内核是Linux,但这将是一个很好的例子。 Before the call to free, this process has a heap size of at least 4GiB, and afterward, does it still have that heap size? 在调用free之前,此进程的堆大小至少为4GiB,然后,它仍然具有该堆大小吗?

How do modern operating systems allow userspace programs to return memory back to kernel space? 现代操作系统如何允许用户空间程序将内存返回到内核空间? Do free implementations execute a syscall to the kernel (or many syscalls) to tell it which areas of memory are now available? free实现是否执行对内核的syscall(或许多syscall),以告诉内核现在有哪些可用内存区域? And is it possible that my 4 GiB allocation will be non-contiguous? 我的4 GiB分配是否可能是不连续的?

On GNU/Linux with Glibc, large memory allocations, of more than a few hundred kilobytes, are handled by calling mmap . 在具有Glibc的GNU / Linux上,可以通过调用mmap处理超过几百千字节的大内存分配。 When the free function is invoked on this, the library knows that the memory was allocated this way (thanks to meta-data stored in a header). 当在此函数上调用free函数时,库知道内存是通过这种方式分配的(由于存储在标头中的元数据)。 It simply calls unmap on it to release it. 它只是对其调用unmap来释放它。 That's how the kernel knows; 内核就是这样知道的。 its mmap and unmap API is being used. 它的mmapunmap API正在使用。

You can see these calls if you run strace on the program. 如果在程序上运行strace ,则可以看到这些调用。

The kernel keeps track of all mmap -ed regions using a red-black tree. 内核使用红黑树跟踪所有mmap区域。 Given an arbitrary virtual address, it can quickly determine whether it lands in the mmap area, and which mapping, by performing a tree walk. 给定任意虚拟地址,它可以通过执行树遍历来快速确定它是否位于mmap区域以及进行哪个映射。

Do free implementations execute a syscall to the kernel (or many syscalls) to tell it which areas of memory are now available? 自由实现是否执行对内核的syscall(或许多syscall),以告诉内核现在有哪些可用内存区域?

Yes. 是。

A modern implementation of malloc on Linux will call mmap to allocate a large amount of memory. Linux上现代的malloc实现将调用mmap分配大量内存。 The kernel will find an unused virtual address, mark it as allocated, and return it. 内核将找到一个未使用的虚拟地址,将其标记为已分配,然后将其返回。 (The kernel may also return an error if there isn't enough free memory) (如果可用内存不足,内核也可能返回错误)

free would then call munmap to deallocate the memory, passing the address and size of the allocation. free然后将调用munmap来释放内存,并传递分配的地址和大小。

On Windows, malloc will call VirtualAlloc and free will call VirtualFree . 在Windows上, malloc将调用VirtualAllocfree将调用VirtualFree

Before the call to free, this process has a heap size of at least 4GiB... 在调用free之前,此过程的堆大小至少为4GiB。

The C language does not define either "heap" or "stack". C语言没有定义“堆”或“堆”。 Before the call to free, this process has a chunk of 4 GB dynamically allocated memory... 在调用free之前,此过程具有4 GB 动态分配的内存块...

and afterward, does it still have that heap size? 然后,它还有那个堆大小吗?

...and after the free() , access to that memory would be undefined behaviour , so for practical purposes, that dynamically allocated memory is no longer "there". ...并且在free() ,对该内存的访问将是未定义的行为 ,因此出于实际目的,动态分配的内存不再“存在”。

What the library does "under the hood" (eg caching, see below) is up to the library, and is subject to change without further notice. 在幕后进行的操作(例如,缓存,请参见下文)取决于库,并且如有更改,恕不另行通知。 This could change with the amount of available physical memory, system load, runtime parameters, ... 这可能会随着可用物理内存,系统负载,运行时参数等的变化而变化。

How do modern operating systems allow userspace programs to return memory back to kernel space? 现代操作系统如何允许用户空间程序将内存返回到内核空间?

It's up to the standard library's implementation to decide (which, of course, has to talk to the operating system to actually, physically allocate / free memory). 取决于标准库的实现(当然,必须与操作系统进行通信才能实际分配/释放内存)。

Others have pointed out how certain, existing implementations do it. 其他人指出了现有的实现是如何确定的 Other libraries, operating systems, and environments exist. 存在其他库,操作系统和环境。

Do free implementations execute a syscall to the kernel (or many syscalls) to tell it which areas of memory are now available? 自由实现是否执行对内核的syscall(或许多syscall),以告诉内核现在有哪些可用内存区域?

Possibly. 可能吧。 A common optimization done by library implementations is to "cache" free()d memory, so subsequent malloc() calls can be served without talking to the kernel (which is a costly operation). 库实现完成的常见优化是“缓存” free()d内存,因此可以在与内核对话的情况下服务后续的malloc()调用(这是一项昂贵的操作)。 When, how much, and how long memory is cached this way is, you guessed it, implementation-defined. 您猜想这种方式何时,多少以及何时缓存内存是实现定义的。

And is it possible that my 4 GiB allocation will be non-contiguous? 我的4 GiB分配是否可能是不连续的?

The process will always "see" contiguous memory. 该过程将始终“看到”连续的内存。 In a system supporting virtual memory (ie "modern" desktop OS's like Linux or Windows), the physical memory might be non-contiguous, but the virtual addresses your process gets to see will be contiguous (or the malloc() would have failed if this requirement could not be serviced). 在支持虚拟内存(例如Linux或Windows等“现代”台式机操作系统)的系统中, 物理内存可能是不连续的,但是您的进程看到的虚拟地址是连续的(否则,如果出现以下情况,malloc()将会失败)此要求无法满足)。

Again, other systems exist. 同样,存在其他系统。 You might be looking at a system that doesn't virtualize addresses (ie gives physical addresses to the process). 您可能正在寻找一个不会虚拟化地址的系统(即为进程提供物理地址)。 You might be looking at a system that assigns a given amount of memory to a process on startup, serves any malloc() requests from that, and doesn't support the allocation of additional memory. 您可能正在看一个系统,该系统在启动时将给定数量的内存分配给进程,为该进程提供任何malloc()请求,并且不支持分配额外的内存。 And so on. 等等。

If we're using Linux as an example it uses mmap to allocate large chunks of memory. 如果我们以Linux为例,它将使用mmap分配大块内存。 This means when you free it it gets umapped ie the kernel gets told that it can now unmap this memory. 这意味着当您释放它时,它将被捆绑,即告诉内核它现在可以取消映射此内存。 Read up on the brk and sbrk system calls. 阅读brksbrk系统调用。 A good place to start would be here... 一个很好的起点将在这里...

What does brk( ) system call do? brk()系统调用做什么?

and here. 和这里。 The following post discusses how malloc is implemented which will give you a good idea what's happening under the covers... 以下文章讨论了如何实现malloc,这将使您更好地了解幕后情况。

How is malloc() implemented internally? malloc()如何在内部实现?

Doug Lea's malloc can be found here. Doug Lea的malloc可以在这里找到。 It's well commented and public domain... 受到好评和公共领域...

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

malloc() and free() are kernel functions (system calls) . malloc()和free()是内核函数(系统调用)。 it is being called by the application to allocate and free memory on the heap. 应用程序正在调用它来分配和释放堆上的内存。 application itself is not allocating/freeing memory . 应用程序本身未分配/释放内存。

the whole mechanism is executed at kernel level . 整个机制是在内核级别执行的。

see the below heap implementation code 见下面的堆实现代码

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;
        }
    }
}

this heap_alloc function uses morecore function which is implemented as below : 这个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;
}

as you can see this function is asking the physical memory manager to allocate a block : 如您所见,此函数要求物理内存管理器分配一个块:

cp = (char *)pmmngr_alloc_block();

and then map the allocated block into virtual memory : 然后将分配的块映射到虚拟内存:

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

as you can see , the whole story is being controlled by the heap manager in kernel level. 如您所见,整个故事由内核级别的堆管理器控制。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM