写给android开发的Linux 信号 - 下篇

2,021 阅读7分钟

前言

本篇是Linux信号的下篇,上篇可见 写给android开发的Linux 信号 - 上篇,本篇主要介绍一些信号阻塞行为与其他发送信号的系统调用,同时也会了解到,android这个运行在linux内核的“庞大应用”是如何处理信号

信号阻塞 - 掩码

我们前文已经说了,信号会由内核投递给每个需要的进程,我们说的阻塞,其实就是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信号

image.png

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 <typename SigactionType>
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<SigactionType>();
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<decltype(action_), SigactionType>) {
        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;
}

这里有一个应用场景就是,上虚拟机如何针对java层的“异常”进行处理的native实现,所以说异常这个概念,真的在虚拟机中做了很多尝试。之前有讲过相关的处理Android性能优化 - 捕获java crash的那些事,因此就不继续了。

最后

最后我们通过本文,应该能了解到相关的信号知识了,同时也明白了android虚拟机对信号做的一个先捕获处理,从而保证了信号会先传递到虚拟机中