我对heap
和free list
感到困惑。我有几个问题,我对malloc如何在C中工作有我自己的理解。如果我错了,请纠正我。
我对存储分配的理解(有待改进): -
当我们调用malloc时,它会在堆中分配内存,并通过从free list
中选择合适大小的数据块来实现,对吗?
当malloc返回某个特定的内存块时,它将从空闲列表中删除,并在页表中更新该内存块的物理地址。
当内存空闲时使用free()
,数据块会被插回到空闲列表中,并可能减少碎片,与相邻块和present
相结合页表条目中的位被清除。
因此整个堆是一个空闲列表(空闲块的链接列表)+已分配的数据块。
这是存储分配的全面情况吗?
编辑:来自Linux内核开发(Robert Love)关于内存管理的章节,平板分配
"空闲列表包含可用的已分配数据块 结构。当代码需要数据结构的新实例时,它 可以从自由列表中获取其中一个结构而不是分配 足够的内存量并为数据结构设置它。 之后,当不再需要数据结构时,它将返回 免费列表而不是取消分配。从这个意义上说,是免费清单 充当对象缓存,缓存常用类型的对象。"
自由列表被称为可用的已分配数据结构块。"
答案 0 :(得分:7)
malloc()
与页面表格无关;它分配虚拟地址,内核负责跟踪页面实际存储在物理RAM或磁盘上的位置。
malloc()
通过brk()
系统调用与内核交互,该调用要求内核为进程分配更多页面,或者将页面释放回内核。所以实际上有两个级别的内存分配:
brk()
操纵的是内核允许您访问的地址之间的边界(因为它们对应于已分配的页面)和地址,如果您尝试访问它们将导致分段错误。malloc()
分配程序数据段的可变大小部分以供程序使用。当数据段的当前大小内没有足够的可用空间时,它使用brk()
从内核获取更多页面,从而使数据段更大。当它发现数据段末尾的某些空间未被使用时,它使用brk()
将未使用的页面返回给内核,使数据段更小。请注意,即使在该过程中运行的程序实际上没有使用任何页面,也可以将页面分配给进程(由内核)。如果您free()
位于数据段中间的内存块,则free()
的实施无法使用brk()
缩小数据段,因为在更高的地址还有其他分配的块。因此,从内核的角度来看,页面仍然会分配给您的程序,即使它们是“自由空间”。从malloc()
角度来看。
您对自由列表如何工作的描述对我来说听起来不错,尽管我并不是如何实现内存分配器的专家。但是您从Robert Love发布的引用听起来像是在谈论Linux内核中的内存分配,这与用户空间进程中malloc()
的内存分配无关。这种免费列表的工作方式可能不同。
答案 1 :(得分:2)
您要做的第一件事是区分内核和程序分配。就像@Wyzard所说,malloc使用brk(sbrk,有时是mmap)从内核获取更多页面。我对malloc的理解非常好,但它跟踪你可能称之为竞技场的东西。它调解对内存的访问,并根据需要进行正确的系统调用以分配内存。
免费列表是管理内存的一种方法。每次从内核需要更多内存时调用mmap或brk都是缓慢且低效的。这两个都需要上下文切换到内核模式,并将分配数据结构来跟踪哪个进程拥有内存。用户级别的空闲列表是一种不经常请求并将内存返回内核的优化。用户程序的目标是完成其工作,而不是等待内核完成其工作。
现在回答你的其他问题:
另一种考虑自由列表的方法是作为缓存。程序将发出请求,内核将尝试按需完成它们而不是一次性完成它们。但是,当一个程序用一块内存完成时,快速路径不是将它返回给内核,而是将它放在安全的地方再次分配。特别是,一个空闲列表可以跟踪分配器可以从中获取的内存区域,但是以这种方式(具有内存保护)这样做,用户代码不能仅仅选择它并开始写入所有内容区域。
如果我们假设真正解除分配块需要将其返回到内核并让它更新其内部页表,那么差异实际上在于对底层物理页(或帧)的控制。实际上释放并将内存返回到内核意味着内核现在可以从这些页面中提取,而将其返回到用户级别的空闲列表意味着该程序的某些部分仍然控制着该内存。
这开始得到一些不同的领域(我并不十分熟悉)。 slab分配器是内核管理内存分配的一种方式。从我所看到的,slab尝试将分配分组为不同的大小,并有一个页面池来满足这些请求。我相信常见的x86架构将允许以16字节到4 MB的两个幂的分配连续的物理内存(尽管我在64位机器上)。我相信在这个级别上有一些自由列表的概念,以及有效升级或降级分配大小以满足不同系统需求的方法。
另一方面,存储分配听起来像是确保硬盘上有空间。我实际上没有听过这个词,所以我只能推测。
答案 2 :(得分:0)
这里指的是两个不同的分配器,
请参阅Heap Memory in C Programming了解堆。
对于用户空间中的c程序,我们在call_stack中有malloc发生的堆内存。这由_break标记,当需要更多内存时,它由sbrk()提前。
在Linux内核中,每个进程都有task_struct,它有自己的堆栈和指向它所使用的页面列表的指针。