linux内核中的信号机制--信号发送

长平狐 发布于 2013/06/03 14:52
阅读 531
收藏 0

linux内核中的信号机制--信号发送

Kernel version:2.6.14

CPU architecture:ARM920T

Author:ce123(http://blog.csdn.net/ce123)


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

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

  • pid>0:目标进程(可能是轻权进程)由pid指定。
  • pid=0:信号被发送到当前进程组中的每一个进程。
  • pid=-1:信号被发送到任何一个进程,init进程(PID=1)和以及当前进程无法发送信号的进程除外。
  • pid<-1:信号被发送到目标进程组,其id由参数中的pid的绝对值指定。
第二个参数为需要发送的信号。

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

/*
 *  Send a signal to only one task, even if it's a CLONE_THREAD task.
 */
asmlinkage long
sys_tkill(int pid, int sig)
{
	struct siginfo info;
	int error;
	struct task_struct *p;

	/* This is only valid for single tasks */
	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);//权限检查
		/*
		 * The null signal is a permissions and process existence
		 * probe.  No signal is actually delivered.
		 */
		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))
		/*
		 * Set up a return to indicate that we dropped the signal.
		 */
		ret = info->si_sys_private;
	/*信号被忽略*/
	/* 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);//实际的发送工作
	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;

	/*
	 * fast-pathed signals for kernel-internal things like SIGSTOP
	 * or SIGKILL.
	 */
	if ((unsigned long)info == 2)
		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.  */

	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)
		/*
		 * Queue overflow, abort.  We may abort if the signal was rt
		 * and sent by user using something other than kill().
		 */
			return -EAGAIN;
		if (((unsigned long)info > 1) && (info->si_code == SI_TIMER))
			/*
			 * Set up a return to indicate that we dropped 
			 * the signal.
			 */
			ret = info->si_sys_private;
	}

out_set:
	sigaddset(&signals->signal, sig);//设置信号位图
	return ret;
}
从上面的分析可以看出,我们看到信号被添加到信号队列之后,会调用signal_wake_up()唤醒这个进程,signal_wake_up()(kernel/signal.c)的定义如下:

/*
 * Tell a process that it has a new active signal..
 *
 * NOTE! we rely on the previous spin_lock to
 * lock interrupts for us! We can only be called with
 * "siglock" held, and the local interrupt must
 * have been disabled when that got acquired!
 *
 * No need to set need_resched since signal event passing
 * goes through ->blocked
 */
void signal_wake_up(struct task_struct *t, int resume)
{
	unsigned int mask;

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

	/*
	 * For SIGKILL, we want to wake it up in the stopped/traced case.
	 * We don't check t->state here because there is a race with it
	 * executing another processor and just now entering stopped state.
	 * By using wake_up_state, we ensure the process will wake up and
	 * handle its death signal.
	 */
	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()处理该进程的信号。


原文链接:http://blog.csdn.net/ce123/article/details/8561831
加载中
返回顶部
顶部