一、Mach虚拟内存
1.1 Mach内存简介
iOS零碎架构可分为内核驱动层(Kernel and Device Drivers Layer)、外围操作系统层(Core OS )、外围服务层(Core Services layer)、媒体层(Media layer可触摸层&应用层(Cocoa&Application layer),内核驱动层就是咱们常常提到的Darwin,Darwin是苹果公司于2000年公布的一个开源操作系统,是由XNU和一些其余的Darwin库组成,XNU是由苹果公司公布的操作系统内核,XNU蕴含三局部:Mach、BSD、I/O Kit。
Mach是一个由卡内基梅隆大学开发的计算机操作系统微内核,是XNU内核,是作为 UNIX 内核的代替,次要解决 UNIX 所有皆文件导致形象机制有余的问题,为古代操作系统做了进一步的形象工作。Mach 负责操作系统最根本的工作,包含过程和线程形象、处理器调度、过程间通信、音讯机制、虚拟内存治理、内存保护等。在iOS零碎架构中,内存治理是由在Mach层中进行的,BSD只是对Mach接口进行了POSIX封装,不便用户态过程调用。
1.2 Mach虚拟内存的特点
1.2.1 虚构段页式内存治理
页是内存治理的根本单位, 在 Intel 和 ARM 中,通常为4K,罕用的查看虚拟内存的命令:hw.pagesize 查看默认页面大小; vm\_page\_free\_count:以后闲暇的 RAM 页数;vm\_stat(1) - 从零碎范畴的角度提供无关虚拟内存的统计信息。
在 iOS ARM64机型中page size是16K,在 JetsamEvent 结尾的系统日志里pageSize 代表以后设施物理内存页的大小。
1.2.2 iOS零碎没有替换空间
手机自带的磁盘空间也很小,属于宝贵资源,同时跟桌面硬件比起来,手机的闪存 I/O 速度太慢,所以iOS零碎没有替换空间;对于Mac零碎,参考 Apple 官网文档About the Virtual Memory System,Mac 上有替换空间有换页行为,也就是当物理内存不够了,就把不沉闷的内存页暂存到磁盘上,以此换取更多的内存空间。
1.2.3 内存压缩技术
内存压缩技术是从 OS X Mavericks (10.9) 开始引入的 (iOS 则是 iOS 7.0 开始),能够参考官网文档: OS X Mavericks Core Technology Overview, 在内存缓和时可能将最近应用过的内存占用压缩至原有大小的一半以下,并且可能在须要时解压复用。简略了解为零碎会在内存缓和的时候寻找 inactive memory pages 而后开始压缩,达到开释内存的成果,以 CPU 工夫来换取内存空间,NSPurgeableData是应用该技术典型的数据结构。所以掂量内存指标肯定要记录 compressed内存 ,另外还须要记录被压缩的 page 的信息。
1.2.4 内存报警
通过后面的内存压缩环节后,设施可用内存若仍处于危险状态,iOS零碎须要各个App过程配合解决,会向各过程发送内存报警要求配合开释内存,具体来说,Mach内核零碎的vm\_pageout 守护程序会查问过程列表及其驻留页面数,向驻留页面数最高的过程发送NOTE\_VM\_PRESSURE ,被选中的过程会响应这个压力告诉,理论体现就是APP收到零碎的didReceiveMemoryWarning 内存报警,开释局部内存以达到升高手机内存负载的指标。
在收到内存报警时,App升高内存负载,能够在很大水平上避免出现OOM,具体源码剖析见第三节。
1.2.5 Jetsam机制
当过程不能通过开释内存缓解内存压力时,Jestam机制开始染指,这是iOS 实现的一个低内存清理的解决机制,也称为MemoryStatus,这个机制有点相似于Linux的“Out-of-Memory”杀手,最后的目标就是杀掉耗费太多内存的过程,这个机制只有在iOS零碎有,在Mac零碎是没有的。零碎在强杀 App 前,会先做优先级判断,那么,这个优先级判断的根据是什么呢?
iOS 零碎内核里有一个数组,专门用于保护线程的优先级。这个优先级规定就是:内核线程的优先级是最高的,操作系统的优先级其次,App 的优先级排在最初,并且,前台 App 程序的优先级是高于后盾运行 App 的,线程应用优先级时,CPU 占用多的线程的优先级会被升高。
1.3 Mach内存治理数据结构
Mach虚拟内存这一层齐全以一种机器无关的形式来治理虚拟内存,这一层通过vm_map、vm_map_entry、vm_objec和vm_page四种要害的数据结构来治理虚拟内存。
第一、vm_map:示意地址空间内的多个虚拟内存区域。每一个区域都由一个独立的条目vm_map_entry示意,这些条目通过一个双向链表vm map links保护,参考XNU开源代码(https://opensource.apple.com/...),代码门路:osftnk/vm/vm_map.h,咱们能够分明地看到vm_map构造。
第二、vm_map_entry:这个数据结构向上承接了vm_map,向下指向vm_object,该数据结构有很多权限拜访标记位,任何一个vm_map entry都示意了虚拟内存中一块间断的区域,每一个这样的区域都能够通过指定的拜访爱护权限进行爱护,在XNU源代码门路(osftnk/vm/vm_map.h)可看到具体数据结构定义。
第三、vm_object:这是一个外围数据结构,将后面介绍的vm_map_entry与理论内存相关联,该数据结构次要蕴含一个vm_page的链表、memory object分页器、标记位(用来示意底层的内存状态如初始化、已创立、已就绪或pageout等状态)和一些计数器(援用计数、驻留计数和联动计数等),XNU源代码门路:osfmk/vm/vm_object.h;
第四、vm_page: 重点蕴含offset偏移量和很多状态位:驻留内存、正在清理、替换出、加密、重写、和脏等,XNU源代码门路(osftnk/vm/vm_page.h)。
1.4 Mach内核提供的内存操作接口
XNU内存治理的外围机制是虚拟内存治理,在Mach 层中进行的,Mach 管制了分页器,并且提供了各种 vm_ 和 mach_vm_ 音讯接口。
Mach内核是依照page size大小来调配的内存的,对于苹果的arm64机型来说的page size是16K大小,然而咱们通常在应用程序中在堆上申请内存的时候,单位都是字节,很显然内核提供的函数不适宜间接提供给下层应用,这儿存在一个GAP,在iOS零碎中libsystem_malloc.dylib就是用来补救GAP的。
libsystem_malloc.dylib是iOS内核之外的一个内存库,开源地址:https://opensource.apple.com/...。当咱们App过程须要的创立新的对象时,如调用[NSObject alloc],或开释对象调用release办法时,申请先会走到libsystem_malloc.dylib的malloc()和free()函数,而后libsystem_malloc会向iOS的零碎内核发动内存申请或开释内存,具体来说就是调用操作系统Mach内核提供的内存调配接口去分配内存或开释内存,苹果操作系统Mach内核提供了如下内存操作的相干接口。
<section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">函数名</span></section> | <section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">阐明</span></section> |
<section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">mach_vm_allocate</span></section> | <section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">allocates "zero fill" memory in the specfied map</span></section> |
<section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">mach_vm_deallocate</span></section> | <section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">deallocates the specified range of addresses in the specified address map</span></section> |
<section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">mach_vm_protect</span></section> | <section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">sets the protection of the specified range in the specified map</span></section> |
<section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">mach_vm_map</span></section> | <section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">maps a memory object to a task’s address space</span></section> |
<section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">mach_vm_page_query</span></section> | <section style="text-align: justify;margin-left: 8px;margin-right: 8px;"><span style="font-size: 14px;">query page infomation</span></section> |
`
+ (id)alloc { return _objc_rootAlloc(self);}id _objc_rootAlloc(Class cls){ return callAlloc(cls, false/checkNil/, true/allocWithZone/);}`
调用函数callAlloc,并传入两个值checkNil为false以及allocWithZone为true。 ## 2.2 callAlloc函数`
static ALWAYS_INLINE idcallAlloc(Class cls, bool checkNil, bool allocWithZone=false){#if OBJC2 if (slowpath(checkNil && !cls)) return nil; if (fastpath(!cls->ISA()->hasCustomAWZ())) { return _objc_rootAllocWithZone(cls, nil); }#endif / 省略 / }`
首先_OBJC2_宏定义,代表objc的版本,当初编译器应用的都是Objective-C2.0,进入if语句,slowpath(通知编译器,传入的条件后果为假的可能性很大),因为objc_rootAlloc传入的checkNil为false,所以不会返回nil,接着执行fastpath(通知编译器,传入的条件后果为真的可能性很大), 这个判断就是去检测传入的这个类是否实现了allocWithZone办法, 如果没有实现进入下一个函数。## 2.3 objc_rootAllocWithZone函数调用_class_createInstanceFromZone`
NEVER_INLINE id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused){ // allocWithZone under OBJC2 ignores the zone parameter return _class_createInstanceFromZone(cls, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC);}`
## 2.4 class_createInstanceFromZone外围函数`
static ALWAYS_INLINE id_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, int construct_flags = OBJECT_CONSTRUCT_NONE, bool cxxConstruct = true, size_t *outAllocatedSize = nil){ //断言机制,避免类并发创立 ASSERT(cls->isRealized()); //读取类的标记位,减速类对象的创立 bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); size_t size; // 计算内存空间大小 size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (zone) { obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } / 省略 / }`
咱们能够看到先调用instanceSize函数计算出创建对象须要的内存空间大小,而后再调用malloc\_zone\_calloc或者calloc去分配内存空间。## 2.5 instanceSize计算内存空间大小`
inline size_t instanceSize(size_t extraBytes) const { if (fastpath(cache.hasFastInstanceSize(extraBytes))) { return cache.fastInstanceSize(extraBytes); } size_t size = alignedInstanceSize() + extraBytes; if (size < 16) size = 16; return size;}`
为了缩小计算工夫,先判断缓存是否有值,如果有先从缓存取值,否则须要进入计算逻辑,从下面的代码逻辑中咱们看到入参extraBytes值为0,返回值就是alignedInstanceSize,源码如下:`
uint32_t alignedInstanceSize() { return word_align(unalignedInstanceSize());}uint32_t unalignedInstanceSize() const { ASSERT(isRealized()); return data()->ro()->instanceSize;}`
咱们晓得OC类构造中,data字段存储类相干信息,其中ro数据结构存储了以后类在编译期就曾经确定的属性、办法以及遵循的协定,所以从以后对象所属类的ro中获取instanceSize代表了调配对象所需内存空间。`
# define WORD_MASK 7UL static inline uint32_t word_align(uint32_t x) { return (x + WORD_MASK) & ~WORD_MASK; }`
接下来调用word\_align做内存对齐操作,从上述源码能够发现类对象的创立是按16字节对齐,有余16字节的返回16,这是iOS在堆上调配OC对象根本准则,都是以16倍数做调配。 ## 2.6 malloc_zone_calloc函数malloc_zone_calloc或者calloc去分配内存空间,这两个函数正是libmalloc中重要的内存调配函数,libmalloc库中门路src/malloc能够看到源码https://opensource.apple.com/...`
void *calloc(size_t num_items, size_t size){ return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);}MALLOC_NOINLINEstatic void *_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size, malloc_zone_options_t mzo){ MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0); void *ptr; if (malloc_check_start) { internal_check(); } ptr = zone->calloc(zone, num_items, size); if (os_unlikely(malloc_logger)) { malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone, (uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0); } MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr); if (os_unlikely(ptr == NULL)) { malloc_set_errno_fast(mzo, ENOMEM); } return ptr;}void *malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size){ return _malloc_zone_calloc(zone, num_items, size, MZ_NONE);}`
# 三、内存报警源码剖析## 3.1 总体流程图## 3.2 系统启动初始化mach系统启动后先做一系列内核初始化工作,函数调用门路为arm_init->machine_startup->kernel_bootstrap->kernel_bootstrap_thread,arm_init函数,XNU代码门路:/osfmk/arm/arm_init.c`
void arm_init( boot_args *args){ / 省略 / machine_startup(args);}`
machine\_routines函数,XNU代码门路:/osfmk/arm/machine_routines.c`
voidmachine_startup(__unused boot_args * args){ machine_conf(); /* * Kick off the kernel bootstrap. */ kernel_bootstrap(); / NOTREACHED /}`
kernel_bootstrap函数,XNU代码门路: /osfmk/kern/startup.c`
voidkernel_bootstrap(void){ /* * Create a kernel thread to execute the kernel bootstrap. */ kernel_bootstrap_log("kernel_thread_create"); result = kernel_thread_create((thread_continue_t)kernel_bootstrap_thread, NULL, MAXPRI_KERNEL, &thread); / 省略 / }`
kernel\_bootstrap\_thread函数,XNU代码门路:/osfmk/kern/startup.c,其中vm\_pageout办法进行内存报警初始化,bsd\_init办法进行Jetsam机制初始化。`
static voidkernel_bootstrap_thread(void){ / 省略 / //Jetsam机制初始化 bsd_init(); //内存报警机制 vm_pageout();}`
## 3.3 报警线程创立机会系统启动时在实现内核初始化工作后,会调用vm_pageout( )办法,创立vm_pageout 守护程序,在vm_pageout函数次要性能是治理页面替换的策略,判断哪些页面须要写回到磁盘,此外的一项性能就是通过kernel_thread_start_priority初始化内存报警线程,刚创立的VM_pressure线程设置为阻塞状态,期待唤醒,XNU代码门路:osfmk/vm/vm_pageout.c。`
voidvm_pageout(void){ / 省略 / result = kernel_thread_start_priority((thread_continue_t)vm_pressure_thread, NULL, BASEPRI_DEFAULT, &thread); if (result != KERN_SUCCESS) { panic("vm_pressure_thread: create failed"); } thread_deallocate(thread); / 省略 / }`
## 3.4 创立内存报警线程创立内存报警线程,线程名称为VM_pressure,XNU代码门路:osfmk/vm/vm_pageout.c,具体实现如下所示:`
#if VM_PRESSURE_EVENTSvoidvm_pressure_thread(void){ static boolean_t thread_initialized = FALSE; if (thread_initialized == TRUE) { vm_pageout_state.vm_pressure_thread_running = TRUE; consider_vm_pressure_events(); vm_pageout_state.vm_pressure_thread_running = FALSE; } thread_set_thread_name(current_thread(), "VM_pressure"); thread_initialized = TRUE; assert_wait((event_t) &vm_pressure_thread, THREAD_UNINT); thread_block((thread_continue_t)vm_pressure_thread);}#endif / VM_PRESSURE_EVENTS /`
## 3.5 唤醒报警线程### 3.5.1 内存发生变化时调用在手机的内存发生变化的时候就会调用memorystatus_pages_update函数,XNU代码门路:bsd/kern/kern_memorystatus.c, 其中调用外围函数vm_pressure_response,这是内存报警机制的外围模块。`
#if VM_PRESSURE_EVENTSvoidvm_pressure_thread(void){ static boolean_t thread_initialized = FALSE; if (thread_initialized == TRUE) { vm_pageout_state.vm_pressure_thread_running = TRUE; consider_vm_pressure_events(); vm_pageout_state.vm_pressure_thread_running = FALSE; } thread_set_thread_name(current_thread(), "VM_pressure"); thread_initialized = TRUE; assert_wait((event_t) &vm_pressure_thread, THREAD_UNINT); thread_block((thread_continue_t)vm_pressure_thread);}#endif / VM_PRESSURE_EVENTS /`
### 3.5.2 确定新的内存状态值在vm_pressure_response办法中,通过掂量内存指标来确定是否唤起内存报警线程,进而向各APP发送didReceiveMemoryWarning ,这是内存报警源码的外围模块,XNU代码门路:osfmk/vm/vm_pageout.c。`
void vm_pressure_response(void){ / 省略 / old_level = memorystatus_vm_pressure_level; switch (memorystatus_vm_pressure_level) { case kVMPressureNormal: { if (VM_PRESSURE_WARNING_TO_CRITICAL()) { new_level = kVMPressureCritical; } else if (VM_PRESSURE_NORMAL_TO_WARNING()) { new_level = kVMPressureWarning; } break; } case kVMPressureWarning: case kVMPressureUrgent: { if (VM_PRESSURE_WARNING_TO_NORMAL()) { new_level = kVMPressureNormal; } else if (VM_PRESSURE_WARNING_TO_CRITICAL()) { new_level = kVMPressureCritical; } break; } case kVMPressureCritical: { if (VM_PRESSURE_WARNING_TO_NORMAL()) { new_level = kVMPressureNormal; } else if (VM_PRESSURE_CRITICAL_TO_WARNING()) { new_level = kVMPressureWarning; } break; } default: return; } if (new_level != -1) { memorystatus_vm_pressure_level = (vm_pressure_level_t) new_level; if ((memorystatus_vm_pressure_level != kVMPressureNormal) || (old_level != memorystatus_vm_pressure_level)) { if (vm_pageout_state.vm_pressure_thread_running == FALSE) { thread_wakeup(&vm_pressure_thread); } if (old_level != memorystatus_vm_pressure_level) { thread_wakeup(&vm_pageout_state.vm_pressure_changed); } } }}`
memorystatus_vm_pressure_level是全局变量,代表上一次内存状态,接下来依据其不同的值,调用如下办法确定新的内存状态值new\_level ,分如下四种状况。第一、上次处于kVMPressureNormal状态,判断函数VM_PRESSURE_WARNING_TO_CRITICAL()的值,若为true新内存值为kVMPressureCritical,否则判断函数VM_PRESSURE_NORMAL_TO_WARNING()的值,若为true新内存值为kVMPressureWarning,否则为默认值-1;第二、上次处于kVMPressureWarning、kVMPressureUrgent状态,判断函数VM_PRESSURE_WARNING_TO_NORMAL()的值,若为true,新内存值为kVMPressureNormal,否则判断函数VM_PRESSURE_NORMAL_TO_WARNING()的值,若为true新内存值为kVMPressureCritical,否则为默认值-1;第三、上次处于kVMPressureCritical状态,判断函数VM_PRESSURE_WARNING_TO_NORMAL()的值,若为true,新内存值为kVMPressureNormal,否则判断函数VM_PRESSURE_CRITICAL_TO_WARNING()的值,若为true新内存值为kVMPressureWarning,否则为默认值-1;### 3.5.3 水位等级详情 在XNU代码门路:osfmk/vm/vm\_compressor.h,有如下宏定义`
#define AVAILABLE_NON_COMPRESSED_MEMORY (vm_page_active_count + vm_page_inactive_count + vm_page_free_count + vm_page_speculative_count)#define AVAILABLE_MEMORY (AVAILABLE_NON_COMPRESSED_MEMORY + VM_PAGE_COMPRESSOR_COUNT)#define VM_PAGE_COMPRESSOR_COMPACT_THRESHOLD (((AVAILABLE_MEMORY) * 10) / (vm_compressor_minorcompact_threshold_divisor ? vm_compressor_minorcompact_threshold_divisor : 10))#define VM_PAGE_COMPRESSOR_SWAP_THRESHOLD (((AVAILABLE_MEMORY) * 10) / (vm_compressor_majorcompact_threshold_divisor ? vm_compressor_majorcompact_threshold_divisor : 10))#define VM_PAGE_COMPRESSOR_SWAP_UNTHROTTLE_THRESHOLD (((AVAILABLE_MEMORY) * 10) / (vm_compressor_unthrottle_threshold_divisor ? vm_compressor_unthrottle_threshold_divisor : 10))#define VM_PAGE_COMPRESSOR_SWAP_RETHROTTLE_THRESHOLD (((AVAILABLE_MEMORY) * 11) / (vm_compressor_unthrottle_threshold_divisor ? vm_compressor_unthrottle_threshold_divisor : 11))#define VM_PAGE_COMPRESSOR_SWAP_HAS_CAUGHTUP_THRESHOLD (((AVAILABLE_MEMORY) * 11) / (vm_compressor_catchup_threshold_divisor ? vm_compressor_catchup_threshold_divisor : 11))#define VM_PAGE_COMPRESSOR_SWAP_CATCHUP_THRESHOLD (((AVAILABLE_MEMORY) * 10) / (vm_compressor_catchup_threshold_divisor ? vm_compressor_catchup_threshold_divisor : 10))#define VM_PAGE_COMPRESSOR_HARD_THROTTLE_THRESHOLD (((AVAILABLE_MEMORY) * 9) / (vm_compressor_catchup_threshold_divisor ? vm_compressor_catchup_threshold_divisor : 9))`
在XNU代码门路:osfmk/vm/vm\_compressor.c,有如下赋值,对于iOS零碎,走 !XNU\_TARGET\_OS\_OSX分支`
#if !XNU_TARGET_OS_OSX vm_compressor_minorcompact_threshold_divisor = 20; vm_compressor_majorcompact_threshold_divisor = 30; vm_compressor_unthrottle_threshold_divisor = 40; vm_compressor_catchup_threshold_divisor = 60;#else / !XNU_TARGET_OS_OSX / / 省略 /`
#### 3.5.3.1 VM_PRESSURE_WARNING_TO_CRITICALVM_PRESSURE_WARNING_TO_CRITICAL() 判断内存状态是否从报警到重大,XNU代码门路:osfmk/vm/vm_pageout.c ,在iOS零碎中,VM_CONFIG_COMPRESSOR_IS_ACTIVE为YES, 走else逻辑。`
boolean_t VM_PRESSURE_WARNING_TO_CRITICAL(void){ if (!VM_CONFIG_COMPRESSOR_IS_ACTIVE) { ** return FALSE; } else { return vm_compressor_low_on_space() || (AVAILABLE_NON_COMPRESSED_MEMORY < ((12 * VM_PAGE_COMPRESSOR_SWAP_UNTHROTTLE_THRESHOLD) / 10)) ? 1 : 0; }}`
通过后面的宏定义和赋值带入计算表达式,得出如下论断:非压缩可用内存小于总可用内存的12/40。#### 3.5.3.2 VM_PRESSURE_NORMAL_TO_WARNINGVM_PRESSURE_NORMAL_TO_WARNING()判断内存状态是否从失常到报警,代码门路:osfmk/vm/vm_pageout.c`
boolean_t VM_PRESSURE_NORMAL_TO_WARNING(void){ / 省略 / return (AVAILABLE_NON_COMPRESSED_MEMORY < VM_PAGE_COMPRESSOR_COMPACT_THRESHOLD) ? 1 : 0;}`
同理,带入计算表达式,得出如下论断:非压缩可用内存小于总可用内存的1/2。#### 3.5.3.3 VM_PRESSURE_WARNING_TO_NORMAL VM_PRESSURE_WARNING_TO_NORMAL()判断内存状态是否从报警到失常`
boolean_t VM_PRESSURE_WARNING_TO_NORMAL(void){ / 省略 / return (AVAILABLE_NON_COMPRESSED_MEMORY > ((12 * VM_PAGE_COMPRESSOR_COMPACT_THRESHOLD) / 10)) ? 1 : 0;}`
同理,带入计算表达式,得出如下论断: 非压缩可用内存大于总可用内存(压缩+非压缩)的3/5。#### 3.5.3.4 VM_PRESSURE_CRITICAL_TO_WARNINGVM_PRESSURE_CRITICAL_TO_WARNING()判断内存状态是否从重大到报警`
boolean_t VM_PRESSURE_CRITICAL_TO_WARNING(void){ / 省略 / return (AVAILABLE_NON_COMPRESSED_MEMORY > ((14 * VM_PAGE_COMPRESSOR_SWAP_UNTHROTTLE_THRESHOLD) / 10)) ? 1 : 0;}`
同理,带入计算表达式,得出如下论断:非压缩可用内存大于总可用内存(压缩+非压缩)的7/20。#### 3.5.4 判断是否唤起报警线程如下两个条件满足一个就会唤起vm_pressure_thread。第一、新的内存状态值不等于kVMPressureNormal。第二、新的内存状态和老的内存状态不一样。## 3.6 报警线程操作### 3.6.1 memorystatus_update_vm_pressure实现从3.3节中咱们晓得内存报警线程唤醒后执行consider_vm_pressure_events(),XNU代码门路:/bsd/kern/kern_memorystatus_notify.c`
void consider_vm_pressure_events(void){ vm_dispatch_memory_pressure();}static void vm_dispatch_memory_pressure(void){ memorystatus_update_vm_pressure(FALSE);}`
最终会调用函数memorystatus_update_vm_pressure,XNU代码门路:/bsd/kern/kern_memorystatus_notify.c`
kern_return_tmemorystatus_update_vm_pressure(boolean_t target_foreground_process){ / 省略 / if (level_snapshot != kVMPressureNormal) { /* * 是否处于上一个报警周期 * next_warning_notification_sent_at_ts代表下一次发送报警告诉的最短时间 */ level_snapshot = memorystatus_vm_pressure_level; if (level_snapshot == kVMPressureWarning || level_snapshot == kVMPressureUrgent) { if (next_warning_notification_sent_at_ts) { / * curr_ts示意以后工夫,小于下一次发送报警告诉的最短时间 * 延后执行 */ if (curr_ts < next_warning_notification_sent_at_ts) { delay(INTER_NOTIFICATION_DELAY 4 / 1 sec */); return KERN_SUCCESS; } //下一次发送报警告诉的最短时间设置为零 next_warning_notification_sent_at_ts = 0; memorystatus_klist_reset_all_for_level(kVMPressureWarning); } } else if (level_snapshot == kVMPressureCritical) { / 省略 / } } while (1) { level_snapshot = memorystatus_vm_pressure_level; if (prev_level_snapshot > level_snapshot) { /*prev_level_snapshot:示意上一一次的等级 * 上一次等级小于本次等级,启用滑动窗口逻辑 */ if (smoothing_window_started == FALSE) { smoothing_window_started = TRUE; microuptime(&smoothing_window_start_tstamp); } / 省略 / } prev_level_snapshot = level_snapshot; smoothing_window_started = FALSE; memorystatus_klist_lock(); //从task列表里选取一个task,筹备发动内存正告告诉 kn_max = vm_pressure_select_optimal_candidate_to_notify(&memorystatus_klist, level_snapshot, target_foreground_process); //没有获取能够发动正告的task if (kn_max == NULL) { memorystatus_klist_unlock(); if (level_snapshot != kVMPressureNormal) { //延后告诉 if (level_snapshot == kVMPressureWarning || level_snapshot == kVMPressureUrgent) { nanoseconds_to_absolutetime(WARNING_NOTIFICATION_RESTING_PERIOD * NSEC_PER_SEC, &curr_ts); / Next warning notification (if nothing changes) won't be sent before.../ next_warning_notification_sent_at_ts = mach_absolute_time() + curr_ts; } if (level_snapshot == kVMPressureCritical) { nanoseconds_to_absolutetime(CRITICAL_NOTIFICATION_RESTING_PERIOD * NSEC_PER_SEC, &curr_ts); / Next critical notification (if nothing changes) won't be sent before.../ next_critical_notification_sent_at_ts = mach_absolute_time() + curr_ts; } } return KERN_FAILURE; } //获取选中过程信息 target_proc = knote_get_kq(kn_max)->kq_p; target_pid = target_proc->p_pid; task = (struct task *)(target_proc->task); //调用is_knote_registered_modify_task_pressure_bits //告诉选中过程内存报警 if (level_snapshot != kVMPressureNormal) { if (level_snapshot == kVMPressureWarning || level_snapshot == kVMPressureUrgent) { if (is_knote_registered_modify_task_pressure_bits(kn_max, NOTE_MEMORYSTATUS_PRESSURE_WARN, task, 0, kVMPressureWarning) == TRUE) { found_candidate = TRUE; } } else { if (level_snapshot == kVMPressureCritical) { if (is_knote_registered_modify_task_pressure_bits(kn_max, NOTE_MEMORYSTATUS_PRESSURE_CRITICAL, task, 0, kVMPressureCritical) == TRUE) { found_candidate = TRUE; } } } } else { if (kn_max->kn_sfflags & NOTE_MEMORYSTATUS_PRESSURE_NORMAL) { task_clear_has_been_notified(task, kVMPressureWarning); task_clear_has_been_notified(task, kVMPressureCritical); found_candidate = TRUE; } } if (found_candidate == FALSE) { proc_rele(target_proc); memorystatus_klist_unlock(); continue; } / 省略 / } return KERN_SUCCESS;}`
### 3.6.2 is_knote_registered_modify_task_pressure_bits告诉线程报警is_knote_registered_modify_task_pressure_bits 告诉线程报警,XNU代码门路:/bsd/kern/kern_memorystatus_notify.c`
static boolean_tis_knote_registered_modify_task_pressure_bits(struct knote *kn_max, int knote_pressure_level, task_t task, vm_pressure_level_t pressure_level_to_clear, vm_pressure_level_t pressure_level_to_set){ if (kn_max->kn_sfflags & knote_pressure_level) { if (pressure_level_to_clear && task_has_been_notified(task, pressure_level_to_clear) == TRUE) { task_clear_has_been_notified(task, pressure_level_to_clear); } task_mark_has_been_notified(task, pressure_level_to_set); return TRUE; } return FALSE;}`
task_mark_has_been_notified,XNU代码门路:/bsd/kern/task_policy.c`
void task_mark_has_been_notified(task_t task, int pressurelevel){ if (task == NULL) { return; } if (pressurelevel == kVMPressureWarning) { task->low_mem_notified_warn = 1; } else if (pressurelevel == kVMPressureCritical) { task->low_mem_notified_critical = 1; }}`
# 四、总结本文介绍了Mach虚拟内存的特点、内存治理的数据结构以及Mach内核提供的内存操作接口,同时对OC内存调配外围函数alloc做了源码剖析,此外对iOS端内存报警机制做了具体的源码剖析,对于Jestam机制和libmalloc源码在后续文章做具体介绍,敬请期待。 ——END—— 参考资料:[1] objc源码:https://opensource.apple.com/...[2] libsystem_malloc.dylib源码:https://opensource.apple.com/...[3] XNU源码:https://github.com/apple/darw...[4] 《深刻解析Mac OS X & iOS操作系统》[5] Mach内核介绍:https://developer.apple.com/l...[6] Mach系统结构:https://developer.apple.com/l...[7] Mach虚拟内存零碎:https://developer.apple.com/l...[8] Mach内存替换空间:https://images.apple.com/medi...举荐浏览:百度APP iOS端内存优化实际-内存管控计划百度APP iOS端内存优化实际-大块内存监控计划