Android开发—Linux 信号

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

信号本身,其实是对事件发生时对进程的一种通知机制,当然,这种机制并不是只限于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;
  int sa_flags;
  void (*sa_restorer)(void);
};

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

如果我们对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!这点是非常需要注意的!

信号接收进程的行为

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

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

  • 忽略信号,内核将丢弃该信号,信号对进程不产生任何影响
  • 终止进程,也就是执行信号本身的一些,默认行为
  • 进程停止,比如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排查带来困难

信号阻塞 - 掩码

我们前文已经说了,信号会由内核投递给每个需要的进程,我们说的阻塞,其实就是Linux内核内部给每个进程维护一个信号掩码,其实就是一个信号数组,如果发送给进程的信号位于掩码里面,内核就暂时阻塞该信号对进程的传递,直到进程通过调用通知内核移除为止,此时信号会被重新投递

使用sigprocmask调用,能够通知内核在信号掩码中添加/删除信号

int sigprocmask(int __how, const sigset_t* __new_set, sigset_t* __old_set);

  • 第一个参数how,有如下取之,SIG_BLOCK ,将__new_set参数里面的信号集添加到当前信号集中,当前信号掩码集合结果就是new_set&old_set。SIG_UNBLOCK,移除当前信号掩码集合中new_set所设置的信号,当前信号掩码集合结果就是new_set&~old_set。SIG_SETMASK,将new_set所指向的信号集合设置为当前的信号掩码集合,当前信号掩码集合结果就是new_set
  • new_set 与old_set都是一个结构体为sigset_t的指针,我们常用以下方式添加一个信号到set中
sigset_t blockset;
sigemptyset(&blockset);
sigaddset(&blockset,SIGBUS);

当然sigprocmask是属于进程级别的阻塞,我们还可以用pthread_sigmask指定某个线程独立去修改掩码

int pthread_sigmask(int __how, const sigset_t* __new_set, sigset_t* __old_set);

比如常见apm需要监听ANR产生的SIGQUIT信号时,需要解除当前线程中的SIGQUIT掩码(可见android系统应用线程创建时,会把SIGQUIT加入到掩码集合中,这部分之前文章讲过,这里就不详述)

sigemptyset(&mask);
sigaddset(&mask, SIGQUIT);
if (0 != pthread_sigmask(SIG_UNBLOCK, &mask, &old)) {
   //
}

发送信号方式

我们有以下发送信号的方式

int raise(int __signal); 调用者自身
int kill(pid_t __pid, int __signal); 指定进程/进程组
int killpg(int __pgrp, int __signal); 指定进程组
int tgkill(int __tgid, int __tid, int __signal);发送信号给线程/线程组

当然,还有我们用pthread_create 创建时,也可以直接指定pthread_t针对线程发起信号

int pthread_kill(pthread_t __pthread, int __signal);

上面的调用其实就如同字面所示,不过值得注意的是,当进程调用raise的时候,信号会在raise返回前就被发出,因此需要注意,同时由于不需要进程id,所以我们常用raise去模拟发出信号相关的动作

针对线程的信号发出,使用场景也很多,比如在apm中,如果我们想要获取anr之后的trace文件,当捕获完SIGQUIT后可以通过tgkill发出信号给SignalCatcher线程

其他信号补充

这一小节是对信号的补充

同步信号监听

我们通过sigaction进行的信号监听,也被称为异步信号监听,那么有没有同步的信号监听呢?

有的

int sigwait(const sigset_t* __set, int* __signal);

我们可以通过sigwait等相关调用,去执行一个同步等待,比如SignalCatcher线程会一直通过sigwait去等待SIGQUIT信号

Android开发—Linux 信号_第1张图片

abort()

我们这里还特别介绍了abort()这个系统调用,因为它在android源码中频繁用到,abort调用时会产生SIGABRT信号。那么这个调用有什么特别之处吗?还记得我们上文说的传递给进程的信号是有可能被阻塞或者被忽略的,但是abort()调用却不受影响,Linux遵从SUSV3的要求,abort调用必须终止进程(具体实现就是,在SIGABRT信号处理器结束执行时,会把相应信号处理还原成默认信号处理器,如有),除非进程注册了SIGABRT的信号处理器且处理函数还未被返回(完成后也一定会终止进程),因此,我们能够监听到abort系统调用产生的SIGABRT信号,但是如果信号处理函数正常执行完,就会立即终止进程。但是!这里有趣的是,非局部跳转(非本地跳转 setjmp等)是可以抵消abort产生的影响的,非常强大!

android系统对于信号的处理

我们都知道,android系统运行在Linux内核中,其实算是内核的一个“大应用”(可以认为androix虚拟机是linux操作系统的一个程序),那么我们在android系统上运行的我们自己的应用程序,定义的信号处理器会被第一时间执行到吗(地位等同于android系统吗),非常有趣!答案是,我们部分信号,注意是部分,其实是“二手”信号,这个信号其实是由内核 - android虚拟机 - 应用,经过了这么一层转换关系的。

我们可以在FaultManager中看到,这是android虚拟机信号初始化的起点(值得注意的是,这里以android13源码为分析,每个版本有些差异,但是大体流程相同)

void FaultManager::Init() {
    CHECK(!initialized_);
    sigset_t mask;
    sigfillset(&mask);
    sigdelset(&mask, SIGABRT);
    sigdelset(&mask, SIGBUS);
    sigdelset(&mask, SIGFPE);
    sigdelset(&mask, SIGILL);
    sigdelset(&mask, SIGSEGV);

    SigchainAction sa = {
            .sc_sigaction = art_fault_handler,
            .sc_mask = mask,
            .sc_flags = 0UL,
    };
    // 添加信号处理器
    AddSpecialSignalHandlerFn(SIGSEGV, &sa);

    // Notify the kernel that we intend to use a specific `membarrier()` command.
    int result = art::membarrier(MembarrierCommand::kRegisterPrivateExpedited);
    if (result != 0) {
        LOG(WARNING) << "FaultHandler: MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED failed: "
                     << errno << " " << strerror(errno);
    }

    {
        MutexLock lock(Thread::Current(), generated_code_ranges_lock_);
        for (size_t i = 0; i != kNumLocalGeneratedCodeRanges; ++i) {
            GeneratedCodeRange* next = (i + 1u != kNumLocalGeneratedCodeRanges)
                                       ? &generated_code_ranges_storage_[i + 1u]
                                       : nullptr;
            generated_code_ranges_storage_[i].next.store(next, std::memory_order_relaxed);
            generated_code_ranges_storage_[i].start = nullptr;
            generated_code_ranges_storage_[i].size = 0u;
        }
        free_generated_code_ranges_ = generated_code_ranges_storage_;
    }

    initialized_ = true;
}


extern "C" void AddSpecialSignalHandlerFn(int signal, SigchainAction* sa) {
    InitializeSignalChain();

    if (signal <= 0 || signal >= _NSIG) {
        fatal("Invalid signal %d", signal);
    }

    // Set the managed_handler.
    chains[signal].AddSpecialHandler(sa);
    chains[signal].Claim(signal);
}

这里一件事,就是找到sigaction与sigprocmask这两个符号,并放入了相关的结构体


__attribute__((constructor)) static void InitializeSignalChain() {
    static std::once_flag once;
    std::call_once(once, []() {
        lookup_libc_symbol(&linked_sigaction, sigaction, "sigaction");
        lookup_libc_symbol(&linked_sigprocmask, sigprocmask, "sigprocmask");

#if defined(__BIONIC__)
        lookup_libc_symbol(&linked_sigaction64, sigaction64, "sigaction64");
        lookup_libc_symbol(&linked_sigprocmask64, sigprocmask64, "sigprocmask64");
#endif
    });
}

这里的目的其实就是,通过FaultManager,确保了虚拟机能够第一时间先收到感兴趣的信号,先让虚拟机进行处理,之后有需要再发给我们应用定义的信号处理器,之所以需要找到sigaction与sigprocmask,其实就是采用了hook思想,android应用层调用的sigaction会被虚拟机统一替换成自定义的“sigaction” 处理,注意这里的区别,这个不是Linux调用的sigaction

extern "C" int sigaction(int signal, const struct sigaction* new_action,
                         struct sigaction* old_action) {
    InitializeSignalChain();
    return __sigaction(signal, new_action, old_action, linked_sigaction);
}

template 
static int __sigaction(int signal, const SigactionType* new_action,
                       SigactionType* old_action,
                       int (*linked)(int, const SigactionType*,
                       SigactionType*)) {
if (is_signal_hook_debuggable) {
   return 0;
}

// If this signal has been claimed as a signal chain, record the user's
// action but don't pass it on to the kernel.
// Note that we check that the signal number is in range here.  An out of range signal
// number should behave exactly as the libc sigaction.
if (signal <= 0 || signal >= _NSIG) {
   errno = EINVAL;
   return -1;
}

if (chains[signal].IsClaimed()) {
SigactionType saved_action = chains[signal].GetAction();
if (new_action != nullptr) {
真正对真的sigaction预处理
   chains[signal].SetAction(new_action);
}
if (old_action != nullptr) {
   *old_action = saved_action;
}
return 0;
}

// Will only get here if the signal chain has not been claimed.  We want
// to pass the sigaction on to the kernel via the real sigaction in libc.
return linked(signal, new_action, old_action); 调用真正的Linux sigaction的函数
}

void SetAction(const SigactionType* new_action) {
    if constexpr (std::is_same_v) {
        action_ = *new_action;
    } else {
        action_.sa_flags = new_action->sa_flags;
        action_.sa_handler = new_action->sa_handler;
#if defined(SA_RESTORER)
        action_.sa_restorer = new_action->sa_restorer;
#endif
        sigemptyset(&action_.sa_mask);
        memcpy(&action_.sa_mask, &new_action->sa_mask,
               std::min(sizeof(action_.sa_mask), sizeof(new_action->sa_mask)));
    }
    action_.sa_flags &= kernel_supported_flags_;
}

最后完成了内核 - android虚拟机 - 应用 这么一个信号传递机制,当遇到需要虚拟机先处理的信号时,比如SIGSEGV,就会通过FaultManager::HandleFault去处理,通过预先注册的各个Handler去处理

bool FaultManager::HandleFault(int sig, siginfo_t* info, void* context) {
    if (VLOG_IS_ON(signals)) {
        PrintSignalInfo(VLOG_STREAM(signals) << "Handling fault:" << "\n", info);
    }

#ifdef TEST_NESTED_SIGNAL
    // Simulate a crash in a handler.
  raise(SIGSEGV);
#endif

    if (IsInGeneratedCode(info, context)) {
        VLOG(signals) << "in generated code, looking for handler";
        for (const auto& handler : generated_code_handlers_) {
            VLOG(signals) << "invoking Action on handler " << handler;
            if (handler->Action(sig, info, context)) {
                // We have handled a signal so it's time to return from the
                // signal handler to the appropriate place.
                return true;
            }
        }
    }

    // We hit a signal we didn't handle.  This might be something for which
    // we can give more information about so call all registered handlers to
    // see if it is.
    if (HandleFaultByOtherHandlers(sig, info, context)) {
        return true;
    }

    // Set a breakpoint in this function to catch unhandled signals.
    art_sigsegv_fault();
    return false;
}

可以 点击 “此处” 即可 免费获取 学习资料 以及 更多Android学习笔记+源码解析+面试视频

你可能感兴趣的:(android,linux)