动态内存分配和内存块元数据
Dynamic memory allocation and memory block metadata
我有一个关于动态内存分配的低级东西的问题。我知道可能会有不同的实现,但我需要了解基本的思想。
当一个现代的操作系统内存分配器或类似的分配一个内存块时,这个块需要被释放。
但是,在此之前,需要存在一些系统来控制分配过程。
I need to know:
- 系统如何跟踪已分配和未分配的内存。我的意思是,系统需要知道哪些块已经被分配了,它们的大小是多少,以便在分配和释放过程中使用这些信息。
现代硬件是否支持这个过程,比如分配位或类似的东西?或者是用于存储分配信息的某种数据结构。如果有一个数据结构,与分配的内存相比,它使用了多少内存?
在大块中分配内存比在小块中分配内存好吗?为什么?
任何有助于揭示基本实现细节的答案都是值得赞赏的。
如果需要代码示例,C或c++就可以了。
"系统如何跟踪已分配和未分配的内存。"对于带有操作系统的非嵌入式系统,由操作系统负责组织的虚拟页表(当然要使用硬件TLB支持)可以跟踪程序的内存使用情况。
据我所知(如果我错了,社区肯定会对我大喊大叫),跟踪单个malloc()
大小和位置有很多实现,并且依赖于运行时库。一般来说,无论何时调用malloc()
,大小和位置都存储在一个表中。无论何时调用free()
,都会查找所提供指针的表项。如果找到,则删除该条目。如果没有找到,则忽略free()
(这也表明可能存在内存泄漏)。
当虚拟页中的所有malloc()
条目被释放后,该虚拟页随后被释放回操作系统(这也意味着free()
并不总是将内存释放回操作系统,因为虚拟页中可能仍然有其他malloc()
条目)。如果在给定的虚拟页中没有足够的空间来支持指定大小的另一个malloc()
,则从操作系统请求另一个虚拟页。
嵌入式处理器通常没有操作系统,虚拟页表,也没有多个进程。在这种情况下,不使用虚拟内存。相反,嵌入式处理器的整个内存被视为一个大的虚拟页面(尽管地址实际上是物理地址),并且内存管理遵循与前面描述的类似的过程。
这是一个类似的堆栈溢出问题,有更深入的答案。
"分配大块内存比分配小块内存好吗?为什么?"根据需要分配尽可能多的内存,不多也不少。编译器优化非常智能,并且内存几乎总是比程序员手动做的更有效地管理(即减少内存碎片)。在非嵌入式环境中尤其如此。
这里是一个类似的堆栈溢出问题,有更深入的答案(注意,它属于C而不是c++,但它仍然与本讨论相关)。
要做到这一点,方法不止一种。我曾经为了教育的目的写了一个malloc()
(和free()
)的实现。
这是我的经验,现实世界的实现肯定是不同的。
我使用了一个双链表。调用malloc()
后返回给用户的内存块实际上是一个struct
,其中包含与我的实现相关的信息(即next
和prev
指针,以及一个is_used字节)。
所以当用户请求N个字节时,我分配N + sizeof(my_struct)字节,隐藏next
和prev
指针在块的开头,并返回给用户。
当然,对于使用大量小分配的程序来说,这是一个糟糕的设计(因为每个分配最多占用N + 2个指针+ 1个字节)。
对于一个真实世界的实现,您可以看一下优秀的和众所周知的内存分配器的代码。
通常有两层。
一层位于应用程序级别,通常作为C标准库的一部分。这是您通过malloc
和free
(或c++中的operator new
,通常反过来调用malloc
)等函数调用的。这一层负责分配,但不知道内存或它来自哪里。
另一层,在操作系统级别,不知道也不关心您的分配。它只维护一个已被保留、分配和访问的固定大小的内存页面列表,以及每个页面的信息,比如它映射到哪里。
每一层都有许多不同的实现,但一般情况下是这样的:
当您分配内存时,分配器("应用程序级部分")会查看它的账簿中是否有匹配的块可以分配给您(如果需要,一些分配器会将较大的块分成两部分)。
如果没有找到合适的块,它会从操作系统保留一个新的块(通常比您要求的要大得多)。sbrk
或mmap
在Linux上,或VirtualAlloc
在Windows上可能是典型的函数的例子,它可能会使用的效果
除了向操作系统显示意图和生成一些页表项之外,这几乎没有什么作用。
然后,分配器(逻辑上,在它的账簿中)根据它的正常操作模式将这个大区域分成更小的块,找到一个合适的块,并将其返回给您。请注意,这个返回的内存甚至不一定作为物理内存存在(尽管大多数分配器将一些元数据写入每个分配单元的前几个字节,因此它们必须预先对页面进行故障处理)。
同时,不可见的是,一个后台任务将某个进程曾经使用过但已经释放的内存页面归零。这种情况经常发生,只是暂时的,因为迟早会有人请求内存(通常,这就是空闲任务所做的)。
一旦您第一次访问包含分配块的页面中的地址,就会产生错误。这个不存在的页面的页表条目(如果逻辑上存在,只是物理上不存在)被替换为对零页池中的页面的引用。在不常见的情况下,没有剩余,例如,如果一直分配了大量的内存,操作系统会交换出一个它认为不会很快访问的页面,将其归零,并返回这个页面。
现在,该页成为工作集的一部分,它对应于实际的物理内存,并计入进程的配额。当您的进程正在运行时,页面可能会移进移出您的工作集,或者在超出某些限制时,根据需要多少内存以及访问它的方式,将页面移出或移入。
一旦调用free
,分配器将释放的区域放回它的账本中。它可能会告诉操作系统它不再需要内存,但通常这不会发生,因为它不是真正必要的,并且保留一些额外的内存并重用它会更有效。此外,释放内存可能不容易,因为通常您分配/释放的单元并不直接对应于操作系统使用的单元(并且,在sbrk
的情况下,它们也需要以正确的顺序发生)。
当进程结束时,操作系统简单地丢弃所有页表项,并将所有页面添加到空闲任务将归零的页面列表中。这样物理内存就可以被下一个进程使用了
- 在C++中打印指向不同基元数据类型的指针的内存地址
- 以非特权用户身份查询 NTFS 特殊文件的元数据?
- 如何使用 Google Test 向测试添加元数据 / 如何将数据从 Google Test 发送到 TestEven
- 将复杂的非基元C++数据类型转换为 Erlang/Elixir 格式,以使用 NIF 导出方法
- 从模板创建通用打印函数,以打印基元数据类型变量的值
- 在 C++ 中修改 Grpc 双向流式处理期间的元数据
- cpp / c ++中的grpc客户端代码,元数据x-api-key/x-goog-api-key不起作用,给了我语音A
- 如何使用gRPC在客户端和服务器之间双向发送和接收流元数据
- 即使基类和派生类只使用基元数据类型,我是否需要定义虚拟析构函数
- 如何处理错误"E1696命令行错误:无法在Visual Studio 2017中打开元数据文件"mscorlib.dll"?
- 元数据操作失败LNK2022错误 (8013118D):重复类型中的布局信息不一致 (选择设备参数):(0x020002
- C++11 标准中的哪一部分规定了基元数据类型大小之间的相对顺序?
- 使用 ffmpeg 添加元数据信息
- WIC - Exif 元数据查询 - 如何获取图像说明
- 从Qt应用程序读取元数据
- 如何将编译时元数据/行为添加到特定函数
- 如何在C++中访问有关内存的元数据
- 是否有自定义内存分配器设计模式不在其分配中存储元数据
- 动态内存分配和内存块元数据
- 计算存储缓存元数据所需的缓存内存