信號量機制
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. 如果沒有充足堆??臻g,將會殺死這個進程
*/
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 怎么來實現這種信號量機制!最后用一副圖來表示我們整個 執行過程:
至此,信號量處理機制基本講解完成,信號量機制比較復雜,需要仔細的分析。
新聞熱點
疑難解答