linux进程管理之信号处理(1)

浏览: 223 发布日期: 2016-11-28 分类: linux

信号是操作系统中一种很重要的通信方式.近几个版本中,信号处理这部份很少有大的变动.我们从用户空间的信号应用来分析Linux内核的信号实现方式.

  一:信号有关的数据结构

  在task_struct中有关的信号结构:

struct task_struct {
……
//指向进程信号描述符
struct signal_struct *signal;
//指向信号的处理描述符
struct sighand_struct *sighand;

//阻塞信号的掩码
sigset_t blocked, real_blocked;
//保存的信号掩码.当定义TIF_RESTORE_SIGMASK的时候,恢复信号掩码
sigset_t saved_sigmask;   /* To be restored with TIF_RESTORE_SIGMASK */
//存放挂起的信号
struct sigpending pending;

//指定信号处理程序的栈地址
unsigned long sas_ss_sp;
//信号处理程序的栈大小
size_t sas_ss_size;
//反映向一个函数的指针,设备驱动用此来阻塞进程的某些信号
int (*notifier)(void *priv);
//notifier()的参数
void *notifier_data;
//驱动程序通过notifier()所阻塞信号的位图
sigset_t *notifier_mask;
……
}

  Sigset_t的数据结构如下:

//信号位图.
typedef struct {
//在x86中需要64位掩码,即2元素的32位数组
unsigned long sig[_NSIG_WORDS];
} sigset_t;
#define _NSIG   64

#ifdef __i386__
# define _NSIG_BPW 32
#else
# define _NSIG_BPW 64
#endif

#define _NSIG_WORDS  (_NSIG / _NSIG_BPW)
在linux中共有64个信号.前32个为常规信号.后32个为实时信号.实时信号与常规信号的唯一区别就是实时信号会排队等候.

struct sigpending结构如下:

//信号等待队列
struct sigpending {
struct list_head list;
//如果某信号在等待,则该信号表示的位置1
sigset_t signal;
};

  Struct sighand_struct的结构如下:

struct sighand_struct {
//引用计数
atomic_t   count;
//信号向量表
struct k_sigaction action[_NSIG];
spinlock_t     siglock;
wait_queue_head_t signalfd_wqh;
}

  同中断处理一样,每一个信号都对应action中的一个处理函数.

  struct k_sigaction结构如下示:

struct sigaction {
//信号处理函数
__sighandler_t sa_handler;
//指定的信号处理标志
unsigned long sa_flags;
__sigrestore_t sa_restorer;
//在运行处理信号的时候要屏弊的信号
sigset_t sa_mask;   /* mask last for extensibility */
};
 

  Struct signal_struct结构如下:

struct signal_struct {

//共享计数
atomic_t   count;
//线程组内存活的信号
atomic_t   live;

//wait_chldexit:子进程的等待队列
wait_queue_head_t wait_chldexit;   /* for wait4() */

/* current thread group signal load-balancing target: */
//线程组内最使收到信号的进程
struct task_struct *curr_target;

/* shared signal handling: */
//共享信号的等待队列
struct sigpending shared_pending;

/* thread group exit support */
//线程组的终止码
int      group_exit_code;
/* overloaded:
* - notify group_exit_task when ->count is equal to notify_count
* - everyone except group_exit_task is stopped during signal delivery
*  of fatal signals, group_exit_task processes the signal.
*/

//当kill 掉整个线程组的时候使用
struct task_struct *group_exit_task;
//当kill 掉整个线程组的时候使用
int      notify_count;

/* thread group stop support, overloads group_exit_code too */
//当整个线程组停止的时候使用  
int      group_stop_count;
unsigned int    flags; /* see SIGNAL_* flags below */
……
}

上述所讨论的数据结构可以用下图表示(摘自<<Understanding.the.Linux.Kernel>>):

  linux进程管理之信号处理(1)

  二:更改信号的处理函数

  在用户空间编程的时候,我们常用的注册信号处理函数的API有:

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

  两者都可以更改信号.sigaction是Unix后期才出现的接口.这个接口较signal()更为健壮也更为强大:

  Signal()只能为指定的信号设置信号处理函数.而sigaction()不仅可以设置信号处理函数,还可以设置进程的信号掩码.返回设置之前的sigaction结构.sigaction结构在上面已经分析过了.

  这两个用户空间的接口对应的系统调用为别是:

  sys_signal(int sig, __sighandler_t handler)

  sys_sigaction(int sig, const struct old_sigaction __user *act, struct old_sigaction __user *oact)

  我们来分析一下内核是怎么样处理的.sys_signal()代码如下:

asmlinkage unsigned long
sys_signal(int sig, __sighandler_t handler)
{
struct k_sigaction new_sa, old_sa;
int ret;

new_sa.sa.sa_handler = handler;

//SA_ONESHOT:使用了函数指针之后,将其处理函数设为SIG_DEF
//SA_NOMASK: 在执行信号处理的时候,不执行任何信号屏弊

new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK;
//清除信号掩码.表示在处理该信号的时候不要屏弊任何信号
sigemptyset(&new_sa.sa.sa_mask);

ret = do_sigaction(sig, &new_sa, &old_sa);

//如果调用错误,返回错误码.如果成功,返回之前的处理函数
return ret ? ret : (unsigned long)old_sa.sa.sa_handler;
}

 

sys_sigaction()的代码如下:

asmlinkage int
sys_sigaction(int sig, const struct old_sigaction __user *act,
struct old_sigaction __user *oact)
{
struct k_sigaction new_ka, old_ka;
int ret;

//将用户空间的sigaction 拷贝到内核空间
if (act) {
old_sigset_t mask;
if (!access_ok(VERIFY_READ, act, sizeof(*act)) ||
__get_user(new_ka.sa.sa_handler, &act->sa_handler) ||
__get_user(new_ka.sa.sa_restorer, &act->sa_restorer))
return -EFAULT;
__get_user(new_ka.sa.sa_flags, &act->sa_flags);
__get_user(mask, &act->sa_mask);
siginitset(&new_ka.sa.sa_mask, mask);
}

ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL);

//出错,返回错误代码.否则返回信号的sigaction结构
if (!ret && oact) {
if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)) ||
__put_user(old_ka.sa.sa_handler, &oact->sa_handler) ||
__put_user(old_ka.sa.sa_restorer, &oact->sa_restorer))
return -EFAULT;
__put_user(old_ka.sa.sa_flags, &oact->sa_flags);
__put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask);
}

return ret;
}

 

由此可以看出,两个函数最终都会调用do_sigaction()进行处理.该函数代码如下:

int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
{
struct k_sigaction *k;
sigset_t mask;

//sig_kernel_only:判断sig是否为SIGKILL SIGSTOP

//不能为KILL, STOP信号重设处理函数
if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig)))
return -EINVAL;

//取进程的旧k_sigaction
k = &current->sighand->action[sig-1];

spin_lock_irq(&current->sighand->siglock);

// 如果oact不为空,则将其赋给oact .oact参数返回旧的k_sigaction
if (oact)
*oact = *k;

if (act) {

//使SIGKILL SIGSTOP不可屏弊
sigdelsetmask(&act->sa.sa_mask,
sigmask(SIGKILL) | sigmask(SIGSTOP));

//将新的k_siaction赋值到k
*k = *act;
/*
* POSIX 3.3.1.3:
* "Setting a signal action to SIG_IGN for a signal that is
*  pending shall cause the pending signal to be discarded,
*  whether or not it is blocked."
*
* "Setting a signal action to SIG_DFL for a signal that is
*  pending and whose default action is to ignore the signal
*  (for example, SIGCHLD), shall cause the pending signal to
*  be discarded, whether or not it is blocked"
*/

//POSIX标准:
//如果设置的处理为SIG_IGN 或者是SIG_DEL而且是对SIGCONT SIGCHILD SIGWINCH
//进行重设时
//如果有一个或者几个这样的信号在等待,则删除之
if (act->sa.sa_handler == SIG_IGN ||
(act->sa.sa_handler == SIG_DFL && sig_kernel_ignore(sig))) {
struct task_struct *t = current;
sigemptyset(&mask);
sigaddset(&mask, sig);
rm_from_queue_full(&mask, &t->signal->shared_pending);

//如果不是共享信号,在线程中的线程等待队列中将该信号
//删除
do {
rm_from_queue_full(&mask, &t->pending);
t = next_thread(t);
} while (t != current);
}
}

spin_unlock_irq(&current->sighand->siglock);
return 0;
}

 

Rm_from_queue_full()用来将等待队列中的信号删除.并清除等待队列中的位图.代码如下:

static int rm_from_queue_full(sigset_t *mask, struct sigpending *s)
{
struct sigqueue *q, *n;
sigset_t m;


//如果进程接收到了一个信号,但末处理,只是将sigpending->signal简单置位

//在等待队列中无此信号
sigandsets(&m, mask, &s->signal);
if (sigisemptyset(&m))
return 0;

// 删除等待的信号
signandsets(&s->signal, &s->signal, mask);
list_for_each_entry_safe(q, n, &s->list, list) {
//如果该信号就是mask中设置的信号
if (sigismember(mask, q->info.si_signo)) {
//将其脱链并且初始化
list_del_init(&q->list);
//释放对应项
__sigqueue_free(q);
}
}
return 1;
}
上面有关POSIX标准,请自行查阅相关资料.

  三:发送信号

  在用户空间中,我们可以用kill()给指定进程发送相应信号.它在用户空间的定义如下所示:

  int kill(pid_t pid, int signo)

  pid的含义如下所示:

  pid > 0 将信号发送给进程ID为pid的进程。

  pid == 0 将信号发送给其进程组ID等于发送进程的进程组ID,而且发送进程有许可权向其发送信号的所有进程。

  这里用的术语“所有进程”不包括实现定义的系统进程集。对于大多数U N I X系统,系统进程集包括:交换进程(pid 0),init (pid 1)以及页精灵进程(pid 2)。

 

Pid == -1 将信号发送给所有进程.除了swapper(0),init(1)和当前进程pid < 0 将信号发送给其进程组I D等于p i d绝对值,而且发送进程有许可权向其发送信号的所有进程。如上所述一样,“所有进程”并不包括系统进程集中的进程.

  Kill()的系统调用接口为sys_kill():

asmlinkage long
sys_kill(int pid, int sig)
{
struct siginfo info;

//构造一个siginfo
info.si_signo = sig;
info.si_errno = 0;
info.si_code = SI_USER;
info.si_pid = task_tgid_vnr(current);
info.si_uid = current->uid;

return kill_something_info(sig, &info, pid);
}
转到kill_something_info():

static int kill_something_info(int sig, struct siginfo *info, int pid)
{
int ret;
rcu_read_lock();

if (!pid) {

//将信号发送到进程组
ret = kill_pgrp_info(sig, info, task_pgrp(current));
} else if (pid == -1) {

//将信号发送到所有大于1的进程
int retval = 0, count = 0;
struct task_struct * p;

read_lock(&tasklist_lock);
for_each_process(p) {
if (p->pid > 1 && !same_thread_group(p, current)) {
int err = group_send_sig_info(sig, info, p);
++count;
if (err != -EPERM)
retval = err;
}
}
read_unlock(&tasklist_lock);
ret = count ? retval : -ESRCH;
} else if (pid < 0) {
//把信号发送到进程组-pid的所有进程
ret = kill_pgrp_info(sig, info, find_vpid(-pid));
} else {

//将信号发送到pid的进程
ret = kill_pid_info(sig, info, find_vpid(pid));
}
rcu_read_unlock();
return ret;
}

 

假设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;
}

返回顶部