iOS系列—wakeup in XNU
蘋果在iOS13的時(shí)候,在內(nèi)核中退出了一個(gè)新的性能掂量指標(biāo)wakeup,同時(shí)因?yàn)檫@個(gè)指標(biāo)而被零碎殺死的利用不可勝數(shù),其中也包含咱們罕用的微信淘寶等。而這個(gè)指標(biāo)齊全是由 XNU 內(nèi)核統(tǒng)計(jì)的,所以咱們很難通過日志等一般伎倆去精確的定位問題,所以這里通過另一種思路去解決這個(gè)問題。
為什么要統(tǒng)計(jì) wakeup
要定位這個(gè)問題,首先咱們須要曉得這個(gè)指標(biāo)的目標(biāo)是什么。
XNU 中,對(duì)性能的指標(biāo)有CPU、內(nèi)存、IO,而wakeup屬于 CPU 的性能指標(biāo),同時(shí)屬于 CPU 指標(biāo)的還有 CPU 使用率,上面是XNU中對(duì)其限度的定義。
/** Default parameters for CPU usage monitor.** Default setting is 50% over 3 minutes.*/ #define DEFAULT_CPUMON_PERCENTAGE 50 #define DEFAULT_CPUMON_INTERVAL (3 * 60) #define TASK_WAKEUPS_MONITOR_DEFAULT_LIMIT 150 /* wakeups per second */ #define TASK_WAKEUPS_MONITOR_DEFAULT_INTERVAL 300 /* in seconds. *//** Level (in terms of percentage of the limit) at which the wakeups monitor triggers telemetry.** (ie when the task's wakeups rate exceeds 70% of the limit, start taking user* stacktraces, aka micro-stackshots)*/ #define TASK_WAKEUPS_MONITOR_DEFAULT_USTACKSHOTS_TRIGGER 70總結(jié)來說,當(dāng) CPU 使用率在3分鐘內(nèi)均值超過50%,就認(rèn)為適度應(yīng)用CPU,當(dāng)wakeup在300秒內(nèi)均值超過150次,則認(rèn)為喚起次數(shù)過多,同時(shí)在閾值的70%水位內(nèi)核會(huì)開啟監(jiān)控。
CPU 使用率咱們很容易了解,使用率越高,電池壽命越低,而且并不是線性減少的。那么wakeup又是如何影響電池壽命的呢?
首先咱們須要看看ARM架構(gòu)中對(duì)于 CPU 功耗問題的形容:
Many ARM systems are mobile devices and powered by batteries. In such systems, optimization of power use, and total energy use, is a key design constraint. Programmers often spend significant amounts of time trying to save battery life in such systems.
因?yàn)锳RM被大量應(yīng)用于低功耗設(shè)施,而這些設(shè)施往往會(huì)由電池來作為驅(qū)動(dòng),所以 ARM 在硬件層面就對(duì)功耗這個(gè)問題進(jìn)行了優(yōu)化設(shè)計(jì)。
Energy use can be divided into two components:
-
Static
Static power consumption, also often called leakage, occurs whenever the core logic or RAM blocks have power applied to them. In general terms, the leakage currents are proportional to the total silicon area, meaning that the bigger the chip, the higher the leakage. The proportion of power consumption from leakage gets significantly higher as you move to smaller fabrication geometries. -
Dynamic
Dynamic power consumption occurs because of transistor switching and is a function of the core clock speed and the numbers of transistors that change state per cycle. Clearly, higher clock speeds and more complex cores consume more power.
功耗能夠分為2種類型,即動(dòng)態(tài)功耗與動(dòng)靜功耗。
動(dòng)態(tài)功耗指的是只有 CPU 通上電,因?yàn)樾酒瑹o奈保障相對(duì)絕緣,所以會(huì)存在“漏電”的狀況,而且越大的芯片這種問題越重大,這也是芯片廠家為什么拼命的鉆研更小尺寸芯片的起因。這部分功耗因?yàn)槭怯布陨頉Q定的,所以咱們無奈去管制,而這種類型功耗占比不大。
動(dòng)靜功耗指的是 CPU 運(yùn)行期間,接通時(shí)鐘后,執(zhí)行指令所帶來的額定開銷,而這個(gè)開銷會(huì)和時(shí)鐘周期頻率相干,頻率越高,耗電量越大。這也就闡明了蘋果為什么會(huì)管制 CPU 使用率,而相干鉆研(Facebook 也做過)也表明,CPU 在20以下和20以上的能耗簡(jiǎn)直是成倍的減少。
CPU 使用率曾經(jīng)可能從肯定水平上限度電池?fù)p耗問題了,那么wakeup又是什么指標(biāo)呢?
wakeup 是什么
要理解wakeup是什么,首先要曉得ARM低功耗模式的2個(gè)重要指令WFI和WFE。
通過這2個(gè)指令進(jìn)入低功耗模式后,時(shí)鐘將會(huì)被敞開,這個(gè) CPU 將不會(huì)再執(zhí)行任何指令,這樣這個(gè) CPU 的動(dòng)靜能耗就沒有了。這個(gè)能力的實(shí)現(xiàn)是由和 CPU 外圍強(qiáng)綁定的空轉(zhuǎn)線程idle thread實(shí)現(xiàn)的,有意思的是XNU中的實(shí)現(xiàn)較為簡(jiǎn)單,而Zircon中則十分間接暴力:
__NO_RETURN int arch_idle_thread_routine(void*) {for (;;) {__asm__ volatile(“wfi”);} }在 XNU 中,一個(gè) CPU 外圍的工作流程被概括為如下狀態(tài)機(jī):
/*
- -------------------- SHUTDOWN
- / ^ ^
- _/ | \
- OFF_LINE —> START —> RUNNING —> IDLE —> DISPATCHING
- \_________________^ ^ ^______/ /
- \__________________/
*/
而wakeup則示意的是,從低功耗模式喚起進(jìn)入運(yùn)行模式的次數(shù)。
wakeup 如何統(tǒng)計(jì)的
ARM異樣零碎
CPU 時(shí)鐘被敞開了,那么又要怎么喚起呢?這就波及到 CPU 的異樣零碎。
在 ARM 中,異樣和中斷的概念比擬含糊,他把所有會(huì)引起 CPU 執(zhí)行狀態(tài)變更的事件都稱為異樣,其中包含軟中斷,debug 中斷,硬件中斷等。
從觸發(fā)機(jī)會(huì)上能夠辨別為同步異樣與異步異樣。這里指的同步異步并不是應(yīng)用程序的概念,這里同步指的是領(lǐng)有明確的觸發(fā)機(jī)會(huì),比方零碎調(diào)用,缺頁(yè)中斷等,都會(huì)產(chǎn)生在明確的機(jī)會(huì),而異步中斷,則齊全忽視指令的邏輯,會(huì)強(qiáng)行打斷指令執(zhí)行,比方 FIQ 和 IRQ,這里比擬典型的是定時(shí)器中斷。
異樣零碎有很多能力,其中一個(gè)重要的能力就是內(nèi)核態(tài)與用戶態(tài)切換。ARM的執(zhí)行權(quán)限分為4個(gè)等級(jí),EL0,EL1,EL2,EL3。其中 EL0 代表用戶態(tài),而 EL1 代表內(nèi)核態(tài),當(dāng)用戶態(tài)想要切換至內(nèi)核態(tài)的時(shí)候,必須通過異樣零碎進(jìn)行切換,而且異樣零碎只能向等同或更高等級(jí)權(quán)限進(jìn)行切換。那么這么多類型的異樣,又是如何響應(yīng)的呢?這里就波及到一個(gè)異樣處理表(exception table),在系統(tǒng)啟動(dòng)的時(shí)候,須要首先就去注冊(cè)這個(gè)表,在XNU中,這個(gè)表如下:
.section __DATA_CONST,__const.align 3.globl EXT(exc_vectors_table) LEXT(exc_vectors_table)/* Table of exception handlers.* These handlers sometimes contain deadloops. * It's nice to have symbols for them when debugging. */.quad el1_sp0_synchronous_vector_long.quad el1_sp0_irq_vector_long.quad el1_sp0_fiq_vector_long.quad el1_sp0_serror_vector_long.quad el1_sp1_synchronous_vector_long.quad el1_sp1_irq_vector_long.quad el1_sp1_fiq_vector_long.quad el1_sp1_serror_vector_long.quad el0_synchronous_vector_64_long.quad el0_irq_vector_64_long.quad el0_fiq_vector_64_long.quad el0_serror_vector_64_longwakeup 計(jì)數(shù)
那么咱們回過頭來看看wakeup計(jì)數(shù)的中央:
而這里的aticontext則是通過ml_at_interrupt_context獲取的,其含意則是是否處于中斷上下文中。
/** Routine: ml_at_interrupt_context* Function: Check if running at interrupt context*/ boolean_t ml_at_interrupt_context(void) {/* Do not use a stack-based check here, as the top-level exception handler* is free to use some other stack besides the per-CPU interrupt stack.* Interrupts should always be disabled if we’re at interrupt context.* Check that first, as we may be in a preemptible non-interrupt context, in* which case we could be migrated to a different CPU between obtaining* the per-cpu data pointer and loading cpu_int_state. We then might end* up checking the interrupt state of a different CPU, resulting in a false* positive. But if interrupts are disabled, we also know we cannot be* preempted. */return !ml_get_interrupts_enabled() && (getCpuDatap()->cpu_int_state != NULL); }那么cpu_int_state標(biāo)記又是在什么時(shí)候設(shè)置下來的呢?只有在locore.S中,才會(huì)更新該標(biāo)記:
str x0, [x23, CPU_INT_STATE] // Saved context in cpu_int_state
同時(shí)發(fā)現(xiàn)如下幾個(gè)辦法會(huì)配置這個(gè)標(biāo)記:
聯(lián)合上述的異樣處理表的注冊(cè)地位,與ARM官網(wǎng)文檔的地位進(jìn)行比照,能夠發(fā)現(xiàn):
[點(diǎn)擊可獲取資料大全]
這幾個(gè)中斷類型均為 FIQ 或者 IRQ,也就是硬中斷。由此咱們能夠判斷,wakeup必然是由硬中斷引起的,而像零碎調(diào)用,線程切換,缺頁(yè)中斷這種并不會(huì)引起wakeup。
過程統(tǒng)計(jì)
由上能夠看出,wakeup其實(shí)是對(duì)CPU外圍喚起次數(shù)的統(tǒng)計(jì),和應(yīng)用層的線程與過程仿佛毫不相干。但從程序執(zhí)行的角度思考,如果一個(gè)程序始終在運(yùn)行,就不會(huì)進(jìn)入期待狀態(tài),而從期待狀態(tài)喚醒,必定是因?yàn)槟承┊惓V袛?#xff0c;比方網(wǎng)絡(luò),vsync 等。
在 CPU 外圍被喚醒后,在以后 CPU 外圍執(zhí)行的線程會(huì)進(jìn)行wakeup++,而零碎統(tǒng)計(jì)維度是利用維度,也就是過程維度,所以會(huì)累計(jì)該過程上面的所有線程的wakeup計(jì)數(shù)。
queue_iterate(&task->threads, thread, thread_t, task_threads) {info->task_timer_wakeups_bin_1 += thread->thread_timer_wakeups_bin_1;info->task_timer_wakeups_bin_2 += thread->thread_timer_wakeups_bin_2; }所以在咱們代碼中,如果在2個(gè)不同線程啟用用同樣的定時(shí)器,wakeup是同一個(gè)線程起2個(gè)定時(shí)器的2倍(同樣的定時(shí)器在底層其實(shí)是一顆樹,注冊(cè)同樣的定時(shí)器理論只注冊(cè)了一個(gè))。
用戶層獲取該統(tǒng)計(jì)值則能夠通過如下形式:
#include <mach/task.h> #include <mach/mach.h>BOOL GetSystemWakeup(NSInteger *interrupt_wakeup, NSInteger *timer_wakeup) {struct task_power_info info = {0};mach_msg_type_number_t count = TASK_POWER_INFO_COUNT;kern_return_t ret = task_info(current_task(), TASK_POWER_INFO, (task_info_t)&info, &count);if (ret == KERN_SUCCESS) {if (interrupt_wakeup) {*interrupt_wakeup = info.task_interrupt_wakeups;}if (timer_wakeup) {*timer_wakeup = info.task_timer_wakeups_bin_1 + info.task_timer_wakeups_bin_2;}return true;}else {if (interrupt_wakeup) {*interrupt_wakeup = 0;}if (timer_wakeup) {*timer_wakeup = 0;}return false;} }wakeup 治理
從以上剖析來看,咱們只須要排查各種硬件相干事件即可。
從理論排查后果來看,目前只有定時(shí)器或者領(lǐng)有定時(shí)能力的類型是最廣泛的場(chǎng)景。
比方NSTimer,CADisplayLink,dispatch_semaphore_wait,pthread_cond_timedwait等。
對(duì)于定時(shí)器,咱們盡量復(fù)用其能力,防止在不同線程去創(chuàng)立同樣的定時(shí)能力,同時(shí)在回到后盾的時(shí)候,敞開不須要的定時(shí)器,因?yàn)榇蟛糠侄〞r(shí)器都是UI相干的,敞開定時(shí)器也是一種規(guī)范的做法。
對(duì)于 wait 類型的能力,從計(jì)劃抉擇上防止輪詢的計(jì)劃,或者減少輪詢間隔時(shí)間,比方能夠通過 try_wait,runloop或者 EventKit 等能力進(jìn)行優(yōu)化。
ps;iOS開發(fā)交流技術(shù):[歡迎你的加入],不管你是大牛還是小白都?xì)g迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗(yàn),討論技術(shù), 大家一起交流學(xué)習(xí)成長(zhǎng)
監(jiān)控與防劣化
一旦咱們曉得了問題起因,那么對(duì)問題的治理比較簡(jiǎn)單,而后續(xù)咱們須要建設(shè)繼續(xù)的管控等長(zhǎng)效措施才能夠。
在此咱們能夠簡(jiǎn)略的定義一些規(guī)定,并且嵌入線下監(jiān)控能力中:
定時(shí)器工夫周期小于1s的,在進(jìn)入后盾須要進(jìn)行暫停
wait 類型提早小于1s,并且繼續(xù)應(yīng)用10次以上的狀況須要進(jìn)行優(yōu)化
總結(jié)
wakeup因?yàn)槭?XNU 內(nèi)核統(tǒng)計(jì)數(shù)據(jù),所以在問題定位排查方面特地艱難,所以從另一個(gè)角度去解決這個(gè)問題反而是一種更好的形式。
同時(shí)從 XNU 中對(duì) CPU 功耗的管制粒度能夠看出,蘋果在極致的優(yōu)化方面做的很好,在本身的軟件生態(tài)中要求也比擬高。電量問題在短時(shí)間內(nèi)應(yīng)該不會(huì)有技術(shù)上的沖破,所以咱們本身也須要多思考如何縮小電池?fù)p耗。
總結(jié)
以上是生活随笔為你收集整理的iOS系列—wakeup in XNU的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AdminLTE2的模态框(弹出框)
- 下一篇: PCIe扫盲系列博文连载目录