SylixOS 下的信号系统
1、概述
1.1 信号系统
信号是一种软中断,用来通知进程或者线程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程或者线程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是一种异步通信机制,一个进程或者线程不必通过任何操作来等待信号的到达,事实上,进程或者线程也不知道信号到底什么时候到达。进程之间可以互相 kill 函数发送信号。内核也可以因为内部事件而给进程或者线程发送信号,通知进程或者线程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。
很多条件可以产生信号:
- 当用户按某些键时,引发终端产生信号,例如: Ctrl+C 产生 SIGINT 信号;
- alarm 函数设置的定时器超时后产生 SIGALRM 信号;
- 子进程退出或被异常终止后产生 SIGCHLD 信号;
- 访问非法内存产生 SIGSEGV 信号;
- 用户可以调用 kill 命令将信号发送给其他进程,常用此命令终止一个失控的后台进程
在某信号发生时,可以告诉内核按下列 3 种方式之一进行处理:
- 忽略信号:大多数信号都可使用这种方式进行处理,但有两种信号不能被忽略。它们是
SIGKILL
和SIGSTOP
。这两种信号不能被忽略的原因是:它们向内核提供了是进程终止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(如非法内存访问)则进程的运行行为是未定义的 - 捕捉信号:为了做到这一点,要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户想要的动作。例如,捕捉到
SIGALRM
信号后,用户可在相应的处理函数中去控制某个线程。如果捕捉到SIGCHLD
信号,则表示一个子进程已经终止,所以此信号的捕捉函数可以调用waitpid
以取得该子进程的退出状态。又例如,如果进程创建了临时文件,那么可能要为SIGTERM
信号编写一个信号捕捉函数以清除临时文件(SIGTERM
是终止信号,kill 命令传送的系统默认信号是终止信号)。注意,不能捕捉SIGKILL
和SIGSTOP
信号。 - 执行系统默认动作:对大多数信号的系统默认动作是终止该进程。
1.2 不可靠信号与可靠信号
在早期的 UNIX 版本中,信号是不可靠的,也就是说,信号可能会丢失,这通常会表现为:
- 信号不会排队
- 同一类型信号在处理前如果多次到达,内核只保留一次,多余的丢失
- 信号处理函数执行时,若再次收到相同信号,可能会丢失或覆盖
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。信号支持排队,不会丢失。
进程/线程可以屏蔽(或者说阻塞)信号,如果信号在被屏蔽期间,信号产生了并且对该信号的动作是系统默认或者捕捉,则此信号将保持为未决状态,直到该进程/线程对此信号解除屏蔽,或者设置信号动作为忽略。
如果在进程/线程解除对某个信号的屏蔽之前,这种信号发生了多次, SylixOS 内核将有两种对待方法,一种是 SI_KILL
方式产生的信号将只递送一次,也即信号不会排队,另一种是非 SI_KILL
方式产生的信号将递送多次,也即信号产生了排队。
SylixOS 内核实现中,如果多个不同信号从屏蔽状态被解除时,则优先递送信号数字小的信号。由此可见, SylixOS 的信号机制摒弃了之前不可靠的信号机制,只要是非 SI_KILL
方式产生的信号,都将会排队。
2、信号的屏蔽
信号为什么需要 “屏蔽”?
屏蔽信号的意义在于 “延迟处理”,而不是彻底无效化。常见用途:
- 临界区保护
- 例如进程正在更新全局数据结构时,不希望 handler 中途打断造成数据不一致。
- 就可以在进入关键区时 sigprocmask(SIG_BLOCK, …),退出时再解除。
- 可控时机处理
- 程序可能希望在某个安全点批量处理信号,而不是随时被打断。
- 多线程环境下控制
- 信号投递到进程时,具体由哪个线程处理,和该线程的 mask 有关。
- 可以通过在某个专门线程里解除屏蔽,让它作为 signal-handling thread 专门收信号。
对比:屏蔽 vs 忽略
- 忽略 (ignore) = 信号彻底没用了。
- 屏蔽 (block) = 信号还是有作用,只是暂时不递送,等解除屏蔽时会补发。
SylixOS 可以通过 sigprocmask
函数对某个信号集内的信号进行屏蔽。在信号被屏蔽的期间,进程对所屏蔽的大部分信号不会进行响应,只有解除屏蔽后才会响应。
当然,也不是所有信号都可以被屏蔽。SylixOS 可以设置进程屏蔽任意的信号,但是某些信号即使被设置屏蔽也无法生效,无法被屏蔽的信号如下表所示。
信号名 | 信号描述 |
---|---|
SIGKILL | 强迫进程结束 |
SIGABRT | 异常结束 |
SIGSEGV | 无效内存引用 |
SIGFPE | 协处理器出错 |
SIGILL | 非法指令 |
SIGBUS | bus error |
3、信号的安装
3.1 signal
SylixOS 信号机制中最简单的接口是 signal 函数:
#include <signal.h>
void (*signal(int iSigNo, void (*pfuncHandler)(int)))(int);
函数 signal 原型分析:
- 参数 iSigNo 是 0-63 中的任一信号 number
- 参数 pfuncHandler 是要安装的信号函数或常量 SIG_IGN、常量 SIG_DFL
注意,这个信号,是被安装到当前线程中的!
3.2 sigaction
sigaction 函数检查或修改与指定信号相关联的处理动作。此函数取代了 signal 函数,在 SylixOS 中 signal 函数通过调用 sigaction 函数实现。
#include <signal.h>int sigaction(int iSigNo,const struct sigaction *psigactionNew,struct sigaction *psigactionOld);
- 参数 iSigNo 是 0-63 中的任一信号 number
- 参数 psigactionNew 是新的信号处理结构
- 输出参数 psigactionOld 保存之前的处理结构
sigaction 函数使用下面的结构来检查或修改指定信号相关联的处理动作:
struct sigaction {union {PSIGNAL_HANDLE _sa_handler;PSIGNAL_HANDLE_ACT _sa_sigaction;} _u; /* 信号服务函数句柄 */sigset_t sa_mask; /* 执行时的信号屏蔽码 */INT sa_flags; /* 该句柄处理标志 */PSIGNAL_HANDLE sa_restorer; /* 恢复处理函数指针 */
};
sigaction 结构,保存在 __sig_context 信号上下文中的 SIGCTX_sigaction 变量中。可以看到 SIGCTX_sigaction 是一个结构体数组,数组下标就是信号的 numer。
/*********************************************************************************************************信号线程上下文
*********************************************************************************************************/typedef struct __sig_context {sigset_t SIGCTX_sigsetMask; /* 当前信号屏蔽位 */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 > 0BOOL SIGCTX_bRead; /* 是否在读 signalfd */sigset_t SIGCTX_sigsetFdw; /* 正在等待的 sigset */LW_SEL_WAKEUPLIST SIGCTX_selwulist; /* signalfd select list */
#endif
} LW_CLASS_SIGCONTEXT;
typedef LW_CLASS_SIGCONTEXT *PLW_CLASS_SIGCONTEXT;
SylixOS 中每一个线程对应了一个信号上下文,信号上下文记录对应了线程的信号信息,如线程等待什么信号,线程屏蔽了什么信号,哪些信号正在排队递送等。
4、信号的发送
SylixOS 的信号机制分为 3 类发送信号方法:kill 类型、队列类型和事件类型,这 3 中类型分别由 _doKill
、_doSigQueue
、_doSigEvent
函数实现,上层所有的发送信号函数都直接或者间接地通过调用这几个函数来实现,而这 3 个函数中,_doKill
设置信号源为 SI_KILL
、_doSigQueue
设置信号源为 SI_QUEUE
、_doSigEvent
设置信号源分为:SI_TIMER
、SI_ASYNCIO
等。
4.1 相关结构讲解
除了上一章节提到的 sigaction 、__sig_context 结构,SylixOS 信号机制的实现离开不了下面几个关键数据结构:
递送信号信息结构:
每一种信号在递送到指定线程的时候,都需要一些关于该信号的一些关键信息,如信号来源等,下面我们来讲解这种结构的详细信息。
/*********************************************************************************************************信号阻塞等待处理的信息
*********************************************************************************************************/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;
typedef LW_CLASS_SIGPEND *PLW_CLASS_SIGPEND;
- SIGPEND_ringSigQ 同一种信号将可能产生多次,如果是非 kill 函数发送将由此链表管理
- SIGPEND_siginfo 包含了信号的信息,如信号名、信号来源、附加信号数据、用户信息等
- SIGPEND_psigctx 回指向信号上下文结构
- SIGPEND_uiTimes 信号被屏蔽期间产生了多次
- SIGPEND_iNotify 信号通知类型,如通知、不通知等
信号控制信息结构:
信号的每一次递送都讲被嵌入到一段堆栈空间中,当线程产生调度时,将处理这些内容,这些内容不但包含了堆栈的返回地址(线程被中断处的地址)还包含了信号的一些信息等,如信号安装函数。
/*********************************************************************************************************SIGNAL CONTRL MESSAGE (每产生一个信号 _doSignal() 都会自动生成一个以下结构数据, 存放在堆栈间隙)
*********************************************************************************************************/typedef struct {ARCH_REG_CTX SIGCTLMSG_archRegCtx; /* 寄存器上下文 */INT SIGCTLMSG_iSchedRet; /* 信号调度器返回值 */INT SIGCTLMSG_iKernelSpace; /* 产生信号是的内核空间情况 *//* 信号退出时需要返回之前的状态*/sigset_t SIGCTLMSG_sigsetMask; /* 信号句柄退出需要恢复的掩码 */struct siginfo SIGCTLMSG_siginfo; /* 信号相关信息 */ULONG SIGCTLMSG_ulLastError; /* 保存的原始错误号 */UINT8 SIGCTLMSG_ucWaitTimeout; /* 保存的原始 timeout 标记 */UINT8 SIGCTLMSG_ucIsEventDelete; /* 事件是否被删除 */#if LW_CFG_CPU_FPU_EN > 0LW_FPU_CONTEXT *SIGCTLMSG_pfpuctx; /* FPU 上下文 */
#endif /* LW_CFG_CPU_FPU_EN > 0 */#if LW_CFG_CPU_DSP_EN > 0LW_DSP_CONTEXT *SIGCTLMSG_pdspctx; /* DSP 上下文 */
#endif /* LW_CFG_CPU_DSP_EN > 0 */
} LW_CLASS_SIGCTLMSG;
typedef LW_CLASS_SIGCTLMSG *PLW_CLASS_SIGCTLMSG;
当信号处理函数完成返回后,内核将通过调用 __sigReturn 函数返回到被中断的地址处,并且在返回前,总会检查能够运行还没有运行的信号,这样避免了再次的堆栈开销。
信号等待信息结构:
/*********************************************************************************************************信号等待信息
*********************************************************************************************************/typedef struct __sig_wait {sigset_t SIGWT_sigset;struct siginfo SIGWT_siginfo;
} LW_CLASS_SIGWAIT;
- SIGWT_sigset 线程等待的信号集,1 位对应了正在等待的信号
- SIGWT_siginfo 是等待信号的信息
4.2 非排队信号
SylixOS 可以通过下面函数发送非排队信号,这意味着,如果发送的信号在线程的信号屏蔽字中(信号被屏蔽了),此时该信号被发送了多次,那么当此信号被取消屏蔽时,将只被递送一次:
LW_API INT kill(LW_OBJECT_HANDLE ulId, INT iSigNo);
- ulId :发送信号的目标线程
- iSigNo:发送的信号
kill()+-> _doKill()+-> _doSignal+-> __sigMakeReady+-> __sigCtlCreate+-> archTaskCtxCreate+-> archTaskCtxSetFp
kill
函数中,需要关注的几个点:
LW_API
INT kill (LW_OBJECT_HANDLE ulId, INT iSigNo)
{
......
//如果 kill 的是自己,就会 kill 工作加入到内核工作队列if (LW_CPU_GET_CUR_NESTING() || (ulId == API_ThreadIdSelf())) {_excJobAdd((VOIDFUNCPTR)kill, (PVOID)ulId, (PVOID)(LONG)iSigNo, 0, 0, 0, 0);return (ERROR_NONE);}//将信号发送的目标线程(如果 kill 的是进程,则该线程为其主线程),强行停止
#if LW_CFG_SMP_EN > 0if (LW_NCPUS > 1) { /* 正工作在 SMP 多核模式 */if (API_ThreadStop(ulId)) {return (PX_ERROR);}}
#endif //如果是 SIGKILL 信号,则直接进行特殊处理(如果 kill 的是进程,则这里停止进程内的除主线程外的所有线程)
#if LW_CFG_MODULELOADER_EN > 0if ((iSigNo == SIGKILL) && (pid > 0)) {vprocKillPrepare(pid, ulId); /* 进程 KILL 预处理 */}
#endif
......
#if LW_CFG_SIGNALFD_EN > 0sendval = _doKill(ptcb, iSigNo); /* 产生信号 */
#else_doKill(ptcb, iSigNo); /* 产生信号 */
#endif//这里会恢复刚刚被停止的线程(这里要注意,虽然恢复的是刚刚被停止的线程,但是线程上下文 "可能" 已经被替换掉了!下面会详细讲解)
#if LW_CFG_SMP_EN > 0if (LW_NCPUS > 1) { /* 正工作在 SMP 多核模式 */_ThreadContinue(ptcb, LW_FALSE); /* 在内核状态下唤醒被停止线程 */}
#endif //这里是真正产生调度的地方 /* LW_CFG_SMP_EN */__KERNEL_EXIT(); /* 退出内核 */......
}
_doKill
函数,创建了 sigpend
,并初始化了 sigpend
和 psiginfo
结构
LW_SEND_VAL _doKill (PLW_CLASS_TCB ptcb, INT iSigNo)
{struct siginfo *psiginfo;PLW_CLASS_TCB ptcbCur;LW_CLASS_SIGPEND sigpend; /* 由于是 kill 调用, 所以绝对 *//* 不会产生队列, 这里不用初始化*//* 相关的链表 */LW_TCB_GET_CUR_SAFE(ptcbCur);psiginfo = &sigpend.SIGPEND_siginfo;psiginfo->si_signo = iSigNo;psiginfo->si_errno = errno;psiginfo->si_code = SI_KILL; /* 不可排队 */psiginfo->si_pid = __tcb_pid(ptcbCur);psiginfo->si_uid = ptcbCur->TCB_uid;psiginfo->si_int = EXIT_FAILURE; /* 默认信号参数 */sigpend.SIGPEND_iNotify = SIGEV_SIGNAL;MONITOR_EVT_LONG2(MONITOR_EVENT_ID_SIGNAL, MONITOR_EVENT_SIGNAL_KILL, ptcb->TCB_ulId, iSigNo, LW_NULL);return (_doSignal(ptcb, &sigpend)); /* 产生信号 */
}
_doSignal
函数需要关注的点:
LW_SEND_VAL _doSignal (PLW_CLASS_TCB ptcb, PLW_CLASS_SIGPEND psigpend)
{......//根据 tcb 获取目标线程的信号上下文psigctx = _signalGetCtx(ptcb);//如果是等待信号,则修改目标线程状态、返回if (psigctx->SIGCTX_sigwait) { /* 目标线程在等待信号 */if (psigctx->SIGCTX_sigwait->SIGWT_sigset & __sigmask(iSigNo)) {/* 属于关心的信号 */psigctx->SIGCTX_sigwait->SIGWT_siginfo = psigpend->SIGPEND_siginfo;__sigMakeReady(ptcb, iSigNo, &iSchedRet, LW_SIGNAL_EINTR); /* 就绪任务 */psigctx->SIGCTX_sigwait = LW_NULL; /* 删除等待信息 */return (SEND_INFO);}}......__sigMakeReady(ptcb, iSigNo, &iSchedRet, iSaType); /* 强制进入就绪状态 */__sigCtlCreate(ptcb, psigctx, psiginfo, iSchedRet, &sigsetOld); /* 创建信号上下文环境 */return (SEND_OK);
}
__sigMakeReady
函数需要关注的点:
static VOID __sigMakeReady (PLW_CLASS_TCB ptcb, INT iSigNo,INT *piSchedRet,INT iSaType)
{......//如果目标线程在唤醒队列中,则从等待队列删除,并修改线程状态 TCB_usStatus(__DEL_FROM_WAKEUP_LINE 函数中去做修改)ppcb = _GetPcb(ptcb); /* 获得优先级控制块 */if (ptcb->TCB_usStatus & LW_THREAD_STATUS_DELAY) { /* 存在于唤醒队列中 */__DEL_FROM_WAKEUP_LINE(ptcb); /* 从等待链中删除 */ptcb->TCB_ulDelay = 0ul;*piSchedRet = iSaType; /* 设置调度器返回值 */}......if (ptcb->TCB_usStatus & LW_THREAD_STATUS_PEND_ANY) { /* 检查是否在等待事件 */......//将一个线程从事件等待队列中解锁
#if (LW_CFG_EVENT_EN > 0) && (LW_CFG_MAX_EVENTS > 0)if (ptcb->TCB_peventPtr) {_EventUnQueue(ptcb);} else
#endif /* (LW_CFG_EVENT_EN > 0) && */{
#if (LW_CFG_EVENTSET_EN > 0) && (LW_CFG_MAX_EVENTSETS > 0)if (ptcb->TCB_pesnPtr) {_EventSetUnQueue(ptcb->TCB_pesnPtr);} else
#endif /* (LW_CFG_EVENTSET_EN > 0) && */if (__VUTEX_IS_WAITING(ptcb)) { /* 等待变量条件 */_VutexUnQueue(ptcb);}}......}......//将目标线程加入就绪队列if (__LW_THREAD_IS_READY(ptcb)) {ptcb->TCB_ucSchedActivate = LW_SCHED_ACT_INTERRUPT;__ADD_TO_READY_RING(ptcb, ppcb); /* 加入就绪环 */}......
}
__sigCtlCreate
函数是重点。该函数,会去创建、切换成目标线程的寄存器上下文(寄存器上下文中包含了 PC)。切换后,正常返回,等待系统调度目标线程。
static VOID __sigCtlCreate (PLW_CLASS_TCB ptcb,PLW_CLASS_SIGCONTEXT psigctx,struct siginfo *psiginfo,INT iSchedRet,sigset_t *psigsetMask)
{......//获取该线程栈的结束地址pucStkNow = (BYTE *)archCtxStackEnd(&ptcb->TCB_archRegCtx);......//保存目标线程的寄存器上下文psigctlmsg->SIGCTLMSG_archRegCtx = ptcb->TCB_archRegCtx;......//为目标线程创建新的寄存器上下文,pc 指针指向的就是 __sigShell。从这里也就知道了,目标线程的寄存器上下文已经被切换了/** ptcb->TCB_archRegCtx:要创建的寄存器上下文(这里是作为出参,不是入参!!!)* __sigShell:PC 指针 psigctlmsg:入口参数 ptcb:任务控制块* pucStkNow:初始化堆栈起点 ptcb->TCB_ulOption:任务创建选项*/pstkSignalShell = archTaskCtxCreate(&ptcb->TCB_archRegCtx,(PTHREAD_START_ROUTINE)__sigShell,(PVOID)psigctlmsg,ptcb, (PLW_STACK)pucStkNow,ptcb->TCB_ulOption); /* 建立信号外壳环境 *///调用 __sigShell,可以理解为一次函数调用。所以需要保存 fp 和 lr(入栈保存),供后面 __sigShell 函数返回使用archTaskCtxSetFp(pstkSignalShell,&ptcb->TCB_archRegCtx,&psigctlmsg->SIGCTLMSG_archRegCtx); /* 保存 fp, 使 callstack 正常 */_StackCheckGuard(ptcb); /* 堆栈警戒检查 */__KERNEL_SPACE_SET2(ptcb, 0); /* 信号句柄运行在任务状态下 */
}
__sigShell
,号外壳环境,主要完成以下两个功能:
- 执行信号对应的句柄
- 从信号上下文返回,同时恢复目标线程源寄存器上下文
static VOID __sigShell (PLW_CLASS_SIGCTLMSG psigctlmsg)
{
......__sigRunHandle(psigctx, iSigNo, psiginfo, psigctlmsg); /* 运行信号句柄 */__sigReturn(psigctx, ptcbCur, psigctlmsg); /* 信号返回 */
......
}
__sigRunHandle
函数,信号运行已经安装的句柄(可能是用户句柄,也可能是内核句柄)
static VOID __sigRunHandle (PLW_CLASS_SIGCONTEXT psigctx, INT iSigNo, struct siginfo *psiginfo, PLW_CLASS_SIGCTLMSG psigctlmsg)
{......psigaction = &psigctx->SIGCTX_sigaction[__sigindex(iSigNo)];pfuncHandle = (VOIDFUNCPTR)psigaction->sa_handler; /* 获得信号执行函数句柄 */......
#if LW_CFG_MODULELOADER_EN > 0 /* 进程 KILL 不执行安装句柄 */if (iSigNo == SIGKILL && __LW_VP_GET_TCB_PROC(ptcbCur)) {__signalKillHandle(ptcbCur, iSigNo, psiginfo); /* 立即退出 */} else
#endif /* LW_CFG_MODULELOADER_EN > 0 */{if ((pfuncHandle != SIG_IGN) &&(pfuncHandle != SIG_ERR) &&(pfuncHandle != SIG_DFL) &&(pfuncHandle != SIG_CATCH) &&(pfuncHandle != SIG_HOLD)) { /* 需要执行用户句柄 */pvCtx = (psigctlmsg)? &psigctlmsg->SIGCTLMSG_archRegCtx: LW_NULL;if (psigaction->sa_flags & SA_SIGINFO) { /* 需要 siginfo_t 信息 */LW_SOFUNC_PREPARE(pfuncHandle);pfuncHandle(iSigNo, psiginfo, pvCtx); /* 执行信号句柄 */} else {LW_SOFUNC_PREPARE(pfuncHandle);pfuncHandle(iSigNo, pvCtx); /* XXX 是否传入 pvCtx 参数 ? */}if (__SIGNO_MUST_EXIT & __sigmask(iSigNo)) { /* 必须退出 */__signalExitHandle(ptcbCur, iSigNo, psiginfo);} else if (iSigNo == SIGCNCL) { /* 线程取消信号 */__signalCnclHandle(ptcbCur, iSigNo, psiginfo);}} else { /* 其他处理 */switch (iSigNo) { /* 默认处理句柄 */case SIGINT:case SIGQUIT:case SIGFPE:case SIGKILL:case SIGBUS:case SIGTERM:case SIGABRT:case SIGILL:case SIGSEGV:case SIGSYS:__signalExitHandle(ptcbCur, iSigNo, psiginfo);break;case SIGSTOP:__signalStopHandle(ptcbCur, iSigNo, psiginfo);break;case SIGTSTP:if (pfuncHandle == SIG_DFL) {__signalStopHandle(ptcbCur, iSigNo, psiginfo);}break;case SIGCHLD:if (pfuncHandle == SIG_IGN) { /* IGN 时自动回收 */if ((psiginfo->si_code == CLD_EXITED) ||(psiginfo->si_code == CLD_KILLED) ||(psiginfo->si_code == CLD_DUMPED)) { /* 回收子进程资源 */__signalWaitHandle(ptcbCur, iSigNo, psiginfo);}}break;case SIGCNCL:__signalCnclHandle(ptcbCur, iSigNo, psiginfo);break;case SIGSTKSHOW:if (pfuncHandle == SIG_DFL) {__signalStkShowHandle(ptcbCur, psigctlmsg);}break;default:break;}}}
}
__sigReturn
函数中:
static VOID __sigReturn (PLW_CLASS_SIGCONTEXT psigctx, PLW_CLASS_TCB ptcbCur, PLW_CLASS_SIGCTLMSG psigctlmsg)
{......KN_SMP_MB();//可以看到,这里执行了上下文加载(切换),而加载的就是之前保存在 psigctlmsg 结构中的寄存器上下文archSigCtxLoad(&psigctlmsg->SIGCTLMSG_archRegCtx); /* 从信号上下文中返回 */KN_INT_ENABLE(iregInterLevel);
}
4.3 队列信号
sigqueue
相比 kill
多了一个参数 sigvalue
。这个参数的作用是为信号携带用户自定义的数据,从而支持所谓的 “可靠信号”(也称为可排队信号)。
对于标准信号(kill 发送的),如果同一类型的信号在递送前被多次发送,内核只会保留一次,不记录额外信息。而通过 sigqueue
发送的实时信号则不同:每一次调用都会在内核的信号队列中单独保存下来,并且可以附带一个 sigvalue
。这样,即便是同一种信号,多次发送也不会被合并,接收方可以通过 sigvalue
来区分不同的发送事件。
LW_API INT kill(LW_OBJECT_HANDLE ulId, INT iSigNo);
LW_API INT sigqueue(LW_OBJECT_HANDLE ulId, INT iSigNo, const union sigval sigvalue);
详细的代码调用流程如下:
sigqueue ()+-> __sigqueue()+->_doSigQueue+-> _doSignal+-> __sigMakeReady+-> __sigCtlCreate+-> archTaskCtxCreate+-> archTaskCtxSetFp
static INT __sigqueue (LW_OBJECT_HANDLE ulId, INT iSigNo, PVOID psigvalue)
{
......
//将信号发送的目标线程,强行停止
#if LW_CFG_SMP_EN > 0if (LW_NCPUS > 1) { /* 正工作在 SMP 多核模式 */if (API_ThreadStop(ulId)) {return (PX_ERROR);}}
#endif
......
#if LW_CFG_SIGNALFD_EN > 0sendval = _doSigQueue(ptcb, iSigNo, sigvalue);
#else_doSigQueue(ptcb, iSigNo, sigvalue);
#endif//恢复刚刚被停止的线程(线程上下文 "可能" 已经被替换掉了!)
#if LW_CFG_SMP_EN > 0if (LW_NCPUS > 1) { /* 正工作在 SMP 多核模式 */_ThreadContinue(ptcb, LW_FALSE); /* 在内核状态下唤醒被停止线程 */}
#endif /* LW_CFG_SMP_EN *///开始调度__KERNEL_EXIT(); /* 退出内核 */......
}
_doSigQueue
函数,和 _doKill
函数不同的是,设置了信号类型:psiginfo->si_code = SI_QUEUE;
、psiginfo
中保存了用户传递下来的 sigvalue
参数
LW_SEND_VAL _doSigQueue (PLW_CLASS_TCB ptcb, INT iSigNo, const union sigval sigvalue)
{struct siginfo *psiginfo;PLW_CLASS_TCB ptcbCur;LW_CLASS_SIGPEND sigpend;_sigPendInit(&sigpend); /* 初始化可产生排队信号 */LW_TCB_GET_CUR_SAFE(ptcbCur);psiginfo = &sigpend.SIGPEND_siginfo;psiginfo->si_signo = iSigNo;psiginfo->si_errno = errno;psiginfo->si_code = SI_QUEUE; /* 排队信号 */psiginfo->si_pid = __tcb_pid(ptcbCur);psiginfo->si_uid = ptcbCur->TCB_uid;psiginfo->si_value = sigvalue;MONITOR_EVT_LONG3(MONITOR_EVENT_ID_SIGNAL, MONITOR_EVENT_SIGNAL_SIGQUEUE, ptcb->TCB_ulId, iSigNo, sigvalue.sival_ptr, LW_NULL);return (_doSignal(ptcb, &sigpend)); /* 产生信号 */
_doSignal
函数中,
LW_SEND_VAL _doSignal (PLW_CLASS_TCB ptcb, PLW_CLASS_SIGPEND psigpend)
{....../** 从这里可以看到,所谓的排队信号和非排队信号,区别就在屏蔽信号上* 对于相同的信号,排队信号会记录每一次信号和其对应的私有数据 psigpend->SIGPEND_siginfo->si_value(应用层传递)* 而非排队信号,无法传递应用层对于每一次调用其对应的私有数据*/if (sigsetSigMaskBit & psigctx->SIGCTX_sigsetMask) { /* 被屏蔽了 */if (psiginfo->si_code == SI_KILL) { /* kill 产生了信号, 不能排队 */psigctx->SIGCTX_sigsetKill |= sigsetSigMaskBit; /* 有 kill 的信号被屏蔽了 */psigctx->SIGCTX_sigsetPending |= sigsetSigMaskBit; /* iSigNo 由于屏蔽等待运行 */} else if (psigpend->SIGPEND_ringSigQ.RING_plistNext) { /* 除了 kill 产生的信号, 如果 */psigpend->SIGPEND_uiTimes++; /* 在队列中, 就需要队列缓冲信号*//* 已经存在在队列中 */} else {if (psigpend->SIGPEND_iNotify == SIGEV_SIGNAL) { /* 需要排队信号 */PLW_CLASS_SIGPEND psigpendNew = _sigPendAlloc(); /* 从缓冲区中获取一个空闲的 */LW_LIST_RING_HEADER *ppringHeader = &psigctx->SIGCTX_pringSigQ[iSigIndex];if (psigpendNew == LW_NULL) {_DebugHandle(__ERRORMESSAGE_LEVEL, "no node can allocate from free sigqueue.\r\n");_ErrorHandle(ERROR_SIGNAL_SIGQUEUE_NODES_NULL);return (SEND_ERROR);}*psigpendNew = *psigpend; /* 拷贝信息 */_List_Ring_Add_Last(&psigpendNew->SIGPEND_ringSigQ, ppringHeader); /* 加入队列链表 */psigpendNew->SIGPEND_psigctx = psigctx;psigctx->SIGCTX_sigsetPending |= sigsetSigMaskBit; /* iSigNo 由于屏蔽等待运行 */}}return (SEND_BLOCK); /* 被 mask 的都不执行 */}
......//注意,这里会去关注,对于相同的信号,是否允许重入。如果不允许冲入,则将该信号设置为屏蔽。第二次再进来,就会被加入到队列中psigctx->SIGCTX_sigsetMask |= psigaction->sa_mask;if ((psigaction->sa_flags & SA_NOMASK) == 0) { /* 阻止相同信号嵌套 */psigctx->SIGCTX_sigsetMask |= sigsetSigMaskBit;}//对于非屏蔽信号,可以看到会立刻执行__sigMakeReady(ptcb, iSigNo, &iSchedRet, iSaType); /* 强制进入就绪状态 */__sigCtlCreate(ptcb, psigctx, psiginfo, iSchedRet, &sigsetOld); /* 创建信号上下文环境 */return (SEND_OK);
}
而对于屏蔽信号,执行的时机为,应用层调用 sigprocmask
接口,解除信号屏蔽:
BOOL _sigPendRun (PLW_CLASS_TCB ptcb)
{
......iSigNo = _sigPendGet(psigctx, &sigset, &siginfo); /* 获得需要运行的信号 */if (__issig(iSigNo)) {struct sigaction *psigaction = &psigctx->SIGCTX_sigaction[__sigindex(iSigNo)];INT iSaType;if (psigaction->sa_flags & SA_RESTART) {iSaType = LW_SIGNAL_RESTART; /* 重启调用 */} else {iSaType = LW_SIGNAL_EINTR; /* 正常 EINTR */}__sigMakeReady(ptcb, iSigNo, &iSchedRet, iSaType); /* 强制进入就绪状态 */__sigCtlCreate(ptcb, psigctx, &siginfo, iSchedRet, &sigsetOld); /* 创建信号上下文环境 */return (LW_TRUE);} else {return (LW_FALSE);}
}LW_API
INT sigprocmask (INT iCmd, const sigset_t *psigset, sigset_t *psigsetOld)
{
......case SIG_SETMASK: /* 设置阻塞 */sigsetBlock = *psigset;sigsetBlock &= ~__SIGNO_UNMASK; /* 有些信号是不可屏蔽的 */psigctx->SIGCTX_sigsetMask = sigsetBlock;break;
......_sigPendRun(ptcbCur); /* 可能有先前被阻塞信号需要运行 */__KERNEL_EXIT(); /* 退出内核 */return (ERROR_NONE);
}
到这里,可能会会有一个疑问,_sigPendRun
这里只是执行了一个信号啊,如果有多个信号、每个信号有多个都在排队呢?
__sigShell ()+-> __sigRunHandle()+-> __sigReturn()+-> _sigPendRunSelf()while() {+-> __sigRunHandle()}
static BOOL _sigPendRunSelf (VOID)
{
......do {iSigNo = _sigPendGet(psigctx, &sigset, &siginfo); /* 获得需要运行的信号 */if (__issig(iSigNo)) {struct sigaction *psigaction;psigaction = &psigctx->SIGCTX_sigaction[__sigindex(iSigNo)];psigctx->SIGCTX_sigsetMask |= psigaction->sa_mask;if ((psigaction->sa_flags & SA_NOMASK) == 0) { /* 阻止相同信号嵌套 */psigctx->SIGCTX_sigsetMask |= __sigmask(iSigNo);}__KERNEL_EXIT(); /* 退出内核 */__sigRunHandle(psigctx, iSigNo, &siginfo, LW_NULL); /* 直接运行信号句柄 */__KERNEL_ENTER(); /* 重新进入内核 */psigctx->SIGCTX_sigsetMask = sigsetOld;bIsRun = LW_TRUE;} else {break;}} while (1);return (bIsRun);
}static VOID __sigReturn (PLW_CLASS_SIGCONTEXT psigctx, PLW_CLASS_TCB ptcbCur, PLW_CLASS_SIGCTLMSG psigctlmsg)
{INTREG iregInterLevel;__KERNEL_ENTER(); /* 进入内核 */psigctx->SIGCTX_sigsetMask = psigctlmsg->SIGCTLMSG_sigsetMask;/* 恢复原先的掩码 */_sigPendRunSelf(); ......
}static VOID __sigShell (PLW_CLASS_SIGCTLMSG psigctlmsg)
{
......__sigRunHandle(psigctx, iSigNo, psiginfo, psigctlmsg); /* 运行信号句柄 */__sigReturn(psigctx, ptcbCur, psigctlmsg); /* 信号返回 */
}
5、信号的阻塞
#include <signal.h>int sigsuspend(const sigset_t *sigsetMask);
int pause(void);
sigsuspend 函数将进程的当前信号屏蔽字设置为由 sigsetMask 指定的值,并且使得当前进程挂起
pause 函数将使调用进程挂起直到捕捉到任何一个信号,只有执行了一个信号处理函数并从其返回, pause 函数才返回
6、总结
当内核决定某线程要处理某个信号时,它不会新建线程,而是创建、修改该线程的 “线程上下文”。执行完信号句柄后,再返回。