Android 信号处理面面观
首先澄清,本文讨论的信号是 Linux 软中断信号,而不是手机状态条里面用于显示当前手机通信强度的那个信号。 本文是 增量型博客,内容会不断更新,请改话题感兴趣的朋友偶尔可以再回头来看看更新的内容。
我们知道,Unix系统里信号是一种软中断。尽管本身存在缺陷(后面会讨论到),但是作为Unix系统重要的异步事件处理方式之一,在Unix系统 中发挥重要的作用。可以说,所有Unix系统(包括Linux)都不可能忽略信号的支持。 Android 本质上也是个在 Linux 系统,自然也少不了对 信号处理的支持。
但我们也知道,Android和其他Linux系统一个很大的差异就是增加了虚拟机的支持(Dalvik vm),所有的应用程序都会在虚拟机实例里运行 (当然,虚拟机实例还是在传统的进程里运行)。为了更好的支持应用程序的开发和调试,Android对信号的处理增加了额外的逻辑(下面会详细讨论到)。 这也使得 Android系统中信号的处理行为和传统的Linux系统有所区别。 很多朋友,在开始接触 Android信号处理时,可能也会碰到一些困惑。尽管其实答案很简答, 但是网上这方面的资料少,还是需要花不少时间去摸透。 本文写作的目的就是试图 将 android信号处理的方方面面呈现给大家。 让大家在最短的时间内掌握android信号处理的知识,并学以致用。
虽然,android 信号处理并不复杂,但是如果用一篇文章还真难以描述清楚而又有条理。所以对信号处理打算分几部分讨论:
1. 概述 就是本文。 简述 Android 系统对信号处理的概貌,并说明测试环境。
2. 信号产生。讨论 android信号的产生原因以及最简单的测试方法。
3. 信号处理。 讨论Android系统对传统的信号处理的扩展是怎么实现的。
4. 应用扩展。 讨论在实际开发中,怎样利用android信号机制为我们服务。
请读者先看看自己对 Android信号处理的了解程度再决定是否需要 花时间关注该系列的博文。以下是几个相关的问题:
1. Android信号处理比起传统的linux系统(如 Ubuntu)有什么区别么?
2. Android信号来源于哪里?怎样用最简单的方式产生信号,并测试信号处理的行为。?怎样才能最快的分析有信号处理产生的问题?
3. Android是如何实现对传统信号处理的扩展。
4. 我们日常开发中,Android信号处理机制可以帮助我们处理那类问题?
如果你能回答全部问题,恭喜你。你对Android信号处理的理解别我深(请留下联系方式,有事我好请教你)。如果有些问题你不能回答,这个系列的文章正是帮你补充这方面的知识。
本文的测试环境是:
模拟器: Ubuntu 11.04 运行 最新的Android 4.0.1 模拟器 (搭配相关的环境请看 Android4.0.1 源码下载,模拟器编译和运行 一文)
手机: Droid3 with android 2.3.6
平板: Xoom2 with android 3.2
除非有行为上得差异,否则所有测试结果都将出自 模拟器(方便,呵呵)
对了,上面提到的传统信号处理模型的缺陷,主要是有如下几个点:
1. 难以扩展。 这可能是历史原因造成的,早期信号处理模型中,为了效率和方便,大多使用整型位码来表示某一信号。而总数控制在32位之内。大多数已经有明确的含义,而大多只提供 SIGUSR1 和 SIGUSR2供用户使用。
2. 某些情境下的行为不可靠。比如相同的信号连续到达后,大多只作为一个信号处理。也就是说你只能知道该信号是否到达,而不能确定到达了一个还是是个。
对于,缺陷1,似乎没有什么好办法(总不能另外造出信号类型啊)。而可行的方法就是重用这两个信号。
对于缺陷2,如果需要连续产生相同的信号而又要处理,可以在期间加入延迟。后面会看到 Android就是这么做的。
如果有问题想讨论或纠正我的错误,请留言。我会尽快回复,或者直接在正文中增加改话题的讨论。谢谢。
传统 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)中:
- /* Signals. */
- #define SIGHUP 1 /* Hangup (POSIX). */
- #define SIGINT 2 /* Interrupt (ANSI). */
- #define SIGQUIT 3 /* Quit (POSIX). */
- #define SIGILL 4 /* Illegal instruction (ANSI). */
- #define SIGTRAP 5 /* Trace trap (POSIX). */
- #define SIGABRT 6 /* Abort (ANSI). */
- #define SIGIOT 6 /* IOT trap (4.2 BSD). */
- #define SIGBUS 7 /* BUS error (4.2 BSD). */
- #define SIGFPE 8 /* Floating-point exception (ANSI). */
- #define SIGKILL 9 /* Kill, unblockable (POSIX). */
- #define SIGUSR1 10 /* User-defined signal 1 (POSIX). */
- #define SIGSEGV 11 /* Segmentation violation (ANSI). */
- #define SIGUSR2 12 /* User-defined signal 2 (POSIX). */
- #define SIGPIPE 13 /* Broken pipe (POSIX). */
- #define SIGALRM 14 /* Alarm clock (POSIX). */
- #define SIGTERM 15 /* Termination (ANSI). */
- #define SIGSTKFLT 16 /* Stack fault. */
- #define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
- #define SIGCHLD 17 /* Child status has changed (POSIX). */
- #define SIGCONT 18 /* Continue (POSIX). */
- #define SIGSTOP 19 /* Stop, unblockable (POSIX). */
- #define SIGTSTP 20 /* Keyboard stop (POSIX). */
- #define SIGTTIN 21 /* Background read from tty (POSIX). */
- #define SIGTTOU 22 /* Background write to tty (POSIX). */
- #define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */
- #define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */
- #define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */
- #define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
- #define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */
- #define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
- #define SIGPOLL SIGIO /* Pollable event occurred (System V). */
- #define SIGIO 29 /* I/O now possible (4.2 BSD). */
- #define SIGPWR 30 /* Power failure restart (System V). */
- #define SIGSYS 31 /* Bad system call. */
- #define SIGUNUSED 31
我们知道,信号处理的方式一般有三种:
1. 忽略 接收到信号后不做任何反应。
2. 自定义 用自定义的信号处理函数来执行特定的动作
3. 默认 接收到信号后按默认得行为处理该信号。 这是多数应用采取的处理方式。
而 传统 UNIX系统对以上信号的默认处理如下图所示 (来自 APUT ):
Android 系统 信号处理的行为
我们知道,信号处理的行为是以进程级的。就是说不同的进程可以分别设置不同的信号处理方式而互不干扰。同一进程中的不同线程虽然可以设置不同的信号屏蔽字,但是却共享相同的信号处理方式 (也就是说 在一个线程里改变信号处理方式,将作用于该进程中的所有线程)。
Android也是Linux系统。所以其信号处理方式不会有本质的改变。但是为了开发和调试的需要,android对一些信号的处理定义了额外的行为。 下面是这些典型的信号在Android系统上的行为:
1. SIGQUIT ( 整型值为 3)
上面的表10-1显示,传统UNIX系统应用,对SIGQUIT信号的默认行为是 "终止 + CORE"。也就是产生core dump文件后,立即终于运行。
Android Dalvik应用收到该信号后,会 打印改应用中所有线程的当前状态,并且并不是强制退出。这些状态通常保存在一个特定的叫做trace的文件中。一般的路径是/data/anr/trace.txt. 下面是一个典型的trace文件的内容:
- ----- pid 503 at 2011-11-21 21:59:12 -----
- Cmd line: com.android.phone
- DALVIK THREADS:
- (mutexes: tll=0 tsl=0 tscl=0 ghl=0 hwl=0 hwll=0)
- "main" prio=5 tid=1 NATIVE
- | group="main" sCount=1 dsCount=0 obj=0x400246a0 self=0x12770
- | sysTid=503 nice=0 sched=0/0 cgrp=default handle=-1342909272
- | schedstat=( 15165039025 12197235258 23068 ) utm=182 stm=1334 core=0
- at android.os.MessageQueue.nativePollOnce(Native Method)
- at android.os.MessageQueue.next(MessageQueue.java:119)
- at android.os.Looper.loop(Looper.java:122)
- at android.app.ActivityThread.main(ActivityThread.java:4134)
- at java.lang.reflect.Method.invokeNative(Native Method)
- at java.lang.reflect.Method.invoke(Method.java:491)
- at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
- at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
- at dalvik.system.NativeStart.main(Native Method)
- "Thread-29" prio=5 tid=24 WAIT
- | group="main" sCount=1 dsCount=0 obj=0x406f0d50 self=0x208c18
- | sysTid=1095 nice=0 sched=0/0 cgrp=default handle=2133304
- | schedstat=( 9521483 7029937750 720 ) utm=0 stm=0 core=0
- at java.lang.Object.wait(Native Method)
- - waiting on <0x406f0d50> (a com.motorola.android.telephony.cdma.OemCdmaTelephonyManager$Watchdog)
- at java.lang.Object.wait(Object.java:361)
- at com.motorola.android.telephony.cdma.OemCdmaTelephonyManager$Watchdog.run(OemCdmaTelephonyManager.java:229)
- "FileObserver" prio=5 tid=23 NATIVE
- | group="main" sCount=1 dsCount=0 obj=0x4068b2f8 self=0x1ed278
- | sysTid=909 nice=0 sched=0/0 cgrp=default handle=2019248
- | schedstat=( 11810291 7018493670 720 ) utm=0 stm=0 core=0
- at android.os.FileObserver$ObserverThread.observe(Native Method)
- at android.os.FileObserver$ObserverThread.run(FileObserver.java:88)
- "android.hardware.SensorManager$SensorThread" prio=5 tid=22 NATIVE
- | group="main" sCount=1 dsCount=0 obj=0x406bbd90 self=0x1b2ec0
- | sysTid=869 nice=-8 sched=0/0 cgrp=default handle=1974064
- | schedstat=( 3014251483 8295989933 15621 ) utm=171 stm=128 core=0
- at android.hardware.SensorManager.sensors_data_poll(Native Method)
- at android.hardware.SensorManager$SensorThread$SensorThreadRunnable.run(SensorManager.java:498)
- at java.lang.Thread.run(Thread.java:1020)
- ...
2. 对于很多其他的异常信号 (SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV, SIGSTKFLT ), Android进程 在退出前,会生成 tombstone文件。记录该进程退出前的轨迹。一个典型的tombstone文件内容如下:
- *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
- Build fingerprint: 'verizon/pasteur/pasteur:3.2.2/1.6.0_241/eng.drmn68.20111115.094123:eng/test-keys'
- pid: 181, tid: 322 >>> /system/bin/mediaserver <<<
- signal 8 (SIGFPE), code 0 (?), fault addr 000000b5
- r0 00000000 r1 00000008 r2 ffffffff r3 00000020
- r4 00000008 r5 00000000 r6 000000a5 r7 00000025
- r8 662f9c00 r9 662f9c00 10 00000001 fp 00000000
- ip aff17699 sp 4057f9dc lr aff176a7 pc aff0c684 cpsr 00000010
- d0 6f762f6f69647502 d1 0000562202000000
- d2 0000000400000300 d3 400120dc00000000
- d4 0000000000000000 d5 0000000000000000
- d6 3ce449db86666666 d7 3e4ccccd3e4ccccd
- d8 000000000035c6a8 d9 000000000035c6a8
- d10 0000000000000000 d11 0000000000000000
- d12 0000000000000000 d13 0000000000000000
- d14 0000000000000000 d15 0000000000000000
- d16 0000000000000000 d17 3e582f8f86b6a000
- d18 3fe0000000000000 d19 3fe000000c17c7c3
- d20 3f11504c292739d4 d21 bebbb371092382c4
- d22 3ff0000000000000 d23 3ff43d135cda918c
- d24 3e66376972bea4d0 d25 0000000000000000
- d26 0000000000000000 d27 0000000000000000
- d28 0000000000000000 d29 0000000000000000
- d30 0000000000000000 d31 0000000000000000
- scr 20000010
- #00 pc 0000c684 /system/lib/libc.so (kill)
- #01 pc 000176a4 /system/lib/libc.so (raise)
- libc base address: aff00000
- code around pc:
- aff0c664 e2601000 e0100001 116f0f10 12600020
- aff0c674 e12fff1e e92d50f0 e3a07025 ef000000
- aff0c684 e8bd50f0 e1b00000 512fff1e ea00ade7
- aff0c694 e92d50f0 e3a070ee ef000000 e8bd50f0
- aff0c6a4 e1b00000 512fff1e ea00ade0 f5d0f000
- code around lr:
- aff17684 00029e2e 461cb537 e9cd17dd f7f34500
- aff17694 bd3eef02 4604b510 ed5ef7f3 f7f44621
- aff176a4 bd10efea 49034602 2300b510 f7f44802
- aff176b4 bd10edf6 28121969 fee1dead 2400b513
- aff176c4 94019400 ec9cf7f4 bf00bd1c 4c11b570
- stack:
- 4057f99c a2b6fd15 /system/lib/libstagefright.so
- 4057f9a0 00000000
- 4057f9a4 a2b6fe51 /system/lib/libstagefright.so
- 4057f9a8 000fb02c
- 4057f9ac a2b6fde7 /system/lib/libstagefright.so
- 4057f9b0 4057fa14
- 4057f9b4 000fb030
- 4057f9b8 00000000
- 4057f9bc a2b6fe79 /system/lib/libstagefright.so
- 4057f9c0 000fafe0
- 4057f9c4 00000000
- 4057f9c8 4057fa14
- 4057f9cc a2b6fe59 /system/lib/libstagefright.so
- 4057f9d0 00000001
- 4057f9d4 a801e509 /system/lib/libutils.so
- 4057f9d8 4057fa14
- #01 4057f9dc 00000008
- 4057f9e0 00000000
- 4057f9e4 000000a5
- 4057f9e8 00000000
- 4057f9ec aff17699 /system/lib/libc.so
- 4057f9f0 aff176a7 /system/lib/libc.so
- 4057f9f4 00000000
- 4057f9f8 aff0e154 /system/lib/libc.so
- 4057f9fc 00000000
- 4057fa00 aff0cf84 /system/lib/libc.so
- 4057fa04 aff0cf94 /system/lib/libc.so
- 4057fa08 00000000
- 4057fa0c 000000a5
- 4057fa10 00000000
- 4057fa14 aff0fca4 /system/lib/libc.so
- 4057fa18 662f9c00
- 4057fa1c 000000a5
- 4057fa20 00000000
- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
- pid: 181, tid: 181
- r0 fffffe00 r1 c0186201 r2 be8b8b98 r3 be8b8b94
- r4 0000f5e0 r5 0000f5b0 r6 0000f610 r7 00000036
- r8 00000001 r9 0000f5cc 10 0000f5b8 fp 00000000
- ip a812336c sp be8b8b78 lr aff25e19 pc aff0b680 cpsr 80000010
- d0 000f891000000000 d1 00000004be8b8b00
- d2 0069006400650000 d3 00410049002e0000
- d4 0000000000000000 d5 0000000000000000
- d6 4208000041880000 d7 0000000041a00000
- d8 0000000000000000 d9 0000000000000000
- d10 0000000000000000 d11 0000000000000000
- d12 0000000000000000 d13 0000000000000000
- d14 0000000000000000 d15 0000000000000000
- d16 0000000000000000 d17 0000000000000000
- d18 4000000000000000 d19 3fcce7359d4792d9
- d20 3f11504c292739d4 d21 bebbb371092382c4
- d22 3ff0000000000000 d23 3ff43d135cda918c
- d24 3e66376972bea4d0 d25 0000000000000000
- d26 0000000000000000 d27 0000000000000000
- d28 0000000000000000 d29 0000000000000000
- d30 0000000000000000 d31 0000000000000000
- scr 60000010
- #00 pc 0000b680 /system/lib/libc.so (__ioctl)
- #01 pc 00025e16 /system/lib/libc.so (ioctl)
- #02 pc 00016202 /system/lib/libbinder.so (_ZN7android14IPCThreadState14talkWithDriverEb)
- #03 pc 00016afc /system/lib/libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb)
- #04 pc 00008a94 /system/bin/mediaserver
- #05 pc 00014aa0 /system/lib/libc.so (__libc_init)
- libc base address: aff00000
- code around pc:
- aff0b660 ef000000 e8bd0090 e1b00000 512fff1e
- aff0b670 ea00b1ef e92d0090 e3a07036 ef000000
- aff0b680 e8bd0090 e1b00000 512fff1e ea00b1e8
- aff0b690 e92d0090 e3a07091 ef000000 e8bd0090
- aff0b6a0 e1b00000 512fff1e ea00b1e1 e92d0090
- ...
Android信号的产生和测试
我们看到,多数signal的产生是由于某种内部错误。我们在在开发过程中,当然也可以通过系统调用故意生成signal给某进程。主要的方法如果:
1. 在kernel里 使用 kill_proc_info()
2. 在native应用中 使用 kill() 或者raise()
3. java 应用中使用 Procees.sendSignal()等
但是在测试中,最简单的方法某过于通过 adb 工具了。一个典型场景是:
- adb root
- adb shell ps
- 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就这么几个,他们的值应该记住的。
在前一章Android 信号处理面面观 之 信号定义、行为和来源 中,我们讨论过,Android 应用在收到异常终止信号(SIGQUIT)时,没有遵循传统 UNIX信号模型的默认行为 (终止 + core )。而是打印出trace 文件来,以利于记录应用异常终止的原因。 本文就重点分析 trace 文件是怎么产生的,并详细解释trace文件的各个字段的含义。
一. TRACE 文件的产生
Trace文件是 android davik 虚拟机在收到异常终止信号 (SIGQUIT)时产生的。 最经常的触发条件是 android应用中产生了 FC (force close)。由于是该文件的产生是在 DVM里,所以只有运行 dvm实例的进程(如普通的java应用,java服务等)才会产生该文件,android 本地应用 (native app,指 运行在 android lib层,用c/c++编写的linux应用、库、服务等)在收到 SIGQUIT时是不会产生 trace文件的。
如上文Android 信号处理面面观 之 信号定义、行为和来源所述,我们可以在终端通过adb发送SIGQUIT给应用来生成trace文件。
二. 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中如下:
- int dvmStartup(int argc, const char* const argv[], bool ignoreUnrecognized,
- JNIEnv* pEnv)
- {
- ...
- /* configure signal handling */
- if (!gDvm.reduceSignals)
- blockSignals();
- ...
- }
blockSignals()的实现很简答,它是通过 sigprocmask() 函数调用实现的,代码在init.c如下:
- /*
- * Configure signals. We need to block SIGQUIT so that the signal only
- * reaches the dump-stack-trace thread.
- *
- * This can be disabled with the "-Xrs" flag.
- */
- static void blockSignals()
- {
- sigset_t mask;
- int cc;
- sigemptyset(&mask);
- sigaddset(&mask, SIGQUIT);
- sigaddset(&mask, SIGUSR1); // used to initiate heap dump
- #if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
- sigaddset(&mask, SIGUSR2); // used to investigate JIT internals
- #endif
- //sigaddset(&mask, SIGPIPE);
- cc = sigprocmask(SIG_BLOCK, &mask, NULL);
- assert(cc == 0);
- }
Step #2: DVM 生成单独的信号处理线程,用来对三个信号做特殊处理 (init.c):
- /*
- * Do non-zygote-mode initialization. This is done during VM init for
- * standard startup, or after a "zygote fork" when creating a new process.
- */
- bool dvmInitAfterZygote(void)
- {
- ...
- /* start signal catcher thread that dumps stacks on SIGQUIT */
- if (!gDvm.reduceSignals && !gDvm.noQuitHandler) {
- if (!dvmSignalCatcherStartup())
- return false;
- }
- ...
- }
- /*
- * Crank up the signal catcher thread.
- *
- * Returns immediately.
- */
- bool dvmSignalCatcherStartup(void)
- {
- gDvm.haltSignalCatcher = false;
- if (!dvmCreateInternalThread(&gDvm.signalCatcherHandle,
- "Signal Catcher", signalCatcherThreadStart, NULL))
- return false;
- return true;
- }
我们看到,DVM调用dvmCreateInternalThread()来生成一个新的内部线程 来专门处理dvm进程里的信号。 后面我们会看到,dvmCreateInternalThread()其实是使用pthread_create()来产生新的线程。 该线程的处理函数是 signalCatcherThreadStart()。 (dvm里所谓的 内部线程,就是用来帮助dvm实现本身使用的线程,比如 信号处理线程,binder线程,Compiler线程,JDWP线程等,而不是应用程序申请的线程。 在后面我们计划用专门的一章来讨论DVM线程模式)
signalCatcherThreadStart() 实现框架如下:
- /*
- * Sleep in sigwait() until a signal arrives.
- */
- static void* signalCatcherThreadStart(void* arg)
- {
- ...
- /* set up mask with signals we want to handle */
- sigemptyset(&mask);
- sigaddset(&mask, SIGQUIT);
- sigaddset(&mask, SIGUSR1);
- #if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
- sigaddset(&mask, SIGUSR2);
- #endif
- ...
- while (true) {
- ...
- loop:
- cc = sigwait(&mask, &rcvd);
- ...
- switch (rcvd) {
- case SIGQUIT:
- handleSigQuit();
- break;
- case SIGUSR1:
- handleSigUsr1();
- break;
- #if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
- case SIGUSR2:
- handleSigUsr2();
- break;
- #endif
- ...
- }
至此我们已经能够看到,dvm对三个信号分别所做的特殊用途:
1. SIGUSR1 被用来 做手工垃圾收集。处理函数是 HandleSigUsr1()
- static void handleSigUsr1(void)
- {
- LOGI("SIGUSR1 forcing GC (no HPROF)\n");
- dvmCollectGarbage(false);
- }
2. SIGUSR2 被用来做 JIT的调试。如果JIT下编译时打开,收到SIGUSR2是dvm会dump出相关的调试信息。处理逻辑如下:
- #if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
- /*
- * Respond to a SIGUSR2 by dumping some JIT stats and possibly resetting
- * the code cache.
- */
- static void handleSigUsr2(void)
- {
- static int codeCacheResetCount = 0;
- if ((--codeCacheResetCount & 7) == 0) {
- gDvmJit.codeCacheFull = true;
- } else {
- dvmCompilerDumpStats();
- /* Stress-test unchain all */
- dvmJitUnchainAll();
- LOGD("Send %d more signals to rest the code cache",
- codeCacheResetCount & 7);
- }
- }
- #endif
由于以上两个信号都仅用于DVM的内部实现的调试,本文不作详细的分析。读者可以在终端通过adb发送 SIGUSR1 和SIGUSR2信号来观察它的行为。
3. SIGQUIT 用来 输出trace文件,以记录异常终止是dvm的上下文信息.
SIGQUIT的处理函数如下所示:
- static void handleSigQuit(void)
- { ...
- dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP);
- if (gDvm.stackTraceFile == NULL) {
- /* just dump to log */
- DebugOutputTarget target;
- dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG);
- dvmDumpAllThreadsEx(&target, true);
- } else {
- /* write to memory buffer */
- FILE* memfp = open_memstream(&traceBuf, &traceLen);
- if (memfp == NULL) {
- LOGE("Unable to create memstream for stack traces\n");
- traceBuf = NULL; /* make sure it didn't touch this */
- /* continue on */
- } else {
- logThreadStacks(memfp);
- fclose(memfp);
- }
- }
- #if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
- dvmCompilerDumpStats();
- #endif
- dvmResumeAllThreads(SUSPEND_FOR_STACK_DUMP);
- if (traceBuf != NULL) {
- int fd = open(gDvm.stackTraceFile, O_WRONLY | O_APPEND | O_CREAT, 0666);
- if (fd < 0) {
- LOGE("Unable to open stack trace file '%s': %s\n",
- gDvm.stackTraceFile, strerror(errno));
- } else {
- ...
- }
- ...
- }
它首先查看有木有指定 trace输出文件,没有就将trace信息打印到log里。如果有,就先将trace信息打印到内存文件中,然后再讲改内存文件内容输出到指定trace文件中。
有些读者肯能觉得奇怪,为什么指定了trace文件后,不直接打印trace信息到trace文件中呢。 原因是 trace文件实际上记录的是当前运行的所有的线程的上下文信息。他需要 暂停所有的线程才能输出。 dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP);的调用正式这个目的。可以看出,这个操作代价是很高的,它 把当前所有的线程都停了下来。执行的时间越短,对正常运行的线程的影响越小。 输出信息到内存比直接到外部文件要快得多。所以 dvm采取了先输出到内存,马上恢复线程程,然后就可以慢慢的输出到外部文件里了。
而这真正的输出信息实现在 logThreadStacks()中:
- static void logThreadStacks(FILE* fp)
- {
- dvmPrintDebugMessage(&target,
- "\n\n----- pid %d at %04d-%02d-%02d %02d:%02d:%02d -----\n",
- pid, ptm->tm_year + 1900, ptm->tm_mon+1, ptm->tm_mday,
- ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
- printProcessName(&target);
- dvmPrintDebugMessage(&target, "\n");
- dvmDumpAllThreadsEx(&target, true);
- fprintf(fp, "----- end %d -----\n", pid);
- }
- ----- pid 503 at 2011-11-21 21:59:12 -----
- Cmd line: com.android.phone
- <Thread_info>
- ----- end 503 -----
- void dvmDumpAllThreadsEx(const DebugOutputTarget* target, bool grabLock)
- {
- Thread* thread;
- dvmPrintDebugMessage(target, "DALVIK THREADS:\n");
- #ifdef HAVE_ANDROID_OS
- dvmPrintDebugMessage(target,
- "(mutexes: tll=%x tsl=%x tscl=%x ghl=%x hwl=%x hwll=%x)\n",
- gDvm.threadListLock.value,
- gDvm._threadSuspendLock.value,
- gDvm.threadSuspendCountLock.value,
- gDvm.gcHeapLock.value,
- gDvm.heapWorkerLock.value,
- gDvm.heapWorkerListLock.value);
- #endif
- if (grabLock)
- dvmLockThreadList(dvmThreadSelf());
- thread = gDvm.threadList;
- while (thread != NULL) {
- dvmDumpThreadEx(target, thread, false);
- /* verify link */
- assert(thread->next == NULL || thread->next->prev == thread);
- thread = thread->next;
- }
- if (grabLock)
- dvmUnlockThreadList();
- }
它的输出格式如下:
- DALVIK THREADS:
- (mutexes: tll=0 tsl=0 tscl=0 ghl=0 hwl=0 hwll=0)
- "main" prio=5 tid=1 NATIVE
- | group="main" sCount=1 dsCount=0 obj=0x400246a0 self=0x12770
- | sysTid=503 nice=0 sched=0/0 cgrp=default handle=-1342909272
- | schedstat=( 15165039025 12197235258 23068 ) utm=182 stm=1334 core=0
- at android.os.MessageQueue.nativePollOnce(Native Method)
- at android.os.MessageQueue.next(MessageQueue.java:119)
- at android.os.Looper.loop(Looper.java:122)
- at android.app.ActivityThread.main(ActivityThread.java:4134)
- at java.lang.reflect.Method.invokeNative(Native Method)
- at java.lang.reflect.Method.invoke(Method.java:491)
- at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
- at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
- 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.后面几行输出 该线程 调用栈。