热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

利用信号机制监控AndroidAppANR

ANR机制ANR发生之后,AMS(位于system_server进程)会给APP进程发送SIGQUIT信号,APP的SignalCatcher线程在捕获这个信号后打印本进程的调用栈

ANR机制

ANR发生之后,AMS(位于system_server进程) 会给APP进程发送SIGQUIT信号,APP的Signal Catcher线程在捕获这个信号后打印本进程的调用栈。也就是说,ANR发生一定伴随着SIGQUIT信号的产生,那么我们监控此信号是不是可以监控ANR了吗?


怎么监控SIGQUIT信号

APP进程中有一个Signal Catcher线程会监听SIGQUIT信号,接收到信号后会执行dump anr trace等操作,这就是发生ANR时我们看到的一堆跟踪信息。

但是一次信号只能被一个线程消费,消费了就没了,所以问题的关键在于如何提前截胡,把信号捕获到!


先看看Android 是怎么监听的

源码可以查看:http://androidxref.com/9.0.0_r3/xref/art/runtime/runtime.cc#930

Runtime::init(){
....

BlockSignals();
....
}
// 主线程忽略SIGPIPE,SIGQUIT,SIGUSR1信号
void Runtime::BlockSignals() {
SignalSet signals;
signals.Add(SIGPIPE);
// SIGQUIT is used to dump the runtime's state (including stack traces).
signals.Add(SIGQUIT);
// SIGUSR1 is used to initiate a GC.
signals.Add(SIGUSR1);
signals.Block();
}

源码地址: http://androidxref.com/9.0.0_r3/xref/art/runtime/signal_catcher.cc#234

void* SignalCatcher::Run(void* arg) {
SignalCatcher* signal_catcher = reinterpret_cast(arg);
CHECK(signal_catcher != nullptr);
Runtime* runtime = Runtime::Current();
CHECK(runtime->AttachCurrentThread("Signal Catcher", true, runtime->GetSystemThreadGroup(),
!runtime->IsAotCompiler()));
Thread* self = Thread::Current();
DCHECK_NE(self->GetState(), kRunnable);
{
MutexLock mu(self, signal_catcher->lock_);
signal_catcher->thread_ = self;
signal_catcher->cond_.Broadcast(self);
}
// Set up mask with signals we want to handle.
SignalSet signals;
signals.Add(SIGQUIT); //监听SIGQUIT信号
signals.Add(SIGUSR1);
while (true) {
int signal_number = signal_catcher->WaitForSignal(self, signals); //监听
if (signal_catcher->ShouldHalt()) {
runtime->DetachCurrentThread();
return nullptr;
}
switch (signal_number) {
case SIGQUIT:
signal_catcher->HandleSigQuit();
break;
case SIGUSR1:
signal_catcher->HandleSigUsr1();
break;
default:
LOG(ERROR) <<"Unexpected signal %d" < break;
}
}
}

总的来说就是: 主线程忽略SIGQUIT信号,由于子线程都是基于主线程fork出来的,如果未作特殊处理,也和主线程一样忽略了SIGQUIT信号;

然后Android专门用了一个SignalCatcher 线程来监听SIGQUIT信号,这样就保证了只有这个线程接收到SIGQUIT信号,也就能够处理ANR了事件了。


注册我们的信号处理器

仿照Android,我们新建一个线程,并且监听SIGQUIT信号,是不是就可以大功告成了呢?

直觉告诉我没那么容易,不然Android为啥还要特意让SignalCatcher线程外的所有线程忽略sigquit信号呢?

事实上,linux kernel 对于发给进程(线程组)信号的处理原则是,根据curr_target来确定信号的处理线程,而curr_target 并不是固定不变的,它是最新一次处理信号的线程。

那么我们怎么做才能让curr_target变为我们的线程呢?

源码地址:https://elixir.bootlin.com/linux/latest/source/kernel/signal.c#L997

static void complete_signal(int sig, struct task_struct *p, enum pid_type type)
{
struct signal_struct *signal = p->signal;
struct task_struct *t;
/*
* Now find a thread we can wake up to take the signal off the queue.
*
* If the main thread wants the signal, it gets first crack.
* Probably the least surprising to the average bear.
*/
if (wants_signal(sig, p))
t = p;
else if ((type == PIDTYPE_PID) || thread_group_empty(p))
/*
* There is just one thread and it does not need to be woken.
* It will dequeue unblocked signals before it runs again.
*/
return;
else {
/*
* Otherwise try to find a suitable thread.
*/
t = signal->curr_target;
while (!wants_signal(sig, t)) {
t = next_thread(t);
if (t == signal->curr_target)
/*
* No thread needs to be woken.
* Any eligible threads will see
* the signal in the queue soon.
*/
return;
}
signal->curr_target = t; // 更新curr_target
}
/*
* Found a killable thread. If the signal will be fatal,
* then start taking the whole group down immediately.
*/
if (sig_fatal(p, sig) &&
(signal->core_state || !(signal->flags & SIGNAL_GROUP_EXIT)) &&
!sigismember(&t->real_blocked, sig) &&
(sig == SIGKILL || !p->ptrace)) {
/*
* This signal will be fatal to the whole group.
*/
if (!sig_kernel_coredump(sig)) {
/*
* Start a group exit and wake everybody up.
* This way we don't have other threads
* running and doing things after a slower
* thread has the fatal signal pending.
*/
signal->flags = SIGNAL_GROUP_EXIT;
signal->group_exit_code = sig;
signal->group_stop_count = 0;
t = p;
do {
task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
sigaddset(&t->pending.signal, SIGKILL);
signal_wake_up(t, 1);
} while_each_thread(p, t);
return;
}
}
/*
* The signal is already in the shared-pending queue.
* Tell the chosen thread to wake up and dequeue it.
*/
signal_wake_up(t, sig == SIGKILL);
return;
}

监听思路

我们能否发一个信号,且只能被我们的线程捕获到,那么这样之后,curr_target 就会变为我们的线程了,以后的SIGQUIT信号就会提前被我们的线程截胡。

事实上,从上一节我们看到,App进程的所有线程都忽略了SIGPIPE信号,那么我们是否可以利用这个信号呢?完全可以。我们可以给进程发送一个SIGPIPE信号,这样kernel遍历了一遍之后发现只有我们的线程能够处理,就将curr_target 变为我们的线程了。

等等,那么我们把SIGQUIT信号截胡了,那么系统的ANR怎么办?别慌,简单,我们只需要在我们的线程通过tgkill 给Signal Catcher线程定向发送一个SIGQUI信号就行了,它不关心谁发的信号,只要能收到就行。

至此,通过Linux信号机制监听Android app ANR的方案就基本完成了!



推荐阅读
  • 通过Web界面管理Linux日志的解决方案
    本指南介绍了一种利用rsyslog、MariaDB和LogAnalyzer搭建集中式日志管理平台的方法,使用户可以通过Web界面查看和分析Linux系统的日志记录。此方案不仅适用于服务器环境,还提供了详细的步骤来确保系统的稳定性和安全性。 ... [详细]
  • 深入理解T-SQL中的NULL与三值逻辑
    本文探讨了SQL Server中的三值逻辑,解释了谓词计算结果为TRUE、FALSE和UNKNOWN的规则。通过具体示例,详细说明了如何正确处理NULL值,并探讨了在不同约束条件下的行为。 ... [详细]
  • 创建项目:Visual Studio Online 入门指南
    本文介绍如何使用微软的 Visual Studio Online(VSO)创建和管理开发项目。作为一款基于云计算的开发平台,VSO 提供了丰富的工具和服务,简化了项目的配置和部署流程。 ... [详细]
  • FinOps 与 Serverless 的结合:破解云成本难题
    本文探讨了如何通过 FinOps 实践优化 Serverless 应用的成本管理,提出了首个 Serverless 函数总成本估计模型,并分享了多种有效的成本优化策略。 ... [详细]
  • 百度搜索结果链接提取工具 UrlGetter V1.43
    该工具专为获取百度搜索引擎的结果页面中的网址链接而设计,能够解析并转换为原始URL。通过正则表达式匹配技术,精准提取网页链接,并提供详细的使用说明和下载资源。 ... [详细]
  • 本文详细介绍了 Java 中 org.apache.qpid.server.model.VirtualHost 类的 closeAsync() 方法,提供了具体的代码示例和应用场景。通过这些示例,读者可以更好地理解和使用该方法。 ... [详细]
  • 本文探讨了Microsoft OLE DB Provider for SQL Server错误80004005的成因与解决方法,详细分析了SQL Server连接失败的原因,并提供了多个有效的解决方案。 ... [详细]
  • 华为USG基于源地址的多出口策略路由配置
    网络拓扑如下:组网情况:企业用户主要有技术部(VLAN10)和行政部(VLAN20),通过汇聚交换机连接到USG。企业分别通过两个不同运营商(ISP1和ISP2)连接到 ... [详细]
  • 使用 GitHub、JSDelivr、PicGo 和 Typora 构建高效的图床解决方案
    本文详细介绍了如何利用 GitHub 仓库、JSDelivr CDN、PicGo 图床工具和 Typora 编辑器,搭建一个高效且免费的图床系统。通过此方案,用户可以轻松管理和上传图片,并在 Markdown 文档中快速插入高质量的图片链接。 ... [详细]
  • 探讨如何从数据库中按分组获取最大N条记录的方法,并分享新年祝福。本文提供多种解决方案,适用于不同数据库系统,如MySQL、Oracle等。 ... [详细]
  • 本文介绍如何在SQL Server中对Name列进行排序,使特定值(如Default Deliverable Submission Notification)显示在结果集的顶部。 ... [详细]
  • 本文详细介绍了在 Windows 2000 系统中启用 TELNET 服务时需要注意的 NTLM 配置问题,帮助用户解决常见的身份验证失败错误。 ... [详细]
  • 选择适合生产环境的Docker存储驱动
    本文旨在探讨如何在生产环境中选择合适的Docker存储驱动,并详细介绍不同Linux发行版下的配置方法。通过参考官方文档和兼容性矩阵,提供实用的操作指南。 ... [详细]
  • 本文详细探讨了如何在Docker环境中实现单机部署Redis集群的方法,提供了详细的步骤和配置示例,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 本文详细介绍了网络存储技术的基本概念、分类及应用场景。通过分析直连式存储(DAS)、网络附加存储(NAS)和存储区域网络(SAN)的特点,帮助读者理解不同存储方式的优势与局限性。 ... [详细]
author-avatar
流浪的牛仔2011Ting_883
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有