信号量机制,信号机制


信号量机制

11.1 2 个程序的例子

先看 2 个程序;

#include<unistd.h>
int main(void)

{

allarm(10);
for(;;;);

}

这段程序的含义比较明显:这个程序在一个无限循环中,直到过了 10 秒,之后

程序被终止。

在来看另外一个程序:

Static void setvalue(void)

{

Flag=1;

}

int main(void)
{

int sum=0;
int flag=0;

struct sigaction act; act.sa_handler=setvalue,act. sa_sa_flags=0;

sigempteyset(&act,sa_mask);

sigaction(SIGINT,&act,NULL);
while(flag==0)

{

sum+=1;

}

Printf(“ the sum is %d”,&sum);

}

分析这个程序:这个程序就是注册一个捕获信号量 SIGINT(在兼容 POSIX 内会

169

默认是 Ctrl+C),捕获到这个信号量之后执行 setvalue 函数,就停止加,最后将结 果打印出来之后程序(进程)结束。

举这 2 个程序的目的是:用户程序编写者需要异步执行,需要异步触发时间,操 作系统必须提供这样的功能---异步执行。在 MINX3 中,用信号量来实现这种异 步功能。

11.2 MINIX 信号量机制概述

MINIX3  采用微内核结构,PM(进程管理器)是放到内核外执行,但是你或许 已经感受到,信号量的机制实现一定要靠内核的帮助来完成。所以信号量的实现 还是比较复杂。

首先我们想象一个事情:一个进程怎么能够让 PM 和内核知道它设定了一个信号 量呢?事实上内核部分是不知道的,内核的功能就是接受消息之后将它发送出 去。事实上,真正实现信号量的主体部分是 PM 部分,这点到后会非常的清楚。 先默认是 PM 来控制信号量主体实现,我们来看看 PM:

在 PM 中,我们同样定义了一个进程表数组,为每一个进程设定一个数据结构,
这个结构是

EXTERN struct mproc
{

……….

/* Signal handling information. */

sigset_t mp_ignore;

sigset_t mp_catch;
sigset_t mp_sig2mess;
sigset_t mp_sigmask;
sigset_t mp_sigmask2;

/* 1 means ignore the signal, 0 means don't */

/* 1 means catch the signal, 0 means don't */
/* 1 means transform into notify message */
/* signals to be blocked */

/* saved copy of mp_sigmask */

sigset_t mp_sigpending; /* pending signals to be handled */

struct sigaction mp_sigact[_NSIG + 1]; /* as in sigaction(2) */

vir_bytes mp_sigreturn;  /* address of C library __sigreturn function */

struct timer mp_timer;  /* watchdog timer for alarm(2) */ …………

}mproc[NR_PROCS];

可以看到这里定义了一个静态变量数组。上面暂且展现了与信号量有关的域

先看下 strcut sigaction mp_sigact[NSIG+1];这个是一个结构体数组,里面每一项
都是 sigaction。这里每一个项就是存储一个信号量信息。如果进程有需要标记信

号量,则就是在这个结构体数组标志,这里到后面还要详细介绍。sigset_t *

都是一些位图,用于标志一些 flag.之后 vir_bytes mp_sigreturn,这是 C 语言库里面
_sigreturn 函数的地址,我们可以设想下,当捕获函数完成后,我们应该怎么办,
必须要让用户进程毫无察觉到的放回,所以必须有一个函数来完成这个任务,而
且最终是靠内核设置相关栈来完成,这个数据项就是这个函数地址。最后一个数
据结构就是一个看门狗时钟,用于处理与时钟相关的例程。这个在后面会详细的
介绍。

170

11.3 MINIX3 信号量机制源码导读

函数主要放在 pm/signal.c 中,我在这里会把与内核相关的系统调用函数也拿出 来,主要是为了讲解的方便。首先是看 do_sigaction()函数:
这个函数调用的宏观过程如下图:

/*=================================================================== ========*

* do_sigaction 执行SIGACTION系统调用,这个系统调用没

有参数,参数都是在m_in信息里     这个函数主体就是注册一个信号量 *

*==================================================================== =======*/

PUBLIC int do_sigaction()
{

int r;

struct sigaction svec;
struct sigaction  *svp;

//SIGKILL代表不能捕获信号量,就返回

if  (m_in.sig_nr  == SIGKILL) return(OK); //不在范围内,返回错误信息

if  (m_in.sig_nr  <  1  || m_in.sig_nr  >  _NSIG) return  (EINVAL);

//svp是指向sigaction的地址,在这里,要理解mp_sigact[]数组的含义,里面 每一个元值都表示一个信号处理结构.用m_in.sig_nr做索引

svp  = &mp->mp_sigact[m_in.sig_nr];

//准备注册一个信号量 将系统任务的当前属性全部复制到m_in.sig_osa所指向 //的变量中

171

if ((struct sigaction *) m_in.sig_osa != (struct sigaction *) NULL) { r  = sys_datacopy(PM_PROC_NR,(vir_bytes) svp,

who,  (vir_bytes) m_in.sig_osa,  (phys_bytes) sizeof(svec)); if  (r  != OK) return(r);

}

if  ((struct sigaction  *) m_in.sig_nsa  ==  (struct sigaction  *) NULL) return(OK);

/* Read in the sigaction structure.  */

r  = sys_datacopy(who,  (vir_bytes) m_in.sig_nsa,

PM_PROC_NR,  (vir_bytes) &svec,  (phys_bytes) sizeof(svec)); if  (r  != OK) return(r);

// SIG_IGN代表忽略这个信号量

if  (svec.sa_handler  == SIG_IGN)  {

sigaddset(&mp->mp_ignore, m_in.sig_nr);

sigdelset(&mp->mp_sigpending, m_in.sig_nr);
sigdelset(&mp->mp_catch, m_in.sig_nr);
sigdelset(&mp->mp_sig2mess, m_in.sig_nr);

//默认信号处理函数

} else if  (svec.sa_handler  == SIG_DFL)  {
sigdelset(&mp->mp_ignore, m_in.sig_nr);
sigdelset(&mp->mp_catch, m_in.sig_nr);

sigdelset(&mp->mp_sig2mess, m_in.sig_nr);

//做为一个信息传递到到MINIX中

} else if  (svec.sa_handler  == SIG_MESS)  {

if  (!  (mp->mp_flags & PRIV_PROC)) return(EPERM); sigdelset(&mp->mp_ignore, m_in.sig_nr);
sigaddset(&mp->mp_sig2mess, m_in.sig_nr);
sigdelset(&mp->mp_catch, m_in.sig_nr);

//捕获自己定义的函数

} else  {

sigdelset(&mp->mp_ignore, m_in.sig_nr);
sigaddset(&mp->mp_catch, m_in.sig_nr);
sigdelset(&mp->mp_sig2mess, m_in.sig_nr);
}

//将svec结构体各个域传递给mp结构体,也将其他的一些信息传递给 mp->mp_sigaction[],最终确定注册成功。

mp->mp_sigact[m_in.sig_nr].sa_handler  = svec.sa_handler; sigdelset(&svec.sa_mask, SIGKILL);

mp->mp_sigact[m_in.sig_nr].sa_mask  = svec.sa_mask;

mp->mp_sigact[m_in.sig_nr].sa_flags  = svec.sa_flags; mp->mp_sigreturn  =  (vir_bytes) m_in.sig_ret;
return(OK);

}

172

/*=================================================================== ========*

* check_sig 这个函数就是发送信号的一个接口 *

*====================================================================

=======*/

PUBLIC int check_sig(proc_id, signo)

pid_t proc_id; /* pid of proc to sig, or 0 or -1, or -pgrp */

int signo; /* signal to send to process  (0 to  _NSIG)  */

{

/* Check to see if it is possible to send a signal.    The signal may have
to be sent to a group of processes.    This routine is invoked by the KILL
system call, and also when the kernel catches a DEL or other signal.

*/

//检查看是否需要发送一个信号量,信号量也许不得不发送给一组进程。

//当然当内核捕获到一个DEL或者其他信号时,这个例程涉及到KILL系统调用
register struct mproc  *rmp;

int count; /* count  # of signals sent  */

int error_code;

//检查要发送的信号量是否合法

if  (signo  <  0  || signo  >  _NSIG) return(EINVAL);

//如果尝试给INIT_PID进程发送SIGKILL信号,将会产生错误 //这个在开机时永远不会被杀死

/* Return EINVAL for attempts to send SIGKILL to INIT alone.  */ if  (proc_id  == INIT_PID && signo  == SIGKILL) return(EINVAL); //对进程表搜索进程信号量

/* Search the proc table for processes to signal.

* pid magic.)

*/

count  =  0;

error_code  = ESRCH;

(See forkexit.c about

for  (rmp  = &mproc[0]; rmp  < &mproc[NR_PROCS]; rmp++)  { if  (!(rmp->mp_flags & IN_USE)) continue;

if  ((rmp->mp_flags & ZOMBIE) && signo  !=  0) continue;

/* Check for selection.  */

if  (proc_id  >  0 && proc_id  != rmp->mp_pid) continue;

if  (proc_id  ==  0 && mp->mp_procgrp  != rmp->mp_procgrp) continue; if  (proc_id  ==  -1 && rmp->mp_pid  <= INIT_PID) continue;

173

if  (proc_id  <  -1 && rmp->mp_procgrp  !=  -proc_id) continue;

/* Check for permission.  */

if  (mp->mp_effuid  != SUPER_USER

&& mp->mp_realuid  != rmp->mp_realuid
&& mp->mp_effuid  != rmp->mp_realuid
&& mp->mp_realuid  != rmp->mp_effuid

&& mp->mp_effuid  != rmp->mp_effuid)  { error_code  = EPERM;

continue;

}

count++;

if  (signo  ==  0) continue;

/* 'sig_proc' will handle the disposition of the signal.    The * signal may be caught, blocked, ignored, or cause process * termination, possibly with core dump.

*/

//找到满足条件后,将调用sig_proc()函数,来处理这个信号量

sig_proc(rmp, signo);

if  (proc_id  >  0) break; /* only one process being signaled  */

}

/* If the calling process has killed itself, don't reply.  */

if  ((mp->mp_flags &  (IN_USE  | ZOMBIE))  != IN_USE) return(SUSPEND); return(count  >  0  ? OK  : error_code);

}

在往下面分析之前,先看一个结构体。

/* PM passes the address of a structure of this type to KERNEL when

* sys_sendsig() is invoked as part of the signal catching mechanism.
* The structure contain all the information that KERNEL needs to build
* the signal stack.当使用sys_sendsing()这个函数来进行捕获 PM传递将这
个结构体传递给内核。这个结构体包含了所用内核想要建造的信号栈信息

*/

struct sigmsg  {

int sm_signo; /* signal number being caught 捕获信号量号  */

unsigned long sm_mask; /* mask to restore when handler returns 当处

理返回时 重新标记一些标记位*/

vir_bytes sm_sighandler; /* address of handler 信号量处理地址  */

vir_bytes sm_sigreturn; /* address of _sigreturn in C library 事实

上是一个信号量返回地址,在这里就是_sigreturn地址*/

vir_bytes sm_stkptr;  /* user stack pointer 用户栈的指针  */

174

};

这个结构体得功能其实就是PM所处理的一个专门为信号量所设置的结构体,主要 功能是为内核提供所有想要的栈信息。

/*=================================================================== ========*

* sig_proc   这个函数是最重要的是处理信号量的核心函数

*

*==================================================================== =======*/

PUBLIC void sig_proc(rmp, signo)

register struct mproc  *rmp;  /* pointer to the process to be signaled */

int signo; /* signal to send to process  (1 to  _NSIG)  */

{

/* Send a signal to a process.    Check to see if the signal is to be caught, * ignored, tranformed into a message (for system processes) or blocked. * 给一个进程发送一个信号量。检查看看这个信号是否被捕获,忽略,转变成 一个消息或者阻塞

*  - If the signal is to be transformed into a message, request the KERNEL
to send the target process a system notification with the pending signal
as an    argument.

如果这个信号量被转成一个消息,请求内核发送一个系统通知给这个目标进程
* - If the signal is to be caught, request the KERNEL to push a sigcontext
* structure and a sigframe structure onto the catcher's stack.    Also,
KERNEL will reset the program counter and stack pointer, so that when the
process next runs, it will be executing the signal handler. When the signal
handler    returns,    sigreturn(2) will be called.    Then KERNEL will

restore the signal context from the sigcontext structure.

如果这个信号量被捕获,请求内核将sigcontext压入栈中并且将sigframe结构
压入捕获者的栈中。当然,内核将会重新设置这个程序计数器和堆栈指针,所以
当进程在下次来运行时,他将会执行信号处理函数。当信号处理函数返回时,
sigreturn(2)例程将会被调用。内核将会重新从sigcontext结构重新设置信号量
上下文

* If there is insufficient stack space, kill the process. 如果没有充足堆栈空间,将会杀死这个进程

*/

vir_bytes new_sp;
int s;

int slot;

int sigflags;

struct sigmsg sm;

slot  =  (int)  (rmp  - mproc);

175

//查看进程表中目的进程的标志位,看看其是不是IN_USE,如果该进程已经结 束或者僵死

//将不能执行这个操作。

if  ((rmp->mp_flags &  (IN_USE  | ZOMBIE))  != IN_USE)  {

printf("PM: signal  %d sent to  %s process  %d\n",

signo,  (rmp->mp_flags & ZOMBIE)  ? "zombie"  : "dead", slot); panic(__FILE__,"", NO_NUM);

}

//查看进程的标志位,如果该进程正在被跟踪,那么就收到这个信号量后,将 停止操作

if  ((rmp->mp_flags & TRACED) && signo  != SIGKILL)  { /* A traced process has special handling.  */

unpause(slot);

stop_proc(rmp, signo); /* a signal causes it to stop  */

return;

}

//如果信号量被忽略,则函数结束

/* Some signals are ignored by default.  */ if  (sigismember(&rmp->mp_ignore, signo))  { return;

}

//如果信号量被阻塞,将mp_sigpending位图设置相应的位 if  (sigismember(&rmp->mp_sigmask, signo))  {
/* Signal should be blocked.  */

sigaddset(&rmp->mp_sigpending, signo); return;

}

#if ENABLE_SWAP

if  (rmp->mp_flags & ONSWAP)  {

/* Process is swapped out, leave signal pending.  */

sigaddset(&rmp->mp_sigpending, signo);

swap_inqueue(rmp);

return;

}

#endif

//函数的主体部分,非常重要

sigflags  = rmp->mp_sigact[signo].sa_flags;

//首先判断时候捕获这个信号量

if  (sigismember(&rmp->mp_catch, signo))  { if  (rmp->mp_flags & SIGSUSPENDED)
sm.sm_mask  = rmp->mp_sigmask2;

else

sm.sm_mask  = rmp->mp_sigmask;

sm.sm_signo  = signo;//被捕获的信号量号

176

//被捕获信号量处理函数的首地址

sm.sm_sighandler  =  (vir_bytes) rmp->mp_sigact[signo].sa_handler;

//被捕获的信号量处理函数的返回地址,这个地址是在库函数中

sm.sm_sigreturn  = rmp->mp_sigreturn;

//分配新的堆栈主要用于储存信息

if  ((s=get_stack_ptr(slot, &new_sp))  != OK)

panic(__FILE__,"couldn't get new stack pointer",s); //将捕获的信号量处理函数的堆栈地址储存起来

sm.sm_stkptr  = new_sp;

//给sigcontext和sigframe结构腾出地址来,new_sp的地址也就随之改变 /* Make room for the sigcontext and sigframe struct.  */
new_sp  -= sizeof(struct sigcontext)

+  3  * sizeof(char  *)  +  2  * sizeof(int);

if  (adjust(rmp, rmp->mp_seg[D].mem_len, new_sp)  != OK)
goto doterminate;

//给进程的信号量进行标识,也就是设定标识位

rmp->mp_sigmask  |= rmp->mp_sigact[signo].sa_mask; if  (sigflags & SA_NODEFER)

sigdelset(&rmp->mp_sigmask, signo);

else

sigaddset(&rmp->mp_sigmask, signo);

if  (sigflags & SA_RESETHAND)  {

sigdelset(&rmp->mp_catch, signo);

rmp->mp_sigact[signo].sa_handler  = SIG_DFL;
}

//给内核发送消息,消息存储在sm内。当然这不是进入内核的最终消息格式 //关于sys_sigsend在下面有详细分析

if  (OK  ==  (s=sys_sigsend(slot, &sm)))  {

sigdelset(&rmp->mp_sigpending, signo);

/* If process is hanging on PAUSE, WAIT, SIGSUSPEND, tty, * pipe, etc., release it.

*/

unpause(slot);
return;

}

panic(__FILE__, "warning, sys_sigsend failed", s);

}

//

else if  (sigismember(&rmp->mp_sig2mess, signo))  { if  (OK  !=  (s=sys_kill(slot,signo)))

panic(__FILE__, "warning, sys_kill failed", s);

177

return;

}

doterminate:

/* Signal should not or cannot be caught.    Take default action.  */ if  (sigismember(&ign_sset, signo)) return;

rmp->mp_sigstatus  =  (char) signo;

if  (sigismember(&core_sset, signo))  { #if ENABLE_SWAP

if  (rmp->mp_flags & ONSWAP)  {

/* Process is swapped out, leave signal pending.  */

sigaddset(&rmp->mp_sigpending, signo);

swap_inqueue(rmp);

return;

}

#endif

/* Switch to the user's FS environment and dump core.  */

tell_fs(CHDIR, slot, FALSE,  0);

dump_core(rmp);

}

pm_exit(rmp,  0); /* terminate process  */

}

往下讲解之前先看下几个结构体:
struct sigregs  {

#if  _WORD_SIZE  ==  4
short sr_gs;

short sr_fs;

#endif  /*  _WORD_SIZE  ==  4  */
short sr_es;

short sr_ds;
int sr_di;
int sr_si;
int sr_bp;

int sr_st; /* stack top  -- used in kernel  */

int sr_bx;
int sr_dx;
int sr_cx;

int sr_retreg;

int sr_retadr;  /* return address to caller of save  -- used

* in kernel  */

int sr_pc;

int sr_cs;

178

int sr_psw;
int sr_sp;
int sr_ss;
};

struct sigframe  {  /* stack frame created for signalled process  */

_PROTOTYPE( void  (*sf_retadr),  (void)  );

int sf_signo;

int sf_code;

struct sigcontext  *sf_scp;
int sf_fp;

_PROTOTYPE( void  (*sf_retadr2),  (void)  ); struct sigcontext  *sf_scpcopy;

};

struct sigcontext  {

int sc_flags; /* sigstack state to restore  */

long sc_mask; /* signal mask to restore  */

struct sigregs sc_regs; /* register set to restore  */

};

对上面的结构体一一做出一个解释:

第1:struct sigregs 这个结构体存储的是CPU各个寄存器的值和一些其他的值: 一些通用寄存器的值就不介绍了,现在来介绍几个与寄存器值无关的值。

int sr_st; /* stack top  -- used in kernel  */

这个值的意思就是栈顶指针,先记住,在后面的分析中会有介绍

int sr_retadr; 这个值的意思是调用者保留的返回地址,在进行信号量的处理 过程中,会涉及到处理调用者的最后用户状态的下地址,所以这里必须要设一个 值进行保存

第2:sigframe 这个结构体主作用的就是3个: struct sigcontext  *sf_scopy 和 int sf_signo以及 void(*sf_retadr)(void)

struct sigcontext  *sf_scopy 这个是指向sigcontext结构体,下面会介绍。 int sf_signo 是被信号量号

void  (*sf_retadr)(void)是返回函数的地址。

第3 sigcontext结构体,主要是储存做上下文切换动作一些必需量。 先看下执行流程图(对于上下函数的一个衔接)

179

#include "syslib.h"

/*=================================================================== ========*

* sys_sigsend (本来本函数在Lib文件中

但是方便讲解,在这里拿出来)       接着上面的动作进行分析 *

*====================================================================

=======*/

PUBLIC int sys_sigsend(proc_ep, sig_ctxt)

endpoint_t proc_ep; /* for which process  */

struct sigmsg  *sig_ctxt; /* POSIX style handling  */

{ //这个函数主要是构造一个消息,之后发往内核

message m;
int result;

m.SIG_ENDPT  = proc_ep;

m.SIG_CTXT_PTR  =  (char  *) sig_ctxt;

// SIG_CTXT_PTR代表的就是信号量上下文地址

result  =  _kernel_call(SYS_SIGSEND, &m);

//给内核发送一条消息,将执行系统调用

//系统调用号时SYS_SIGSEND,下面也将这个do_sigsend函数附上来

180

return(result);

}

这个函数的主体是进行一系列复制动作,最关键的信息就是将用户栈的

rp_p_regs结构体复制sigmsg这个结构。这个结构体是非常重要的。你想象一下, 如果我现在准备捕获一个信号量,我们动用了这个信号量处理函数之后,栈和各 个寄存器值都可能发生变化,所以必须要做出一个保留工作,这个函数主体就是 做这个工作。之后将各个结构体的各个域初始化。

看看这个函数主体是做一个什么工作,先看下这个函数执行流程图:

/*=================================================================== ========*

* do_sigsend 这里的m_ptr其实就是上面的消息m

函数主要设置相关信息到用户栈 *

*==================================================================== =======*/

PUBLIC int do_sigsend(m_ptr)

message  *m_ptr; /* pointer to request message  */

{

/* Handle sys_sigsend, POSIX-style signal handling.  */ //处理sys_sigsend 这么做是为了符合POSIX标准
struct sigmsg smsg;

register struct proc  *rp;

phys_bytes src_phys, dst_phys;
struct sigcontext sc,  *scp;
struct sigframe fr,  *frp;

181

if  (! isokprocn(m_ptr->SIG_PROC)) return(EINVAL);

if  (iskerneln(m_ptr->SIG_PROC)) return(EPERM);

rp  = proc_addr(m_ptr->SIG_PROC);//待通知进程的地址

//sigmsg结构从用户状态复制到内核地址空间 下面这调用这2个函数的目的 比较明确,就是把用户态已经写好的sigmsg结构体复制到这里所定义的smsg结构 体中。这个起始地址是src_phys

/* Get the sigmsg structure into our address space.  */

src_phys  = umap_local(proc_addr(PM_PROC_NR), D,  (vir_bytes)
m_ptr->SIG_CTXT_PTR,  (vir_bytes) sizeof(struct sigmsg));
if  (src_phys  ==  0) return(EFAULT);

phys_copy(src_phys,vir2phys(&smsg),(phys_bytes) sizeof(struct sigmsg));

/* Compute the user stack pointer where sigcontext will be stored. */ //smsg.sm_stkptr-1是用户栈指针所指向的地方

scp  =  (struct sigcontext  *) smsg.sm_stkptr  -  1;

/* Copy the registers to the sigcontext structure.  */ //将CPU中的寄存器都复制到sigcontext结构

memcpy(&sc.sc_regs,  (char  *) &rp->p_reg, sizeof(struct sigregs));

/* Finish the sigcontext initialization.  */

//将sc其他2个量进行设置

sc.sc_flags  = SC_SIGCONTEXT;
sc.sc_mask  = smsg.sm_mask;

/* Copy the sigcontext structure to the user's stack.  */

//将刚刚设置好的sigcontext结构体复制到要发送信号量的用户栈进程中

dst_phys  = umap_local(rp, D,  (vir_bytes) scp,

(vir_bytes) sizeof(struct sigcontext)); if  (dst_phys  ==  0) return(EFAULT);

phys_copy(vir2phys(&sc), dst_phys,  (phys_bytes) sizeof(struct sigcontext));

/* Initialize the sigframe structure.  */

//现在做最后一步工作,初始化sigframe结构体工作。这个结构体对从信号量的 //返回有非常中要的作用

frp  =  (struct sigframe  *) scp  -  1; fr.sf_scpcopy  = scp;

fr.sf_retadr2=  (void  (*)()) rp->p_reg.pc; fr.sf_fp  = rp->p_reg.fp;

rp->p_reg.fp  =  (reg_t) &frp->sf_fp; fr.sf_scp  = scp;

fr.sf_code  =  0; /* XXX  - should be used for type of FP exception  */

182

fr.sf_signo  = smsg.sm_signo;

fr.sf_retadr  =  (void  (*)()) smsg.sm_sigreturn;

/* Copy the sigframe structure to the user's stack.  */

dst_phys  = umap_local(rp, D,  (vir_bytes) frp,
(vir_bytes) sizeof(struct sigframe));
if  (dst_phys  ==  0) return(EFAULT);

phys_copy(vir2phys(&fr), dst_phys,  (phys_bytes) sizeof(struct sigframe));

/* Reset user registers to execute the signal handler.  */ //设置用户寄存器来执行信号处理函数

rp->p_reg.sp  =  (reg_t) frp;

rp->p_reg.pc  =  (reg_t) smsg.sm_sighandler;

return(OK);

}

从时间上看,执行完上一个函数之后,还是在内核中,内核调度器调度上面被设 置信号量的进程之后,进程就会调用信号量处理函数,这个宏观上只是一个顺序 执行,但是在微观上还是做了非常多动作。

现在我们分析这个问题,信号量函数处理完之后,我们是不是应该设置一个函数 来执行信号量返回调用的操作?答案是毫无疑问的,因为这个动作能需要重新设 置相关的寄存器的量。我们就从这个动作开始 也就是 do_sigreturn,这个函数是 PM 一个调用函数。我们假设已经进入了这个函数。我先看下 do_sigreturn 主要 是干了什么工作?

do_sigreturn 函数主要是调用这个 sys_sigreturn 函数,这个函数是与内核函数的 一个接口,调用系统任务的系统函数。

/*=================================================================== ========*

* do_sigreturn   注意这个函数是PM机制下的信号返回函数,下面

那个函数是内核状态下的信号返回函数 *

*==================================================================== =======*/

PUBLIC int do_sigreturn()
{

/* A user signal handler is done.    Restore context and check for * pending unblocked signals.

*/

int r;

mp->mp_sigmask  =  (sigset_t) m_in.sig_set; sigdelset(&mp->mp_sigmask, SIGKILL);

183

r  = sys_sigreturn(who,  (struct sigmsg  *) m_in.sig_context); check_pending(mp);

return(r);

}

/*=================================================================== ========*

* sys_sigreturn   与内核的一个接口函

数,函数主体构造一个消息,调用do_sigreturn函数,我们就顺着往后面那个

函数 *

*====================================================================

=======*/

PUBLIC int sys_sigreturn(proc_ep, sig_ctxt)

endpoint_t proc_ep;

struct sigmsg  *sig_ctxt;
{

message m;

int result;

m.SIG_ENDPT  = proc_ep;

/* for which process  */
/* POSIX style handling  */

m.SIG_CTXT_PTR  =  (char  *) sig_ctxt;

result  =  _kernel_call(SYS_SIGRETURN, &m); return(result);

}

我们接着上面的那个函数,现在来看看这个函数,这个函数的主体就是将之前保 留的p_regs都还原到现在这个进程的p_regs

184

/*=================================================================== ========*

* do_sigreturn 内核处理信号返回函数,这个函数上面那个函

数调用SYSTEM进程,之后SYSTEM进程就调用这个函数 *

*==================================================================== =======*/

PUBLIC int do_sigreturn(m_ptr)

message  *m_ptr; /* pointer to request message  */

{

/* POSIX style signals require sys_sigreturn to put things in order before * the signalled process can resume execution

*/

struct sigcontext sc;

register struct proc  *rp;
phys_bytes src_phys;

if  (! isokprocn(m_ptr->SIG_PROC)) return(EINVAL); if  (iskerneln(m_ptr->SIG_PROC)) return(EPERM); rp  = proc_addr(m_ptr->SIG_PROC);

185

/* Copy in the sigcontext structure.  */

//复制sigcontext结构,分析下从哪里复制到哪里!

//这种复制就是从外核复制到内核中,将m_ptr结构所指向的sigcontext结构体 复制到前面所定义的结构体sc中。

//注意这里是返回操作!返回就是将之前所定义好的内核寄存器复原。这点比较 的总要,在后的代码会有所涉及!

src_phys  = umap_local(rp, D,  (vir_bytes) m_ptr->SIG_CTXT_PTR,
(vir_bytes) sizeof(struct sigcontext));

if  (src_phys  ==  0) return(EFAULT);

phys_copy(src_phys, vir2phys(&sc),  (phys_bytes) sizeof(struct sigcontext));

/* Make sure that this is not just a jump buffer.  */

if  ((sc.sc_flags & SC_SIGCONTEXT)  ==  0) return(EINVAL);

/* Fix up only certain key registers if the compiler doesn't use
* register variables within functions containing setjmp.
*/

//将rp.p_reg复原捕获信号量前得各个值,这点很重要,因为在捕获信号量时, 指向信号处理函数,可能各个寄存器的值都发生了变化,为了保持他们原来的状 态,这里必须进行在一次的复制

if  (sc.sc_flags & SC_NOREGLOCALS)  {
rp->p_reg.retreg  = sc.sc_retreg;
rp->p_reg.fp  = sc.sc_fp;
rp->p_reg.pc  = sc.sc_pc;
rp->p_reg.sp  = sc.sc_sp;
return(OK);

}

sc.sc_psw  = rp->p_reg.psw;

#if  (CHIP  == INTEL)

/* Don't panic kernel if user gave bad selectors.  */

//段选择器的恢复工作,将rp指向的进程各个段选择器都复制到sc结构体相应的 域中。为后面的还原过程做出基础

sc.sc_cs  = rp->p_reg.cs;
sc.sc_ds  = rp->p_reg.ds;
sc.sc_es  = rp->p_reg.es;
#if  _WORD_SIZE  ==  4
sc.sc_fs  = rp->p_reg.fs;
sc.sc_gs  = rp->p_reg.gs;

#endif

#endif

186

/* Restore the registers.  */

//这里就是全部重新赋值,sc.sc_regs的各个值都恢复到被信号量的栈值。
memcpy(&rp->p_reg, &sc.sc_regs, sizeof(struct sigregs));
return(OK);

}

从时间轴上来看。执行完这个函数,只要调度器调度这个进程,这个进程就可以 继续被信号量中断前得指令继续执行。从宏观上,用户只是会觉得整个调用过程 就是一个普通的调用函数一样。!

MINIX3 的信号量机制基本上就是这样,事实上,读者可以结合前面的时钟机制, 继续分析一次 alarm 怎么来实现这种信号量机制!最后用一副图来表示我们整个
执行过程:

至此,信号量处理机制基本讲解完成,信号量机制比较复杂,需要仔细的分析。


信号量机制

《Operating Systems Design and Implementation》中Andrew S. Tanenbaum对信号量的描述和以前看过的教材有区别。但其核心思想是类似的。

以前的书上(包括网上不少帖子)是这么叙述的:

―――――――――――――――――――――――――――――――――――――

信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用临界区的进程数。

Dijkstra同时提出了对信号量操作的PV原语。

P原语操作的动作是:

(1)S减1;

(2)若S减1后仍大于或等于零,则进程继续执行;

(3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。

V原语操作的动作是:

(1)S加1;

(2)若相加结果大于零,则进程继续执行;

(3)若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。

PV操作对于每一个进程来说,都只能进行一次,而且必须成对使用。在PV原语执行期间不允许有中断的发生。

―――――――――――――――――――――――――――――――――――――

说明:要保证PV是原子操作,对于操作系统,只须在操作过程中关中断即可。

Andrew S. Tanenbaum对信号量的定义有所不同,其PV操作也有区别。

―――――――――――――――――――――――――――――――――――――

信号量是一个整数,其值不小于0。它表示被积累下来的唤醒操作数。

P原语操作的动作是:

(1) 检查S是否大于0。

(2) 若S>0,则S = S – 1;否则,执行P操作的进程将睡眠,并且此时P操作并未结束。

V原语操作的动作是:

(1)S = S + 1。

(2)如果一个或多个进程在该信号量上睡眠,无法完成先前的P操作,则有系统选择其中一个并允许它完成P操作。

――――――――――――――――――――――――――――――――――――――

Andrew S. Tanenbaum的观点认为:当S = 0时,P操作不会马上结束。其作用(S = S – 1)必须在该进程被唤醒后才能实现。

两种定义的区别是:

前者定义的PV操作都不会阻塞,而后者中P操作可能被阻塞。

前者的S的初值一般大于0,而后者中,S通常初值为0。
 

信号量机制

《Operating Systems Design and Implementation》中Andrew S. Tanenbaum对信号量的描述和以前看过的教材有区别。但其核心思想是类似的。

以前的书上(包括网上不少帖子)是这么叙述的:

―――――――――――――――――――――――――――――――――――――

信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用临界区的进程数。

Dijkstra同时提出了对信号量操作的PV原语。

P原语操作的动作是:

(1)S减1;

(2)若S减1后仍大于或等于零,则进程继续执行;

(3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。

V原语操作的动作是:

(1)S加1;

(2)若相加结果大于零,则进程继续执行;

(3)若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。

PV操作对于每一个进程来说,都只能进行一次,而且必须成对使用。在PV原语执行期间不允许有中断的发生。

―――――――――――――――――――――――――――――――――――――

说明:要保证PV是原子操作,对于操作系统,只须在操作过程中关中断即可。

Andrew S. Tanenbaum对信号量的定义有所不同,其PV操作也有区别。

―――――――――――――――――――――――――――――――――――――

信号量是一个整数,其值不小于0。它表示被积累下来的唤醒操作数。

P原语操作的动作是:

(1) 检查S是否大于0。

(2) 若S>0,则S = S – 1;否则,执行P操作的进程将睡眠,并且此时P操作并未结束。

V原语操作的动作是:

(1)S = S + 1。

(2)如果一个或多个进程在该信号量上睡眠,无法完成先前的P操作,则有系统选择其中一个并允许它完成P操作。

――――――――――――――――――――――――――――――――――――――

Andrew S. Tanenbaum的观点认为:当S = 0时,P操作不会马上结束。其作用(S = S – 1)必须在该进程被唤醒后才能实现。

两种定义的区别是:

前者定义的PV操作都不会阻塞,而后者中P操作可能被阻塞。

前者的S的初值一般大于0,而后者中,S通常初值为0。
 

相关内容

    暂无相关文章