linux内核信号处理

这篇博客详细介绍了Linux内核中信号处理的相关数据结构,包括task_struct、sigset_t、sigpending、sighand_struct等,并分析了如何通过signal()和sigaction()更改信号处理函数。此外,还讲解了发送信号的系统调用如kill()的工作原理,以及信号在用户空间的处理流程,涉及setup_frame()、get_sigframe()等函数。
摘要由CSDN通过智能技术生成
信号是操作系统中一种很重要的通信方式.近几个版本中,信号处理这部份很少有大的变动.我们从用户空间的信号应用来分析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>>):
 
2011年02月26日 - ququ - linux 学习
二:更改信号的处理函数
在用户空间编程的时候,我们常用的注册信号处理函数的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的进程。
pi
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值