我一直在阅读很多关于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()
调用返回一个实际的指针,而不是NULL
, size_t
可以在我的给定平台上保存一个与4294967296
一样大的整数,malloc
调用实现的分配器实际上会在堆中分配该数量的RAM 。假设上面的代码完全符合它的样子。
在调用free
之后,内核如何知道那些4 GiB的RAM现在是否有资格用于其他进程和内核本身?我不认为内核是Linux,但那将是一个很好的例子。在调用free之前,这个进程的堆大小至少为4GiB,之后它还有堆大小吗?
现代操作系统如何允许用户空间程序将内存返回内核空间? free
实现是否对内核(或许多系统调用)执行系统调用,以告诉它现在哪些内存区域可用?我的4 GiB分配是否可能是非连续的?
答案 0 :(得分:1)
在带有Glibc的GNU / Linux上,通过调用mmap
来处理大于几百千字节的大内存分配。当对此调用free
函数时,库知道内存是以这种方式分配的(感谢存储在头文件中的元数据)。它只需在其上调用unmap
即可释放它。这就是内核知道的方式;正在使用其mmap
和unmap
API。
如果您在程序上运行strace
,则可以看到这些调用。
内核使用红黑树跟踪所有mmap
个ed区域。给定一个任意虚拟地址,它可以通过执行树步行来快速确定它是否落在mmap
区域以及哪个映射。
答案 1 :(得分:1)
自由实现是否对内核(或许多系统调用)执行系统调用,以告诉它现在哪些内存区域可用?
是
Linux上malloc
的现代实现将调用mmap
来分配大量内存。内核将找到一个未使用的虚拟地址,将其标记为已分配,然后将其返回。 (如果没有足够的可用内存,内核也可能会返回错误)
free
然后调用munmap
来释放内存,传递分配的地址和大小。
在Windows上,malloc
将致电VirtualAlloc
,free
将致电VirtualFree
。
答案 2 :(得分:1)
在调用free之前,此进程的堆大小至少为4GiB ...
C语言没有定义“堆”或“堆栈”。在调用free之前,这个进程有一块4 GB 动态分配的内存...
之后,它还有那个堆大小吗?
...在free()
之后,对该内存的访问将是未定义的行为,因此出于实际目的,动态分配的内存不再是“那里”。
库做什么“幕后”(例如缓存,见下文)取决于图书馆,如有更改,恕不另行通知。这可能会随着可用物理内存量,系统负载,运行时参数......而改变。
现代操作系统如何允许用户空间程序将内存返回内核空间?
由标准库的实现决定(当然,必须与操作系统进行实际,物理分配/释放内存)。
其他人已经指出某些现有的实现是如何做到的。存在其他库,操作系统和环境。
自由实现是否对内核(或许多系统调用)执行系统调用,以告诉它现在哪些内存区域可用?
可能。库实现完成的常见优化是“缓存”free()d内存,因此后续的malloc()调用可以在没有与内核通信的情况下提供(这是一项代价高昂的操作)。通过这种方式缓存内存的时间,数量和时长是,你猜对了,实现定义了。
我的4 GiB分配是否可能是非连续的?
该过程将始终“看到”连续的内存。在支持虚拟内存的系统中(即“现代”桌面操作系统,如Linux或Windows),物理内存可能是非连续的,但虚拟地址会使您的进程进入看将连续(或者如果无法满足此要求,则malloc()将失败。)
同样,存在其他系统。您可能正在查看不虚拟化地址的系统(即为进程提供物理地址)。您可能正在查看一个系统,该系统在启动时为进程分配给定数量的内存,从中提供任何malloc()请求,并且不支持额外内存的分配。等等。
答案 3 :(得分:0)
如果我们使用Linux作为示例,它使用mmap来分配大块内存。这意味着当你释放它时它会被umap,即内核被告知它现在可以取消映射这个内存。阅读brk
和sbrk
系统调用。一个好的起点就在这里......
What does brk( ) system call do?
在这里。以下文章讨论了如何实现malloc,这将使您了解幕后发生的事情......
可以在这里找到Doug Lea的malloc。它评论很好,公共领域......答案 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);
正如您所看到的,整个故事由内核级别的堆管理器控制。