内存管理是由底层操作系统提供的服务。当我们调用malloc()/free()
并且没有操作系统在运行时(例如裸机嵌入式系统),如何处理内存分配和跟踪?
应该有一个实体来跟踪哪些地址是空闲的,哪些不是。那是操作系统内存管理单元。 malloc()/free()
将必须调用OS系统调用。因此,没有操作系统意味着没有malloc()/free()
。我承担这个错误吗?
更新:
所有答案都指出,malloc/free
可以使用静态池分配(在没有可用操作系统的情况下),也可以使用sbrk/brk
(它们是内核系统调用)。问题是malloc/free
如何知道下面是否有内核?
答案(请参见“ Kuba Ober”在下面的回答下的评论):
malloc不需要了解任何信息,因为链接项目所用的C库是特定于目标的:如果您是为Linux开发的,则使用Linux的C库,这不同于为OS X开发的,Windows或裸机ARM Cortex M0。或者,准系统x86。正是编写C库的人知道如何实现它,以便它可以在所需的目标上运行。例如,用于x86的准系统C库将使用EFI和ACPI查询硬件或BIOS未使用的RAM可用块的列表,然后将其用于满足分配请求。
答案 0 :(得分:7)
malloc()
和free()
不需要操作系统支持。它们可以(而且经常是!)在裸机系统上实现。例如,Arduino库使用malloc()
和free()
来管理字符串。
在托管的实现中(即,在操作系统上运行的应用程序),malloc()
和free()
通常将使用操作系统服务来分配新的“大块”内存-通常多达一次几兆字节-并在未使用时将这些块返回到操作系统。通过将那些内存块切成应用程序所需的大小来处理较小的分配。这样可以管理较小的分配,而不会产生系统调用的开销。
在无托管的实现中(如裸机系统),应用程序已经可以访问系统上存在的所有内存,并且可以按自己的意愿拆分出该内存的大块。
在较低的级别:malloc()
的托管和非托管实现通常通过将每个已分配或未分配的内存块视为链表中的条目来工作。这通常是通过在每次分配开始之前立即存储结构来实现的,例如
struct malloc_block {
struct malloc_block *prev, *next;
size_t size;
...
char allocation[];
};
并返回指向allocation
的指针作为malloc()
的返回值。 realloc()
和free()
之类的函数可以通过从分配指针中减去结构的大小来检索结构。
答案 1 :(得分:2)
Malloc / free函数管理一个内存池。这些功能通常不是操作系统服务。
然后如何创建该池?
在大多数系统上,malloc调用操作系统服务以将页面映射到进程地址空间中,以创建和扩展内存池。如果调用malloc,并且没有可用的内存,则大多数实现将调用系统服务以映射更多的内存并扩展池。
malloc实现需要维护数据结构,以跟踪池中的哪些可用内存以及已分配的内存。这可以通过许多不同的方式来完成。对于程序员来说,选择最适合他们的malloc / free组合并将其链接到他们的应用程序中并不少见。
因此,是的,通常涉及操作系统。
但是您询问是否可以在没有操作系统的情况下实现它们。
假设您做了:
static char pool [POOLSIZE] ;
在您的malloc实现中。这样,您将不需要系统服务即可在执行期间创建池。另一方面,您的泳池大小固定。
答案 2 :(得分:1)
通常来说:如果我们将运行时定义为main()
进入和返回之间的时刻,则不会,或者至少在运行时不会。
假设您实现了在固定大小的池上运行的malloc:
static char pool[MALLOC_POOL_SIZE];
void *malloc(size_t size) {
…
}
void free(void *block) {
…
}
然后,在托管和非托管实现上,您都可以使用动态内存分配。在托管的实现中,二进制加载程序将确保在pool
后面映射内存。在非托管实现中,链接器会将池预先定位在可用RAM中,如果池太大,则链接将失败。
因此,不需要,通常在代码运行后就不需要OS,但是需要OS的参与才能使代码首先运行(如果有OS)。
当然,“不需要”意味着没有必要,但并不排除操作系统对特定C运行时库中动态内存分配的支持。在大多数托管的运行时中,malloc
使用的池是通过调用相关的OS API而动态扩展(或收缩)的。在经典Unix上,扩展将通过brk
syscall完成。