在计算机科学中,信号(英语:Signals)是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。
信号类似于中断,不同之处在于中断由处理器调解并由内核处理,而信号由内核调解(可能通过系统调用)并由进程处理。内核可以将中断作为信号传递给导致中断的进程(典型的例子有SIGSEGV、SIGBUS、SIGILL和SIGFPE)。
在一个运行的程序的控制终端键入特定的组合键可以向它发送某些信号:
Ctrl-C
发送INT信号(SIGINT);默认情况下,这会导致进程终止。Ctrl-Z
发送TSTP信号(SIGTSTP);默认情况下,这会导致进程挂起。Ctrl-\
发送QUIT信号(SIGQUIT);默认情况下,这会导致进程终止并且将内存中的信息转储到硬盘(核心转储)。kill()
系统调用会在权限允许的情况下向进程发送特定的信号,类似地,kill命令允许用户向进程发送信号。raise(int signal)
库函数可以将特定信号发送给当前进程。
文档在这里https://developer.apple.com/library/archive/technotes/tn2151/_index.html,搜索SIGQUIT
即可快速定位到。翻译大概是这样的。
该进程在另一个具有管理该进程生命周期特权的进程请求下停止的。SIGQUIT不意味着该进程已崩溃,但是它确实被检测出来发生错误了。
在iOS中,如果键盘加载时间过长,宿主进程会给键盘扩展发送SIGQUIT信号。崩溃日志中显示的调用栈回溯信息没有太多的用途。最有可能发生SIGQUIT的原因是,键盘扩展的启动过程的代码花费了很长时间才能完成,但是确实在时间限制内完成了,但当SIGQUIT发出SIGQUIT信号时,代码已经执行到日志中显示的调用栈回溯信息中了。您应该对键盘扩展进行分析,以更好的了解键盘启动时都做了些什么事情,然后将一些不必要的事情移到子线程或者推迟到键盘加载完毕之后再执行。
信号处理函数可以通过signal()
系统调用来设置。如果没有为一个信号设置对应的处理函数,就会使用默认的处理函数,否则信号就被进程截获并调用相应的处理函数。在没有处理函数的情况下,程序可以指定两种行为:忽略这个信号(SIG_IGN)或者用默认的处理函数(SIG_DFL)。但是有两个信号是无法被截获并处理的:SIGKILL和SIGSTOP。
因为竞态条件的存在,信号的处理是有弱点的。因为信号是异步的,所以在处理一个信号的过程中,进程可能收到另一个信号(甚至是相同的信号)。sigprocmask()
系统调用可以用来阻塞和恢复信号的传递。信号可以造成进程中系统调用的中断,并在信号处理完后重新开始未完成的系统调用。信号处理函数应该没有任何不想要的副作用,比如,errno
的改变、信号掩码的改变、信号处理方法的改变,以及其他全局进程性质的改变。在信号处理函数内使用不可重入函数,如malloc
和printf
,也是不安全的。
SIG_IGN
还是SIG_DFL
上面我们说到:在没有处理函数的情况下,程序可以指定两种行为:忽略这个信号(SIG_IGN)或者用默认的处理函数(SIG_DFL)。那我们来测试iOS键盘的SIGQUIT信号处理机制是SIG_IGN
还是SIG_DFL
。
测试方案:新建一个空的键盘项目,我们在ViewDidLoad
函数中,随机做一些时长为0ms~3000ms
的耗时操作。同时在+(void)load
函数中进行打印。在iOS11设备上进行测试。测试流程为频繁的调起键盘并收起键盘,最终通过log查看结果。
经测试在已有的三类设备中:iOS11设备复现SIGQUIT的几率较高,iOS9和iOS13复现的几率极低。
默认 10:55:16.767137+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8239
默认 10:55:22.399583+0800 Keyboard ----- SGIDebug viewDidLoad time : 594.86ms processID:8239
默认 10:55:22.589510+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8241
默认 10:55:30.203572+0800 Keyboard ----- SGIDebug viewDidLoad time : 1762.28ms processID:8241
默认 10:55:32.229868+0800 Keyboard ----- SGIDebug viewDidLoad time : 1898.79ms processID:8241
默认 10:55:33.230580+0800 Keyboard ----- SGIDebug viewDidLoad time : 409.31ms processID:8241
默认 10:55:34.979780+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8243
默认 10:55:43.241684+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8244
默认 10:55:46.390089+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8246
默认 10:55:56.152045+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8247
默认 10:55:59.780951+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8249
默认 10:56:04.449111+0800 Keyboard ----- SGIDebug viewDidLoad time : 1887.10ms processID:8249
默认 10:56:08.151585+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8250
默认 10:56:15.609184+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8252
默认 10:56:18.662599+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8253
默认 10:56:23.697057+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8255
默认 10:56:26.876691+0800 Keyboard ----- SGIDebug viewDidLoad time : 1146.67ms processID:8255
我们可以看到,很多关于是+(void)load
的log,同时进程的pid发生变化,说明进程重启了。然后我们再通过系统设置-隐私-分析数据中查看,发现了10个关于SIGQUIT
的崩溃日志,正好对应后10次的+(void)load
的log。
signal(SIGQUIT, SIG_DFL)
时默认 11:04:36.417079+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8351
默认 11:04:39.071723+0800 Keyboard ----- SGIDebug viewDidLoad time : 956.85ms processID:8351
默认 11:04:41.899271+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8391
默认 11:04:45.559485+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8392
默认 11:04:47.438328+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8393
默认 11:04:49.725146+0800 Keyboard ----- SGIDebug viewDidLoad time : 1334.17ms processID:8393
默认 11:04:51.523124+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8395
默认 11:04:57.324517+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8397
默认 11:05:01.870287+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8398
默认 11:05:04.226078+0800 Keyboard ----- SGIDebug viewDidLoad time : 328.21ms processID:8398
默认 11:05:07.329288+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8400
默认 11:05:09.780019+0800 Keyboard ----- SGIDebug viewDidLoad time : 473.52ms processID:8400
默认 11:05:16.478241+0800 Keyboard ----- SGIDebug viewDidLoad time : 1075.12ms processID:8400
默认 11:05:20.206613+0800 Keyboard ----- SGIDebug viewDidLoad time : 1962.51ms processID:8400
默认 11:05:20.704045+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8402
默认 11:05:27.016272+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8403
默认 11:05:30.646437+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8404
我们发现日志与不做任何signal处理时的日志打印基本一致。同样在数据分析中也找到了10条SIGQUIT
的崩溃日志。
signal(SIGQUIT, SIG_IGN)
时默认 11:09:46.018568+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8536
默认 11:09:51.788130+0800 Keyboard ----- SGIDebug viewDidLoad time : 1844.02ms processID:8536
默认 11:09:52.554243+0800 Keyboard ----- SGIDebug viewDidLoad time : 761.81ms processID:8536
默认 11:09:55.039405+0800 Keyboard ----- SGIDebug viewDidLoad time : 2263.81ms processID:8536
默认 11:09:56.335422+0800 Keyboard ----- SGIDebug viewDidLoad time : 1068.47ms processID:8536
默认 11:09:57.533077+0800 Keyboard ----- SGIDebug viewDidLoad time : 1194.43ms processID:8536
默认 11:10:03.510833+0800 Keyboard ----- SGIDebug viewDidLoad time : 1017.10ms processID:8536
默认 11:10:04.666973+0800 Keyboard ----- SGIDebug viewDidLoad time : 799.31ms processID:8536
默认 11:10:07.115586+0800 Keyboard ----- SGIDebug viewDidLoad time : 1961.38ms processID:8536
默认 11:10:08.250567+0800 Keyboard ----- SGIDebug viewDidLoad time : 1131.50ms processID:8536
默认 11:10:11.414621+0800 Keyboard ----- SGIDebug viewDidLoad time : 1123.73ms processID:8536
默认 11:10:14.375550+0800 Keyboard ----- SGIDebug viewDidLoad time : 2107.79ms processID:8536
默认 11:10:15.833425+0800 Keyboard ----- SGIDebug viewDidLoad time : 738.65ms processID:8536
默认 11:10:17.686347+0800 Keyboard ----- SGIDebug viewDidLoad time : 1864.35ms processID:8536
默认 11:10:18.873011+0800 Keyboard ----- SGIDebug viewDidLoad time : 875.18ms processID:8536
默认 11:10:20.903778+0800 Keyboard ----- SGIDebug viewDidLoad time : 1565.67ms processID:8536
默认 11:10:22.704031+0800 Keyboard ----- SGIDebug viewDidLoad time : 675.52ms processID:8536
默认 11:10:25.583807+0800 Keyboard ----- SGIDebug viewDidLoad time : 1263.46ms processID:8536
默认 11:10:29.533700+0800 Keyboard ----- SGIDebug viewDidLoad time : 2070.15ms processID:8536
默认 11:10:32.017082+0800 Keyboard ----- SGIDebug viewDidLoad time : 1547.02ms processID:8536
默认 11:10:34.044078+0800 Keyboard ----- SGIDebug viewDidLoad time : 2024.01ms processID:8536
默认 11:10:37.525322+0800 Keyboard ----- SGIDebug viewDidLoad time : 1290.21ms processID:8536
默认 11:10:39.553857+0800 Keyboard ----- SGIDebug viewDidLoad time : 1747.27ms processID:8536
从执行过程中的现象来看,我们观察到键盘会被切换成系统键盘。但是log中,我们没有发现任何进程重启的现象。同样在分析数据中,没有任何SIGQUIT
的崩溃日志。
signal(SIGQUIT, signalQuitHandler)
时void signalQuitHandler(int sig)
{
NSLog(@"----- SGIDebug %s processID:%d", __FUNCTION__, [NSProcessInfo processInfo].processIdentifier);
}
当我们设置为自定义函数时,我们来看看log是什么样的。
默认 11:15:44.751765+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:8928
默认 11:15:48.469873+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:15:48.470165+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:15:48.697348+0800 Keyboard ----- SGIDebug viewDidLoad time : 1295.26ms processID:8928
默认 11:15:49.236325+0800 Keyboard ----- SGIDebug viewDidLoad time : 531.80ms processID:8928
默认 11:15:50.643042+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:15:50.643121+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:15:51.772356+0800 Keyboard ----- SGIDebug viewDidLoad time : 2100.50ms processID:8928
默认 11:15:53.984736+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:15:53.984787+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:15:54.592767+0800 Keyboard ----- SGIDebug viewDidLoad time : 2128.13ms processID:8928
默认 11:15:57.876701+0800 Keyboard ----- SGIDebug viewDidLoad time : 1378.18ms processID:8928
默认 11:15:59.463721+0800 Keyboard ----- SGIDebug viewDidLoad time : 1018.97ms processID:8928
默认 11:16:01.940692+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:16:01.940831+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:16:02.344180+0800 Keyboard ----- SGIDebug viewDidLoad time : 2100.48ms processID:8928
默认 11:16:03.536956+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:16:03.537108+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:16:04.308509+0800 Keyboard ----- SGIDebug viewDidLoad time : 1961.05ms processID:8928
默认 11:16:07.489481+0800 Keyboard ----- SGIDebug viewDidLoad time : 999.39ms processID:8928
默认 11:16:08.996407+0800 Keyboard ----- SGIDebug viewDidLoad time : 634.44ms processID:8928
默认 11:16:11.203330+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:16:11.203435+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:16:11.945976+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:16:11.946227+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:16:11.994779+0800 Keyboard ----- SGIDebug viewDidLoad time : 2102.37ms processID:8928
默认 11:16:13.564042+0800 Keyboard ----- SGIDebug viewDidLoad time : 1574.79ms processID:8928
默认 11:16:15.888691+0800 Keyboard ----- SGIDebug viewDidLoad time : 1308.88ms processID:8928
默认 11:16:17.058090+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:16:17.058131+0800 Keyboard ----- SGIDebug signalQuitHandler processID:8928
默认 11:16:18.114001+0800 Keyboard ----- SGIDebug viewDidLoad time : 1808.26ms processID:8928
默认 11:16:18.987198+0800 Keyboard ----- SGIDebug viewDidLoad time : 869.59ms processID:8928
通过log看,我们确实处理的SIGQUIT
,并且键盘进程没有重启,分析数据中没有SIGQUIT
的崩溃日志。
SIG_DFL
的逻辑。键盘进程会重启,并且在数据分析中会产生SIGQUIT
相关的崩溃日志,进程重启的次数与SIGQUIT
崩溃的次数一致。SIG_IGN
和自定义函数时,键盘进程不会重启,并且在数据分析中不会产生SIGQUIT
相关的崩溃日志。通过上面的评测,我们了解到,如果我们要统计SIGQUIT
的次数,就必须要自定义处理信号的函数。而自定义处理信号,就会要忽略掉这个信号,导致键盘不会重启,与键盘默认处理信号的逻辑不一致,可能会出现其他问题。
经过测试,我们可以多次调用signal()
函数,来设置处理信号的回调函数。那么我们可以在首个自定义处理信号的函数中进行记录,并且再将处理信号重新设置回SIG_DFL
,这样下次SIGQUIT
时,就可以走正常的流程了。为了将自定义处理信号的表现与SIG_DFL
的表现一致,我们可以在将处理信号设置回SIG_DFL
后,手动调用raise(SIGQUIT)
来触发一次SIGQUIT
。
// 代码调用处
signal(SIGQUIT, signalQuitHandler);
// signalQuitHandler函数定义
void signalQuitHandler(int sig)
{
NSLog(@"----- SGIDebug %s processID:%d", __FUNCTION__, [NSProcessInfo processInfo].processIdentifier);
signal(SIGQUIT, SIG_DFL);
raise(SIGQUIT);
}
我们来看下执行结果:
默认 11:34:00.054070+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:9068
默认 11:34:03.583420+0800 Keyboard ----- SGIDebug signalQuitHandler processID:9068
默认 11:34:03.955060+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:9070
默认 11:34:05.507952+0800 Keyboard ----- SGIDebug signalQuitHandler processID:9070
默认 11:34:05.508435+0800 Keyboard ----- SGIDebug signalQuitHandler processID:9070
默认 11:34:05.664773+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:9071
默认 11:34:10.390774+0800 Keyboard ----- SGIDebug signalQuitHandler processID:9071
默认 11:34:10.546643+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:9072
默认 11:34:12.549918+0800 Keyboard ----- SGIDebug signalQuitHandler processID:9072
默认 11:34:17.221589+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:9073
默认 11:34:21.522573+0800 Keyboard ----- SGIDebug signalQuitHandler processID:9073
默认 11:34:21.970238+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:9075
默认 11:34:23.987462+0800 Keyboard ----- SGIDebug viewDidLoad time : 0.08ms processID:9075
默认 11:34:26.458283+0800 Keyboard ----- SGIDebug signalQuitHandler processID:9075
默认 11:34:26.622109+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:9076
默认 11:34:30.809283+0800 Keyboard ----- SGIDebug viewDidLoad time : 2086.79ms processID:9076
默认 11:34:34.996783+0800 Keyboard ----- SGIDebug viewDidLoad time : 729.69ms processID:9076
默认 11:34:37.540586+0800 Keyboard ----- SGIDebug viewDidLoad time : 1205.70ms processID:9076
默认 11:34:39.438812+0800 Keyboard ----- SGIDebug signalQuitHandler processID:9076
默认 11:34:39.851161+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:9078
默认 11:34:42.935266+0800 Keyboard ----- SGIDebug signalQuitHandler processID:9078
默认 11:34:43.097889+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:9079
默认 11:35:01.570907+0800 Keyboard ----- SGIDebug viewDidLoad time : 2068.85ms processID:9079
默认 11:35:04.184268+0800 Keyboard ----- SGIDebug viewDidLoad time : 1051.43ms processID:9079
默认 11:35:07.248899+0800 Keyboard ----- SGIDebug viewDidLoad time : 1810.91ms processID:9079
默认 11:35:08.981190+0800 Keyboard ----- SGIDebug viewDidLoad time : 767.95ms processID:9079
默认 11:35:09.025391+0800 Keyboard ----- SGIDebug signalQuitHandler processID:9079
默认 11:35:09.115436+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:9082
默认 11:35:12.548235+0800 Keyboard ----- SGIDebug signalQuitHandler processID:9082
默认 11:35:18.919975+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:9083
不难看出,这个如果去掉signalQuitHandler
的log,其余的log与SIG_DFL
的log一致,且分析数据中的崩溃日志数量,也可以跟+(void)load
的数量对的上。所以如果我们需要统计SIGQUIT
的次数,我们需要先自定义信号的处理函数,在处理函数里面记录SIGQUIT
的次数,并在处理函数里面通过singal(SIGQUIT, SIG_DFL)
重新将处理函数设置为默认函数,然后手动调用raise(SIGQUIT)
来触发SIGQUIT
即可。
通过fishhook对raise()
函数进行hook,同时不设置任何信号处理,得到的log。
默认 15:24:07.996198+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:10847
默认 15:24:29.008861+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:10871
默认 15:24:33.625747+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:10872
默认 15:24:35.512911+0800 Keyboard ----- SGIDebug my_raise signal:3 processID:10872
默认 15:24:39.616856+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:10873
默认 15:24:39.904361+0800 Keyboard ----- SGIDebug my_raise signal:3 processID:10873
默认 15:24:43.556727+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:10875
默认 15:24:52.592057+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:10877
默认 15:24:55.932611+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:10878
默认 15:25:00.866935+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:10879
默认 15:25:01.325544+0800 Keyboard ----- SGIDebug my_raise signal:3 processID:10879
默认 15:25:07.464576+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:10880
默认 15:25:12.994054+0800 Keyboard ----- SGIDebug +[KeyboardViewController load] processID:10882
从log中,不难看出,系统在某些时刻,也是通过raise()
函数来发SIGQUIT
的。这些时刻基本上都是手动把输入法从系统键盘切换到我们开发的键盘时出现的。也就是当我们自身的调起时间较长时,键盘本身会调用raise()
来发送SIGQUIT
信号。所以我们自身在程序中通过raise()
函数来发SIGQUIT
信号,应该也是没有问题的。
在越狱的机器上验证,通过命令行kill -SIGQUIT 键盘pid
可以给键盘发送SIGQUIT
信号。比较遗憾的是,没有找到宿主进程是如何与键盘通讯,发送SIGQUIT
信号给键盘的。