Android跨进程通信-信号(Signal)

2021-01-16  本文已影响0人  凯玲之恋

信号的使用及原理

信号实质上是一种软中断,既然是一种中断,就说明信号是异步的,信号接收函数不需要一直阻塞等待信号的到达。
当信号发出后,如果有地方注册了这个信号,就会执行响应函数,如果没有地方注册这个信号,该信号就会被忽略。

#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);  //信号注册函数
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); //信号注册函数
struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
 };
​
int kill(pid_t pid, int sig);  //信号发送函数
int sigqueue(pid_t pid, int sig, const union sigval value);   //信号发送函数
//……

注册信号有两个方法

信号发送

信号发送函数比较多,这里我列举一下。

通过kill -l指令可以查看Android手机支持的信号,从下图可以看到,总共有64个,前31个信号是普通信号,后33个信号是实时信号,实时信号支持队列,可以保证信号不会丢失。

前几个信号的作用

1 SIGHUP 挂起
2 SIGINT 中断
3 SIGQUIT 中断
3 SIGQUIT 退出
4 SIGILL 非法指令
5 SIGTRAP 断点或陷阱指令
6 SIGABRT abort发出的信号
7 SIGBUS 非法内存访问
8 SIGFPE 浮点异常
9 SIGKILL 杀进程信息

当我们调用信号发送函数后,信号是怎么传到注册的方法调用中去的呢?这里以kill()这个信号发送函数讲解一下这个流程。

kill()函数会经过系统调用方法sys_tkill()进入内核,sys_tkill是SYSCALL_DEFINE2这个方法来实现,这个方法的实现是一个宏定义。
我会从这个方法一路往底追踪,这里我会忽略细节实现,只看关键部分的代码。

//文件-->syscalls.h
asmlinkage long sys_kill(int pid, int sig);
​
//文件-->kernel/signal.c
SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
{
  struct kernel_siginfo info;
​
  clear_siginfo(&info);
  info.si_signo = sig;
  info.si_errno = 0;
  info.si_code = SI_USER;
  info.si_pid = task_tgid_vnr(current);
  info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
​
  return kill_something_info(sig, &info, pid);
}
​
static int kill_something_info(int sig, struct kernel_siginfo *info, pid_t pid)
{
  int ret;
​
  if (pid > 0) {  
    ret = kill_pid_info(sig, info, find_vpid(pid));
    return ret;
  }
  ……
}
​
int kill_pid_info(int sig, struct kernel_siginfo *info, struct pid *pid)
{
  ……
  for (;;) {
        error = group_send_sig_info(sig, info, p, PIDTYPE_TGID);
    ……
  }
}
​
int group_send_sig_info(int sig, struct kernel_siginfo *info,
      struct task_struct *p, enum pid_type type)
{
  ……
   ret = do_send_sig_info(sig, info, p, type);
  return ret;
}
​
int do_send_sig_info(int sig, struct kernel_siginfo *info, struct task_struct *p,
      enum pid_type type)
{
  ……
  ret = send_signal(sig, info, p, type);
  return ret;
}
​
static int send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t,
      enum pid_type type)
{
  return __send_signal(sig, info, t, type, from_ancestor_ns);
}

我们从sys_kill函数一路追踪,最终调用了__send_signal函数,我们接着看这个函数的实现。

//文件-->kernel/signal.c
static int __send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t,
      enum pid_type type, int from_ancestor_ns)
{
  ……
out_set:
  signalfd_notify(t, sig);  //将信号发送给监听的fd
  sigaddset(&pending->signal, sig);
  complete_signal(sig, t, type);  //完成信号发送
ret:
  trace_signal_generate(sig, info, t, type != PIDTYPE_PID, result);
  return ret;
}
​
static void complete_signal(int sig, struct task_struct *p, enum pid_type type)
{
  struct signal_struct *signal = p->signal;
  struct task_struct *t;
​
  //寻找处理信号的线程
  if (wants_signal(sig, p))
    t = p;
  else if ((type == PIDTYPE_PID) || thread_group_empty(p))
    return;
  else {
    t = signal->curr_target;
    while (!wants_signal(sig, t)) {
      t = next_thread(t);
      if (t == signal->curr_target)
        return;
    }
    signal->curr_target = t;
  }
​
  //如果是SIGKILL信号,则杀掉线程组
  if (sig_fatal(p, sig) &&
      !(signal->flags & SIGNAL_GROUP_EXIT) &&
      !sigismember(&t->real_blocked, sig) &&
      (sig == SIGKILL || !p->ptrace)) {
    /*
     * This signal will be fatal to the whole group.
     */
    if (!sig_kernel_coredump(sig)) {
      /*
       * Start a group exit and wake everybody up.
       * This way we don't have other threads
       * running and doing things after a slower
       * thread has the fatal signal pending.
       */
      signal->flags = SIGNAL_GROUP_EXIT;
      signal->group_exit_code = sig;
      signal->group_stop_count = 0;
      t = p;
      do {
        task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
        sigaddset(&t->pending.signal, SIGKILL);
        signal_wake_up(t, 1);
      } while_each_thread(p, t);
      return;
    }
  }
  /*
   * The signal is already in the shared-pending queue.
   * Tell the chosen thread to wake up and dequeue it.
   */
  signal_wake_up(t, sig == SIGKILL);
  return;
}

可以看到,信号最终被分发到了监听的fd中,交给了我们注册的函数处理,从最后部分也可以看到,如果是SIGKILL信号,内核会专门处理去杀进程。

信号在Android中的使用场景

那么我们在来看一个Android系统中使用信号的场景:杀进程。从上面部分可以看到,SIGKILL信号是由内核捕获并处理的,我们看一下Android是怎么调用杀进程的信号的吧。

//文件-->Process.java
public static final void killProcess(int pid) {
    sendSignal(pid, SIGNAL_KILL); 
}
​
//文件-->android_util_Process.cpp
void android_os_Process_sendSignal(JNIEnv* env, jobject clazz, jint pid, jint sig) {
    if (pid > 0) {
        //打印Signal信息
        ALOGI("Sending signal. PID: %" PRId32 " SIG: %" PRId32, pid, sig);
        kill(pid, sig);
    }
}

可以看到,当我们调用Process的killProcess函数杀掉某个进程时,最终会调用到native方法kill(),入参sig信号量就是SIGKILL,这个kill()方法,就是我在上面讲的信号量发送函数,最终内核会响应我们的SIGKILL,杀掉进程。

4 信号(Signal)

4.1 什么是信号?

4.2 信号的种类

5713484-b703c7840590eaeb.png
  • 各种代替操作是
  • 忽略信号。随着这一选项的设置,进程将忽略信号的出现。有两个信号不可以被忽略:SIGKILL,它将结束进程:SIGSTOP,它是作业控制机制的一部分,将挂起作业的执行。
  • 恢复信号的默认操作
  • 执行一个预先安排的信号处理函数。进程可以登记特殊的信号处理函数。当进程收到信号时,信号处理函数将像中断服务例程一样被调用,当从信号处理函数返回时,控制被返回给主程序,并且继续正常执行。

那么什么时候检测和响应信号?通常发生在两种情况下:

4.3 信号的本质

4.4 信号来源

4.5 关于信号处理机制的原理(内核角度)

4.6 信号的生命周期

5713484-025dbc4847a838ef.png
上一篇下一篇

猜你喜欢

热点阅读