iOS SIGKILL初探

13 篇文章 0 订阅

特别提示

本文是苹果关于SIGKILL文献的的简单翻译和简单处理,不代表本人观点

什么是 CRASH (SIGKILL)

当崩溃日志中有 SIGKILL 或者EXC_CRASH (SIGKILL) 时,表示操作系统从上层杀死了我们的进程。通常来说,崩溃日志里面通常都会包含有具体的原因以及一个表示该原因的错误代码。如下,这个崩溃的错误代码就是0xdead10cc

Exception Type:  EXC_CRASH (SIGKILL)Exception Codes: 0x0000000000000000, 0x0000000000000000Exception Note:  EXC_CORPSE_NOTIFYTermination Reason: Namespace RUNNINGBOARD, Code 0xdead10cc

下面列举并详细解释下各个错误代码的含义

0x8badf00d 发音(ate bad food),意思就是吃了坏的食物。表示操作系统因为看门狗原因杀死了app;具体可以参见苹果文档 Addressing Watchdog Terminations.
0xc00010ff 发音 (cool off)。 表示操作系统因为发热杀死了App, 关于怎么样使你的程序更高效,更低消耗,可以观看iOS Performance and Power Optimization with Instruments WWDC session.
0xdead10cc 发音(dead lock), 表示操作系统因为 app 在挂起的时候发生了文件和数据库锁操作而杀死App;这就要求我们执行后台任务必须在主线程调用API beginBackgroundTask(withName:expirationHandler:)之后,保证我们的任务和锁清除操作在 app 挂起前完成。同样在 app 插件里面,使用API  beginActivity(options:reason:)  完成这个工作。
0xbaadca11  发音(bad call)。表示 app 表示响应 PushKit 的消息并且CallKit调用失败,从而 app 被系统杀死。
0xbad22222. 表示因为通过 VoIP 调起程序太频繁而被系统杀死。
0xc51bad01. watchOS 因为后台任务耗费太多 CPU 时间而被杀死。这就需要优化和减少后台任务的 CPU 时间,提高 CPU 使用效率,或者在后台的时候减少大量任务。
0xc51bad02. watchOS 因为后台任务不能在初始化时间内完成而杀死 app,减少在后台任务的数量可以解决这个问题。

定位看门狗问题(Addressing Watchdog Terminations)

前言

用户通常都希望 app 秒开,并及时响应手势和点击。所以 iOS 操作系统在启动阶段开启了看门狗监控,如果 app 在启动期间长时间不响应用户操作,则操作系统就会杀死 app。看门狗崩溃在日志中通常用代码 0x8badf00d  (发音 ate bad food) 表示

Exception Type:  EXC_CRASH (SIGKILL)Exception Codes: 0x0000000000000000, 0x0000000000000000Exception Note:  EXC_CORPSE_NOTIFYTermination Reason: Namespace SPRINGBOARD, Code 0x8badf00d

当 app 在阻塞主线程一段时间之后就会被看门狗杀死,一般的事件如下:

  • 同步的网络请求
  • 处理大量的数据的任务,比如大的JSON文件或者 3D 模型的加载和处理
  • 触发大量的 Core Data 同步保存操作
  • Vision的请求操作

为什么阻塞主线程会崩溃,首先我们考虑最普通的例子:通过同步的网络请求数据刷新UI。如果主线程一直在忙于网络请求,那么 app 在网络请求返回数据之前就不能处理 UI 事件了,比如点击以及滑动事件。如果一个 app 花费了太多时间在网络请求上,而不是响应用户滑动的手势。这会让人感觉 app 没有响应,体验极差。

崩溃日志中 App 看门狗原因解释信息

当 app 因为启动慢或者无响应被杀死的时候,崩溃日志里面会有一些重要的信息,比如 app 花费了多少时间启动。如下,一个iOS app 在启动期间不能快速的绘制完 UI:

Termination Description: SPRINGBOARD,     scene-create watchdog transgression: application<com.example.MyCoolApp>:667    exhausted real (wall clock) time allowance of 19.97 seconds     | ProcessVisibility: Foreground     | ProcessState: Running     | WatchdogEvent: scene-create     | WatchdogVisibility: Foreground     | WatchdogCPUStatistics: (     |  "Elapsed total CPU time (seconds): 15.290 (user 15.290, system 0.000), 28% CPU",     |  "Elapsed application CPU time (seconds): 0.367, 1% CPU"     | )

特别提醒:

为了读起来方便,这个例子包含了换行。在原始的崩溃日志里面,看门狗信息很少换行。

scene-create 出现在 Termination Description 的时候,表示 app 在允许的 wall clock time 内还没有绘制完第一帧。如果 scene-update 出现在 Termination Description 的时候,表示 app 因为主线程太忙而没有刷新 UI。

特别提醒:

scene-create 和 scene-update 出现在崩溃日志里面是标识屏幕展现在设备上。与 UIKit 里面UIScene 类无关。

Elapsed total CPU time 表示在 wall clock time 内 整个系统的所有进程花费了多少时间。这个CPU 时间,和应用程序 CPU 时间一样,包含所有的 CPU 内核时间,可能超过100%。比如,如果一个CPU内核耗费了100%,另外一个耗费了20%,总的耗时就是120%。

Elapsed application CPU time 表示我们的 app 花费了多少时间在 wall clock time 里,如果这个数字很极端,表示这里代码可能有问题。如果这个数字极其高,表示 app 所有的线程里都在做重要的任务—这数字表示所有的线程,不仅仅是主线程。如果这个数字低就表示 app 很闲,原因可能是在等待系统资源或者网络请求返回。

App 后台任务看门狗原因解释信息(watchOS)

除了 app 有看门狗外, watchOS 在后台任务执行时也有看门狗监控。如下:app 在时间允许范围内没有处理完 Watch Connectivity 的后台任务。

Termination Reason: CAROUSEL, WatchConnectivity watchdog transgression.     Exhausted wall time allowance of 15.00 seconds.Termination Description: SPRINGBOARD,    CSLHandleBackgroundWCSessionAction watchdog transgression: xpcservice<com.example.MyCoolApp.watchkitapp.watchextension>:220:220     exhausted real (wall clock) time allowance of 15.00 seconds     | <FBExtensionProcess: 0x16df02a0; xpcservice<com.example.MyCoolApp.watchkitapp.watchextension>:220:220; typeID: com.apple.watchkit>       Elapsed total CPU time (seconds): 24.040 (user 24.040, system 0.000), 81% CPU     | Elapsed application CPU time (seconds): 1.223, 6% CPU, lastUpdate 2020-01-20 11:56:01 +0000

特别备注:

为了方便读者可读,这个例子包含了多处换行。在原始的崩溃日志里面,看门狗信息很少换行。

关于 wall clock time 和 CPU 时间的更多描述请参考文档 Interpret the App Responsiveness Watchdog Information.

识别看门狗触发的原因

堆栈在大部分时候能够正常显示出主线程耗时在什么地方。例如,如果一个 app 在主线程使用了同步网络请求,网络请求方法在主线程的堆栈会展示出来。

Thread 0 name:  Dispatch queue: com.apple.main-threadThread 0 Crashed:0   libsystem_kernel.dylib            0x00000001c22f8670 semaphore_wait_trap + 81   libdispatch.dylib                 0x00000001c2195890 _dispatch_sema4_wait$VARIANT$mp + 242   
libdispatch.dylib                 0x00000001c2195ed4 _dispatch_semaphore_wait_slow + 1403   CFNetwork                         0x00000001c57d9d34 CFURLConnectionSendSynchronousRequest + 3884   
CFNetwork                         0x00000001c5753988 +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 116  + 147285  
 Foundation                        0x00000001c287821c -[NSString initWithContentsOfURL:usedEncoding:error:] + 2566   
 libswiftFoundation.dylib          0x00000001f7127284 NSString.__allocating_init+ 680580 (contentsOf:usedEncoding:) + 1047  
 libswiftFoundation.dylib          0x00000001f712738c String.init+ 680844 (contentsOf:) + 96
  MyCoolApp                         0x00000001009d31e0 ViewController.loadData() (in MyCoolApp) (ViewController.swift:21)

然而,主线程的堆栈并不一定包含引起问题的代码。例如,想象一下,假如你的 app 执行一个任务,需要花费4s ,但是系统给的 wall clock time 只有5s。当程序在 5s 后被杀死,耗费4s 的任务函数就不会显示在堆栈里,即使他消耗了80%的时间。崩溃日志记录的是程序被杀死的当前堆栈,即使当前的堆栈不是问题的源头。在 app 启动的过程中,你可以用看门狗崩溃日志里面的所有堆栈帮你去找到程序结束时的堆栈。 通过当前的堆栈,你可以回溯之前的任务,通过当前的代码去缩小问题的范围。

除此之外,在 app 发布前,在开发的时候要把潜在的问题解决,并且 app 发布之后也需要监控程序的性能,Apple 的 Reducing Your App’s Launch Time 和 Improving App Responsiveness 这两篇文档提供了更多的信息。

找到隐藏的网络库同步代码

主线程被同步网络请求阻塞并导致被看门狗杀死的问题,有时候并不直接显示,可能隐藏在暗处,虎视眈眈。例如这段堆栈:

Thread 0 name:  Dispatch queue: com.apple.main-threadThread 0 Crashed:0   libsystem_kernel.dylib            0x00000001c22f8670 semaphore_wait_trap + 81   libdispatch.dylib                 0x00000001c2195890 _dispatch_sema4_wait$VARIANT$mp + 242   
libdispatch.dylib                 0x00000001c2195ed4 _dispatch_semaphore_wait_slow + 1403   CFNetwork                         0x00000001c57d9d34 CFURLConnectionSendSynchronousRequest + 3884   
CFNetwork                         0x00000001c5753988 +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 116  + 147285  
 Foundation                        0x00000001c287821c -[NSString initWithContentsOfURL:usedEncoding:error:] + 2566   
 libswiftFoundation.dylib          0x00000001f7127284 NSString.__allocating_init+ 680580 (contentsOf:usedEncoding:) + 1047  
 libswiftFoundation.dylib          0x00000001f712738c String.init+ 680844 (contentsOf:) + 96
  MyCoolApp                         0x00000001009d31e0 ViewController.loadData() (in MyCoolApp) (ViewController.swift:21)

展示了 app 触发了一个通过 https URL 调用init(contentsOf:) 下载。在第7帧,这个 API 隐式的在初始化之前发起了一个同步网络请求。即使这个初始化很快完成并且没有显示在崩溃日志中,但它仍然可能是引起看门狗的原因。其它类初始化的时候带着一个URL参数的,比如XMLParser 和 NSData 类,效果类似:

其他相同的暗中含有同步网络请求操作的例子如下

  • SCNetworkReachability, 可达性 API, 默认操作是同步的。看起来无害的方法比如 SCNetworkReachabilityGetFlags(:😃 ,却有可能触发看门狗崩溃。
  • BSD提供的 DNS 系列方法, 比如 gethostbyname(_:) 和 gethostbyaddr(_:_:_:), 在主线程调用从来不是安全的。方法像getnameinfo(_:_:_:_:_:_:_:) 和 getaddrinfo(_:_:_:_:) 只有当你仅仅使用 IP地址而不是 DNS 名字的时候(指定 AI_NUMERICHOSTNI_NUMERICHOST)才是安全的。

同步网络请求的问题大部分取决于当前的网络环境。如果你总是在办公场所,网络好的地方测试,你将永远发现不了问题。然而, 一旦 app 上线给到用户,用户的网络环境千奇百怪:同步网络请求引发的问题就会变得寻常。在Xcode中,你可以模拟各种用户可能的网络环境帮助你去发现问题。参考Test under adverse device conditions (iOS) 文档。

将代码从主线程移走

把 app 所有非必须的长任务移到后台线程。通过移动任务到后台线程, app 主线程就可以快速开启和处理响应事件手势。如下,将网络请求放在子线程,而不是主线程。通过这个操作,主线程就可以处理点击、滑动等事件,使得 app 是流畅的。

如果长任务代码是在一个系统库代码里,可以看下系统库是否提供非主线的方式。例如,你可以在RealityKit 使用异步的方法loadAsync(contentsOf:withName:) 而不是使用同步方法load(contentsOf:withName:)。同样的,在 Vision 中提供了 preferBackgroundProcessing,相关的处理逻辑就不在主线程中。

如果网络请求代码是引起看门狗的原因,可以考虑如下常用方法解决

  • 使用异步网络代码  URLSession. 这是最好的方法,异步的方法有很多好处,包括 安全的访问网络而不用担心多线程问题
  • 使用 NWPathMonitor  而不是  SCNetworkReachability, 同监听网络路径的变化。 当你调用 start(queue:)的时候,操作系统会在一个队列里传递网络变化,这样,网络状态的变化在主线程就是安全的
  • 在子线程进行同步的网络请求。如果你的网络代码异步调用很困难,比如当使用跨平台的代码请求网络时,为了避免看门狗,可以讲代码同步代码放在子线程
  • 很多情况下不推荐解析手动解析。使用URLSession 里面有DNS解析的代码,而不是手动解析。 如果代码切换困难,你仍然需要手动解析 DNS 地址,请使用异步API,CFHost 或者 在<dns_sd.h>中声明的API。
参考文献
1.Addressing Watchdog Terminations
2.https://developer.apple.com/documentation/xcode/understanding-the-exception-types-in-a-crash-report#EXC_CRASH- (SIGKILL)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值