macOS以及ios上的一些内存函数具体实现

629 阅读3分钟

Libc_darwin源码阅读-malloc

简介

macOS/iOS上的libc实现,源码是apple开源的,代码地址如下:

Tips for Allocating Memory 这篇文章介绍了苹果对于分配内存这一块的逻辑。 ​

Malloc

本节主要关注 apple libc中关于 malloc 等相关内存分配和释放的函数实现。

malloc 等函数的定义是在 include/stdlib.h 文件中,在该文件中并没有直接定义 malloc 等函数,而是:

#include <malloc/_malloc.h>

所有的具体malloc等函数实现其实是在另一个库 libmalloc 中,源码也是开源的,代码地址如下:

在该库的 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

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);
}

man - valloc

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