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

应用程序发送信号时,主要通过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),其定义如下:

/*

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

(0)

相关推荐

  • Linux文件系统中的inode节点详细介绍

    一、inode是什么? 理解inode,要从文件储存说起。 文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。 操作系统读取硬盘的时候,不会 ...

  • Linux内核中的文件描述符

    Kernel version:2.6.14 CPU architecture:ARM920T 作为文件的使用者,进程理所当然的要将所使用的文件记录于自己的控制块中,也就是task_struct。另外, ...

  • linux系统中scp命令的使用介绍

    scp命令的用处: scp在网络上不同的主机之间复制文件,它使用ssh安全协议传输数据,具有和ssh一样的验证机制,从而安全的远程拷贝文件。 scp命令基本格式: scp [-1246BCpqrv] ...

  • linux系统中zabbix客户端安装配置介绍

    linux系统中zabbix客户端安装配置介绍

  • Linux内核驱动fsync机制实现图解

    在Linux内核中的IO模型基本分为4类: 1、同步阻塞I/O 2、同步非阻塞I/O 3、异步阻塞I/O 4、异步非阻塞I/O 同步:应用显式地通过函数访问数据,在此函数返回时就会得到结果(成功或失败 ...

  • 深入解析Linux系统中的SELinux访问控制功能

    SELinux(Security-Enhanced Linux) 是美国国家安全局(NSA)对于强制访问控制的实现,是 Linux历史上最杰出的新安全子系统.NSA是在Linux社区的帮助下开发了一种 ...

  • 减少Linux内核空循环,降低系统能耗技巧

    如果不花更多的时间看表,你将有更多充裕的时间。 通俗地讲,这就是Linux内核中一个重要变化的基本原理,编程人员希望这一变化能够提高Linux的效率。新版Linux操作系统将采用“tickless”( ...

  • 减少Linux内核空循环 降低系统能耗技巧 1

    如果不花更多的时间看表,你将有更多充裕的时间。 通俗地讲,这就是Linux内核中一个重要变化的基本原理,编程人员希望这一变化能够提高Linux的效率。新版Linux操作系统将采用“tickless”( ...

  • Linux系统中最实用的十大开源防火墙

    如今,开源防火墙可谓数目繁多。本文将涉及十个适合企业需求的最实用的开源防火墙。 1. Iptables Iptables/Netfilter是基于防火墙的最流行的命令行。它是Linux服务器安全的头道 ...