Android Linux 软中断信号处理面面观-android100学习网
Android Linux 软中断信号处理面面观
Android 信号处理面面观首先澄清,本文讨论的信号是 Linux 软中断信号,而不是手机状态条里面用于显示当前手机通信强度的那个信号。 本文是 增量型博客,内容会不断更新,请改话题感兴趣的朋友偶尔可以再回...
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&&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=0x&self=0x12770&&
&&|&sysTid=503&nice=0&sched=0/0&cgrp=default&handle=-&&
&&|&schedstat=(&&&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=(&&)&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=0x&self=0x1ed278&&
&&|&sysTid=909&nice=0&sched=0/0&cgrp=default&handle=2019248&&
&&|&schedstat=(&&)&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=(&&&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.123:eng/test-keys'&&
pid:&181,&tid:&322&&&&&&/system/bin/mediaserver&&&&&&
signal&8&(SIGFPE),&code&0&(?),&fault&addr&&&
&r0&&&r1&&&r2&ffffffff&&r3&&&
&r4&&&r5&&&r6&&&r7&&&
&r8&662f9c00&&r9&662f9c00&&10&&&fp&&&
&ip&aff17699&&sp&4057f9dc&&lr&aff176a7&&pc&aff0c684&&cpsr&&&
&d0&&6f762f6f&&d1&&0000&&
&d2&&0300&&d3&&000000&&
&d4&&0000&&d5&&0000&&
&d6&&3ce449db&&d7&&3e4ccccd3e4ccccd&&
&d8&&c6a8&&d9&&c6a8&&
&d10&0000&&d11&0000&&
&d12&0000&&d13&0000&&
&d14&0000&&d15&0000&&
&d16&0000&&d17&3e582f8f86b6a000&&
&d18&3fe0&&d19&3fec7c3&&
&d20&3fd4&&d21&bebbb&&
&d22&3ff0&&d23&3ff43d135cda918c&&
&d24&3ebea4d0&&d25&0000&&
&d26&0000&&d27&0000&&
&d28&0000&&d29&0000&&
&d30&0000&&d31&0000&&
&&&&&&&&&&pc&&&/system/lib/libc.so&(kill)&&
&&&&&&&&&&pc&&&/system/lib/libc.so&(raise)&&
libc&base&address:&aff00000&&
code&around&pc:&&
aff0c664&ef10&&&&
aff0c674&e12fff1e&e92d50f0&e3a07025&ef000000&&&
aff0c684&e8bd50f0&e1bfff1e&ea00ade7&&&
aff0c694&e92d50f0&e3a070ee&efbd50f0&&&
aff0c6a4&e1bfff1e&ea00ade0&f5d0f000&&&
code&around&lr:&&
affe2e&461cb537&e9cd17dd&f7f34500&&&
aff17694&bd3eef02&&ed5ef7f3&f7f44621&&&
aff176a4&bd10efea&0b510&f7f44802&&&
aff176b4&bd10edf6&&fee1dead&&&&
aff176c4&&ec9cf7f4&bf00bd1c&4c11b570&&&
&&&&4057f99c&&a2b6fd15&&/system/lib/libstagefright.so&&
&&&&&&&&&&
&&&&&&a2b6fe51&&/system/lib/libstagefright.so&&
&&&&&&000fb02c&&&&
&&&&4057f9ac&&a2b6fde7&&/system/lib/libstagefright.so&&
&&&&&&4057fa14&&&&
&&&&&&000fb030&&&&
&&&&&&&&&&
&&&&4057f9bc&&a2b6fe79&&/system/lib/libstagefright.so&&
&&&&&&000fafe0&&&&
&&&&&&&&&&
&&&&&&4057fa14&&&&
&&&&4057f9cc&&a2b6fe59&&/system/lib/libstagefright.so&&
&&&&&&&&&&
&&&&&&a801e509&&/system/lib/libutils.so&&
&&&&&&4057fa14&&&&
#01&4057f9dc&&&&&&
&&&&&&&&&&
&&&&&&&&&&
&&&&&&&&&&
&&&&4057f9ec&&aff17699&&/system/lib/libc.so&&
&&&&&&aff176a7&&/system/lib/libc.so&&
&&&&&&&&&&
&&&&&&aff0e154&&/system/lib/libc.so&&
&&&&4057f9fc&&&&&&
&&&&4057fa00&&aff0cf84&&/system/lib/libc.so&&
&&&&4057fa04&&aff0cf94&&/system/lib/libc.so&&
&&&&000000&&&&
&&&&4057fa0c&&&&&&
&&&&000000&&&&
&&&&4057fa14&&aff0fca4&&/system/lib/libc.so&&
&&&&2f9c00&&&&
&&&&4057fa1c&&&&&&
&&&&000000&&&&
---&---&---&---&---&---&---&---&---&---&---&---&---&---&---&---&&
pid:&181,&tid:&181&&
&r0&fffffe00&&r1&c0186201&&r2&be8b8b98&&r3&be8b8b94&&
&r4&&&r5&&&r6&&&r7&&&
&r8&&&r9&0000f5cc&&10&&&fp&&&
&ip&a812336c&&sp&be8b8b78&&lr&aff25e19&&pc&aff0b680&&cpsr&&&
&d0&&000f&&d1&&b8b00&&
&d2&&0000&&d3&&e0000&&
&d4&&0000&&d5&&0000&&
&d6&&0000&&d7&&a00000&&
&d8&&0000&&d9&&0000&&
&d10&0000&&d11&0000&&
&d12&0000&&d13&0000&&
&d14&0000&&d15&0000&&
&d16&0000&&d17&0000&&
&d18&0000&&d19&3fcce&&
&d20&3fd4&&d21&bebbb&&
&d22&3ff0&&d23&3ff43d135cda918c&&
&d24&3ebea4d0&&d25&0000&&
&d26&0000&&d27&0000&&
&d28&0000&&d29&0000&&
&d30&0000&&d31&0000&&
&&&&&&&&&&pc&&&/system/lib/libc.so&(__ioctl)&&
&&&&&&&&&&pc&00025e16&&/system/lib/libc.so&(ioctl)&&
&&&&&&&&&&pc&&&/system/lib/libbinder.so&(_ZN7android14IPCThreadState14talkWithDriverEb)&&
&&&&&&&&&&pc&00016afc&&/system/lib/libbinder.so&(_ZN7android14IPCThreadState14joinThreadPoolEb)&&
&&&&&&&&&&pc&00008a94&&/system/bin/mediaserver&&
&&&&&&&&&&pc&00014aa0&&/system/lib/libc.so&(__libc_init)&&
libc&base&address:&aff00000&&
code&around&pc:&&
aff0b660&efbd00&512fff1e&&&
aff0b670&ea00b1ef&e92d36&ef000000&&&
aff0b680&e8bd00&512fff1e&ea00b1e8&&&
aff0b690&e92d91&efbd0090&&&
aff0b6a0&e1bfff1e&ea00b1e1&e92d0090&&&
可以看出,它同样包含很多重要的信息(特别是 stack )来帮助我们查找异常的原因。分析tombstone的方法,将单独成篇。
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中如下:
[plain] view plain copy print ?
int&dvmStartup(int&argc,&const&char*&const&argv[],&bool&ignoreUnrecognized,&&
&&&&JNIEnv*&pEnv)&&
&&&&/*&configure&signal&handling&*/&&
&&&&if&(!gDvm.reduceSignals)&&
&&&&&&&&blockSignals();&&
blockSignals()的实现很简答,它是通过 sigprocmask() 函数调用实现的,代码在init.c如下:
[plain] view plain copy print ?
&*&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&&&
&&&&int&&&
&&&&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&&
&&&&//sigaddset(&mask,&SIGPIPE);&&
&&&&cc&=&sigprocmask(SIG_BLOCK,&&mask,&NULL);&&
&&&&assert(cc&==&0);&&
Step #2: DVM 生成单独的信号处理线程,用来对三个信号做特殊处理 (init.c):
[plain] view plain copy print ?
&*&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&&&
dvmSignalCatcherStartup() 实现在 SignalCatcher.c 中:
[plain] view plain copy print ?
&*&Crank&up&the&signal&catcher&thread.&&
&*&Returns&immediately.&&
bool&dvmSignalCatcherStartup(void)&&
&&&&gDvm.haltSignalCatcher&=&&&
&&&&if&(!dvmCreateInternalThread(&gDvm.signalCatcherHandle,&&
&&&&&&&&&&&&&&&&&Signal&Catcher&,&signalCatcherThreadStart,&NULL))&&
&&&&&&&&return&&&
&&&&return&&&
我们看到,DVM调用dvmCreateInternalThread()来生成一个新的内部线程 来专门处理dvm进程里的信号。 后面我们会看到,dvmCreateInternalThread()其实是使用pthread_create()来产生新的线程。 该线程的处理函数是 signalCatcherThreadStart()。& (dvm里所谓的 内部线程,就是用来帮助dvm实现本身使用的线程,比如 信号处理线程,binder线程,Compiler线程,JDWP线程等,而不是应用程序申请的线程。 在后面我们计划用专门的一章来讨论DVM线程模式)
signalCatcherThreadStart() 实现框架如下:
[plain] view plain copy print ?
&*&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);&&
&&&&while&(true)&{&&
&&&&&&&&cc&=&sigwait(&mask,&&rcvd);&&
&&&&&&&&...&&
&&&&&&&&switch&(rcvd)&{&&
&&&&&&&&case&SIGQUIT:&&
&&&&&&&&&&&&handleSigQuit();&&
&&&&&&&&&&&&&&
&&&&&&&&case&SIGUSR1:&&
&&&&&&&&&&&&handleSigUsr1();&&
&&&&&&&&&&&&&&
#if&defined(WITH_JIT)&&&&defined(WITH_JIT_TUNING)&&
&&&&&&&&case&SIGUSR2:&&
&&&&&&&&&&&&handleSigUsr2();&&
&&&&&&&&&&&&&&
&&&&&&&&...&&
它 首先设置我们要处理的信号集(SIGQUIT, SIGUSR1, SIGUSR2), 然后 调用 sigwait()。 我们知道sigwait()会在当前的线程里 重新 打开 指定的信号屏蔽字屏蔽的信号集。& 在刚才的分析中,我们看到,dvm在启动时,首先在整个进程里设置信号屏蔽字屏蔽掉三个信号,sigwait()的调用,使的这三个信号只在 SignalCatcher线程里响应。
至此我们已经能够看到,dvm对三个信号分别所做的特殊用途:
1. SIGUSR1 被用来 做手工垃圾收集。处理函数是 HandleSigUsr1()
[plain] view plain copy print ?
static&void&handleSigUsr1(void)&&
&&&&LOGI(&SIGUSR1&forcing&GC&(no&HPROF)\n&);&&
&&&&dvmCollectGarbage(false);&&
2. SIGUSR2 被用来做 JIT的调试。如果JIT下编译时打开,收到SIGUSR2是dvm会dump出相关的调试信息。处理逻辑如下:
[plain] view plain copy print ?
#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&=&&&
&&&&}&else&{&&
&&&&&&&&dvmCompilerDumpStats();&&
&&&&&&&&/*&Stress-test&unchain&all&*/&&
&&&&&&&&dvmJitUnchainAll();&&
&&&&&&&&LOGD(&Send&%d&more&signals&to&rest&the&code&cache&,&&
&&&&&&&&&&&&&codeCacheResetCount&&&7);&&
由于以上两个信号都仅用于DVM的内部实现的调试,本文不作详细的分析。读者可以在终端通过adb发送 SIGUSR1 和SIGUSR2信号来观察它的行为。
3.& SIGQUIT 用来 输出trace文件,以记录异常终止是dvm的上下文信息.
SIGQUIT的处理函数如下所示:
[plain] view plain copy print ?
static&void&handleSigQuit(void)&&
&&&&dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP);&&
&&&&if&(gDvm.stackTraceFile&==&NULL)&{&&
&&&&&&&&/*&just&dump&to&log&*/&&
&&&&&&&&DebugOutputTarget&&&
&&&&&&&&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();&&
&&&&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()中:
[plain] view plain copy print ?
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);&&
该函数打印了trace文件的框架,其输出类似如下所示:
[plain] view plain copy print ?
-----&pid&503&at&&21:59:12&-----&&
Cmd&line:&com.android.phone&&
&Thread_info&&&
-----&end&503&-----&&
它显示当前dvm进程的进程id,名字,输出的时间。最重要的所有线程的上下文信息是有函数 dvmDumpAllThreadsEx()里实现的,该函数定义在 thread.c里:
[plain] view plain copy print ?
void&dvmDumpAllThreadsEx(const&DebugOutputTarget*&target,&bool&grabLock)&&
&&&&Thread*&&&
&&&&dvmPrintDebugMessage(target,&&DALVIK&THREADS:\n&);&&
#ifdef&H***E_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);&&
&&&&if&(grabLock)&&
&&&&&&&&dvmLockThreadList(dvmThreadSelf());&&
&&&&thread&=&gDvm.threadL&&
&&&&while&(thread&!=&NULL)&{&&
&&&&&&&&dvmDumpThreadEx(target,&thread,&false);&&
&&&&&&&&/*&verify&link&*/&&
&&&&&&&&assert(thread-&next&==&NULL&||&thread-&next-&prev&==&thread);&&
&&&&&&&&thread&=&thread-&&&
&&&&if&(grabLock)&&
&&&&&&&&dvmUnlockThreadList();&&
它的输出格式如下:
[plain] view plain copy print ?
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=0x&self=0x12770&&
&&|&sysTid=503&nice=0&sched=0/0&cgrp=default&handle=-&&
&&|&schedstat=(&&&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=0x&)以及该线程本身的地址(&self=0x12770&)。
5. 第五行 显示 线程调度信息。 分别是该线程在linux系统下得本地线程id (&&sysTid=503&),线程的调度有优先级(&nice=0&),调度策略(sched=0/0),优先组属(&cgrp=default&) 以及 处理函数地址(&handle=-&)
6 第六行 显示更多该线程当前上下文,分别是 调度状态(从 /proc/[pid]/task/[tid]/schedstat读出)(&schedstat=(
23068 )&),以及该线程运行信息 ,它们是 线程用户态下使用的时间值(单位是jiffies)(&utm=182&), 内核态下得调度时间值(&stm=1334&),以及最后运行改线程的cup标识(&core=0&);
7.后面几行输出 该线程 调用栈。
有了以上信息,我们便更容易分析出app是为什么被异常终止的了。我们会在单独的一章分析, 怎样利用trace文件里的信息寻找app异常终止的原因。敬请期待。