Libc_darwin源码阅读-malloc
简介
macOS/iOS上的libc实现,源码是apple开源的,代码地址如下:
- opensource.apple.com/source/Libc…
- github.com/apple-oss-d… (github mirror)
- github.com/apple-oss-d… (kernal)
- opensource.apple.com/source/xnu/… (syscalls)
Tips for Allocating Memory 这篇文章介绍了苹果对于分配内存这一块的逻辑。
Malloc
本节主要关注 apple libc中关于 malloc 等相关内存分配和释放的函数实现。
malloc
等函数的定义是在 include/stdlib.h
文件中,在该文件中并没有直接定义 malloc
等函数,而是:
#include <malloc/_malloc.h>
所有的具体malloc等函数实现其实是在另一个库 libmalloc
中,源码也是开源的,代码地址如下:
- opensource.apple.com/source/libm…
- github.com/apple-oss-d… (github mirror)
在该库的 include/malloc/_malloc.h 中真正定义内存分配的一系列函数:
void *malloc(size_t __size) __result_use_check __alloc_size(1);
void *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);
void free(void *);
void *realloc(void *__ptr, size_t __size) __result_use_check __alloc_size(2);
#if !defined(_ANSI_SOURCE) && (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE))
void *valloc(size_t) __alloc_size(1);
#endif // !defined(_ANSI_SOURCE) && (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE))
#if (__DARWIN_C_LEVEL >= __DARWIN_C_FULL) || \
(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) || \
(defined(__cplusplus) && __cplusplus >= 201703L)
void *aligned_alloc(size_t __alignment, size_t __size) __result_use_check __alloc_size(2) __OSX_AVAILABLE(10.15) __IOS_AVAILABLE(13.0) __TVOS_AVAILABLE(13.0) __WATCHOS_AVAILABLE(6.0);
#endif
int posix_memalign(void **__memptr, size_t __alignment, size_t __size) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0);
malloc
调用栈:
- malloc
- _malloc_zone_malloc
- _malloc_zone_t.malloc
- szone_malloc
- szone_malloc_should_clear
- tiny_malloc_should_clear
- small_malloc_should_clear
- medium_malloc_should_clear
- large_malloc
- mvm_allocate_pages
- mach_vm_map
- mvm_allocate_pages
- szone_malloc_should_clear
- szone_malloc
- _malloc_zone_t.malloc
- _malloc_zone_malloc
TODO: malloc底层会调用mach内核的 vm_allocate 虚拟内存分配函数。
void *
malloc(size_t size)
{
return _malloc_zone_malloc(default_zone, size, MZ_POSIX);
}
static void *
_malloc_zone_malloc(malloc_zone_t *zone, size_t size, malloc_zone_options_t mzo)
{
if (zone == default_zone && !lite_zone) {
// Eagerly resolve the virtual default zone to make the zone version
// check accurate
zone = malloc_zones[0];
}
// 开启了malloc trace,在这个实现中,会记录malloc的行为,用于debug
if (os_unlikely(malloc_instrumented || malloc_check_start ||
malloc_logger || zone->version < 13)) {
return _malloc_zone_malloc_instrumented_or_legacy(zone, size, mzo);
}
// malloc的最大size为:#define _MALLOC_ABSOLUTE_MAX_SIZE (SIZE_T_MAX - (2 * large_vm_page_quanta_size))
if (os_unlikely(size > malloc_absolute_max_size)) {
malloc_set_errno_fast(mzo, ENOMEM);
return NULL;
}
// zone versions >= 13 set errno on failure so we can tail-call
return zone->malloc(zone, size);
}
/src/malloc.c
这里有一个概念就是 malloc_zone
, 默认有一个全局的 default_zone
, 这些和 zone 相关的malloc函数定义在 include/malloc/malloc.h
)
// 获取全局的 default zone(即默认 malloc 使用的 zone)
extern malloc_zone_t *malloc_default_zone(void);
// 创建一个新的zone
extern malloc_zone_t *malloc_create_zone(vm_size_t start_size, unsigned flags);
// 销毁一个zone
extern void malloc_destroy_zone(malloc_zone_t *zone);
// 在一个zone中分配一块内存(malloc内部调用的就是该函数的具体实现)
extern void *malloc_zone_malloc(malloc_zone_t *zone, size_t size) __alloc_size(2);
calloc
void *
calloc(size_t num_items, size_t size)
{
return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}
free
void
free(void *ptr)
{
if (!ptr) {
return;
}
if (os_unlikely(malloc_instrumented ||
malloc_check_start ||
malloc_logger ||
lite_zone ||
malloc_num_zones == 0 ||
malloc_zones[0]->version < 13 ||
!malloc_zones[0]->try_free_default)) {
find_zone_and_free(ptr, false);
return;
}
malloc_zone_t *zone0 = malloc_zones[0];
zone0->try_free_default(zone0, ptr);
}
realloc
void *
realloc(void *in_ptr, size_t new_size)
{
void *retval = NULL;
void *old_ptr;
malloc_zone_t *zone;
// SUSv3: "If size is 0 and ptr is not a null pointer, the object
// pointed to is freed. If the space cannot be allocated, the object
// shall remain unchanged." Also "If size is 0, either a null pointer
// or a unique pointer that can be successfully passed to free() shall
// be returned." We choose to allocate a minimum size object by calling
// malloc_zone_malloc with zero size, which matches "If ptr is a null
// pointer, realloc() shall be equivalent to malloc() for the specified
// size." So we only free the original memory if the allocation succeeds.
old_ptr = (new_size == 0) ? NULL : in_ptr;
if (!old_ptr) {
retval = malloc_zone_malloc(default_zone, new_size);
} else {
zone = find_registered_zone(old_ptr, NULL, false);
if (!zone) {
int flags = MALLOC_REPORT_DEBUG | MALLOC_REPORT_NOLOG;
if (malloc_debug_flags & (MALLOC_ABORT_ON_CORRUPTION | MALLOC_ABORT_ON_ERROR)) {
flags = MALLOC_REPORT_CRASH | MALLOC_REPORT_NOLOG;
}
malloc_report(flags, "*** error for object %p: pointer being realloc'd was not allocated\n", in_ptr);
} else {
retval = malloc_zone_realloc(zone, old_ptr, new_size);
}
}
if (retval == NULL) {
malloc_set_errno_fast(MZ_POSIX, ENOMEM);
} else if (new_size == 0) {
free(in_ptr);
}
return retval;
}
NOTE: 注意如果参数的new_size为0,则会直接调用 free
valloc
void *
valloc(size_t size)
{
return _malloc_zone_valloc(default_zone, size, MZ_POSIX);
}
aligned_alloc
void *
aligned_alloc(size_t alignment, size_t size)
{
return _malloc_zone_memalign(default_zone, alignment, size,
MZ_POSIX | MZ_C11);
}
posix_memalign
int
posix_memalign(void **memptr, size_t alignment, size_t size)
{
void *retval;
/* POSIX is silent on NULL == memptr !?! */
retval = malloc_zone_memalign(default_zone, alignment, size);
if (retval == NULL) {
// To avoid testing the alignment constraints redundantly, we'll rely on the
// test made in malloc_zone_memalign to vet each request. Only if that test fails
// and returns NULL, do we arrive here to detect the bogus alignment and give the
// required EINVAL return.
if (alignment < sizeof(void *) || // excludes 0 == alignment
0 != (alignment & (alignment - 1))) { // relies on sizeof(void *) being a power of two.
return EINVAL;
}
return ENOMEM;
} else {
*memptr = retval; // Set iff allocation succeeded
return 0;
}
}
Mmap
mmap
函数并不是libc的标准,而是linux的标准,具体可参考man7 - mmap
头文件在: /sys/mman.h
mmap
是一个system call,其实现在xnu kernal, 路径libsyscall\wrappers\unix03\mmap.c
下。
void *__mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
/*
* mmap stub, with preemptory failures due to extra parameter checking
* mandated for conformance.
*
* This is for UNIX03 only.
*/
void *
mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
{
/*
* Preemptory failures:
*
* o off is not a multiple of the page size
* [ This is enforced by the kernel with MAP_UNIX03 ]
* o flags does not contain either MAP_PRIVATE or MAP_SHARED
* o len is zero
*
* Now enforced by the kernel when the MAP_UNIX03 flag is provided.
*/
extern void cerror_nocancel(int);
if ((((flags & MAP_PRIVATE) != MAP_PRIVATE) &&
((flags & MAP_SHARED) != MAP_SHARED)) ||
(len == 0)) {
cerror_nocancel(EINVAL);
return MAP_FAILED;
}
void *ptr = __mmap(addr, len, prot, flags | MAP_UNIX03, fildes, off);
if (__syscall_logger) {
int stackLoggingFlags = stack_logging_type_vm_allocate;
if (flags & MAP_ANON) {
stackLoggingFlags |= (fildes & VM_FLAGS_ALIAS_MASK);
} else {
stackLoggingFlags |= stack_logging_type_mapped_file_or_shared_mem;
}
__syscall_logger(stackLoggingFlags, (uintptr_t)mach_task_self(), (uintptr_t)len, 0, (uintptr_t)ptr, 0);
}
return ptr;
}
可以看到, mmap
调用了__mmap
。
munmap
munmap
是一个system call,其实现在xnu kernal, 路径libsyscall\wrappers\unix03\munmap.c
下。
/*
* munmap stub, for stack logging of VM allocations.
*
* This is for UNIX03 only.
*/
extern int __munmap(void *, size_t);
int
munmap(void *addr, size_t len)
{
if (__syscall_logger) {
__syscall_logger(stack_logging_type_vm_deallocate, (uintptr_t)mach_task_self(), (uintptr_t)addr, len, 0, 0);
}
int result = __munmap(addr, len);
return result;
}
可以看到, munmap
调用了__munmap
。