iOS系列—wakeup in XNU
蘋果在iOS13的時候,在內核中退出了一個新的性能掂量指標wakeup,同時因為這個指標而被零碎殺死的利用不可勝數,其中也包含咱們罕用的微信淘寶等。而這個指標齊全是由 XNU 內核統計的,所以咱們很難通過日志等一般伎倆去精確的定位問題,所以這里通過另一種思路去解決這個問題。
為什么要統計 wakeup
要定位這個問題,首先咱們須要曉得這個指標的目標是什么。
XNU 中,對性能的指標有CPU、內存、IO,而wakeup屬于 CPU 的性能指標,同時屬于 CPU 指標的還有 CPU 使用率,上面是XNU中對其限度的定義。
/** 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總結來說,當 CPU 使用率在3分鐘內均值超過50%,就認為適度應用CPU,當wakeup在300秒內均值超過150次,則認為喚起次數過多,同時在閾值的70%水位內核會開啟監控。
CPU 使用率咱們很容易了解,使用率越高,電池壽命越低,而且并不是線性減少的。那么wakeup又是如何影響電池壽命的呢?
首先咱們須要看看ARM架構中對于 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.
因為ARM被大量應用于低功耗設施,而這些設施往往會由電池來作為驅動,所以 ARM 在硬件層面就對功耗這個問題進行了優化設計。
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種類型,即動態功耗與動靜功耗。
動態功耗指的是只有 CPU 通上電,因為芯片無奈保障相對絕緣,所以會存在“漏電”的狀況,而且越大的芯片這種問題越重大,這也是芯片廠家為什么拼命的鉆研更小尺寸芯片的起因。這部分功耗因為是硬件自身決定的,所以咱們無奈去管制,而這種類型功耗占比不大。
動靜功耗指的是 CPU 運行期間,接通時鐘后,執行指令所帶來的額定開銷,而這個開銷會和時鐘周期頻率相干,頻率越高,耗電量越大。這也就闡明了蘋果為什么會管制 CPU 使用率,而相干鉆研(Facebook 也做過)也表明,CPU 在20以下和20以上的能耗簡直是成倍的減少。
CPU 使用率曾經可能從肯定水平上限度電池損耗問題了,那么wakeup又是什么指標呢?
wakeup 是什么
要理解wakeup是什么,首先要曉得ARM低功耗模式的2個重要指令WFI和WFE。
通過這2個指令進入低功耗模式后,時鐘將會被敞開,這個 CPU 將不會再執行任何指令,這樣這個 CPU 的動靜能耗就沒有了。這個能力的實現是由和 CPU 外圍強綁定的空轉線程idle thread實現的,有意思的是XNU中的實現較為簡單,而Zircon中則十分間接暴力:
__NO_RETURN int arch_idle_thread_routine(void*) {for (;;) {__asm__ volatile(“wfi”);} }在 XNU 中,一個 CPU 外圍的工作流程被概括為如下狀態機:
/*
- -------------------- SHUTDOWN
- / ^ ^
- _/ | \
- OFF_LINE —> START —> RUNNING —> IDLE —> DISPATCHING
- \_________________^ ^ ^______/ /
- \__________________/
*/
而wakeup則示意的是,從低功耗模式喚起進入運行模式的次數。
wakeup 如何統計的
ARM異樣零碎
CPU 時鐘被敞開了,那么又要怎么喚起呢?這就波及到 CPU 的異樣零碎。
在 ARM 中,異樣和中斷的概念比擬含糊,他把所有會引起 CPU 執行狀態變更的事件都稱為異樣,其中包含軟中斷,debug 中斷,硬件中斷等。
從觸發機會上能夠辨別為同步異樣與異步異樣。這里指的同步異步并不是應用程序的概念,這里同步指的是領有明確的觸發機會,比方零碎調用,缺頁中斷等,都會產生在明確的機會,而異步中斷,則齊全忽視指令的邏輯,會強行打斷指令執行,比方 FIQ 和 IRQ,這里比擬典型的是定時器中斷。
異樣零碎有很多能力,其中一個重要的能力就是內核態與用戶態切換。ARM的執行權限分為4個等級,EL0,EL1,EL2,EL3。其中 EL0 代表用戶態,而 EL1 代表內核態,當用戶態想要切換至內核態的時候,必須通過異樣零碎進行切換,而且異樣零碎只能向等同或更高等級權限進行切換。那么這么多類型的異樣,又是如何響應的呢?這里就波及到一個異樣處理表(exception table),在系統啟動的時候,須要首先就去注冊這個表,在XNU中,這個表如下:
.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 計數
那么咱們回過頭來看看wakeup計數的中央:
而這里的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標記又是在什么時候設置下來的呢?只有在locore.S中,才會更新該標記:
str x0, [x23, CPU_INT_STATE] // Saved context in cpu_int_state
同時發現如下幾個辦法會配置這個標記:
聯合上述的異樣處理表的注冊地位,與ARM官網文檔的地位進行比照,能夠發現:
[點擊可獲取資料大全]
這幾個中斷類型均為 FIQ 或者 IRQ,也就是硬中斷。由此咱們能夠判斷,wakeup必然是由硬中斷引起的,而像零碎調用,線程切換,缺頁中斷這種并不會引起wakeup。
過程統計
由上能夠看出,wakeup其實是對CPU外圍喚起次數的統計,和應用層的線程與過程仿佛毫不相干。但從程序執行的角度思考,如果一個程序始終在運行,就不會進入期待狀態,而從期待狀態喚醒,必定是因為某些異常中斷,比方網絡,vsync 等。
在 CPU 外圍被喚醒后,在以后 CPU 外圍執行的線程會進行wakeup++,而零碎統計維度是利用維度,也就是過程維度,所以會累計該過程上面的所有線程的wakeup計數。
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個不同線程啟用用同樣的定時器,wakeup是同一個線程起2個定時器的2倍(同樣的定時器在底層其實是一顆樹,注冊同樣的定時器理論只注冊了一個)。
用戶層獲取該統計值則能夠通過如下形式:
#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 治理
從以上剖析來看,咱們只須要排查各種硬件相干事件即可。
從理論排查后果來看,目前只有定時器或者領有定時能力的類型是最廣泛的場景。
比方NSTimer,CADisplayLink,dispatch_semaphore_wait,pthread_cond_timedwait等。
對于定時器,咱們盡量復用其能力,防止在不同線程去創立同樣的定時能力,同時在回到后盾的時候,敞開不須要的定時器,因為大部分定時器都是UI相干的,敞開定時器也是一種規范的做法。
對于 wait 類型的能力,從計劃抉擇上防止輪詢的計劃,或者減少輪詢間隔時間,比方能夠通過 try_wait,runloop或者 EventKit 等能力進行優化。
ps;iOS開發交流技術:[歡迎你的加入],不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長
監控與防劣化
一旦咱們曉得了問題起因,那么對問題的治理比較簡單,而后續咱們須要建設繼續的管控等長效措施才能夠。
在此咱們能夠簡略的定義一些規定,并且嵌入線下監控能力中:
定時器工夫周期小于1s的,在進入后盾須要進行暫停
wait 類型提早小于1s,并且繼續應用10次以上的狀況須要進行優化
總結
wakeup因為是 XNU 內核統計數據,所以在問題定位排查方面特地艱難,所以從另一個角度去解決這個問題反而是一種更好的形式。
同時從 XNU 中對 CPU 功耗的管制粒度能夠看出,蘋果在極致的優化方面做的很好,在本身的軟件生態中要求也比擬高。電量問題在短時間內應該不會有技術上的沖破,所以咱們本身也須要多思考如何縮小電池損耗。
總結
以上是生活随笔為你收集整理的iOS系列—wakeup in XNU的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AdminLTE2的模态框(弹出框)
- 下一篇: PCIe扫盲系列博文连载目录