Android中的软件Watchdog

由于Android的SystemServer内有一票重要Service,所以在进程内有一个软件实现的Watchdog机制,用于监视SystemServer中各Service是否正常工作。如果超过一定时间(默认30秒),就dump现场便于分析,再超时(默认60秒)就重启SystemServer保证系统可用性。同时logcat中会打印类似下面信息:

W Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: Blocked in monitor com.android.server.am.ActivityManagerService on foreground thread (android.fg), Blocked in handler on ActivityManager (ActivityManager), Blocked in handler on WindowManager thread (WindowManager)

主要实现代码位于/frameworks/base/services/core/java/com/android/server/Watchdog.java和/frameworks/base/core/jni/android_server_Watchdog.cpp。大体的框架很简单。Watchdog是SystemServer中的独立线程,它隔一定时间间隔会向各监视线程调度一次检查操作。这个检查操作当中会调用已注册的Monitor对象。如果Monitor对象上产生死锁,或是关键线程hang住,那么该检查必定不能按时结束,这样就被Watchdog检查到。

先来看看总体类图。因为是唯一的,Watchdog实现为Singleton。其中维护HandlerChecker数组,对应要检查的线程。HandlerChecker数组中有Monitor数组,对应要检查的Monitor对象。要接受检查的对象需要实现Monitor接口。

初始化是从SystemServer的startOtherServices()中开始的,其大体流程如下:

首先,在SystemServer.java中,会创建Watchdog并启动。

472            Slog.i(TAG, "Init Watchdog");
473            final Watchdog watchdog = Watchdog.getInstance();
474            watchdog.init(context, mActivityManagerService);
…
1120                Watchdog.getInstance().start();

在Watchdog的构造函数中,会为每个要检查的线程创建HandlerChecker,并加到mHandlerCheckers这个队列中。首先是FgThread。它继承自ServiceThread,是一个单例,负责那些常规的前台操作,它不应该被后台的操作所阻塞。在Watchdog.java中:

215        mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
216                "foreground thread", DEFAULT_TIMEOUT);
217        mHandlerCheckers.add(mMonitorChecker);

接下来,对于System Server的主线程,UI线程,IO线程和Display线程,分别做相同操作,这坨线程和FgThread一样都继承自ServiceThread。在init()函数中,接下来会调用registerReceiver()来注册系统重启的BroadcastReceiver。在收到系统重启广播时会执行RebootRequestReceiver的onReceive()函数,继而调用rebootSystem()重启系统。它允许其它模块(如CTS)通过发广播来让系统重启。

然后,各个需要被Watchdog监视的Service需要将自己进行注册。它们都实现了Watchdog.Monitor接口,其中主要是monitor()函数。例如ActivityManagerService:

2150        Watchdog.getInstance().addMonitor(this);
2151        Watchdog.getInstance().addThread(mHandler);

其中addMonitor()将自身放到foreground thread的HandlerChecker的monitor队列中,addThread()根据当前线程的Handler创建HandlerChecker并放到mHandlerCheckers队列中。monitor()的实现一般很简单,只是尝试去获得锁再释放锁。如果有deadlock,就会卡住无法返回。

18767    public void monitor() {
18768        synchronized (this) { }
18769    }

回到SystemServer中,通过Watchdog的start()方法启动Watchdog线程,Watchdog.run()被执行。

Watchdog的主体是一个循环。在每一次循环中,所有HandlerChecker的scheduleCheckLocked()函数被调用。其中主要是往被监视线程的Looper中放入HandlerChecker对象,HandlerChecker本身作为Runnable,是线程的可执行体。因此当被监视线程把它从Looper中拿出来后,它的run()函数被调用。然后,Watchdog.run()等待最长30秒后,调用evaluateCheckerCompletionLocked()检查各HandlerChecker结果状态。一个HandlerChecker结果状态有四种,COMPLETED(0),WAITING(1),
WAITED_HALF(2)和OVERDUE(3)。分别代表目标Looper已处理monitor,延时小于30秒,延时大于30秒小于60秒,延时大于60秒。最终的总状态是它们的最大值(也就是意味着最坏情况的那种情况)。如果总状态是COMPLETED,WAITING或WAITED_HALF,则进入循环下一轮。注意如果是WAITED_HALF,也就是说等了大于30秒,需要调用AMS.dumpStackTraces()来dump栈。如果状态为WAITED_HALF,进入下一轮循环后,又会等待最长30秒。

假设有线程阻塞,对于阻塞线程的HandlerChecker,它的延迟超过60秒,导致总状态为OVERDUE。这里会调用getBlockedCheckersLocked()和describeCheckersLocked()打印出是哪个Handler阻塞了。在Eventlog中打出信息后,把当前pid和相关进程pid放入待杀列表。然后和上面一样调用AMS.dumpStackTraces()打印栈。之后等待2秒等stacktrace写完。如有需要还会调用dumpKernelStackTraces()将kernel部分的stacktrace打出来。本质上是读取/proc/[pid]/task下的线程的和相应的/proc/[tid]/stack文件。然后调用doSyncRq()通知kernel把阻塞线程信息和backtrace打印出来(通过写/proc/sysrq-trigger)。之后会创建专门的线程来将信息写入到Dropbox,该线程会执行AMS.addErrorToDropBox()。Dropbox是Android中的一套日志记录系统,作用是将系统出错信息记录下来并且保留一段时间,避免被覆盖。当出现crash,
wtf, lowmem或者Watchdog被触发都会通过Dropbox来记录。

425            Thread dropboxThread = new Thread("watchdogWriteToDropbox") {
426                    public void run() {
427                        mActivity.addErrorToDropBox(
428                                "watchdog", null, "system_server", null, null,
429                                subject, null, stack, null);
430                    }
431                };
432            dropboxThread.start();
433            try {
434                dropboxThread.join(2000);  // wait up to 2 seconds for it to return.
435            } catch (InterruptedException ignored) {}

DropBoxManagerService在SystemServer的startOtherServices()中添加,信息默认存放路径为/data/system/dropbox。DropBoxManagerService实现了IDropBoxManagerService服务接口,Client通过DropBoxManager来访问服务。在Watchdog中调用AMS.addErrorToDropBox()后,该函数会起工作线程(因为涉及I/O),将前面得到的stack信息dump到Dropbox,并且获取最近的logcat,最后通过DBMS的addText()接口进行存储。

接下来,如果设置了ActivityController就调用其systemNotResponding()接口(IActivityController是给测试开发时用的接口,用于监视AMS里的行为)。然后判断Debugger是否连着和是否允许restart。如果没有连着debugger且允许restart,就开始大开杀戒了。

467                Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
...
476                Slog.w(TAG, "*** GOODBYE!");
477                Process.killProcess(Process.myPid());
478                System.exit(10);

因为Watchdog和SystemServer是同一进程,这里Watchdog kill掉了自己,也就是kill了SystemServer。因它是主要进程,杀掉后会被init重启。

这就是Watchdog的大体流程,回过头看下AMS中dumpStackTraces()的一些细节。参数中的pids包含了本进程,阻塞线程以及phone进程等。NATIVE_STACKS_OF_INTEREST包含了下面三个关键进程。

67    public static final String[] NATIVE_STACKS_OF_INTEREST = new String[] {
68        "/system/bin/mediaserver",
69        "/system/bin/sdcard",
70        "/system/bin/surfaceflinger"
71    };

注意虽然它们不在SystemServer中,但因为SystemServer中的Service会用Binder同步调用它们的方法。如果这些进程中阻塞,也可能导致SystemServer中发生阻塞。

dumpStackTraces()实现中先从dalvik.vm.stack-trace-file这个system property中取出trace路径,默认为/data/anr/traces.txt。接着它创建该文件(需要的话),设置属性,最后调用同名函数dumpStackTraces()完成真正的dump工作。dump工作首先会用FileObserver(利用inotify机制)监视trace文件什么时候写完。它会创建一个独立的线程ObserverThread并运行。然后对于前面加入到要dump线程列表中的进程,发送SIGQUIT信号。如果是虚拟机进程,ART中的SignalCatcher::HandleSigQuit()(在/art/runtime/signal_catcher.cc)会被调用来dump信息(DVM也是类似的)。对于前面的核心Service,调用Debug.dumpNativeBacktraceToFile()来输出它们的backtrace。

总结下来,dumpStackTraces()大体流程如下:

可以看到,其中主要收集三类信息:一是关键进程(也就是上面收集的pid)的stacktrace;二是几个关键native服务的stacktrace;三是CPU的使用率。其中一是通过往目标进程发SIGQUIT来获取,因为Java虚拟机的Runtime会捕获SIGQUIT信号打印栈信息。二的原理是向后台debuggerd这个daemon发起申请,让其用ptrace打印目标进程的stacktrace然后用本地socket传回来。部分实现位于android_os_Debug.cpp和/system/core/libcutils/debugger.c。发起申请和接收数据的代码在以下函数:

131int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) {
132  int sock_fd = make_dump_request(DEBUGGER_ACTION_DUMP_BACKTRACE, tid, timeout_secs);
...
137  /* Write the data read from the socket to the fd. */
...
141  while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) {
142    if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) {
143      result = -1;
144      break;
145    }
146  }
...

最后使用ProcessCpuTracker类测量CPU使用率。它主要是通过读系统的/proc/[pid]/stat文件。里边可以读到进程所占用的时间(user mode和kernel mode)。统计半秒后,排序后输出最占CPU的前几名的stacktrace以便分析谁可能是罪魁祸首。

总得来说,Watchdog是一个软件实现的检测SystemServer进程内死锁或挂起问题,并能够从中恢复的机制。除了Watchdog外,Android中还有一些自检容错及出错信息收集机制,前者有ANR,OOM Killer,init中的重启机制等,后者有Dropbox,Debuggerd,Eventlog,Bugreport等。除此之外,其它的信息查看和调试命令就不计其数了,如dumpsys, dumpstate, showslab, procrank, procmem, latencytop, librank,
schedtop, svc, am ,wm, atrace, proc, pm, service, getprop/setprop, logwrapper, input, getevent/sendevent等。充分利用这些工具能够有效提高分析问题的效率。

时间: 2024-10-21 06:38:41

Android中的软件Watchdog的相关文章

android 中自定义软件盘用于特需界面的输入

在做p2p理财项目,有些界面避免有校身份证号码及购买数量的输入,所以采取自定义软件盘的方式来实现更好的输入体验. 那么怎么弹出和隐藏自己自定义的软键盘呢?关键代码如下 if (SDK_INT <= 10) { // 屏蔽默认输入法 edText.setInputType(InputType.TYPE_NULL); } else { //反射的方法实现避免弹出系统自带的软键盘 getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_

Android中调用系统所装的软件打开文件(转)

Android中调用系统所装的软件打开文件(转) 在应用中如何调用系统所装的软件打开一个文件,这是我们经常碰到的问题,下面是我所用到的一种方法,和大家一起分享一下! 这个是打开文件的一个方法: Java代码 /** * 打开文件 * @param file */ private void openFile(File file){ Intent intent = new Intent(); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //设置in

Android中Serializable和Parcelable序列化对象详解

学习内容: 1.序列化的目的 2.Android中序列化的两种方式 3.Parcelable与Serializable的性能比较 4.Android中如何使用Parcelable进行序列化操作 5.Parcelable的工作原理 6.相关实例 1.序列化的目的 (1).永久的保存对象数据(将对象数据保存在文件当中,或者是磁盘中 (2).通过序列化操作将对象数据在网络上进行传输(由于网络传输是以字节流的方式对数据进行传输的.因此序列化的目的是将对象数据转换成字节流的形式) (3).将对象数据在进程

AccessibilityService辅助类用法(Android 中的另类钩子)

说在前面的话 为什么会把AccessibilityService叫做Android 中的另类钩子呢?搞过windows的都知道,钩子的含义就是能够监听到一切你想监听的内容,而Android中的AccessibilityService也可以监听到我们需要的某些功能. 用法 简介 AccessibilityService是一个辅助类,可以监听我们手机的焦点,窗口变化,按钮点击等等.实现它的服务需要在手机设置里面->辅助功能在这里面找到你自己实现的辅助类,然后打开它就可以进行我们一系列的监听了. 实例

Android基础入门教程——8.1.1 Android中的13种Drawable小结 Part 1

Android基础入门教程--8.1.1 Android中的13种Drawable小结 Part 1 标签(空格分隔): Android基础入门教程 本节引言: 从本节开始我们来学习Android中绘图与动画中的一些基础知识,为我们进阶部分的自定义 打下基础!而第一节我们来扣下Android中的Drawable!Android中给我们提供了多达13种的 Drawable,本节我们就来一个个撸一遍! Drawable资源使用注意事项 Drawable分为两种: 一种是我们普通的图片资源,在Andr

android中的回调

回调这种思想大家应该都用过,只是很多人不知道那是回调的一种罢了,前几天整理了一下自己对于回调的理解,就顺便把自己的一些想法整理到博客中. 让我们从一个小故事开始. 某天,我打电话向你请教问题,当然是个难题,你一时想不出解决方法,我又不能拿着电话在那里傻等,于是我们约定:等你想出办法后打手机通知我,这样,我就挂掉电话办其它事情去了.过了XX分钟,我的手机响了,你兴高采烈的说问题已经搞定,应该如此这般处理. OK,这个故事我们先告一段落,其实,这就是一个典型的回调过程. C不会自己调用b,C提供b的

Android中adb push和adb install的使用区别

Android中adb push和adb install的使用区别  转载 本篇文章由史迎春(@三俗小女子)投稿.转载请注明原文地址. 在Android实际开发中,经常会使用adb命令,安装应用程序可以使用adb push 或者adb install.下面就来讲讲这两种安装方式的区别. adb push 能够指定安装目录.比如执行”adb push xxx.apk system/app” 后,xxx.apk被安装到了system/app目录下,此目录下的软件为上文中提到的system appli

详解Android中那些酷炫返回方式的实现

Android手机都会有返回键,不管是实体键,还是虚拟键.Android用户主要也都是通过这个返回键操控页面返回方式的,不比IOS逼格甚高的只保留一个操作键.这种方式是最普遍的返回方式,还有一种也是比较常见的,那就是页面内部自己响应.绝大多数APP每个页面的设计图顶部左侧都会有一个返回键图标,偶尔也有奇葩的设计放在底部左侧,点击这个图标即finish掉当前页面.简单的介绍完了最常见的两种方式,下面为大家介绍两种更友好的交互方式. 拿大家比较常用的三款社交软件的交互来说.腾讯微博的返回方式除去上述

Android中处理崩溃异常CrashHandler

来源:http://blog.csdn.net/liuhe688/article/details/6584143 大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个调试,所以在程序发布出去之后,如果出现了崩溃现象,开发者应该及时获取在该设备上导致崩溃的信息,这对于下一个版本的bug修复帮助极大,所以今天就来介绍一下如何在程序崩溃的情况下收集相关的设备参数信息和具体的异常信息,并发送这些信