信号是操作系统中一种很重要的通信方式.近几个版本中,信号处理这部份很少有大的变动.我们从用户空间的信号应用来分析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>>):
二:更改信号的处理函数
在用户空间编程的时候,我们常用的注册信号处理函数的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 = ¤t->sighand->action[sig-1];
spin_lock_irq(¤t->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(¤t->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的进程。
pi