Board logo

标题: linux进程管理之信号处理(转)(3) [打印本页]

作者: yuyang911220    时间: 2015-7-28 10:38     标题: linux进程管理之信号处理(转)(3)

假设pid > 0.转入kill_pid_info().即把信号发送到pid的进程
int kill_pid_info(int sig, struct siginfo *info, struct pid *pid)
{
     int error;
     struct task_struct *p;

     rcu_read_lock();
     if (unlikely(sig_needs_tasklist(sig)))
         read_lock(&tasklist_lock);

     //找到进程号为pid 的进程
     p = pid_task(pid, PIDTYPE_PID);
     error = -ESRCH;
     if (p)
         error = group_send_sig_info(sig, info, p);

     if (unlikely(sig_needs_tasklist(sig)))
         read_unlock(&tasklist_lock);
     rcu_read_unlock();
     return error;
}
在这里将pid转化为对应的task_struct.然后调用group_send_sig_info().代码如下:
int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
     unsigned long flags;
     int ret;

     //检查是否有权限发送信号
     ret = check_kill_permission(sig, info, p);

     if (!ret && sig) {
         ret = -ESRCH;

              //为了防止竞争.加锁
         if (lock_task_sighand(p, &flags)) {
              //发送信号
              ret = __group_send_sig_info(sig, info, p);

              //解锁
              unlock_task_sighand(p, &flags);
         }
     }

     return ret;
}
首先,要给进程发送信号,应该先判断它是否具有这样的权限.判断的依据为:
如果是用户空间发送的信号,检查其是否有相应的权限
必须要满足以下几个条件中的任一个才可以发送:
1:发送信号者必须拥有相关的权能
2: 如果是发送SIGCONT且发送进程与种目标进程处于同一个注册会话中
3:属于同一个用户的进程
转入__group_send_sig_info():
int
__group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
     int ret = 0;

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

     //对会引起进程停止的进程进行一些特定的处理
     handle_stop_signal(sig, p);

     /* Short-circuit ignored signals.  */

     //判断信号是不是被忽略
     if (sig_ignored(p, sig))
         return ret;

     //如果不是一个RT信号,且等待队列中已经有这个信号了,返回即可
     //TODO: 常规信号是不会排队的
     if (LEGACY_QUEUE(&p->signal->shared_pending, sig))
         /* This is a non-RT signal and we already have one queued.  */
         return ret;

     /*
      * Put this signal on the shared-pending queue, or fail with EAGAIN.
      * We always use the shared queue for process-wide signals,
      * to avoid several races.
      */
     ret = send_signal(sig, info, p, &p->signal->shared_pending);
     if (unlikely(ret))
         return ret;

     //唤醒该进程对该信号进行处理
     //如果该进程对此信号进行了屏弊,则选择线程组中一个合适的进程来唤醒
     __group_complete_signal(sig, p);
     return 0;
}

具体的进程发送过程是在send_signal()完成的.它的代码如下:

static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
              struct sigpending *signals)
{
     struct sigqueue * q = NULL;
     int ret = 0;

     /*
      * Deliver the signal to listening signalfds. This must be called
      * with the sighand lock held.
      */

     //选择编译函数
     signalfd_notify(t, sig);

     /*
      * fast-pathed signals for kernel-internal things like SIGSTOP
      * or SIGKILL.
      */
     if (info == SEND_SIG_FORCED)
         goto out_set;

     /* Real-time signals must be queued if sent by sigqueue, or
        some other real-time mechanism.  It is implementation
        defined whether kill() does so.  We attempt to do so, on
        the principle of least surprise, but since kill is not
        allowed to fail with EAGAIN when low on memory we just
        make sure at least one signal gets delivered and don't
        pass on the info struct.  */

     //分配一个sigqueue
     q = __sigqueue_alloc(t, GFP_ATOMIC, (sig < SIGRTMIN &&
                            (is_si_special(info) ||
                             info->si_code >= 0)));
     if (q) {

         //将分配的sigqueue 加入等待队列
         list_add_tail(&q->list, &signals->list);
         switch ((unsigned long) info) {
         case (unsigned long) SEND_SIG_NOINFO:
              q->info.si_signo = sig;
              q->info.si_errno = 0;
              q->info.si_code = SI_USER;
              q->info.si_pid = task_pid_vnr(current);
              q->info.si_uid = current->uid;
              break;
         case (unsigned long) SEND_SIG_PRIV:
              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);
              break;
         }
     } else if (!is_si_special(info)) {
         if (sig >= SIGRTMIN && info->si_code != SI_USER)
         /*
          * Queue overflow, abort.  We may abort if the signal was rt
          * and sent by user using something other than kill().
          */
              return -EAGAIN;
     }

out_set:
     //更新等待队列的signal 位图,表示收到了一个信号,但没有处理
     sigaddset(&signals->signal, sig);
     return ret;
}
经过这个过程,我们看到了进程怎么将信号发送到另外的进程.特别要注意的是,目标进程接收到信号之后会将其唤醒.这时如果目标进程是系统调用阻塞状态就会将它的系统调用中断.
另外,内核经常使用force_sig_info()/force_sig()来给进程发送信号.这样的信号经常不可以忽略,不可以阻塞.我们来看一下它的处理.代码如下:
int
force_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
     unsigned long int flags;
     int ret, blocked, ignored;
     struct k_sigaction *action;

     spin_lock_irqsave(&t->sighand->siglock, flags);

     //取进程的信号的处理函数
     action = &t->sighand->action[sig-1];

     //如果该信号被忽略或者该信号被阻塞
     ignored = action->sa.sa_handler == SIG_IGN;
     blocked = sigismember(&t->blocked, sig);
     if (blocked || ignored) {
         //重信号处理函数为默认的处理
         action->sa.sa_handler = SIG_DFL;

         //如果信号被屏弊
         if (blocked) {
              //清除信号屏弊位
              sigdelset(&t->blocked, sig);
              //重新计算进程是否有末处理的信号
              recalc_sigpending_and_wake(t);
         }
     }

     //"特殊"的信号发送
     ret = specific_send_sig_info(sig, info, t);
     spin_unlock_irqrestore(&t->sighand->siglock, flags);

     return ret;
}
当进程的信号阻塞标志被更改时,就会引起TIF_SIGPENDING标志的变化.对于TIF_SIGPENDING标志的检测是在recalc_sigpending_and_wake()调用recalc_sigpending_tsk()来完成的.它实际是判断等待队列中是否有没有被阻塞的信号.如果有,则设置TIF_SIGPENDING标志.

specific_send_sig_info()内核用于将信号发送到进程.我们比较一下它跟用户空间的发送有什么不同.它的代码如下:
static int
specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
     int ret = 0;

     BUG_ON(!irqs_disabled());
     assert_spin_locked(&t->sighand->siglock);

     /* Short-circuit ignored signals.  */

     //信号被忽略,退出
     if (sig_ignored(t, sig))
         goto out;

     /* Support queueing exactly one non-rt signal, so that we
        can get more detailed information about the cause of
        the signal. */

     //如果不是实时信号,且已经有信号在等待队列中了.直接等待(不排队)
     if (LEGACY_QUEUE(&t->pending, sig))
         goto out;
     //将信号发送到目标进程
     ret = send_signal(sig, info, t, &t->pending);
     // TODO: 这里调用signal_wake_up()直接唤醒进程
     if (!ret && !sigismember(&t->blocked, sig))
         signal_wake_up(t, sig == SIGKILL);
out:
     return ret;
}
这样,内核就将信号传送给目标进程.无论进程用什么样的方式,都不能阻止对此信号的处理.




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0