欢迎来到山村网

Linux内核中的信号机制的介绍

2019-03-09 09:28:10浏览:111 来源:山村网   
核心摘要:  应用程序发送信号时,主要通过kill进行。注意:不要被kill迷惑,它并不是发送SIGKILL信号专用函数。这个函数主要通过系统调

  应用程序发送信号时,主要通过kill进行。注意:不要被“kill”迷惑,它并不是发送SIGKILL信号专用函数。这个函数主要通过系统调用sys_kill()进入内核,它接收两个参数:

  第一个参数为目标进程id,kill()可以向进程(或进程组),线程(轻权线程)发送信号,因此pid有以下几种情况:

  ● pid>0:目标进程(可能是轻权进程)由pid指定。

  ● pid=0:信号被发送到当前进程组中的每一个进程。

  ● pid=-1:信号被发送到任何一个进程,init进程(PID=1)和以及当前进程无法发送信号的进程除外。

  ● pid<-1:信号被发送到目标进程组,其id由参数中的pid的绝对值指定。

  第二个参数为需要发送的信号。

  由于sys_kill处理的情况比较多,分析起来比较复杂,我们从太累了入手,这个函数把信号发送到由参数指定pid指定的线程(轻权进程)中。tkill的内核入口是sys_tkill(kernel/signal.c),其定义如下:

  

  asmlinkage long

  sys_tkill(int pid, int sig)

  {

  struct siginfo info;

  int error;

  struct task_struct *p;

  

  if (pid <= 0)//对参数pid进行检查

  return -EINVAL;

  info.si_signo = sig; //根据参数初始化一个siginfo结构

  info.si_errno = 0;

  info.si_code = SI_TKILL;

  info.si_pid = current->tgid;

  info.si_uid = current->uid;

  read_lock(&tasklist_lock);

  p = find_task_by_pid(pid);//获取由pid指定的线程的task_struct结构

  error = -ESRCH;

  if (p) {

  error = check_kill_permission(sig, &info, p);//权限检查

  

  if (!error && sig && p->sighand) {

  spin_lock_irq(&p->sighand->siglock);

  handle_stop_signal(sig, p);

  //对某些特殊信号进程处理,例如当收到SIGSTOP时,需要把信号队列中的SIGCONT全部删除

  error = specific_send_sig_info(sig, &info, p);//把信号加入到信号队列

  spin_unlock_irq(&p->sighand->siglock);

  }

  }

  read_unlock(&tasklist_lock);

  return error;

  }

  sys_tkill函数主要是通过pecific_send_sig_info()函数实现的,下面我们看一下pecific_send_sig_info()(kernel/signal.c)的定义:

  static int

  specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)

  {

  int ret = 0;

  if (!irqs_disabled())

  BUG();

  assert_spin_locked(&t->sighand->siglock);

  if (((unsigned long)info > 2) && (info->si_code == SI_TIMER))

  

  ret = info->si_sys_private;

  

  

  if (sig_ignored(t, sig))

  goto out;

  

  if (LEGACY_QUEUE(&t->pending, sig))

  goto out;

  ret = send_signal(sig, info, t, &t->pending);//实际的发送工作

  if (!ret && !sigismember(&t->blocked, sig))

  signal_wake_up(t, sig == SIGKILL);

  out:

  return ret;

  }

  首先调用sig_ignored检查信号是否被忽略,然后检查发送的信号是不是普通信号,如果是普通信号,就需要根据信号位图来检查当前信号队列中是否已经存在该信号,如果已经存在,对于普通信号不需要做任何处理。然后调用send_signal来完成实际的发送工作,send_signal()是信号发送的重点,除sys_tkill之外的函数,最终都是通过send_signal()来完成信号的发送工作的。

  这里注意到想send_signal()传递的参数时t->pending,也就是连接Private Signal Queue的那条链。最后,如果发送成功就调用signal_wake_up()来唤醒目标进程,这样可以保证该进程进入就绪状态,从而有机会被调度执行信号处理函数。

  现在我们来看看send_signal()(kernel/signal.c)函数,这个函数的主要工作就是分配并初始化一个sigqueue结构,然后把它添加到信号队列中。

  static int send_signal(int sig, struct siginfo *info, struct task_struct *t,

  struct sigpending *signals)

  {

  struct sigqueue * q = NULL;

  int ret = 0;

  

  if ((unsigned long)info == 2)

  goto out_set;

  

  q = __sigqueue_alloc(t, GFP_ATOMIC, (sig < SIGRTMIN &&

  ((unsigned long) info < 2 ||

  info->si_code >= 0)));//分配sigqueue结构

  if (q) {//如果成功分配到sigqueue结构,就把它添加到队列中,并对其初始化

  list_add_tail(&q->list, &signals->list);

  switch ((unsigned long) info) {

  case 0:

  q->info.si_signo = sig;

  q->info.si_errno = 0;

  q->info.si_code = SI_USER;

  q->info.si_pid = current->pid;

  q->info.si_uid = current->uid;

  break;

  case 1:

  q->info.si_signo = sig;

  q->info.si_errno = 0;

  q->info.si_code = SI_KERNEL;

  q->info.si_pid = 0;

  q->info.si_uid = 0;

  break;

  default:

  copy_siginfo(&q->info, info);//拷贝sigqueue结构

  break;

  }

  } else {

  if (sig >= SIGRTMIN && info && (unsigned long)info != 1

  && info->si_code != SI_USER)

  

  return -EAGAIN;

  if (((unsigned long)info > 1) && (info->si_code == SI_TIMER))

  

  ret = info->si_sys_private;

  }

  out_set:

  sigaddset(&signals->signal, sig);//设置信号位图

  return ret;

  }

  从上面的分析可以看出,我们看到信号被添加到信号队列之后,会调用signal_wake_up()唤醒这个进程,signal_wake_up()(kernel/signal.c)的定义如下:

  

  void signal_wake_up(struct task_struct *t, int resume)

  {

  unsigned int mask;

  set_tsk_thread_flag(t, TIF_SIGPENDING);//为进程设置TIF_SIGPENDING标志

  

  mask = TASK_INTERRUPTIBLE;

  if (resume)

  mask |= TASK_STOPPED | TASK_TRACED;

  if (!wake_up_state(t, mask))

  kick_process(t);

  }

  signal_wake_up()首先为进程设置TIF_SIGPENDING标志,说明该进程有延迟的信号要等待处理。然后再调用wake_up_state()唤醒目标进程,如果目标进程在其他的CPU上运行,wake_up_state()将返回0,此时调用kick_process()向该CPU发送一个处理器间中断。当中断返回前戏,会为当前进程处理延迟的信号。

  此后当该进程被调度时,在进程返回用户空间前,会调用do_notify_resume()处理该进程的信号。

(责任编辑:豆豆)
下一篇:

路由器怎么连接台式电脑?

上一篇:

Linux中文件执行中的锁定的怪现象

  • 信息二维码

    手机看新闻

  • 分享到
打赏
免责声明
• 
本文仅代表作者个人观点,本站未对其内容进行核实,请读者仅做参考,如若文中涉及有违公德、触犯法律的内容,一经发现,立即删除,作者需自行承担相应责任。涉及到版权或其他问题,请及时联系我们 xfptx@outlook.com