惯例分析函数之前先看数据构造
信号相关数据结构
#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;
};
整个数据结构图(书上的)
一些信号产生函数最终还是调用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。