Android信号处理

  首先澄清,本文讨论的信号是 Linux 软中断信号,而不是手机状态条里面用于显示当前手机通信强度的那个信号。

  我们知道,Unix系统里信号是一种软中断。尽管本身存在缺陷(后面会讨论到),但是作为Unix系统重要的异步事件处理方式之一,在Unix系统中发挥重要的作用。可以说,所有Unix系统(包括Linux)都不可能忽略信号的支持。 Android 本质上也是个在 Linux 系统,自然也少不了对 信号处理的支持。

  但我们也知道,Android和其他Linux系统一个很大的差异就是增加了虚拟机的支持(Dalvik vm),所有的应用程序都会在虚拟机实例里运行 (当然,虚拟机实例还是在传统的进程里运行)。为了更好的支持应用程序的开发和调试,Android对信号的处理增加了额外的逻辑(下面会详细讨论到)。这也使得 Android系统中信号的处理行为和传统的Linux系统有所区别。 很多朋友,在开始接触 Android信号处理时,可能也会碰到一些困惑。尽管其实答案很简答, 但是网上这方面的资料少,还是需要花不少时间去摸透。 本文写作的目的就是试图 将 android信号处理的方方面面呈现给大家。 让大家在最短的时间内掌握android信号处理的知识,并学以致用。

  一、Android 信号处理面面观之概述

  虽然,android 信号处理并不复杂,但是如果用一篇文章还真难以描述清楚而又有条理。所以对信号处理打算分几部分讨论:

  1. 概述  就是本文。 简述 Android 系统对信号处理的概貌,并说明测试环境。

  2. 信号产生。讨论 android信号的产生原因以及最简单的测试方法。

  3. 信号处理。 讨论Android系统对传统的信号处理的扩展是怎么实现的。

  4. 应用扩展。 讨论在实际开发中,怎样利用android信号机制为我们服务。

  请读者先看看自己对 Android信号处理的了解程度再决定是否需要 花时间关注该系列的博文。以下是几个相关的问题:

  1. Android信号处理比起传统的linux系统(如 Ubuntu)有什么区别么?

  2. Android信号来源于哪里?怎样用最简单的方式产生信号,并测试信号处理的行为。?怎样才能最快的分析有信号处理产生的问题?

  3. Android是如何实现对传统信号处理的扩展。

  4. 我们日常开发中,Android信号处理机制可以帮助我们处理那类问题?

  本文的测试环境是:

  模拟器: Ubuntu 11.04 运行 最新的Android 4.0.1 模拟器

  手机: Droid3 with android 2.3.6

  平板: Xoom2 with android 3.2

  除非有行为上得差异,否则所有测试结果都将出自 模拟器(方便,呵呵)

  对了,上面提到的传统信号处理模型的缺陷,主要是有如下几个点:

  1. 难以扩展。 这可能是历史原因造成的,早期信号处理模型中,为了效率和方便,大多使用整型位码来表示某一信号。而总数控制在32位之内。大多数已经有明确的含义,而大多只提供 SIGUSR1 和 SIGUSR2供用户使用。

  2. 某些情境下的行为不可靠。比如相同的信号连续到达后,大多只作为一个信号处理。也就是说你只能知道该信号是否到达,而不能确定到达了一个还是是个。

  对于缺陷1,似乎没有什么好办法(总不能另外造出信号类型啊)。而可行的方法就是重用这两个信号。

  对于缺陷2,如果需要连续产生相同的信号而又要处理,可以在期间加入延迟。后面会看到 Android就是这么做的。

   二、Android 信号处理面面观之信号定义、行为和来源

  1,传统 Unix系统的信号定义和行为

  所有的符合Unix规范(如POSIX)的系统都统一定义了SIGNAL的数量、含义和行为。 作为Linux系统,Android自然不会更改SIGNAL的定义。在Android代码中,signal的定义一般在 signum.h (prebuilt/linux-x86/toolchain/i686-linux-glibc2.7-4.4.3/sysroot/usr/include/bits/signum.h)中:

 1 /* Signals.  */
 2 #define    SIGHUP        1    /* Hangup (POSIX).  */
 3 #define    SIGINT        2    /* Interrupt (ANSI).  */
 4 #define    SIGQUIT       3    /* Quit (POSIX).  */
 5 #define    SIGILL        4    /* Illegal instruction (ANSI).  */
 6 #define    SIGTRAP       5    /* Trace trap (POSIX).  */
 7 #define    SIGABRT       6    /* Abort (ANSI).  */
 8 #define    SIGIOT        6    /* IOT trap (4.2 BSD).  */
 9 #define    SIGBUS        7    /* BUS error (4.2 BSD).  */
10 #define    SIGFPE        8    /* Floating-point exception (ANSI).  */
11 #define    SIGKILL       9    /* Kill, unblockable (POSIX).  */
12 #define    SIGUSR1       10    /* User-defined signal 1 (POSIX).  */
13 #define    SIGSEGV       11    /* Segmentation violation (ANSI).  */
14 #define    SIGUSR2       12    /* User-defined signal 2 (POSIX).  */
15 #define    SIGPIPE       13    /* Broken pipe (POSIX).  */
16 #define    SIGALRM       14    /* Alarm clock (POSIX).  */
17 #define    SIGTERM       15    /* Termination (ANSI).  */
18 #define    SIGSTKFLT     16    /* Stack fault.  */
19 #define    SIGCLD        SIGCHLD    /* Same as SIGCHLD (System V).  */
20 #define    SIGCHLD       17    /* Child status has changed (POSIX).  */
21 #define    SIGCONT       18    /* Continue (POSIX).  */
22 #define    SIGSTOP       19    /* Stop, unblockable (POSIX).  */
23 #define    SIGTSTP       20    /* Keyboard stop (POSIX).  */
24 #define    SIGTTIN       21    /* Background read from tty (POSIX).  */
25 #define    SIGTTOU       22    /* Background write to tty (POSIX).  */
26 #define    SIGURG        23    /* Urgent condition on socket (4.2 BSD).  */
27 #define    SIGXCPU       24    /* CPU limit exceeded (4.2 BSD).  */
28 #define    SIGXFSZ       25    /* File size limit exceeded (4.2 BSD).  */
29 #define    SIGVTALRM     26    /* Virtual alarm clock (4.2 BSD).  */
30 #define    SIGPROF       27    /* Profiling alarm clock (4.2 BSD).  */
31 #define    SIGWINCH      28    /* Window size change (4.3 BSD, Sun).  */
32 #define    SIGPOLL       SIGIO    /* Pollable event occurred (System V).  */
33 #define    SIGIO         29    /* I/O now possible (4.2 BSD).  */
34 #define    SIGPWR        30    /* Power failure restart (System V).  */
35 #define    SIGSYS        31    /* Bad system call.  */
36 #define    SIGUNUSED     31  

  我们知道,信号处理的方式一般有三种:

  (1). 忽略  接收到信号后不做任何反应。

  (2). 自定义  用自定义的信号处理函数来执行特定的动作

  (3). 默认  接收到信号后按默认得行为处理该信号。 这是多数应用采取的处理方式。

  而 传统 UNIX系统对以上信号的默认处理如下图所示 (来自 APUT ):

  2,Android 系统 信号处理的行为

  我们知道,信号处理的行为是以进程级的。就是说不同的进程可以分别设置不同的信号处理方式而互不干扰。同一进程中的不同线程虽然可以设置不同的信号屏蔽字,但是却共享相同的信号处理方式 (也就是说 在一个线程里改变信号处理方式,将作用于该进程中的所有线程)。

  Android也是Linux系统。所以其信号处理方式不会有本质的改变。但是为了开发和调试的需要,android对一些信号的处理定义了额外的行为。 下面是这些典型的信号在Android系统上的行为:

  (1). SIGQUIT ( 整型值为 3)

  上面的表10-1显示,传统UNIX系统应用,对SIGQUIT信号的默认行为是 "终止 + CORE"。也就是产生core dump文件后,立即终于运行。

  Android Dalvik应用收到该信号后,会 打印改应用中所有线程的当前状态,并且并不是强制退出。这些状态通常保存在一个特定的叫做trace的文件中。一般的路径是/data/anr/trace.txt. 下面是一个典型的trace文件的内容:

 1 ----- pid 503 at 2011-11-21 21:59:12 -----
 2 Cmd line: com.android.phone
 3
 4 DALVIK THREADS:
 5 (mutexes: tll=0 tsl=0 tscl=0 ghl=0 hwl=0 hwll=0)
 6 "main" prio=5 tid=1 NATIVE
 7   | group="main" sCount=1 dsCount=0 obj=0x400246a0 self=0x12770
 8   | sysTid=503 nice=0 sched=0/0 cgrp=default handle=-1342909272
 9   | schedstat=( 15165039025 12197235258 23068 ) utm=182 stm=1334 core=0
10   at android.os.MessageQueue.nativePollOnce(Native Method)
11   at android.os.MessageQueue.next(MessageQueue.java:119)
12   at android.os.Looper.loop(Looper.java:122)
13   at android.app.ActivityThread.main(ActivityThread.java:4134)
14   at java.lang.reflect.Method.invokeNative(Native Method)
15   at java.lang.reflect.Method.invoke(Method.java:491)
16   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
17   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
18   at dalvik.system.NativeStart.main(Native Method)
19
20 "Thread-29" prio=5 tid=24 WAIT
21   | group="main" sCount=1 dsCount=0 obj=0x406f0d50 self=0x208c18
22   | sysTid=1095 nice=0 sched=0/0 cgrp=default handle=2133304
23   | schedstat=( 9521483 7029937750 720 ) utm=0 stm=0 core=0
24   at java.lang.Object.wait(Native Method)
25   - waiting on <0x406f0d50> (a com.motorola.android.telephony.cdma.OemCdmaTelephonyManager$Watchdog)
26   at java.lang.Object.wait(Object.java:361)
27   at com.motorola.android.telephony.cdma.OemCdmaTelephonyManager$Watchdog.run(OemCdmaTelephonyManager.java:229)
28
29 "FileObserver" prio=5 tid=23 NATIVE
30   | group="main" sCount=1 dsCount=0 obj=0x4068b2f8 self=0x1ed278
31   | sysTid=909 nice=0 sched=0/0 cgrp=default handle=2019248
32   | schedstat=( 11810291 7018493670 720 ) utm=0 stm=0 core=0
33   at android.os.FileObserver$ObserverThread.observe(Native Method)
34   at android.os.FileObserver$ObserverThread.run(FileObserver.java:88)
35
36 "android.hardware.SensorManager$SensorThread" prio=5 tid=22 NATIVE
37   | group="main" sCount=1 dsCount=0 obj=0x406bbd90 self=0x1b2ec0
38   | sysTid=869 nice=-8 sched=0/0 cgrp=default handle=1974064
39   | schedstat=( 3014251483 8295989933 15621 ) utm=171 stm=128 core=0
40   at android.hardware.SensorManager.sensors_data_poll(Native Method)
41   at android.hardware.SensorManager$SensorThread$SensorThreadRunnable.run(SensorManager.java:498)
42   at java.lang.Thread.run(Thread.java:1020)
43 ...

  该文件包好很多重要的信息,可以说明在发生异常是,当前进程的状态 (后面有单独的一篇文章分析改文件)

  (2). 对于很多其他的异常信号 (SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV, SIGSTKFLT ), Android进程 在退出前,会生成 tombstone文件。记录该进程退出前的轨迹。一个典型的tombstone文件内容如下:

  1 *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
  2 Build fingerprint: ‘verizon/pasteur/pasteur:3.2.2/1.6.0_241/eng.drmn68.20111115.094123:eng/test-keys‘
  3 pid: 181, tid: 322  >>> /system/bin/mediaserver <<<
  4 signal 8 (SIGFPE), code 0 (?), fault addr 000000b5
  5  r0 00000000  r1 00000008  r2 ffffffff  r3 00000020
  6  r4 00000008  r5 00000000  r6 000000a5  r7 00000025
  7  r8 662f9c00  r9 662f9c00  10 00000001  fp 00000000
  8  ip aff17699  sp 4057f9dc  lr aff176a7  pc aff0c684  cpsr 00000010
  9  d0  6f762f6f69647502  d1  0000562202000000
 10  d2  0000000400000300  d3  400120dc00000000
 11  d4  0000000000000000  d5  0000000000000000
 12  d6  3ce449db86666666  d7  3e4ccccd3e4ccccd
 13  d8  000000000035c6a8  d9  000000000035c6a8
 14  d10 0000000000000000  d11 0000000000000000
 15  d12 0000000000000000  d13 0000000000000000
 16  d14 0000000000000000  d15 0000000000000000
 17  d16 0000000000000000  d17 3e582f8f86b6a000
 18  d18 3fe0000000000000  d19 3fe000000c17c7c3
 19  d20 3f11504c292739d4  d21 bebbb371092382c4
 20  d22 3ff0000000000000  d23 3ff43d135cda918c
 21  d24 3e66376972bea4d0  d25 0000000000000000
 22  d26 0000000000000000  d27 0000000000000000
 23  d28 0000000000000000  d29 0000000000000000
 24  d30 0000000000000000  d31 0000000000000000
 25  scr 20000010
 26
 27          #00  pc 0000c684  /system/lib/libc.so (kill)
 28          #01  pc 000176a4  /system/lib/libc.so (raise)
 29
 30 libc base address: aff00000
 31
 32 code around pc:
 33 aff0c664 e2601000 e0100001 116f0f10 12600020
 34 aff0c674 e12fff1e e92d50f0 e3a07025 ef000000
 35 aff0c684 e8bd50f0 e1b00000 512fff1e ea00ade7
 36 aff0c694 e92d50f0 e3a070ee ef000000 e8bd50f0
 37 aff0c6a4 e1b00000 512fff1e ea00ade0 f5d0f000
 38
 39 code around lr:
 40 aff17684 00029e2e 461cb537 e9cd17dd f7f34500
 41 aff17694 bd3eef02 4604b510 ed5ef7f3 f7f44621
 42 aff176a4 bd10efea 49034602 2300b510 f7f44802
 43 aff176b4 bd10edf6 28121969 fee1dead 2400b513
 44 aff176c4 94019400 ec9cf7f4 bf00bd1c 4c11b570
 45
 46 stack:
 47     4057f99c  a2b6fd15  /system/lib/libstagefright.so
 48     4057f9a0  00000000
 49     4057f9a4  a2b6fe51  /system/lib/libstagefright.so
 50     4057f9a8  000fb02c
 51     4057f9ac  a2b6fde7  /system/lib/libstagefright.so
 52     4057f9b0  4057fa14
 53     4057f9b4  000fb030
 54     4057f9b8  00000000
 55     4057f9bc  a2b6fe79  /system/lib/libstagefright.so
 56     4057f9c0  000fafe0
 57     4057f9c4  00000000
 58     4057f9c8  4057fa14
 59     4057f9cc  a2b6fe59  /system/lib/libstagefright.so
 60     4057f9d0  00000001
 61     4057f9d4  a801e509  /system/lib/libutils.so
 62     4057f9d8  4057fa14
 63 #01 4057f9dc  00000008
 64     4057f9e0  00000000
 65     4057f9e4  000000a5
 66     4057f9e8  00000000
 67     4057f9ec  aff17699  /system/lib/libc.so
 68     4057f9f0  aff176a7  /system/lib/libc.so
 69     4057f9f4  00000000
 70     4057f9f8  aff0e154  /system/lib/libc.so
 71     4057f9fc  00000000
 72     4057fa00  aff0cf84  /system/lib/libc.so
 73     4057fa04  aff0cf94  /system/lib/libc.so
 74     4057fa08  00000000
 75     4057fa0c  000000a5
 76     4057fa10  00000000
 77     4057fa14  aff0fca4  /system/lib/libc.so
 78     4057fa18  662f9c00
 79     4057fa1c  000000a5
 80     4057fa20  00000000
 81 --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
 82 pid: 181, tid: 181
 83  r0 fffffe00  r1 c0186201  r2 be8b8b98  r3 be8b8b94
 84  r4 0000f5e0  r5 0000f5b0  r6 0000f610  r7 00000036
 85  r8 00000001  r9 0000f5cc  10 0000f5b8  fp 00000000
 86  ip a812336c  sp be8b8b78  lr aff25e19  pc aff0b680  cpsr 80000010
 87  d0  000f891000000000  d1  00000004be8b8b00
 88  d2  0069006400650000  d3  00410049002e0000
 89  d4  0000000000000000  d5  0000000000000000
 90  d6  4208000041880000  d7  0000000041a00000
 91  d8  0000000000000000  d9  0000000000000000
 92  d10 0000000000000000  d11 0000000000000000
 93  d12 0000000000000000  d13 0000000000000000
 94  d14 0000000000000000  d15 0000000000000000
 95  d16 0000000000000000  d17 0000000000000000
 96  d18 4000000000000000  d19 3fcce7359d4792d9
 97  d20 3f11504c292739d4  d21 bebbb371092382c4
 98  d22 3ff0000000000000  d23 3ff43d135cda918c
 99  d24 3e66376972bea4d0  d25 0000000000000000
100  d26 0000000000000000  d27 0000000000000000
101  d28 0000000000000000  d29 0000000000000000
102  d30 0000000000000000  d31 0000000000000000
103  scr 60000010
104
105          #00  pc 0000b680  /system/lib/libc.so (__ioctl)
106          #01  pc 00025e16  /system/lib/libc.so (ioctl)
107          #02  pc 00016202  /system/lib/libbinder.so (_ZN7android14IPCThreadState14talkWithDriverEb)
108          #03  pc 00016afc  /system/lib/libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb)
109          #04  pc 00008a94  /system/bin/mediaserver
110          #05  pc 00014aa0  /system/lib/libc.so (__libc_init)
111
112 libc base address: aff00000
113
114 code around pc:
115 aff0b660 ef000000 e8bd0090 e1b00000 512fff1e
116 aff0b670 ea00b1ef e92d0090 e3a07036 ef000000
117 aff0b680 e8bd0090 e1b00000 512fff1e ea00b1e8
118 aff0b690 e92d0090 e3a07091 ef000000 e8bd0090
119 aff0b6a0 e1b00000 512fff1e ea00b1e1 e92d0090
120 ...

  可以看出,它同样包含很多重要的信息(特别是 stack )来帮助我们查找异常的原因。分析tombstone的方法,将单独成篇

  3,Android信号的产生和测试

  我们看到,多数signal的产生是由于某种内部错误。我们在在开发过程中,当然也可以通过系统调用故意生成signal给某进程。主要的方法如果:

  (1). 在kernel里 使用 kill_proc_info()

  (2). 在native应用中 使用 kill() 或者raise()

  (3). java 应用中使用 Procees.sendSignal()等

  但是在测试中,最简单的方法某过于通过 adb 工具了。一个典型场景是:

1 adb root
2 adb shell ps
3 adb shell kill -3 513

  首先是切换到root用户 (普通进程只能发个自己或者同组进程,而root可以发送signal给任何进程)。然后用 ps命令查看当前系统中所有的进程信息。最后用kill命令发送SIGQUIT给进程号为513的进程。

  android kill程序的实现很简单,他只能支持发送signal的值(如上例中的 “3”)给进程,而不能用名字(如“SIGQUIT”)。 android 中kill程序的代码在system/core/toolbox/kill.c 中。虽然移植linux中kill的实现就能支持名字,但是那个完全没有必要,android需要的signal就这么几个,他们的值应该记住的。

  对于终端发送 SIGQUIT,大多数用户可以直接得到预期的结果 (生成相应的trace文件)。 最困惑的行为来自于终端发送SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV, SIGSTKFLT等信号,我们常常看到 “不确定” 的行为:有时候能够看到 process 终止,有时候却不能。core dump 也不是总能产生。 例如如下测试场景:

1 adb root
2 adb shell ps
3 adb shell kill -11 299

  从 log里看到 进程299 并没有终止, ps 看到进程还在。Log只有如下输出:

1 F/libc    (  244): Fatal signal 11 (SIGSEGV) at 0x000001c0 (code=0)
2 I/DEBUG   (   31): timed out waiting for pid=244 tid=244 uid=10009 to die

  再试一次:

1 adb shell ps
2 adb shell kill -11 299

  这次 进程299被终止掉了,却没有产生 core dump文件:

1 D/Zygote  (   34): Process 244 terminated by signal (11)
2 I/ActivityManager(   78): Process com.android.calendar (pid 244) has died.

  其实,我们观察到得不确定行为是因为没有了解 android 这些信号的处理。他的行为的确是确定的,要点是:

  要产生core dump并终止某进程,我们需要 连续发送两次改信号,并且中间间隔在0.2秒到3秒之间。

如果间隔过小, Android可能无法接收第一个signal。如果时间过久,android将简单的终止进程,而没有 core dump产生。 我们会在后面 详细的介绍产生这种行为的原因。

  好了,了解到上面的行为后,我们再次试验,连续两次发送 SIGSEGV给进程,看看行为:

1 adb shell ps
2 adb shell kill -11 307
3 adb shell kill -11 307

  我们看到, 该进程被终止, tombstone被打到log里, 并且存储在/data/tombstone_00 中:

 1 F/libc    (  387): Fatal signal 11 (SIGSEGV) at 0x000001cf (code=0)
 2 I/DEBUG   (   31): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
 3 I/DEBUG   (   31): Build fingerprint: ‘generic/sdk/generic:4.0.1/ICS_MR0/eng.drmn68.20111115.224016:eng/test-keys‘
 4 I/DEBUG   (   31): pid: 387, tid: 387  >>> com.android.mms <<<
 5 I/DEBUG   (   31): signal 11 (SIGSEGV), code 0 (?), fault addr 000001d2
 6 I/DEBUG   (   31):  r0 fffffffc  r1 beb2b4e0  r2 00000010  r3 ffffffff
 7 I/DEBUG   (   31):  r4 001d0268  r5 001d027c  r6 00000000  r7 000000fc
 8 I/DEBUG   (   31):  r8 00000000  r9 00000014  10 00012820  fp beb2b674
 9 I/DEBUG   (   31):  ip 400c11a4  sp beb2b4b8  lr 400b9993  pc 40011384  cpsr 20000010
10 I/DEBUG   (   31):  d0  000000f043700000  d1  3ff0000043700000
11 I/DEBUG   (   31):  d2  457ff80000000fff  d3  000000003f000000
12 I/DEBUG   (   31):  d4  00001fff00000000  d5  3fe999999999999a
13 I/DEBUG   (   31):  d6  3fe8000000000000  d7  000000f000000000
14 I/DEBUG   (   31):  d8  0000000000000000  d9  0000000000000000
15 I/DEBUG   (   31):  d10 0000000000000000  d11 0000000000000000
16 I/DEBUG   (   31):  d12 0000000000000000  d13 0000000000000000
17 I/DEBUG   (   31):  d14 0000000000000000  d15 0000000000000000
18 I/DEBUG   (   31):  scr 60000012
19 I/DEBUG   (   31):
20 I/DEBUG   (   31):          #00  pc 0000d384  /system/lib/libc.so (epoll_wait)
21 I/DEBUG   (   31):          #01  pc 00026990  /system/lib/libutils.so (_ZN7android6Looper9pollInnerEi)
22 I/DEBUG   (   31):          #02  pc 00026bbe  /system/lib/libutils.so (_ZN7android6Looper8pollOnceEiPiS1_PPv)  

  至此,我们已经掌握 android信号的定义和行为。下面就可以研究产生这种行为的原因了。

  三、Android 信号处理面面观 之 trace 文件含义

  Android 应用在收到异常终止信号(SIGQUIT)时,没有遵循传统 UNIX信号模型的默认行为 (终止 + core )。而是打印出trace 文件来,以利于记录应用异常终止的原因。 本文就重点分析 trace 文件是怎么产生的,并详细解释trace文件的各个字段的含义。

  1,TRACE 文件的产生

  Trace文件是 android davik 虚拟机在收到异常终止信号 (SIGQUIT)时产生的。 最经常的触发条件是 android应用中产生了 FC (force close)。由于是该文件的产生是在 DVM里,所以只有运行 dvm实例的进程(如普通的java应用,java服务等)才会产生该文件,android 本地应用 (native app,指 运行在 android lib层,用c/c++编写的linux应用、库、服务等)在收到 SIGQUIT时是不会产生 trace文件的。

  我们可以在终端通过adb发送SIGQUIT给应用来生成trace文件。

  2,TRACE文件的实现

  相关实现在以下几个文件中:

  dalvik/vm/init.h [.c]

  davik/vm/SignalCatcher.h[.c]

  dalvik/vm/Thread.h[.c]

  Android ICS 实现文件后缀是 .cpp。

  实现过程分以下几步:

  Step #1:  DVM初始化时,设置信号屏蔽字,屏蔽要特殊处理的信号(SIGQUIT, SIGUSR1, SIGUSR2)。由于信号处理方式是进程范围起作用的, 这意味着该进程里所有的线程都将屏蔽该信号。 实现代码在init.c中如下:

1 int dvmStartup(int argc, const char* const argv[], bool ignoreUnrecognized,
2     JNIEnv* pEnv)
3 {
4     ...
5     /* configure signal handling */
6     if (!gDvm.reduceSignals)
7         blockSignals();
8     ...
9 }

  blockSignals()的实现很简答,它是通过 sigprocmask() 函数调用实现的,代码在init.c如下:

 1 /*
 2  * Configure signals.  We need to block SIGQUIT so that the signal only
 3  * reaches the dump-stack-trace thread.
 4  *
 5  * This can be disabled with the "-Xrs" flag.
 6  */
 7 static void blockSignals()
 8 {
 9     sigset_t mask;
10     int cc;
11
12     sigemptyset(&mask);
13     sigaddset(&mask, SIGQUIT);
14     sigaddset(&mask, SIGUSR1);      // used to initiate heap dump
15 #if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
16     sigaddset(&mask, SIGUSR2);      // used to investigate JIT internals
17 #endif
18     //sigaddset(&mask, SIGPIPE);
19     cc = sigprocmask(SIG_BLOCK, &mask, NULL);
20     assert(cc == 0);
21 }

  Step #2: DVM 生成单独的信号处理线程,用来对三个信号做特殊处理 (init.c):

 1 /*
 2  * Do non-zygote-mode initialization.  This is done during VM init for
 3  * standard startup, or after a "zygote fork" when creating a new process.
 4  */
 5 bool dvmInitAfterZygote(void)
 6 {
 7     ...
 8     /* start signal catcher thread that dumps stacks on SIGQUIT */
 9     if (!gDvm.reduceSignals && !gDvm.noQuitHandler) {
10         if (!dvmSignalCatcherStartup())
11             return false;
12     }
13     ...
14 }

  dvmSignalCatcherStartup() 实现在 SignalCatcher.c 中:

 1 /*
 2  * Crank up the signal catcher thread.
 3  *
 4  * Returns immediately.
 5  */
 6 bool dvmSignalCatcherStartup(void)
 7 {
 8     gDvm.haltSignalCatcher = false;
 9
10     if (!dvmCreateInternalThread(&gDvm.signalCatcherHandle,
11                 "Signal Catcher", signalCatcherThreadStart, NULL))
12         return false;
13
14     return true;
15 }

  我们看到,DVM调用dvmCreateInternalThread()来生成一个新的内部线程 来专门处理dvm进程里的信号。 后面我们会看到,dvmCreateInternalThread()其实是使用pthread_create()来产生新的线程。 该线程的处理函数是 signalCatcherThreadStart()。  (dvm里所谓的 内部线程,就是用来帮助dvm实现本身使用的线程,比如 信号处理线程,binder线程,Compiler线程,JDWP线程等,而不是应用程序申请的线程。

  signalCatcherThreadStart() 实现框架如下:

 1 /*
 2  * Sleep in sigwait() until a signal arrives.
 3  */
 4 static void* signalCatcherThreadStart(void* arg)
 5 {
 6     ...
 7 &nbsp;   /* set up mask with signals we want to handle */
 8     sigemptyset(&mask);
 9     sigaddset(&mask, SIGQUIT);
10     sigaddset(&mask, SIGUSR1);
11 #if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
12     sigaddset(&mask, SIGUSR2);
13 #endif
14     ...
15     while (true) {
16     ...
17 loop:
18         cc = sigwait(&mask, &rcvd);
19         ...
20         switch (rcvd) {
21         case SIGQUIT:
22             handleSigQuit();
23             break;
24         case SIGUSR1:
25             handleSigUsr1();
26             break;
27 #if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
28         case SIGUSR2:
29             handleSigUsr2();
30             break;
31 #endif
32         ...
33 }

  它首先设置我们要处理的信号集(SIGQUIT, SIGUSR1, SIGUSR2), 然后 调用 sigwait()。 我们知道sigwait()会在当前的线程里 重新 打开 指定的信号屏蔽字屏蔽的信号集。  在刚才的分析中,我们看到,dvm在启动时,首先在整个进程里设置信号屏蔽字屏蔽掉三个信号,sigwait()的调用,使的这三个信号只在 SignalCatcher线程里响应。

  至此我们已经能够看到,dvm对三个信号分别所做的特殊用途:

  (1). SIGUSR1 被用来 做手工垃圾收集。处理函数是 HandleSigUsr1()

1 static void handleSigUsr1(void)
2 {
3     LOGI("SIGUSR1 forcing GC (no HPROF)\n");
4     dvmCollectGarbage(false);
5 }

  (2). SIGUSR2 被用来做 JIT的调试。如果JIT下编译时打开,收到SIGUSR2是dvm会dump出相关的调试信息。处理逻辑如下:

 1 #if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
 2 /*
 3  * Respond to a SIGUSR2 by dumping some JIT stats and possibly resetting
 4  * the code cache.
 5  */
 6 static void handleSigUsr2(void)
 7 {
 8     static int codeCacheResetCount = 0;
 9     if ((--codeCacheResetCount & 7) == 0) {
10         gDvmJit.codeCacheFull = true;
11     } else {
12         dvmCompilerDumpStats();
13         /* Stress-test unchain all */
14         dvmJitUnchainAll();
15         LOGD("Send %d more signals to rest the code cache",
16              codeCacheResetCount & 7);
17     }
18 }
19 #endif

  由于以上两个信号都仅用于DVM的内部实现的调试,本文不作详细的分析。读者可以在终端通过adb发送 SIGUSR1 和SIGUSR2信号来观察它的行为。

  (3).  SIGQUIT 用来 输出trace文件,以记录异常终止是dvm的上下文信息.

  SIGQUIT的处理函数如下所示:

 1 static void handleSigQuit(void)
 2 {   ...
 3     dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP);
 4
 5     if (gDvm.stackTraceFile == NULL) {
 6         /* just dump to log */
 7         DebugOutputTarget target;
 8         dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG);
 9         dvmDumpAllThreadsEx(&target, true);
10     } else {
11         /* write to memory buffer */
12         FILE* memfp = open_memstream(&traceBuf, &traceLen);
13         if (memfp == NULL) {
14             LOGE("Unable to create memstream for stack traces\n");
15             traceBuf = NULL;        /* make sure it didn‘t touch this */
16             /* continue on */
17         } else {
18             logThreadStacks(memfp);
19             fclose(memfp);
20         }
21     }
22
23 #if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
24     dvmCompilerDumpStats();
25 #endif
26
27     dvmResumeAllThreads(SUSPEND_FOR_STACK_DUMP);
28
29     if (traceBuf != NULL) {
30         int fd = open(gDvm.stackTraceFile, O_WRONLY | O_APPEND | O_CREAT, 0666);
31         if (fd < 0) {
32             LOGE("Unable to open stack trace file ‘%s‘: %s\n",
33                 gDvm.stackTraceFile, strerror(errno));
34         } else {
35         ...
36         }
37     ...
38 }

  它首先查看有木有指定 trace输出文件,没有就将trace信息打印到log里。如果有,就先将trace信息打印到内存文件中,然后再讲改内存文件内容输出到指定trace文件中。

  有些读者肯能觉得奇怪,为什么指定了trace文件后,不直接打印trace信息到trace文件中呢。 原因是 trace文件实际上记录的是当前运行的所有的线程的上下文信息。他需要 暂停所有的线程才能输出。 dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP);的调用正式这个目的。可以看出,这个操作代价是很高的,它把当前所有的线程都停了下来。执行的时间越短,对正常运行的线程的影响越小。 输出信息到内存比直接到外部文件要快得多。所以 dvm采取了先输出到内存,马上恢复线程程,然后就可以慢慢的输出到外部文件里了。

  而这真正的输出信息实现在 logThreadStacks()中:

 1 static void logThreadStacks(FILE* fp)
 2 {
 3     dvmPrintDebugMessage(&target,
 4         "\n\n----- pid %d at %04d-%02d-%02d %02d:%02d:%02d -----\n",
 5         pid, ptm->tm_year + 1900, ptm->tm_mon+1, ptm->tm_mday,
 6         ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
 7     printProcessName(&target);
 8     dvmPrintDebugMessage(&target, "\n");
 9     dvmDumpAllThreadsEx(&target, true);
10     fprintf(fp, "----- end %d -----\n", pid);
11 }

  该函数打印了trace文件的框架,其输出类似如下所示:

1 ----- pid 503 at 2011-11-21 21:59:12 -----
2 Cmd line: com.android.phone
3
4 <Thread_info>
5
6 ----- end 503 -----

  它显示当前dvm进程的进程id,名字,输出的时间。最重要的所有线程的上下文信息是有函数 dvmDumpAllThreadsEx()里实现的,该函数定义在 thread.c里:

 1 void dvmDumpAllThreadsEx(const DebugOutputTarget* target, bool grabLock)
 2 {
 3     Thread* thread;
 4
 5     dvmPrintDebugMessage(target, "DALVIK THREADS:\n");
 6
 7 #ifdef HAVE_ANDROID_OS
 8     dvmPrintDebugMessage(target,
 9         "(mutexes: tll=%x tsl=%x tscl=%x ghl=%x hwl=%x hwll=%x)\n",
10         gDvm.threadListLock.value,
11         gDvm._threadSuspendLock.value,
12         gDvm.threadSuspendCountLock.value,
13         gDvm.gcHeapLock.value,
14         gDvm.heapWorkerLock.value,
15         gDvm.heapWorkerListLock.value);
16 #endif
17
18     if (grabLock)
19         dvmLockThreadList(dvmThreadSelf());
20
21     thread = gDvm.threadList;
22     while (thread != NULL) {
23         dvmDumpThreadEx(target, thread, false);
24
25         /* verify link */
26         assert(thread->next == NULL || thread->next->prev == thread);
27
28         thread = thread->next;
29     }
30
31     if (grabLock)
32         dvmUnlockThreadList();
33 }

  它的输出格式如下:

 1 DALVIK THREADS:
 2 (mutexes: tll=0 tsl=0 tscl=0 ghl=0 hwl=0 hwll=0)
 3 "main" prio=5 tid=1 NATIVE
 4   | group="main" sCount=1 dsCount=0 obj=0x400246a0 self=0x12770
 5   | sysTid=503 nice=0 sched=0/0 cgrp=default handle=-1342909272
 6   | schedstat=( 15165039025 12197235258 23068 ) utm=182 stm=1334 core=0
 7   at android.os.MessageQueue.nativePollOnce(Native Method)
 8   at android.os.MessageQueue.next(MessageQueue.java:119)
 9   at android.os.Looper.loop(Looper.java:122)
10   at android.app.ActivityThread.main(ActivityThread.java:4134)
11   at java.lang.reflect.Method.invokeNative(Native Method)
12   at java.lang.reflect.Method.invoke(Method.java:491)
13   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
14   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
15   at dalvik.system.NativeStart.main(Native Method)

  至此, 我们可以很清楚的 解析 trace文件中 thread信息的含义了:

  1. 第一行是 固定的头, 指明下面的都是 当前运行的 dvm thread :“DALVIK THREADS:”

  2. 第二行输出的是该 进程里各种线程互斥量的值。(具体的互斥量的作用在 dalvik 线程一章 单独陈述)

  3. 第三行输出分别是 线程的名字(“main”),线程优先级(“prio=5”),线程id(“tid=1”) 以及线程的 类型(“NATIVE”)

  4. 第四行分别是线程所述的线程组 (“main”),线程被正常挂起的次处(“sCount=1”),线程因调试而挂起次数(”dsCount=0“),当前线程所关联的java线程对象(”obj=0x400246a0“)以及该线程本身的地址(“self=0x12770”)。

  5. 第五行 显示 线程调度信息。 分别是该线程在linux系统下得本地线程id (“ sysTid=503”),线程的调度有优先级(“nice=0”),调度策略(sched=0/0),优先组属(“cgrp=default”)以及 处理函数地址(“handle=-1342909272”)

  6 第六行 显示更多该线程当前上下文,分别是 调度状态(从 /proc/[pid]/task/[tid]/schedstat读出)(“schedstat=( 15165039025 12197235258 23068 )”),以及该线程运行信息 ,它们是 线程用户态下使用的时间值(单位是jiffies)(“utm=182”), 内核态下得调度时间值(“stm=1334”),以及最后运行改线程的cup标识(“core=0”);

  7.后面几行输出 该线程 调用栈。

  有了以上信息,我们便更容易分析出app是为什么被异常终止的了。

时间: 2024-10-08 23:44:03

Android信号处理的相关文章

Android ANR的产生与分析

ANR即Application Not Responding应用无响应,一般在ANR的时候会弹出一个应用无响应对话框.也许有些开发者在使用某些手机开发中不在弹出应用无响应弹出框,特别是国产手机Android4.0以上的系统中,即使在开发者选项中设置了"显示所有应用无响应-为后台应用显示无响应ANR对话框",主要是因为在某些国产手机系统中就将该选项屏蔽了,应用超过了一定时间无响应也不会弹出ANR对话框了. 一般情况下应用无响应的时候回产生一个日志文件,位于/data/anr/文件夹下面,

Android多媒体开发介绍(转)

Android多媒体开发介绍 转自:http://blog.csdn.net/reiliu/article/details/9060557 一.       多媒体架构 基于第三方PacketVideo公司的OpenCORE来实现,支持所有通用的音频/视频/静态图像格式,包括:MPEG4.H.264.MP3.AAC.AMR.JPG.PNG.GIF等.从功能上分为两部分,一是音/视频的回放(PlayBack),二是音视频的纪录(Recorder). CODEC(编解码器)使用OpenMAX 1L

Android 5.0特性

虽然此前Google对其已经做过了比较深入的介绍,但作为一个如此重要的升级版本,更新内容自然是海量的. Google今天很贴心地在官网上放出了Android 5.0正式版的完整更新日志,涵盖了升级的方方面面,不过说实话也只是一些重要的.大面上的概略更新介绍,还有更多细节等待挖掘. 1.Material Design 醒目的.多彩的.快速响应的UI设计,可为你所有的设备带来一致的直觉体验. - 响应快速.自然流畅的动作,真实的光照和阴影,熟悉的视觉元素,设备导航更加轻而易举. - 惊艳的新色彩,凸

【Android开发经验】移动设备的“声波通信/验证”的实现——SinVoice开源项目介绍(一)

转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 在APP市场上,经常有一些充满新意的应用让我们眼前一亮,比如微信的面对面加好友,支付宝的声波支付等等,都是通过声波的方式进行握手通信,今天这篇文章将介绍声波通信和声波验证的实现原理和代码实现. 首先介绍一下声波验证的原理.如果我们想发出声音,就必须震动,说话是声带在震动,手机能播放音乐是喇叭在震动.既然发出声音必须震动,那么就有震动快慢之分,我们把震动的快慢叫做声音的频率.频率低的声音低沉有力,能传播很远

Android NDK学习笔记(一) 为什么要用NDK?

NDK是什么 NDK是Native Development Kit的简称,即本地开发工具包.通过NDK,Android允许开发人员使用本地代码语言(例如C/C++)来完成应用的部分(甚至全部)功能.注意:由于翻译原因,有些地方也把Native翻译为"原生". NDK是SDK的一个补充,可以帮助你做这些事情: 生成可以在ARM CPU,Android 1.5(及以上)平台运行的JNI兼容的共享库. 将生成的共享库放置在应用程序项目路径的合适位置,使其能自动地添加进你最终的(和经过签名的)

Android编译篇

Android的编译系统涉及面极广,包含编译工具.印像文件编译.SDK编译.NDK编译.目标系统配置等多个方面.尽管这些方面的内容烦琐而晦涩,能够參考的资料不多,可是系统设计尤其是系统架构人员必须熟悉它们. 1.源码编译 基于源码的方式进行开发,一般会依据目标环境的不同,对系统配置进行调整,如採用不同的引导器.特定的驱动.不同的文件系统.特定的属性配置等,这就要求开发人员必须熟练掌握源码的编译方法和配置. (1)映像文件 在编译完源码后.须要将生成的文件等打包成对应的文件系统.然后烧写到移动终端

Android实现双进程守护

做过android开发的人应该都知道应用会在系统资源匮乏的情况下被系统杀死!当后台的应用被系统回收之后,如何重新恢复它呢?网上对此问题有很多的讨论.这里先总结一下网上流传的各种解决方案,看看这些办法是不是真的可行.1.提高优先级这个办法对普通应用而言,应该只是降低了应用被杀死的概率,但是如果真的被系统回收了,还是无法让应用自动重新启动!    2.让service.onStartCommand返回START_STICKY通过实验发现,如果在adb shell当中kill掉进程模拟应用被意外杀死的

保持Service不被Kill掉的方法--双Service守护 &amp;&amp; Android实现双进程守护

本文分为两个部分,第一部分为双Service守护,第二部分为双进程守护 第一部分: 一.Service简介:Java.lang.Object ?Android.content.Context  ?android.content.ContextWrapper  ?android.app.Service Service是应用程序Application的一个组件(component).它的作用有两点:1.用来提供一个长期在后台运行并且不与用户交互的操作,2.也可以为其他应用程序提供服务.Service

Android 启动过程的底层实现

转载请标明出处: http://blog.csdn.net/yujun411522/article/details/46367787 本文出自:[yujun411522的博客] 3.1 android正常模式启动流程 主要流程例如以下: 1.系统加电.运行bootloader,bootloader会将内核载入到内存中. 2.内核载入到内存之后首先进入内核引导阶段,最后调用start_kernel进入内核启动.start_kernel终于会启动init程序 3.init程序负责解析init.rc文