前言

本篇是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信号

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

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虚拟机对信号做的一个先捕获处理,从而确保了信号会先传递到虚拟机中