系统进程的Watchdog
编写者:李文栋 /rayleeya
http://rayleeya.iteye.com/blog/1963408
3.1 Watchdog简介
对于像笔者这样没玩过硬件的纯软程序员来说,第一次看到这个家伙的时候真心一头雾水,只是觉得这个名字很有意思。一番调查后发现,Watchdog机制最早来源于硬件,在计算机系统中,单片机的工作容易受到来自外界电磁场的干扰,而陷入死循环,系统无法继续工作,为了解决这个问题,便产生了一种专门用于监测单片机程序运行状态的芯片,俗称"看门狗"(Watchdog)。
“看门狗”本身是一个定时器电路,内部会不断的进行计时(或计数)操作。计算机系统和“看门狗”有两个引脚相连接,正常运行时每隔一段时间就会通过其中一个引脚向“看门狗”发送信号,“看门狗”接收到信号后会将计时器清零并重新开始计时。而一旦系统出现问题,进入死循环或任何阻塞状态,不能及时发送信号让“看门狗”的计时器清零,当计时结束时,“看门狗”就会通过另一个引脚向系统发送“复位信号”,让系统重启。
这样看来,向“看门狗”发送信号就像是“喂狗”,计时器就是“看门狗”的胃,当计时结束,狗饿了,就一口把系统咬死,让它重生。
软件上的看门狗技术的思想和影响类似,例如Linux自带的Watchdog。下面我们来看看Android系统进程的这条小狗吧。
3.2 系统进程的Watchdog
Android系统进程中的Watchdog(以下简称WD)自然是用来监测系统进程的,它和硬件上的WD有什么区别呢?系统进程中维护着大量的服务对象,其中有一些非常重要的对象,例如ActivityManagerService, WindowManagerService等,这些服务对象能够被正常访问对系统的运行来说至关重要,暂且称它们为关键对象。这些关键对象可能同时会被多个线程使用,所以需要在操作这些对象的地方使用同步锁将它们保护起来,确保对象状态的一致性。但是如果某个线程锁住关键对象后长时间没有释放锁,其他线程无法使用对象完成后续的任务,那么系统就会处于停滞状态无法运行,此时就需要让系统重启以恢复到一个正常的运行状态。检测这些关键服务对象是否被锁住,和重启系统的操作就是由WD完成的。
WD是如何完成这项神圣的使命的呢?我们先来了解一下WD的创建和启动,再来剖析它的结构和流程。
3.2.1 Watchdog初始化和启动
WD对象是一个单例,是在系统启动过程中的ServerThread线程中时创建的。
ServerThread.java → run()
Slog.i(TAG, "Init Watchdog");
Watchdog.getInstance().init(context, battery, power, alarm,
ActivityManagerService.self());
需要注意的是WD的构造方法,其内部创建了一个HeartbeatHandler类型的对象,后面会详细介绍它,不过可以肯定的是,这个Handler实例绑定了ServerThread线程的Looper。
Watchdog.java → Watchdog()
private Watchdog() {
super("watchdog");
mHandler = new HeartbeatHandler();
}
再看一下init方法具体做了什么。
Watchdog.java → init()
publicvoid init(Context context, BatteryService battery,
PowerManagerService power, AlarmManagerService alarm,
ActivityManagerService activity) {
//保存了几个要用到的服务对象
mResolver = context.getContentResolver();
mBattery = battery;
mPower = power;
mAlarm = alarm;
mActivity = activity;
//注册两个BroadcastReceiver,来接收重启的消息
context.registerReceiver(new RebootReceiver(),
new IntentFilter(REBOOT_ACTION));
mRebootIntent = PendingIntent.getBroadcast(context,
0, new Intent(REBOOT_ACTION), 0);
context.registerReceiver(new RebootRequestReceiver(),
new IntentFilter(Intent.ACTION_REBOOT),
android.Manifest.permission.REBOOT, null);
mBootTime = System.currentTimeMillis(); //记录启动时间
}
init方法很简单,涉及的内容后面会做介绍。
完成初始化后,WD还没有启动运行。看一下WD的类声明可以知道,它是Thread类的子类,可以想到WD是在自己的线程中实现“定时器”的功能的,这很合理,要实现类似于独立硬件完成的计时工作,用独立线程完成对所在进程的监控是再好不过的。启动WD线程是在ServerThread线程的最后阶段完成的。
ActivityManagerService.self().systemReady(new Runnable() {
publicvoid run() {
... ...
Watchdog.getInstance().start();
... ...
}
);
3.2.2 Watchdog结构剖析
为了描述方便,先给出WD的类图。
WD的结构还是比较简单的,下面我们分几个部分来分析。
1. Watchdog
WD的“定时器”的功能是在单独的线程中完成的,所以WD本身继承了Thread,如前面所说,它是在ActivityManagerService的systemReady方法中启动的。
2. HeartbeatHandler
Android系统进程的WD和硬件的“看门狗”的思想是一致的,但是实现方式上不同。WD线程在计时的过程中并不是被动的等待系统的“喂狗”信号,而是在每轮计时的开始向ServerThread(以下简称ST)线程发一个检测消息,ST接收到消息后开始遍历Monitor对象集合,尝试获取每个对象的锁,这个消息检测过程就是在HeartbeatHandler(以下简称“HH”)中实现的。需要注意的是,HH绑定的是ST线程,ST作为系统进程的主线程执行检测操作。
3. Monitor和被监控的服务对象
WD监控的是系统进程中几个关键的服务对象,对这类对象进行抽象定义,便有了Monitor接口,它只有一个monitor方法。WD对实现了此接口的对象进行监控,其内部有一个存放Monitor对象的集合,任何对象只要实现了Monitor接口,并且通过WD的addMonitor方法注册进集合即可被监控。
在GingerBread之前,被监控服务对象只<!-- 确认一下是否是从2.3开始的 -->有ActivityManagerService、WindowManagerService和PowerManagerService,在此之后又增加了4个,分别是NetworkManagementService、MountService、NativeDaemonConnector和InputManager。
Monitor接口的实现方法很简单,例如AMS的实现:
ActivityManagerService.java → monitor()
publicvoid monitor() {
synchronized (this) { }
}
可以看到所谓的“监控服务对象”,说白了就是对这些对象进行死锁检测,如果能够顺利的获得被监控对象的锁则认为系统运行正常,如果长时间没有获得则认为系统处于停滞状态,需要采取措施了。
4. RebootReceiver和RebootRequestReceiver
3.2.3 Watchdog工作流程
WD的工作流程主要就是WD线程和HH线程之间的交互,先从WD的run方法看起。
Watchdog.java → run()
publicvoid run() {
boolean waitedHalf = false;//➀记录是否已经等待了一半
while (true) {
mCompleted = false;//用一个布尔变量标记是否完成死锁检测
//➁向HH发送检测信号,它是在ST线程中执行的
mHandler.sendEmptyMessage(MONITOR);
synchronized (this) {
//发送检测信号后会等待检测操作完成,等待时间在正常运行的情况下是30秒
long timeout = TIME_TO_WAIT;
long start = SystemClock.uptimeMillis();
while (timeout > 0 && !mForceKillSystem) {
try {
wait(timeout);
... ...
}
. .. ...
通过以上代码可以知道,监控过程就是一个死循环,每次循环都会做一轮死锁检测。有两个需要注意的点,说明如下:
➀ WD的这个“定时器”每轮检测的超时时间是30秒,但是30秒超时后WD并不会马上重启系统,而是将waitedHalf设置为true,认为只是等待了一半的时间,也就是说WD想多给那个被锁住的对象一次机会,做两轮检测,如果仍然超时再杀也不晚。WD还是很有人情味的,后面会看到waitedHalf何时被设置为true的。
➁ 每轮的检测操作不是由WD线程自己完成的,而是发送一个消息给HH,由HH所绑定的ST线程完成。它是怎么做的呢?接下来转到HH一探究竟。
finalclass HeartbeatHandler extends Handler {
... ...
caseMONITOR: {
... ...
finalint size = mMonitors.size();
for (int i = 0 ; i < size ; i++) {
//记录下当前正在被检测的Monitor对象,这很重要
mCurrentMonitor = mMonitors.get(i);
mCurrentMonitor.monitor();//检测死锁
}
synchronized (Watchdog.this) {
mCompleted = true;//标记检测完成,WD线程会用此判断是否完成
mCurrentMonitor = null;
... ...
逻辑很简单,不多解释,只是请留意HH做完死锁检测后没有用notifyAll唤醒WD线程,所以正常情况下WD线程会在超时后再继续下一轮检测。HH比较会偷懒。
接下来又回到WD线程。
... ... //WD线程结束wait等待
if (mCompleted && !mForceKillSystem) {
//如果检测成功,则重置waitedHalf标记,继续下一轮检测
waitedHalf = false;
continue;
}
if (!waitedHalf) {
//执行到这里,说明检测过程阻塞了,没有完成,并且waitedHalf为false,说明
//这是死锁检测失败的第一轮检测。通过AMS将系统进程中各个线程的函数调用栈
//输出到/data/anr/traces.txt文件中,同时也会输出几个重要的native进程的
//backtrace,以便提供更多信息来定位问题,因为Java层的阻塞很有可能是native
//层的阻塞造成的。
ArrayList<Integer> pids = new ArrayList<Integer>();
pids.add(Process.myPid());
ActivityManagerService.dumpStackTraces(true, pids, null, null,
NATIVE_STACKS_OF_INTEREST);
waitedHalf = true;//设置为true,说明WD线程等了一轮了
continue;//再来一轮,给个机会
}
如果接下来的第二轮死锁检测仍然失败,则上述的代码就不会执行,继续往下走。
//此后便是为了能够方便分析死锁原因,而输出的各种类型的日志信息
final String name = (mCurrentMonitor != null) ?
mCurrentMonitor.getClass().getName() : "null";
//➀记录正在执行死锁检测的对象
EventLog.writeEvent(EventLogTags.WATCHDOG, name);
//➁再次输出系统进程的函数栈信息
ArrayList<Integer> pids = new ArrayList<Integer>();
pids.add(Process.myPid());
//同时输出com.android.phone进程的函数栈,因为电话系统对于手机来说是最重要的
//模块,自然要重点对待
if (mPhonePid > 0) pids.add(mPhonePid);
final File stack = ActivityManagerService.dumpStackTraces(
!waitedHalf, pids, null, null, NATIVE_STACKS_OF_INTEREST);
... ...
if (RECORD_KERNEL_THREADS) {
dumpKernelStackTraces();//输出一部分Kernel的信息帮助定位问题
}
... ...
//➂在一个子线程中输出到DropBox中
mActivity.addErrorToDropBox(
"watchdog", null, "system_server", null, null,
name, null, stack, null);
... ...
//如果调试器没有链接则直接退出进程
if (!Debug.isDebuggerConnected()) {
Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + name);
Process.killProcess(Process.myPid());
System.exit(10);
} else {//如果正在Debug,那你就可以断点调试了
... ...
有三个关键点需要注意:
➀ 记录正在执行死锁检测的对象,如果name为”null”,其实就相当于ServerThread线程还没有执行HH的handleMessage方法,就在其他地方阻塞了。所以要特别注意,如果在分析trace信息时发现没有因为被检测的关键服务对象而发生阻塞,那么就需要看看ServerThread线程的函数调用栈,确定真正的阻塞原因。
➁ 在进行系统重启前会做两轮死锁检测,第一轮会新建traces.txt文件,但第二轮会在原有文件的基础上续写,所以你会在trace信息中看到两次系统进程各个线程的函数调用栈信息。
➂ 在一个子线程中输出到DropBox中,所以如果这次保存在traces.txt中的死锁信息没有来得及查看就被覆盖了,那么可以到/data/system/dropbox目录下找到这次日志的备份。
Watchdog的实现说白了其实就是在一个线程中建立消息循环,通过Message和成员变量在线程间进行通讯,这和Handler机制的本质是一样的。
至此,WD的工作流程介绍完了,还算比较简单,在本章的最后附上了WD的流程图。接下来会介绍一些WD检测到死锁后导致重启的问题的分析方法,对于Android系统工程师来说,处理这类问题肯定是家常便饭了。
3.3 Watchdog引起的重启问题分析方法
3.3.1 被监测对象死锁
(待续)
3.3.2 ServerThread线程阻塞
(待续)
Watchdog的流程图
系统进程的Watchdog的更多相关文章
- Watchdog机制概述
1. Watchdog初始 Watchdog的中文的“看门狗”,有保护的意思.最早引入Watchdog是在单片机系统中,由于单片机的工作环境容易受到外界磁场的干扰,导致程序“跑飞”,造成整个系统无法正 ...
- am335x watchdog 设备出错
问题描述: am335x watchdog 设备节点打开失败. 如果是直接将omap_wdt 直接编译成uImage,这样会出现打开文件节点失败的情况. 如果单独编译成模块在后面文件系统内插入则不会. ...
- Ring3下的DLL注入(NtCreateThreadEx + LdrLoadDll方式实现,可以注入系统进程)
工具介绍及使用请移步:http://blog.csdn.net/sunflover454/article/details/50441014 本文首发在零日安全论坛:http://www.jmpoep. ...
- 获取系统进程信息和进程依赖的dll信息
body { font-family: Bitstream Vera Sans Mono; font-size: 11pt; line-height: 1.5; } html, body { colo ...
- win10下 解决系统进程占用80端口
公司电脑从win7升级到win10,无法启动nginx,日志里输出:2016/05/30 09:26:01 [emerg] 7024#5440: bind() to 0.0.0.0:80 failed ...
- PIC32MZ tutorial -- Watchdog Timer
Watchdog is a very necessary module for embedded system. Someone said that embedded system operates ...
- python watchdog
监视文件变更 #!/usr/bin/python # -*- coding:UTF-8 -*- import time from watchdog.observers import Observer ...
- Watchdog
一.简介 Watchdog主要用于监视系统的运行,Linux内核不仅为各种不同类型的watchdog硬件电路提供了驱动,还提供了一个基于定时器的纯软件watchdog驱动. 驱动源码位于内核源码树dr ...
- watchdog机制
转自:http://blog.sina.com.cn/s/blog_4dff871201012yzh.html 什么是Watchdog? Watchdog,又称watchdog timer,是计算机可 ...
随机推荐
- 带二级目录的Nginx配置------目前找到的最简单的方法
由于项目不知一个,所以不得不为每一个项目建一个专有的文件夹,这就导致了在配置nginx的时候会出现二级目录 目前找到的最简单的方法 - step1:修改 vue.config.js 添加配 ...
- navicat 常用快捷键
1.ctrl+q 打开查询窗口 2.ctrl+/ 注释sql语句3.ctrl+shift +/ 解除注释4.ctrl+r 运行查询窗口的 ...
- PJSIP-iOS源码编译
官方文档https://trac.pjsip.org/repos/wiki/Getting-Started/iPhone 功能 在iPhone上可以实现的功能: 包含基于CoreAudio的音频设备, ...
- 易混淆的table列表和dl表格
dl列表是使用了HTML dl.dt.dd标签的数据列表.首先我们使用dl(definition list-自定义列表)标签来容纳整个数据结构,然后我们使用dt(自定义标题)标签和dd(自定义描述)标 ...
- 什么是无符号段整数,什么又是有符号数,(c++与java语言里边的不同)
c++中:整型数分为有符号数和无符号数两种 unsigned int a;无符号整型变量a,意思是这个数最小值为0,最大值为2的32次方-1,因为一个整型数占四个字节,一个字节8位,共32位 int ...
- ios之UITextfield
//初始化textfield并设置位置及大小 UITextField *text = [[UITextField alloc]initWithFrame:CGRectMake(20, 20, 13 ...
- OpenCV2.4.11+VS2012的环境配置+“fatal error LNK1112: 模块计算机类型“X86”与目标计算机类型“x64”冲突”的问题解决
本来OpenCV环境配置的问题是个基础问题,但是步骤有点小烦,所以几乎每次都要百度一下,加上这次遇到的“fatal error LNK1112: 模块计算机类型“X86”与目标计算机类型“x64”冲突 ...
- [LUOGU] P1063 能量项链
题目描述 在Mars星球上,每个Mars人都随身佩带着一串能量项链.在项链上有N颗能量珠.能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数.并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定 ...
- [LUOGU] P1880 [NOI1995]石子合并
题目描述 在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分. 试设计出1个算法,计算出将N堆石子合并成1 ...
- Linux进程通信之共享内存实现生产者/消费者模式
共享内存 共享内存是内核为进程创建的一个特殊内存段,它将出现在进程自己的地址空间中,其它进程可以将同一段共享内存连接(attach)到自己的地址空间.这是最快的进程间通信方式,但是不提供任何同步功能( ...