用户空间程序如何在 free() 之后将内存传回给内核?
How do userspace programs pass memory back to the kernel after free()?
我已经阅读了很多关于 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()
调用 return 是一个实际的指针,而不是NULL
,size_t
在我给定的平台上可以容纳一个与 4294967296
一样大的整数,malloc
调用实现的分配器 实际上确实分配了那个堆中的 RAM 数量 。假设上面的代码完全按照它看起来的样子去做。
在对 free
的调用执行后,内核如何知道这 4 GiB RAM 现在可以用于其他进程和内核本身?我不假设内核是 Linux,但那将是一个很好的例子。在调用 free 之前,此进程的堆大小至少为 4GiB,之后,它是否仍然具有该堆大小?
现代操作系统如何允许用户space程序将内存return返回给内核space? free
实现是否对内核(或许多系统调用)执行系统调用以告诉它哪些内存区域现在可用?我的 4 GiB 分配有可能不连续吗?
在使用 Glibc 的 GNU/Linux 上,超过几百 KB 的大内存分配是通过调用 mmap
处理的。当调用 free
函数时,库知道内存是这样分配的(感谢 meta-data 存储在 header 中)。它只是调用 unmap
来释放它。内核就是这样知道的;它的 mmap
和 unmap
API 正在使用中。
如果你运行strace
在程序上,你可以看到这些调用。
内核使用 red-black 树跟踪所有 mmap
-ed 区域。给定任意虚拟地址,它可以通过执行树遍历快速确定它是否落在 mmap
区域,以及哪个映射。
如果我们使用 Linux 作为示例,它使用 mmap 分配大块内存。这意味着当您释放它时,它会被取消映射,即内核被告知它现在可以取消映射该内存。阅读 brk
和 sbrk
系统调用。一个好的起点是这里...
What does brk( ) system call do?
在这里。下面的 post 讨论了 malloc 是如何实现的,这将使您对幕后发生的事情有一个很好的了解...
How is malloc() implemented internally?
Doug Lea 的 malloc 可以在这里找到。评论很好,public 域...
Do free implementations execute a syscall to the kernel (or many syscalls) to tell it which areas of memory are now available?
是的。
Linux 上 malloc
的现代实现将调用 mmap
来分配大量内存。内核会找到一个未使用的虚拟地址,将其标记为已分配,并 return 它。 (如果没有足够的可用内存,内核也可能 return 出错)
free
然后调用 munmap
释放内存,传递分配的地址和大小。
在 Windows,malloc
将调用 VirtualAlloc
and free
will call VirtualFree
。
malloc() 和 free() 是内核函数(系统调用)。应用程序调用它来分配和释放堆上的内存。
应用程序本身不是 allocating/freeing 内存。
整个机制在内核级别执行。
看下面的堆实现代码
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);
如您所见,整个故事都由内核级别的堆管理器控制。
Before the call to free, this process has a heap size of at least 4GiB...
C 语言没有定义 "heap" 或 "stack"。在调用 free 之前,这个进程有一块 4 GB 动态分配的 内存...
and afterward, does it still have that heap size?
...在 free()
之后,对该内存的访问将是 未定义行为 ,因此出于实际目的,动态分配的内存不再是 "there".
库 的作用"under the hood"(例如缓存,见下文)由库决定,如有更改,恕不另行通知。这可能会随着可用物理内存量、系统负载、运行时参数的变化而变化,...
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?
可能吧。库实现所做的一个常见优化是 "cache" free()d 内存,因此后续的 malloc() 调用可以被服务 而无需 与内核对话(这是一个代价高昂的操作).您猜对了,以这种方式缓存内存的时间、数量和时长是实现定义的。
And is it possible that my 4 GiB allocation will be non-contiguous?
进程将始终"see"连续内存。在支持虚拟内存的系统中(即 "modern" 桌面 OS 类似于 Linux 或 Windows), 物理 内存可能是不连续,但是 virtual 地址你的进程可以看到 will 是连续的(或者如果这个要求不能满足 malloc() 将会失败得到服务)。
同样,存在其他系统。您可能正在查看一个不虚拟化地址的系统(即为进程提供物理地址)。您可能正在查看一个系统,该系统在启动时将给定数量的内存分配给进程,为来自该进程的任何 malloc() 请求提供服务,并且不支持额外内存的分配。等等。
我已经阅读了很多关于 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()
调用 return 是一个实际的指针,而不是NULL
,size_t
在我给定的平台上可以容纳一个与 4294967296
一样大的整数,malloc
调用实现的分配器 实际上确实分配了那个堆中的 RAM 数量 。假设上面的代码完全按照它看起来的样子去做。
在对 free
的调用执行后,内核如何知道这 4 GiB RAM 现在可以用于其他进程和内核本身?我不假设内核是 Linux,但那将是一个很好的例子。在调用 free 之前,此进程的堆大小至少为 4GiB,之后,它是否仍然具有该堆大小?
现代操作系统如何允许用户space程序将内存return返回给内核space? free
实现是否对内核(或许多系统调用)执行系统调用以告诉它哪些内存区域现在可用?我的 4 GiB 分配有可能不连续吗?
在使用 Glibc 的 GNU/Linux 上,超过几百 KB 的大内存分配是通过调用 mmap
处理的。当调用 free
函数时,库知道内存是这样分配的(感谢 meta-data 存储在 header 中)。它只是调用 unmap
来释放它。内核就是这样知道的;它的 mmap
和 unmap
API 正在使用中。
如果你运行strace
在程序上,你可以看到这些调用。
内核使用 red-black 树跟踪所有 mmap
-ed 区域。给定任意虚拟地址,它可以通过执行树遍历快速确定它是否落在 mmap
区域,以及哪个映射。
如果我们使用 Linux 作为示例,它使用 mmap 分配大块内存。这意味着当您释放它时,它会被取消映射,即内核被告知它现在可以取消映射该内存。阅读 brk
和 sbrk
系统调用。一个好的起点是这里...
What does brk( ) system call do?
在这里。下面的 post 讨论了 malloc 是如何实现的,这将使您对幕后发生的事情有一个很好的了解...
How is malloc() implemented internally?
Doug Lea 的 malloc 可以在这里找到。评论很好,public 域...
Do free implementations execute a syscall to the kernel (or many syscalls) to tell it which areas of memory are now available?
是的。
Linux 上 malloc
的现代实现将调用 mmap
来分配大量内存。内核会找到一个未使用的虚拟地址,将其标记为已分配,并 return 它。 (如果没有足够的可用内存,内核也可能 return 出错)
free
然后调用 munmap
释放内存,传递分配的地址和大小。
在 Windows,malloc
将调用 VirtualAlloc
and free
will call VirtualFree
。
malloc() 和 free() 是内核函数(系统调用)。应用程序调用它来分配和释放堆上的内存。 应用程序本身不是 allocating/freeing 内存。
整个机制在内核级别执行。
看下面的堆实现代码
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);
如您所见,整个故事都由内核级别的堆管理器控制。
Before the call to free, this process has a heap size of at least 4GiB...
C 语言没有定义 "heap" 或 "stack"。在调用 free 之前,这个进程有一块 4 GB 动态分配的 内存...
and afterward, does it still have that heap size?
...在 free()
之后,对该内存的访问将是 未定义行为 ,因此出于实际目的,动态分配的内存不再是 "there".
库 的作用"under the hood"(例如缓存,见下文)由库决定,如有更改,恕不另行通知。这可能会随着可用物理内存量、系统负载、运行时参数的变化而变化,...
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?
可能吧。库实现所做的一个常见优化是 "cache" free()d 内存,因此后续的 malloc() 调用可以被服务 而无需 与内核对话(这是一个代价高昂的操作).您猜对了,以这种方式缓存内存的时间、数量和时长是实现定义的。
And is it possible that my 4 GiB allocation will be non-contiguous?
进程将始终"see"连续内存。在支持虚拟内存的系统中(即 "modern" 桌面 OS 类似于 Linux 或 Windows), 物理 内存可能是不连续,但是 virtual 地址你的进程可以看到 will 是连续的(或者如果这个要求不能满足 malloc() 将会失败得到服务)。
同样,存在其他系统。您可能正在查看一个不虚拟化地址的系统(即为进程提供物理地址)。您可能正在查看一个系统,该系统在启动时将给定数量的内存分配给进程,为来自该进程的任何 malloc() 请求提供服务,并且不支持额外内存的分配。等等。