原创 4.4bsd中的signal实现part2

2008-3-31 21:32 5081 6 6 分类: 软件与OS
psignal()分下面几步post信号:
1. 根据进程描述符里面的p_sigignore, p_sigmask, 和p_sigcatch域确定进程要采取的行为.如果对于这个进程既不block(即mask)也不忽略也不catch,则认为采用default action.(15句),如果被忽略则直接返回什么也不做(8~9句),如果被屏蔽则只记录不deliver(10~11),记录是在26句完成.如果被catch,则我们将在以后调用指定的handler处理它(12~13).
如果进程被traced,则我们总是采用default action(5~6句),但是这个地方有些不明白,因为<the design and implementation of 4.4bsd>中说对于被traced的进程,系统总是让父进程首先进行处理.书中说:"If a process is being traced by its parent—that is, by a debugger—the parent process is always permitted to intercede before the signal is delivered."
我不明白代码中设action=SIG_DFL,怎么就让父进程获得机会处理它呢?
2. 将信号记录,(26句)即加到进程描述符的p_siglist中.19~25句是对一些特殊情况处理,19~20句判断如果这个信号是SIGCONT,则我们把该进程已经接受还没有处理的那些会引起进程stop的信号从记录中remove
p->p_siglist记录的是pending在p这个进程上的那些signal,这些signal以后会在deliver阶段被处理,如果从这里remove掉一个signal,那么等于取消了这个signal.22~24句:如果这个信号是发给一个孤儿进程组的tty stop信号,并且我们没有catch它,则丢弃这个信号,什么也不做.
3. 27~28检查该信号是否被进程mask?如果是,我们的工作完成,退出
4. 根据进程当前状态,分别处理.
a.如果当前状态是SSLEEP:如果进程是处于不可中断休眠,则退出(32~33),如果进程被traced,我们让它运行(34~35);如果信号是SIGCONT,并且我们没有catch它,则丢弃这个信号退出(36~38);如果是那些可导致进程stop的信号(比如:SIGSTOP,SIGTSTOP,SIGTTIN,SIGTTOU),如果我们没有catch它,则stop这个进程,同时发送SIGCHLD通知父进程(44~49),如果catch了,则唤醒这个进程退出(40~41句).对于所有的其他信号,我们总是唤醒这个进程(50~51句).
b.如果进程处于SSTOP状态:
如果进程是因为被traced而停止的,则什么也不做(53~54句);如果该信号是SIGKILL,则总是唤醒进程(55~56);如果这个信号是一个导致进程stop的信号,我们丢弃这个信号,什么也不做,因为当前这个进程已经处于stop状态了(66~68句);如果该signal是SIGCONT,那么根据我们有没有mask它,有没有catch它情况会不同frown.gif58~65句)
mask否?     catch否?      是否在等待事件?  处理方法
没有         没有           有             从p_siglist中清除,进程进入SSLEEP状态
没有         没有           没有           从p_siglist中清除,进程进入SRUN状态
有           没有           有             进程进入SSLEEP状态,但不从p_siglist中清除
有           没有           没有           进程进入SRUN状态,不从p_siglist中清除
X             是             X            进程进入SRUN状态,不从p_siglist中清除
69~71句,如果到这里进程状态为SSLEEP,并且它是在等待事件,而且是可中断的休眠,则唤醒它.
c.如果进程当前状态为SRUN, SIDL, SZOMB,则什么也不做,除非此进程正在运行,这个时候我们调用signotify()发送一个AST告诉它有新signal就绪了.
NOTE:4句中通过sigprop获得是这个signal的default action,sigprop是一个数组,定义如下,表示每个signal对应的default action是什么.
/*
 * Signal properties and actions.
 * The array below categorizes the signals and their default actions
 * according to the following properties:
 */
#defineSA_KILL0x01/* terminates process by default */
#defineSA_CORE0x02/* ditto and coredumps */
#defineSA_STOP0x04/* suspend process */
#defineSA_TTYSTOP0x08/* ditto, from tty */
#defineSA_IGNORE0x10/* ignore by default */
#defineSA_CONT0x20/* continue if suspended */
#defineSA_CANTMASK0x40/* non-maskable, catchable */

#ifdefSIGPROP
int sigprop[NSIG + 1] = {
0,/* unused */
SA_KILL,/* SIGHUP */
SA_KILL,/* SIGINT */
SA_KILL|SA_CORE,/* SIGQUIT */
SA_KILL|SA_CORE,/* SIGILL */
SA_KILL|SA_CORE,/* SIGTRAP */
SA_KILL|SA_CORE,/* SIGABRT */
SA_KILL|SA_CORE,/* SIGEMT */
SA_KILL|SA_CORE,/* SIGFPE */
SA_KILL,/* SIGKILL */
SA_KILL|SA_CORE,/* SIGBUS */
SA_KILL|SA_CORE,/* SIGSEGV */
SA_KILL|SA_CORE,/* SIGSYS */
SA_KILL,/* SIGPIPE */
SA_KILL,/* SIGALRM */
SA_KILL,/* SIGTERM */
SA_IGNORE,/* SIGURG */
SA_STOP,/* SIGSTOP */
SA_STOP|SA_TTYSTOP,/* SIGTSTP */
SA_IGNORE|SA_CONT,/* SIGCONT */
SA_IGNORE,/* SIGCHLD */
SA_STOP|SA_TTYSTOP,/* SIGTTIN */
SA_STOP|SA_TTYSTOP,/* SIGTTOU */
SA_IGNORE,/* SIGIO */
SA_KILL,/* SIGXCPU */
SA_KILL,/* SIGXFSZ */
SA_KILL,/* SIGVTALRM */
SA_KILL,/* SIGPROF */
SA_IGNORE,/* SIGWINCH  */
SA_IGNORE,/* SIGINFO */
SA_KILL,/* SIGUSR1 */
SA_KILL,/* SIGUSR2 */
};
如果要对一个进程组post signal,则用pgsignal()
pgsinal():
/*
 * Send a signal to a process group.  If checktty is 1,
 * limit to members which have a controlling terminal.
 */
void
pgsignal(pgrp, signum, checkctty)
struct pgrp *pgrp;
int signum, checkctty;
{
register struct proc *p;

if (pgrp)
for (p = pgrp->pg_members.lh_first; p != 0;
     p = p->p_pglist.le_next)
if (checkctty == 0 || p->p_flag & P_CONTROLT)
psignal(p, signum);
}
很简单就是对进程组的每个进程调用psignal()。但是如果参数checkctty=1,则只对拥有控制terminal的进程发送信号。如果进程描述符的p_flag指定了P_CONTROLT则表示这个进程拥有控制终端。
六.signal的delivery
大多数与信号deliver相关的操作都在进程上下文中进行,每当进程从kernel mode返回user mode的时候,比如从一个系统调用返回的时候,或者从中断handler,trap handler返回的时候都会调用CURSIG宏检查当前进程有没有signal pending,有则deliver它,也就是处理它.
比如对于i386体系结构来说,其系统调用实现在usr\src\sys\i386\i386\trap.c中
/*
 * syscall(frame):
 *System call request from POSIX system call gate interface to kernel.
 * Like trap(), argument is call by reference.
 */
/*ARGSUSED*/
syscall(frame)
volatile struct syscframe frame;
{
...............//omit here
done:
/*
 * Reinitialize proc pointer `p' as it may be different
 * if this is a child returning from fork syscall.
 */
p = curproc;
while (i = CURSIG(p))
postsig(i);
p->p_priority = p->p_usrpri;
.................//omit here
}
我们看到在syscall退出的时候用
while (i = CURSIG(p))
postsig(i);
检查是否有pending的信号,有就调用postsig()deliver它.
CURSIG(的实现如下:
/*
 * Determine signal that should be delivered to process p, the current
 * process, 0 if none.  If there is a pending stop signal with default
 * action, the process stops in issignal().
 */
#defineCURSIG(p)\
(((p)->p_siglist == 0 ||\
    ((p)->p_flag & P_TRACED) == 0 &&\
    ((p)->p_siglist & ~(p)->p_sigmask) == 0) ?\
    0 : issignal(p))
它判断是否有pengding的signal,如果有调用issignal()获得是哪个signal.如果有pending的stop signal,并且我们没有catch它没有mask它,那么直接在issignal()中处理掉.
/*
 * If the current process has received a signal (should be caught or cause
 * termination, should interrupt current syscall), return the signal number.
 * Stop signals with default action are processed immediately, then cleared;
 * they aren't returned.  This is checked after each entry to the system for
 * a syscall or trap (though this can usually be done without calling issignal
 * by checking the pending signal masks in the CURSIG macro.) The normal call
 * sequence is
 *
 *while (signum = CURSIG(curproc))
 *postsig(signum);
 */
int
issignal(p)
register struct proc *p;
{
register int signum, mask, prop;

1for (;;) {
2mask = p->p_siglist & ~p->p_sigmask;
3if (p->p_flag & P_PPWAIT)
4mask &= ~stopsigmask;
5if (mask == 0) /* no signal to send */
6return (0);
7signum = ffs((long)mask);
8mask = sigmask(signum);
9prop = sigprop[signum];
/*
 * We should see pending but ignored signals
 * only if P_TRACED was on when they were posted.
 */
10if (mask & p->p_sigignore && (p->p_flag & P_TRACED) == 0) {
11p->p_siglist &= ~mask;
12continue;
}
13if (p->p_flag & P_TRACED && (p->p_flag & P_PPWAIT) == 0) {
/*
 * If traced, always stop, and stay
 * stopped until released by the parent.
 *
 * Note that we must clear the pending signal
 * before we call trace_req since that routine
 * might cause a fault, calling tsleep and
 * leading us back here again with the same signal.
 * Then we would be deadlocked because the tracer
 * would still be blocked on the ipc struct from
 * the initial request.
 */
14p->p_xstat = signum;
15p->p_siglist &= ~mask;
16psignal(p->p_pptr, SIGCHLD);
17do {
18stop(p);
19mi_switch();
20} while (!trace_req(p) && p->p_flag & P_TRACED);

/*
 * If parent wants us to take the signal,
 * then it will leave it in p->p_xstat;
 * otherwise we just look for signals again.
 */
21signum = p->p_xstat;
22if (signum == 0)
23continue;

/*
 * Put the new signal into p_siglist.  If the
 * signal is being masked, look for other signals.
 */
24mask = sigmask(signum);
25p->p_siglist |= mask;
26if (p->p_sigmask & mask)
27continue;

/*
 * If the traced bit got turned off, go back up
 * to the top to rescan signals.  This ensures
 * that p_sig* and ps_sigact are consistent.
 */
28if ((p->p_flag & P_TRACED) == 0)
29continue;
}

/*
 * Decide whether the signal should be returned.
 * Return the signal's number, or fall through
 * to clear it from the pending mask.
 */
30switch ((long)p->p_sigacts->ps_sigact[signum]) {

31case (long)SIG_DFL:
/*
 * Don't take default actions on system processes.
 */
32if (p->p_pid <= 1) {
#ifdef DIAGNOSTIC
/*
 * Are you sure you want to ignore SIGSEGV
 * in init? XXX
 */
printf("Process (pid %d) got signal %d\n",
p->p_pid, signum);
#endif
33break;/* == ignore */
}
/*
 * If there is a pending stop signal to process
 * with default action, stop here,
 * then clear the signal.  However,
 * if process is member of an orphaned
 * process group, ignore tty stop signals.
 */
34if (prop & SA_STOP) {
35if (p->p_flag & P_TRACED ||
36        (p->p_pgrp->pg_jobc == 0 &&
37    prop & SA_TTYSTOP))
38break;/* == ignore */
39p->p_xstat = signum;
40stop(p);
41if ((p->p_pptr->p_flag & P_NOCLDSTOP) == 0)
42psignal(p->p_pptr, SIGCHLD);
43mi_switch();
44break;
45} else if (prop & SA_IGNORE) {
/*
 * Except for SIGCONT, shouldn't get here.
 * Default action is to ignore; drop it.
 */
46break;/* == ignore */
47} else
48return (signum);
/*NOTREACHED*/

49case (long)SIG_IGN:
/*
 * Masking above should prevent us ever trying
 * to take action on an ignored signal other
 * than SIGCONT, unless process is traced.
 */
50if ((prop & SA_CONT) == 0 &&
51    (p->p_flag & P_TRACED) == 0)
52printf("issignal\n");
53break;/* == ignore */

54default:
/*
 * This signal has an action, let
 * postsig() process it.
 */
55return (signum);
}
56p->p_siglist &= ~mask;/* take the signal! */
}
/* NOTREACHED */
}
2句获得进程未被mask的就绪signal.3~4句判断其父进程是否在等待它exit或exec,如果是那么我们不能处理可引起该进程stop 的信号,因为这些信号可能导致父进程和子进程的deadlock。5~6句:如果没有需要处理的信号,我们直接退出。
7句:得到mask这个变量第一个非0的位,也就是得到第一个pending的信号,ffs()这个函数从mask这个数的bit 0开始扫描,直到找到第一个不是0的位,返回这个位号(即如果bit9=1,则返回10。代码如下:
int
ffs(mask)
register int mask;
{
register int bit;

if (mask == 0)
return(0);
for (bit = 1; !(mask & 1); bit++)
mask >>= 1;
return(bit);
}
10~12句:如果这个信号被进程忽略了,并且当前这个进程没有被traced,则忽略这个signal,去处理下一个。也就是说,对于那些被ignore的信号,只有进程被调试的时候才会处理。
13~20句:如果进程被traced,且父进程没有等待它exit或exec,则我们总是stop这个进程,直到调试器允许它继续运行,或者调试器终止调试了。14句把这个信号记录在进程描述符p_xstat中,p_xstat保存进程的exit code,或者就是保存进程STOP前接受到而未处理的信号。
21~23句:如果父进程(调试器)唤醒这个进程处理信号,则这个信号就是保存在p_xstat里面的那个,如果父进程唤醒该进程,但不想它处理这个pending的信号,它会清除p_xstat,这种情况下我们continue,去处理下一个信号。
24~27句:我们被唤醒去处理信号,则我们先把这个信号加到p_siglist中,如果这个信号被mask了,我们跳过它去处理下一个,但是这个信号并不取消,它仍然记录在p_siglist中,到以后进程unmask这个信号的时候会得到处理。
28~29句:如果这个进程不再被traced,则我们重新开始处理信号
30句~:我们根据情况决定返回哪个signumber。
32~33句:如果这个信号的handler是default的,并且该进程是系统进程(即pid<1的进程),则我们忽略,因为不允许对系统进程采取default action
34~44句:处理stop signal(即那些可以导致进程stop的信号,包括SIGSTOP,SIGTSTOP等)。35~38句:如果这是个信号是发给孤儿进程组的tty stop信号,或者该进程被traced,则我们简单的忽略这个信号
39~44句:对于采用default action 的stop signal,我们在这里处理它,而不是交给postsig()处理。我们stop 进程,发送SIGCHLD通知父进程。
45~46句:对于被ignore的信号,除非它是SIGCONT,否则全部忽略,因为SIGCONT已经在psignal()中处理了,这里不会遇到这个信号,所以我们忽略所有ignore的信号。
47~48句:对于所有其他采取default action的信号,我们返回这个信号,给postsig() 处理。
49~53句:对于那些被ignore的signal,我们总是忽略它
54~55句:对于那些指定了handler的信号(即被catch了),我们返回其signal number,交给postsig() 处理。

Postsig():
/*
 * Take the action for the specified signal
 * from the current set of pending signals.
 */
void
postsig(signum)
register int signum;
{
register struct proc *p = curproc;
register struct sigacts *ps = p->p_sigacts;
register sig_t action;
u_long code;
int mask, returnmask;

#ifdef DIAGNOSTIC
if (signum == 0)
panic("postsig");
#endif
1mask = sigmask(signum);
2p->p_siglist &= ~mask;
3action = ps->ps_sigact[signum];
#ifdef KTRACE
4if (KTRPOINT(p, KTR_PSIG))
5ktrpsig(p->p_tracep,
6    signum, action, ps->ps_flags & SAS_OLDMASK ?
7    ps->ps_oldmask : p->p_sigmask, 0);
#endif
8if (action == SIG_DFL) {
/*
 * Default action, where the default is to kill
 * the process.  (Other cases were ignored above.)
 */
9sigexit(p, signum);
/* NOTREACHED */
10} else {
/*
 * If we get here, the signal must be caught.
 */
#ifdef DIAGNOSTIC
if (action == SIG_IGN || (p->p_sigmask & mask))
panic("postsig action");
#endif
/*
 * Set the new mask value and also defer further
 * occurences of this signal.
 *
 * Special case: user has done a sigpause.  Here the
 * current mask is not of interest, but rather the
 * mask from before the sigpause is what we want
 * restored after the signal processing is completed.
 */
11(void) splhigh();
12if (ps->ps_flags & SAS_OLDMASK) {
13returnmask = ps->ps_oldmask;
14ps->ps_flags &= ~SAS_OLDMASK;
15} else
16returnmask = p->p_sigmask;
17p->p_sigmask |= ps->ps_catchmask[signum] | mask;
18(void) spl0();
19p->p_stats->p_ru.ru_nsignals++;
20if (ps->ps_sig != signum) {
21code = 0;
22} else {
23code = ps->ps_code;
24ps->ps_code = 0;
25ps->ps_sig = 0;
26}
27sendsig(action, signum, returnmask, code);
}
}
8~9句:对于那些default action的信号,我们调用sigexit() kill这个进程(可能根据需要生成core dump)。前面已经说过Unix中定义的信号default action有3种,一种是忽略,这种情况在前面issignal()中已经处理掉了(参见前面issignal()45~46句),所以到这里碰到的所有default action一定是kill process的。
Sigexit()杀死进程,同时对于某些信号还生成core dump,代码如下:
/*
 * Force the current process to exit with the specified signal, dumping core
 * if appropriate.  We bypass the normal tests for masked and caught signals,
 * allowing unrecoverable failures to terminate the process without changing
 * signal state.  Mark the accounting record with the signal termination.
 * If dumping core, save the signal number for the debugger.  Calls exit and
 * does not return.
 */
void
sigexit(p, signum)
register struct proc *p;
int signum;
{

p->p_acflag |= AXSIG;
if (sigprop[signum] & SA_CORE) {
p->p_sigacts->ps_sig = signum;
if (coredump(p) == 0)
signum |= WCOREFLAG;
}
exit1(p, W_EXITCODE(0, signum));
/* NOTREACHED */
}
10~27句:调用sendsig()去执行signal handler,sendsig()是平台相关的,调用它后,当进程从kernel mode return to user mode的时候就会执行这些handler。
17句:设置新的sig mask,那个ps->ps_catchmask[signum]是在sigaction里面设的,前面提到我对这个域的作用感到困惑,可能唯一用到它的就在这里。ps->ps_catchmask是一个32元素的数组,它对每个signal都对应一个元素,这个元素记录在用户调用sigaction()安装handler的时候,指定的附加signal mask,所谓的附加mask,是指在原先进程的signal mask(记录在进程描述符的p_sigmask中)基础上临时增加的其他mask。这些附加的(additional)mask在signal handler执行前被加到进程的p_sigmask中,当signal handler执行完成后再恢复到原先的mask(即把那些附加的mask取消)。这里第17句就是把这些附加的mask临时加到进程p_sigmask上。而12~16句计算的returnmask作为一个参数传给sendsig(),则是保存了原先进程的p_sigmask,当sengsig()执行完后会恢复进程的mask为returnmask。
12~14句:如果用户先前调用sigsuspend()pause了进程,那么在pause进程的时候会把 pause前的进程p_sigmask保存在ps-> ps_oldmask中,对于这种情况,我们在这里要做特殊处理。
因为我们在这里接受到一个signal,被pause的进程只有接受到信号才能唤醒,这个时候当这个进程唤醒后执行完signal handler后,我们想恢复的进程信号mask并不是当前保存在p_sigmask中的值,而是进程被pause前的值,所以我们这里把returnmask设为ps->ps_oldmask。

提到sigpause,我们来看下sigsuspend()这个系统调用,它可以将进程pause(即设为SSLEEP状态),同时设置进程signal mask,而且这两个操作是原子的。
Sigsuspend()
/*
 * Suspend process until signal, providing mask to be set
 * in the meantime.  Note nonstandard calling convention:
 * libc stub passes mask, not pointer, to save a copyin.
 */
/* ARGSUSED */
int
sigsuspend(p, uap, retval)
register struct proc *p;
struct sigsuspend_args /* {
syscallarg(int) mask;//进程的signal mask将设为这个值
} */ *uap;
register_t *retval;
{
1register struct sigacts *ps = p->p_sigacts;

/*
 * When returning from sigpause, we want
 * the old mask to be restored after the
 * signal handler has finished.  Thus, we
 * save it here and mark the sigacts structure
 * to indicate this.
 */
2ps->ps_oldmask = p->p_sigmask;//保存进程原先的mask
3ps->ps_flags |= SAS_OLDMASK;//设ps_flags标志
4p->p_sigmask = SCARG(uap, mask) &~ sigcantmask;
5while (tsleep((caddr_t) ps, PPAUSE|PCATCH, "pause", 0) == 0)
/* void */;
/* always return EINTR rather than ERESTART... */
6return (EINTR);
}
3句设置ps_flags,这样在postsig()里面就知道刚刚用户调用sigsuspend() pause进程了。
4句:设置进程信号mask为指定的值,注意不能mask SIGKILL和SIGSTOP
5句:进程sleep
6句:进程被唤醒(由于接受到信号),返回EINTR
剩下来的工作由sendsig()继续,所有这些工作都是平台相关的,这几天忙,没有时间仔细看,先放在这里,以后再说。

七.与signal相关的系统调用
除了前面说到的sigsuspend(),sigaction()外,还有以下这些。
Sigpending()
int
sigpending(p, uap, retval)
struct proc *p;
void *uap;
register_t *retval;
{

*retval = p->p_siglist;
return (0);
}
返回进程p已经接受但是还没有deliver的信号
kill系统调用:
int
kill(cp, uap, retval)
register struct proc *cp;
register struct kill_args /* {
syscallarg(int) pid;
syscallarg(int) signum;
} */ *uap;
register_t *retval;
{
register struct proc *p;
register struct pcred *pc = cp->p_cred;

1if ((u_int)SCARG(uap, signum) >= NSIG)
2return (EINVAL);
3if (SCARG(uap, pid) > 0) {
4/* kill single process */
5if ((p = pfind(SCARG(uap, pid))) == NULL)
6return (ESRCH);
7if (!CANSIGNAL(cp, pc, p, SCARG(uap, signum)))
8return (EPERM);
9if (SCARG(uap, signum))
10psignal(p, SCARG(uap, signum));
11return (0);
}
12switch (SCARG(uap, pid)) {
13case -1:/* broadcast signal */
14return (killpg1(cp, SCARG(uap, signum), 0, 1));
15case 0:/* signal own process group */
16return (killpg1(cp, SCARG(uap, signum), 0, 0));
17default:/* negative explicit process group */
18return (killpg1(cp, SCARG(uap, signum), -SCARG(uap, pid), 0));
}
/* NOTREACHED */
}
1~2句:对signal number有效性作判断
3~11句:如果传递的pid>0,表示是对单个进程发送信号,5~6句调用 pfind(),根据pid的hash值找到这个进程,如果找不到返回错误;7~8句:判断进程有无权限发送信号,如果没有返回错误;9~11句:调用psignal()发送信号。
12~18句:处理对一组进程发送信号。如果pid=-1,则表示对系统中所有非ZOMBIE的进程发送信号。如果pid=0,表示对p所属的进程组发送信号;如果pid<-1,则对指定的进程组发送信号(不是对p所属的进程组),这个进程组的pgid(process group id)等于pid的绝对值。

CANSIGNAL()是一个宏,定义如下,注释很清楚:
/*
 * Can process p, with pcred pc, send the signal signum to process q?
 */
#define CANSIGNAL(p, pc, q, signum) \
((pc)->pc_ucred->cr_uid == 0 || \
    (pc)->p_ruid == (q)->p_cred->p_ruid || \
    (pc)->pc_ucred->cr_uid == (q)->p_cred->p_ruid || \
    (pc)->p_ruid == (q)->p_ucred->cr_uid || \
    (pc)->pc_ucred->cr_uid == (q)->p_ucred->cr_uid || \
    ((signum) == SIGCONT && (q)->p_session == (p)->p_session))
累了,不写了,还有sigaltstack()这个系统调用没说明,它的功能在apue等书中说的很清楚,有时间在下篇中再说。
PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
我要评论
0
6
关闭 站长推荐上一条 /3 下一条