linux 内核信号处理概述(send_signal)

惯例分析函数之前先看数据构造

信号相关数据结构

#define _NSIG       64
#define _NSIG_BPW   32

#define _NSIG_WORDS (_NSIG / _NSIG_BPW)

typedef struct {
    unsigned long sig[_NSIG_WORDS];//你猜_NSIG_WORDS这个是多少? 每位表示一个信号
} sigset_t;

struct sigpending {
    struct list_head list; //这个就是挂起信号的链表头,指向的成员就是sigqueue
    sigset_t signal; //表示挂起信号的标志,
}; 

sigset_t 操作函数很有用的,后面分析代码会涉及很多这些操作函数的调用

void sigemptyset(sigset_t *set)和void sigfillset(sigset_t *set)分别是重置为1/0,这个是全部初始化。

void sigaddset(sigset_t *set, int _sig)和void sigaddset(sigset_t *set, int _sig)这两个内联函数大家应该能猜到,就是设置指定位为1/0。

 void sigaddsetmask(sigset_t *set, unsigned long mask)和static inline void sigdelsetmask(sigset_t *set, unsigned long mask) 这两个内联函数根据mask值设置对应的位为1/0。

int sigismember(sigset_t *set, int _sig) 这个就是返回对应位置的信号值也就是判断是否存在sig挂起信号。

sigmask(sig) 信号位索引

signal_pending(struct task_struct *p) 判断指定进程是否有挂起信号,通过TIF_SIGPENDING标志判断,后面说。

recalc_sigpending_tsk(struct task_struct *t)//检查t->pending和t->signal->shared_pending是否有非阻塞信号,如果有则设置TIF_SIGPENDING标志同时返回true。

void recalc_sigpending(void) 如果没有非阻塞的挂起信号以及进程没有冻结(还有个klp条件不管)就清除TIF_SIGPENDING标志。

struct sigqueue {
    struct list_head list;
    int flags;
    siginfo_t info; //信号里面包含的信息
    struct user_struct *user; // 指向用户结构体,后面会说明用处
};

struct signal_struct {
   .....

    /* shared signal handling: */
    struct sigpending   shared_pending;//放未处理的信号链表
  .....

};

struct sigaction {
#ifndef __ARCH_HAS_IRIX_SIGACTION
    __sighandler_t  sa_handler;
    unsigned long   sa_flags;
#else
    unsigned int    sa_flags;
    __sighandler_t  sa_handler;
#endif
#ifdef __ARCH_HAS_SA_RESTORER
    __sigrestore_t sa_restorer;
#endif
    sigset_t    sa_mask;    /* mask last for extensibility */
};


这个sa_flags很有意思:

因为代码一般不会错,错基本发生在错误使用的情况下,所以这个sa_flag要关注

/*  
 * SA_FLAGS values:
 *  
 * SA_NOCLDSTOP     flag to turn off SIGCHLD when children stop.//你想到了啥?
 * SA_NOCLDWAIT     flag on SIGCHLD to inhibit zombies.//你又想到了啥?
 * SA_SIGINFO       deliver the signal with SIGINFO structs//表示传递的信号包含info信息
 * SA_THIRTYTWO     delivers the signal in 32-bit mode, even if the task 
 *          is running in 26-bit.//现在很少用了
 * SA_ONSTACK       allows alternate signal stacks (see sigaltstack(2)).//给信号处理函数执行新创建一个栈
 * SA_RESTART       flag to get restarting signals (which were the default long ago) // 当系统调用被信号中断时候自动重新执行
 * SA_NODEFER       prevents the current signal from being masked in the handler.//当信号处理程序执行的时候不会屏蔽信号
 * SA_RESETHAND     clears the handler when the signal is delivered.//就是当信号处理函数执行一次后恢复该信号的默认行为
 *
 * SA_ONESHOT and SA_NOMASK are the historical Linux names for the Single
 * Unix names RESETHAND and NODEFER respectively.
 */ 
#define SA_NOCLDSTOP    0x00000001
#define SA_NOCLDWAIT    0x00000002
#define SA_SIGINFO  0x00000004
#define SA_THIRTYTWO    0x02000000
#define SA_RESTORER 0x04000000
#define SA_ONSTACK  0x08000000
#define SA_RESTART  0x10000000
#define SA_NODEFER  0x40000000
#define SA_RESETHAND    0x80000000
 

struct k_sigaction {
    struct sigaction sa;
#ifdef __ARCH_HAS_KA_RESTORER
    __sigrestore_t ka_restorer;
#endif
};
 

struct sighand_struct {
    atomic_t        count;//引用计数,当多线程的时候且共享信号处理函数的时候,会count++
    struct k_sigaction  action[_NSIG]; //绑定的fun action
    spinlock_t      siglock; 
    wait_queue_head_t   signalfd_wqh;
};  

整个数据结构图(书上的)

linux 内核信号处理概述(send_signal)_第1张图片

一些信号产生函数最终还是调用send_signal:

send_signal直接调用__send_signal

static int __send_signal(int sig, struct siginfo *info, struct task_struct *t, enum pid_type type, int from_ancestor_ns)

1 参数说明:

   sig就是信号代码。

   info就是发送信号的时候附加信息

  t目标进程task_struct

  pid_type是接受信号对象的类型 

          enum pid_type
         {   
              PIDTYPE_PID,
              PIDTYPE_TGID,
              PIDTYPE_PGID,
              PIDTYPE_SID,
              PIDTYPE_MAX,
       };

 from_ancestor_ns暂时不管当它是0。

函数过程:

 1 上锁t->sighand->siglock。

 2  获取pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending;
 解释下task_struct *t 本身存在一个pending用于私有的信号链表,t->signal->shared_pending则是共享的信号链表,区别我目前想到一个那就是多线程共享信号的时候,则会把信号都串在t->signal->shared_pending上。

3 之前数据结构说过 pending上只是链表头,成员的数据结构是struct sigqueue。所以现在申请个struct sigqueue  q 空间。

4 然后干啥?当然是将q串进pending里啦。

5 有个好玩的东西就是info这个入参存在两种特殊值SEND_SIG_NOINFO和SEND_SIG_PRIV,#define SEND_SIG_NOINFO ((struct siginfo *) 0)

#define SEND_SIG_PRIV   ((struct siginfo *) 1)

也就是0和1,分别表示没有信号附加信息传送,但是0表示这个信号是用户太发的,1表示内核太发送的。当然也会存在附加信息的情况那就直接copy过来,copy_siginfo(&q->info, info);(函数也就是直接调用memcpy(to, from, sizeof(*to));)

6 sigset_t函数上场了,sigaddset(&pending->signal, sig)设置pending->signal对应的位表示存在挂起信号。

7 如果 type类型是PIDTYPE_PGID或者PIDTYPE_SID(这两个看名称猜哈哈哈)类型设置整个组里的进程sigaddset(signal, sig)。

8  好像还有个那个标志没设置吧,嗯不能漏了,complete_signal(sig, t, type),还记得这三个参数表示啥吗?sig表示信号量数值,t是目标结构体,type不用解释了吧。如果目标是单一进程也就是type是PIDTYPE_PID则直接返回。这个函数会将目标是线程组的情况下选择一个线程作为接受目标(条件是信号没被阻塞或者目前正在cpu上运行)调用signal_wake_up处理其实就是设置TIF_SIGPENDING标志,如果信号是SIGKILL则去尝试唤醒进程是一般TASK_INTERRUPTIBLE进程。

本次记录到此为止,后续会继续分析信号响应处理do_signal。        
 

 

你可能感兴趣的:(linux,信号处理,linux,kernel)