写给android开发的Linux 信号 – 上篇「建议收藏」

写给android开发的Linux 信号 – 上篇「建议收藏」对于android jni开发或者阅读其他开源库,我们都会遇到信号相关的处理,了解信号对于我们android开发有很多好处!

前言

本期也是挖坑的系列之一,为什么会选信号当作本期的主题呢?其实是跟笔者的一些学习经验相关的,无论是自己写的一些尝试,还是行业内的一些常见的crash监控方案或者其他一些优秀开源,或多或少都会涉及到对linux信号的处理,因此,作为依赖于Linux内核的android系统,也离不开对Linux信号的一些处理。了解并使用一些信号相关的手段,能更好的帮助我们进行日常的疑难杂症的解决。虽然信号本身涉及到c语言相关的知识,但是也不妨碍我们进行学习!

信号概念与信号处理器的作用

信号本身,其实是对事件发生时对进程的一种通知机制,当然,这种机制并不是只限于Linux系统,其他很多类Unix系统也有,信号的初衷只是通知,比如当硬件异常时,可由硬件本身的错误检测通知到内核,再由内核告诉相关的进程,又或者是,进程间可通过信号本身,去传递一些特有的消息。但是我们在日常开发中经常能够看到,比如nativecrash是由某个信号导致的,比如常见的SIGABRT/SIGSEGV等。但是这个表述,其实是不太准确的,大部分信号(这里特指除了SIGKILL/SIGSTOP)本身其实跟进程crash/exit,本身其实是没有必然关系的,这点需要注意,也是非常容易尝试误会的点。比如我们说,收到了SIGSEGV就会导致进程退出,其实,这只是一个默认的行为,记住,是默认的行为!下面我们列举一下常见的信号默认行为

信号 默认行为
SIGKILL 确保杀死进程,无法更改默认行为
SIGPIPE 管道相关断开,默认杀死进程
SIGSTOP 确保进程停止,无法更改默认行为,常用于debugger
SIGSEGV 无效内存引用,默认杀死进程
SIGABRT 中止进程,android上行为默认也是杀死
SIGQUIT 默认杀死进程,linux上是终端退出,而android用来产生anr

看到这里,我们可能会有疑惑,好像各个信号其实都差不多都是杀死进程。还有就是SIGQUIT,明明android发生了anr会抛出SIGQUIT,但是也不是说进程也会被杀死呀!其实呢,上面列举了这些,都是信号本身的默认行为罢了,我们是可以改变默认行为的,比如通过系统调用signal(),或者sigaction()注册一个信号处理器,其实就会把一个信号从默认行为变成了自定义行为,如果说自定义行为里面没有进程退出的调用,比如exit,那么,及时当前进程收到了信号,也是不会崩溃的。当然这里指的是可更改默认行为的信号,除了SIGKILL与SIGSTOP,我们都可以通过上述的信号处理调用更改,我们举个例子

Java_com_example_signal_MainActivity_throwNativeCrash(JNIEnv *env, jobject thiz) {
    // 向自身发送一个信号
    raise(SIGABRT);
    __android_log_print(ANDROID_LOG_INFO, "hello", "%s", "qwe");
   
}

当我们通过一个jni调用,调用到throwNativeCrash方法时,里面通过一个raise调用(意思是向当前进程本身发送一个信号),如果默认我们什么也不处理,那么就会导致进程中断退出,表现的形式就是app闪退。但是,假如我们加入了一个信号处理函数(添加信号处理函数一般有signal与sigaction)这里我们介绍一下sigaction

int sigaction(int __signal, const struct sigaction* __new_action, struct sigaction* __old_action);

sigaction接受3个参数,第一个是需要添加信号处理器的信号,第二个是一个sigaction的结构体的指针,用于设置当前信号的新信号处理器,第三个也是一个sigaction的结构体的指针,用于返回之前的信号处理器,如果有的话。

接着我们介绍sigaction结构体

struct sigaction {
  union {
    sighandler_t sa_handler;
    void (*sa_sigaction)(int, struct siginfo*, void*);
  };
  sigset_t sa_mask;
  int sa_flags;
  void (*sa_restorer)(void);
};

union是c语言的一个概念,意味着sa_handler与函数sa_sigaction只取其中一个,当sa_flags包含SA_SIGINFO时,就调用sa_sigaction函数,默认就是调用sa_handler函数。

当然,本篇会尽量讲一些涉及到的系统调用,但是就不细致讲下去了,这些都可能google拿到,不明白的也可以直接评论!

好了,回到我们上文,如果我们对SIGABRT设置了一个信号处理函数,即使SigFunc(收到信号要调用的函数)什么也做,进程也不会退出!

struct sigaction sigc;
sigc.sa_sigaction = SigFunc;
sigemptyset(&sigc.sa_mask);
sigc.sa_flags = SA_SIGINFO;
sigaction(SIGABRT, &sigc, nullptr);

这里非常关键啦!只要我们设置了信号处理函数,那么允许默认行为变更的信号的默认行为就会被改变,即使你的信号处理器什么也没干!

到这里,我们应该就能明白了,信号处理器其实就是更改信号默认行为的一种手段,也是对信号自定义处理的一些手段,这也是为什么我们一些apm框架,会通过监听更改信号处理器后,然后通过old_action重新再把信号抛出给默认处理器的原因,因为信号处理器只能设置一个,如果不重新调用old_action,那么app默认行为就会被改变(即产生了错误也不crash)。

这里可能给大家埋下了一个黑暗的想法,如果我们都把所有信号都注册一遍,就算我们信号处理器啥都不干,是不是就实现了native crash == 0 ? 好家伙,一般情况下,还真是,但是正常的错误已经是产生了,即使我们通过信号处理函数绕过了crash行为,但是当错误实在不可控了,系统就会发送SIGKILL/SIGSTOP的信号,而两个信号是无法通过信号处理器去进行修改的,这样造成的问题是,以后app crash了,也就无法定位出根本的堆栈,说不定会造成一个无法解决的BUG!这点是非常需要注意的!

这里再拓展一下,信号处理函数是支持setjump的,也就是我们可回溯到一个安全的阶段避免一次crash,这里不详述,可以看看笔者的mooner

信号接收进程的行为

上面我们说了一大堆,其实只是信号本身的默认行为,但是对于接收信号的进程来说,却有着多种情况,信号你可以发,接不接受,那是进程可控的。

当信号到达后,进程可表现以下几种方式

  • 忽略信号,内核将丢弃该信号,信号对进程不产生任何影响
  • 终止进程,也就是执行信号本身的一些,默认行为
  • 进程停止,比如debug通过信号控制
  • 恢复执行,比如通过信号恢复之前停止的进程

进程也可以通过有无设置信号处理器,将信号处理器定义为以下情况

  • 设置信号默认行为
  • 忽略信号
  • 自定义信号处理器

下面我们来讲一下,如何设置信号处理器的上诉行为

自定义信号处理器

我们上面也说过,我们可以通过,sigaction,设置自定义的信号处理器,比如

struct sigaction sigc;
sigc.sa_sigaction = SigFunc;
sigemptyset(&sigc.sa_mask);
sigc.sa_flags = SA_SIGINFO;

sigaction(SIGABRT, &sigc, nullptr);

这里SigFunc是一个函数,函数定义为

void (*sa_sigaction)(int, struct siginfo*, void*);

即可,里面就可以处理自定义的一些逻辑

void SigFunc(int sig_num, siginfo *info, void *ptr) {
 比如打log
}

默认行为

我们可以通过SIG_DFL这个宏定义,设置信号处理器的默认行为,比如设置SIGABRT默认行为,这个宏需要放在sa_handler处理,意味着我们就不能在sa_flags加上SA_SIGINFO标记,而是采取默认的处理方式

struct sigaction sigc;
sigc.sa_handler = SIG_DFL;
sigfillset(&sigc.sa_mask);
sigc.sa_flags = SA_RESTART;
... 
sigaction(SIGABRT, &sigc, nullptr);



忽略信号

与默认行为相似,我们也有一个忽略信号的宏定义,则是SIG_IGN,我们也可以一样设置在sa_handler中

struct sigaction sigc;
sigc.sa_handler = SIG_IGN;
sigc.sa_flags = SA_RESTART;
sigfillset(&sigc.sa_mask);
...
sigaction(SIGABRT, &sigc, nullptr);


这里还不忘多提一句,当设置SIG_IGN,那么内核就会把该信号丢弃,这也意味值,进程丝毫不感知该信号,这也就意味着,如果发生异常了,虽然进程不会crash,但是错误足够大时同样会产生SIGKILL去终止进程,虽然忽略信号在一些情况可以避免一些潜在的信号错误的默认处理让进程不crash,但是也给bug排查带来困难

小结

关于信号的上篇,我们先到这里结束一个段落,在android中,会对部分信号处理采用hook方式,从而不一定执行我们用户自定义的信号处理函数,这个我们就放在下篇讲,同时还有一些信号的阻塞行为与其他发送信号的系统调用,以及信号处理过程中的重入与坑,我们也会在下篇进一步讲解,感谢观看!!

最后,还别忘对我的github 点上你的小星星!! github.com/TestPlanB/m…

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13440.html

(0)

相关推荐

  •  MYSQL查询三分钟之内是否有数据存在[通俗易懂]

     MYSQL查询三分钟之内是否有数据存在[通俗易懂]查询三分钟之内是否有数据存在,时间字段为bigint的时间戳 select count(id) from table_message where user_id = #{userId} AND in…

    2023-02-26
    152
  • 如何运行Python代码

    如何运行Python代码Python是一种直译式、面向对象、动态类型的高级程序设计语言。它通常被用于编写各种类型的应用程序,包括网络应用程序、桌面应用程序和游戏等。在本篇文章中,我们将从多个方面详细阐述如何运行Python代码,帮助初学者快速入门。

    2024-07-07
    43
  • 图形化Mysql 生成XML,PO,Service,Controller工具,Mybatis-plus

    图形化Mysql 生成XML,PO,Service,Controller工具,Mybatis-plus工欲善其事必先利其器,图形化 mysql 生成XML,PO, Controller, Service工具 项目地址 图片展示

    2023-02-18
    146
  • 整站系统(SEO系统)

    整站系统(SEO系统)

    2023-08-28
    147
  • 5 django mysql[亲测有效]

    5 django mysql[亲测有效]1 mysql 解压版 参考: https://www.cnblogs.com/guanfuchang/p/6019758.html https://blog.csdn.net/ermaner666…

    2023-04-15
    168
  • 基于PyCharm和Jupyter的Python开发

    基于PyCharm和Jupyter的Python开发Python是一种高级的、面向对象的解释型编程语言,在数据科学、机器学习、Web开发、游戏开发等诸多领域都有广泛的应用。Python的简单易学、高效性、可读性等特点使其成为了一种非常流行的编程语言。而PyCharm和Jupyter则是Python中常用的两个开发环境,其中PyCharm是一款专业的Python集成开发环境,Jupyter则是一种Web应用,可以创建和共享文档,其中包括实时代码、方程式、可视化图表等。

    2024-08-05
    33
  • 使用Python进行正弦函数的计算和图形绘制

    使用Python进行正弦函数的计算和图形绘制Python是一种高级编程语言,用于广泛的应用程序开发领域。Python拥有良好的代码可读性、简单易懂的语法,是很多开发者的首选语言。除此之外,Python还有很多强大的功能,例如可以使用Python进行科学运算和绘图。本文将介绍如何使用Python计算正弦函数,并且通过Python库的支持,利用Python进行正弦函数的图形绘制。

    2023-12-26
    109
  • 【静态数据】民族「建议收藏」

    【静态数据】民族「建议收藏」/* Navicat MySQL Data Transfer Source Server : root Source Server Version : 50717 Source Host : loc…

    2022-12-15
    155

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注