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()处理该进程的信号。
|