信号机制原理
关键数据结构
POSIX标准定义了sigset_t类型来定义一个信号集,然而sigset_t类型在不同的系统可能有不同的定义方式,因此去猜测sigset_t类型是不明确的,在SylixOS中,将sigset_t定义成了unsigned long long型,也即是一个64位的数据类型,每一位代表了一个信号,最高位不被使用。
SylixOS信号机制的实现离开不了下面几个关键数据结构;
1. 信号上下文结构
typedef struct __sig_context {
sigset_t SIGCTX_sigsetSigBlockMask; /* 当前信号屏蔽位 */
sigset_t SIGCTX_sigsetPending; /* 当前由于被屏蔽无法运行的信号 */
sigset_t SIGCTX_sigsetKill; /* 由 kill 发送但被屏蔽的信号 */
struct sigaction SIGCTX_sigaction[NSIG]; /* 所有的信号控制块 */
LW_LIST_RING_HEADER SIGCTX_pringSigQ[NSIG]; /* 由于被屏蔽无法运行的信号排队*/
stack_t SIGCTX_stack; /* 用户指定的信号堆栈情况 */
LW_CLASS_SIGWAIT *SIGCTX_sigwait; /* 等待信息 */
#if LW_CFG_SIGNALFD_EN > 0
BOOL SIGCTX_bRead; /* 是否在读 signalfd */
sigset_t SIGCTX_sigsetWait; /* 正在等待的 sigset */
LW_SEL_WAKEUPLIST SIGCTX_selwulist; /* signalfd select list */
#endif
} LW_CLASS_SIGCONTEXT;
下面分析这个结构中个成员含义:
- SIGCTX_sigsetSigBlockMask是线程的信号屏蔽字,1的位对应信号将被屏蔽而不递送;
- SIGCTX_sigsetPending包含了因屏蔽而不被递送的信号;
- SIGCTX_sigsetKill中1的位对应了应kill函数发送的信号,此类信号是不排队的;
- SIGCTX_sigaction[NSIG]存放所有信号的控制结构,信号成功安装后将存放到这里;
- SIGCTX_pringSigQ[NSIG]存放所有因为被屏蔽而不能递送且支持排队的信号,也就是非kil函数发送的信号;
- SIGCTX_stack指向用户提供的堆栈;
- SIGCTX_sigwait标示线程等待的信号信息;
最下面3个成员实现了signalfd标准设备,这里不做详细解释。
SylixOS中每一个线程对应了一个信号上下文,信号上下文记录对应了线程的信号信息,如线程等待什么信号,线程屏蔽了什么信号,哪些信号正在排队递送等。
2. 递送信号信息结构
每一种信号在递送到指定线程的时候,都需要一些关键的信息,如信号来源等,下面我们来讲解这种结构的详细信息。
typedef struct {
LW_LIST_RING SIGPEND_ringSigQ; /* 环形链表 */
struct siginfo SIGPEND_siginfo; /* 信号相关信息 */
PLW_CLASS_SIGCONTEXT SIGPEND_psigctx; /* 信号上下文 */
UINT SIGPEND_uiTimes; /* 被阻塞的次数 */
INT SIGPEND_iNotify; /* sigevent.sigev_notify */
} LW_CLASS_SIGPEND;
- SIGPEND_ringSigQ同一种信号将可能产生多次,如果是非kill函数发送将由此链表管理;
- SIGPEND_siginfo包含了信号的信息,如信号名、信号来源、附加信号数据、用户信息等;
- SIGPEND_psigctx回指向信号上下文结构;
- SIGPEND_uiTimes信号被屏蔽期间产生了多次;
- SIGPEND_iNotify信号通知类型,如通知、不通知等。
3. 信号控制信息结构
信号的每一次递送都讲被嵌入到一段堆栈空间中,当线程产生调度时,将处理这些内容,这些内容不但包含了堆栈的返回地址(线程被中断处的地址)还包含了信号的一些信息等,如信号安装函数。
typedef struct {
PVOID SIGCTLMSG_pvStackRet; /* 跳跃返回堆栈的地址 */
INT SIGCTLMSG_iSchedRet; /* 信号调度器返回值 */
INT SIGCTLMSG_iKernelSpace; /* 产生信号是的内核空间情况 */
/* 信号退出时需要返回之前的状态 */
sigset_t SIGCTLMSG_sigsetMask; /* 信号句柄退出需要恢复的掩码 */
struct siginfo SIGCTLMSG_siginfo; /* 信号相关信息 */
errno_t SIGCTLMSG_iLastErrno; /* 保存的原始错误号 */
} LW_CLASS_SIGCTLMSG;
- SIGCTLMSG_pvStackRet包含被信号中断时的地址;
- SIGCTLMSG_iSchedRet标识调度器是信号中断还是正常调度;
- SIGCTLMSG_iKernelSpace内核空间情况;
- SIGCTLMSG_sigsetMask信号之前的屏蔽字;
- SIGCTLMSG_siginfo信号信息,如信号名、产生源等;
- SIGCTLMSG_iLastErrno保存之前的errno值。
当信号处理函数完成返回后,内核将通过调用__sigReturn函数返回到被中断的地址处,并且在返回前,总会检查能够运行还没有运行的信号,这样避免了再次的堆栈开销。
4. 信号等待信息结构
下面的结构标识了线程等待信号的情况。
typedef struct __sig_wait {
sigset_t SIGWT_sigset;
struct siginfo SIGWT_siginfo;
} LW_CLASS_SIGWAIT;
- SIGWT_sigset线程等待的信号集,1位对应了正在等待的信号;
- SIGWT_siginfo是等待信号的信息。
sigwait函数、sigwaitinfo函数、sigtimedwait函数将关心这个结构。