应用程序发送信号时,主要通过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()处理该进程的信号。