信号内部机制
SylixOS的信号机制分为3类发送信号方法:kill类型、队列类型和事件类型,这3中类型分别由_doKill()、_doSigQueue()、_doSigEvent()函数实现,上层所有的发送信号函数都直接或者间接地通过调用这几个函数来实现,而这3个函数中,_doKill()设置信号源为SI_KILL、_doSigQueue()设置信号源为SI_QUEUE、_doSigEvent()设置信号源分为:SI_TIMER、SI_ASYNCIO等。如下表列出了SylixOS支持的所有信号产生源。
信号产生源 | 说明 |
---|---|
SI_KILL/SI_USER | 使用kill函数发送的信号 |
SI_QUEUE | 使用sigqueue函数发送的信号 |
SI_TIMER | POSIX定时器发送的信号 |
SI_ASYNCIO | 异步I/O系统完成发送的信号 |
SI_MESGQ | 接收到一条消息产生的信号 |
SI_KERNEL | SylixOS内核内部使用 |
无论什么类型的信号都将通过调用_doSignl()函数向指定的线程发送信号,下面我们结合代码分析一下此函数的实现原理:
如果要发送的信号通知类型是SIGEV_NONE类型,则代表不发送信号,_doSignal()直接返回并认为是忽略此信号,如下:
if (psigpend->SIGPEND_iNotify == SIGEV_NONE) { /* 不发送信号 */
return (SEND_IGN);
}
如果此线程正在等某个信号(如:调用sigwaitinfo等,以后我们将介绍),而此时发送的信号正是此线程等待的,此应唤醒目标线程,如下:
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);
}
}
当然,如果在安装此信号的时候(之后我们将介绍如何安装一个信号),指定信号处理函数为SIG_IGN或者SIG_ERR,则不做任何事。
接下来需要注意,因为如果发送的是SIGCHLD信号并且设置了SA_NOCLDSTOP标志,则代表子进程退出时不需要向父进程发送信号,因此,这个时候直接返回就可以了。如下:
if ((psigaction->sa_flags & SA_NOCLDSTOP) &&
(psigpend->SIGPEND_siginfo.si_signo == SIGCHLD) &&
(__SI_CODE_STOP(psigpend->SIGPEND_siginfo.si_code))) { /* 父进程不接收子进程暂停信号 */
return (SEND_IGN);
}
如果发送的信号被屏蔽了,则分为3中情况要考虑:
1. 如果是SI_KILL类型,则代表是不需要排队的,直接设置成未决即可;
2. 如果是非SI_KILL类型,则代表是需要排队的,而如果此信号已然被排队,只需将其未决计数加1;
3. 其他情况,则需要创建新的排队节点,并且放到队列中去。
以上3种情况的代码如下:
if (sigsetSigMaskBit & psigctx->SIGCTX_sigsetSigBlockMask) { /* 被屏蔽了 */
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 的都不执行 */
}
当然我们还需考虑,如果在信号处理函数中,又来了同一个信号,应该如何处理?POSIX规定如果在信号安装时指定了SA_NOMASK标志,以上情况可以被中断,处理新的信号。如果没有指定SA_NOMASK标志,则以上情况不会被中断,SylixOS支持这一特性。
这个时候SylixOS就可以将指定的线程放到就绪表,并且创建信号需要的环境了。下一节我们将详细介绍信号上下文环境的创建。
<本节完>