信号概念与信号处理器的作用
信号本身,其实是对事件发生时对进程的一种通知机制,当然,这种机制并不是只限于Linux系统,其他很多类Unix系统也有,信号的初衷只是通知,比如当硬件异常时,可由硬件本身的错误检测通知到内核,再由内核告诉相关的进程,又或者是,进程间可通过信号本身,去传递一些特有的消息。但是我们在日常开发中经常能够看到,比如nativecrash是由某个信号导致的,比如常见的SIGABRT/SIGSEGV等。但是这个表述,其实是不太准确的,大部分信号(这里特指除了SIGKILL/SIGSTOP)本身其实跟进程crash/exit,本身其实是没有必然关系的,这点需要注意,也是非常容易尝试误会的点。比如我们说,收到了SIGSEGV就会导致进程退出,其实,这只是一个默认的行为,记住,是默认的行为!下面我们列举一下常见的信号默认行为
信号 | 默认行为 |
---|---|
SIGKILL | 确保杀死进程,无法更改默认行为 |
SIGPIPE | 管道相关断开,默认杀死进程 |
SIGSTOP | 确保进程停止,无法更改默认行为,常用于debugger |
SIGSEGV | 无效内存引用,默认杀死进程 |
SIGABRT | 中止进程,android上行为默认也是杀死 |
SIGQUIT | 默认杀死进程,linux上是终端退出,而android用来产生anr |
可以 点击 “此处” 即可 免费获取 学习资料 以及 更多Android学习笔记+源码解析+面试视频
看到这里,我们可能会有疑惑,好像各个信号其实都差不多都是杀死进程。还有就是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