原创 4.4bsd中的signal实现part1

2008-3-31 21:31 4250 8 8 分类: 软件与OS
信号(signal)是Unix中一个标准的机制,并且成为POSIX的一部分,编程中这个东西经常用到,windows NT中没有这么个东西,和signal有点象的是APC,但这两者差别很大。
我自己几乎从来没有真正搞明白signal,主要还是因为在unix/linux下编程太少,没有时间去搞,无奈,人的精力太有限。这个5月我无聊的看起4.4bsd的代码,正好看看这个部分在内核的实现,也让自己把signal搞明白点,以后有时间还是要去把linux的signal详细分析分析,以后再说吧。

一.概述
signal是一个很短的消息,可以把它发送给进程或者进程组,通常是通知进程一些事件,比如异常(如非法指令),如果内核检测到一些硬件事件,它常常通过signal通知进程
signal相当于一种IPC,但是不能指望它成为真正有效的IPC,原因很简单,因为signal没办法包含多余的信息,它其实就是一个数,进程只能获得这个数(即signal number),因为这些signal number是事先定义好的,进程知道它代表的含义,但是没有办法附加其余的信息。所以不能把它当作真正的IPC用。
signal完成两个任务:
1.通知进程某些事件发生了
2.导致进程运行指定的signal handler去实现某中异步调用。
signal可以是内核发送的,当内核检测到一些事件的时候它可能发送信号给进程,但signal也可以是别的进程发送的,进程可以通过kill系统调用象其他进程post signal.
一个进程对于signal的反应可以有3种:
1.忽略这个信号
2.执行这个signal的default action,当没有给这个信号指定我们自己的handler的时候(也即我们没有catch这个signal),通常会执行default的action,default action有几种情况:
1)终止进程
2)忽略这个signal
3)dump.也就是说终止进程同时创建一个core dump
4)停止进程,设进程的状态为SSTOP
5)继续运行,如果进程本来处于stop 状态,接受到一个信号可能将它resume,其状态改为SRUN
3.catch这个signal,运行指定的handler处理它

4.4bsd定义了31个signal,其default action也分别指定,如下:
Name     Default action Description 
SIGHUP    terminate process terminal line hangup 
SIGINT    terminate process interrupt program 
SIGQUIT    create core image          quit program 
SIGILL    create core image illegal instruction 
SIGTRAP    create core image trace trap 
SIGIOT    create core image I/O trap instruction executed 
SIGEMT    create core image emulate instruction executed 
SIGFPE    create core image floating-point exception 
SIGKILL    terminate process kill program 
SIGBUS    create core image bus error 
SIGSEGV    create core image segmentation violation 
SIGSYS    create core image bad argument to system call 
SIGPIPE    terminate process write on a pipe with no one to read it 
SIGALRM    terminate process real-time timer expired 
SIGTERM    terminate process software termination signal 
SIGURG    discard signal         urgent condition on I/O channel 
SIGSTOP    stop process         stop signal not from terminal 
SIGTSTP    stop process         stop signal from terminal 
SIGCONT    discard signal          a stopped process is being continued 
SIGCHLD    discard signal          notification to parent on child stop or exit 
SIGTTIN    stop process          read on terminal by background process 
SIGTTOU    stop process          write to terminal by background process 
SIGIO    discard signal           I/O possible on a descriptor 
SIGXCPU    terminate process   CPU time limit exceeded 
SIGXFSZ    terminate process  file-size limit exceeded 
SIGVTALRM    terminate process  virtual timer expired 
SIGPROF    terminate process  profiling timer expired 
SIGWINCH    discard signal           window size changed 
SIGINFO    discard signal           information request 
SIGUSR1    terminate process  user-defined signal 1 
SIGUSR2    terminate process user-defined signal 2 
4.4bsd实现的是所谓的reliable signal,所谓的可靠信号,是指具有下面特性的signal:signal handler一旦安装,除非重新安装否则一直有效,这样一个信号产生发送被处理后,不需要重新安装这个handler,而早期unix实现的unreliable signal不具备这个特性,对于unreliable signal,你安装一个handler,当该信号产生后,内核会自动reset该信号的处理函数为default action,这导致很多问题。
此外,reliable signal可以被mask,一个signal一旦被mask,那么下次产生该signal时候,内核只记录它但是不 post它,直到该信号被unmask,这个时候这个signal才会被post并处理

二.和signal相关的数据结构
和信号相关的内核数据结构主要有:
1.struct proc---进程描述符,里面有几个域和signal相关,比如哪些signal被该进程mask了,哪些被忽略,哪些需要catch,以及对应的signal handler,这个参看“4.4bsd进程调度”
2.sigacts结构,表示进程的信号handler和状态,定义为:

/*
 * Process signal actions and state, needed only within the process
 * (not necessarily resident).
 */
structsigacts {
sig_tps_sigact[NSIG];/* disposition of signals */
sigset_t ps_catchmask[NSIG];/* signals to be blocked */
sigset_t ps_sigonstack;/* signals to take on sigstack */
sigset_t ps_sigintr;/* signals that interrupt syscalls */
sigset_t ps_oldmask;/* saved mask from before sigpause */
intps_flags;/* signal flags, below */
structsigaltstack ps_sigstk;/* sp & on stack state variable */
intps_sig;/* for core dump/debugger XXX */
longps_code;/* for core dump/debugger XXX */
longps_addr;/* for core dump/debugger XXX */
sigset_t ps_usertramp;/* SunOS compat; libc sigtramp XXX */
};
3. struct sigaction---描述signal handler
structsigaction {
void(*sa_handler)(int);/* signal handler *///signal处理函数
sigset_t sa_mask;/* signal mask to apply *///signal的mask
intsa_flags;/* see signal options below */
};
这个地方唯一没搞清楚的是,在进程描述符中有个p_sigacts的域(即一个struct sigacts的指针),还有描述signal catch ignore的域:p_sigcatch,这个域和sigacts里面的ps_catchmask好象重复了,不知道为什么弄两个?

三.设置signal handler
可以通过sigaction系统调用设置一个进程的某个signal的处理函数
int
sigaction(p, uap, retval)
struct proc *p;
register struct sigaction_args /* {
syscallarg(int) signum;
syscallarg(struct sigaction *) nsa;
syscallarg(struct sigaction *) osa;
} */ *uap;
register_t *retval;
{
1struct sigaction vec;
2register struct sigaction *sa;
3register struct sigacts *ps = p->p_sigacts;
4register int signum;
5int bit, error;

6signum = SCARG(uap, signum);
7if (signum <= 0 || signum >= NSIG ||
8    signum == SIGKILL || signum == SIGSTOP)
9return (EINVAL);
10sa = &vec;
11if (SCARG(uap, osa)) {
12sa->sa_handler = ps->ps_sigact[signum];
13sa->sa_mask = ps->ps_catchmask[signum];
14bit = sigmask(signum);
15sa->sa_flags = 0;
16if ((ps->ps_sigonstack & bit) != 0)
17sa->sa_flags |= SA_ONSTACK;
18if ((ps->ps_sigintr & bit) == 0)
19sa->sa_flags |= SA_RESTART;
20if (p->p_flag & P_NOCLDSTOP)
21sa->sa_flags |= SA_NOCLDSTOP;
22if (error = copyout((caddr_t)sa, (caddr_t)SCARG(uap, osa),
23    sizeof (vec)))
24return (error);
}
25if (SCARG(uap, nsa)) {
26if (error = copyin((caddr_t)SCARG(uap, nsa), (caddr_t)sa,
27    sizeof (vec)))
28return (error);
29setsigvec(p, signum, sa);
}
30return (0);
}
sigaction是用来添加signal处理函数的,这个系统调用可以为某个signal设置新的处理函数,当然也可以取消原来的处理函数,但是SIGKILL和SIGSTOP两个signal是不可以使用这个sigaction来设置处理函数的。它的参数有两个,含义是:
p---进程描述符,表示要设置哪个进程的signal处理函数
uap--一个struct sigaction_args的变量,定义为:
struct sigaction_args {
syscallarg(int) signum;
syscallarg(struct sigaction *) nsa;
syscallarg(struct sigaction *) osa;
};
有3个域,第一个signum---就是signal的号,如SIGCHLD,SIGTTIN等等
nsa---指向新的signal handler的一个结构指针
osa---指向老的signal handler的结构指针,最后sigaction在设置该signal的处理函数的同时会把原先的handler通过这个osa返回给调用者
3句获得该进程的signal信息,这个信息保存在进程描述符的p_sigacts里面,它是个struct sigacts结构
6句获得我们将处理的signal号
7~9:有效的signal是0~NSIG(32),如果传递来的信号不在这个范围或者是SIGKILL或SIGSTOP,则我们返回错误,因为SIGKILL和SIGSTOP是不允许处理的,即不允许忽略也不允许block也不允许修改其handler
11~24:如果用户传递了osa参数表示调用者想获得原先(老的)signal handler,则我们在这段返回老handler的信息。具体如下:12句:从p->p_sigacts里面获得原先handler的函数地址,13句:获得进程原先的signal mask,即哪些signal被该进程block了。14句:调用sigmask,sigmask是一个宏实现很简单,它实际上就是根据signum得到一个mask,这个mask的用处下面很清楚
/*
 * Macro for converting signal number to a mask suitable for
 * sigblock().
 */
#define sigmask(m)(1 << ((m)-1))
16~17:判断该signal是否属于进程p的on stack signal,也就是说处理这个signal的时候是否需要在一个alternative的signal stack上进行,通常signal的处理是在进程的kernel stack上做的,但有时候需要在一个单独的stack上做,
这个时候就要指定ps_sigonstack标志.这里如果该signal是需要take on stack的,则设置sgaction *sa-sa_flags=SA_ONSTACK。
18~19句:判断该signal是否属于那种可打断syscall的信号,一个进程在它的进程描述符p_sigacts->ps_sigintr记录那些interrupt系统调用的signal.如果该signal不属于这类,则设置sa->sa_flags|=SA_RESTART,表示进程在收到这个
signal时要restart 阻塞的系统调用,这个可以参考前面“4.4bsd进程调度”中对tsleep的说明。
20~21句:如果进程的p_flags设置了 P_NOCLDSTOP,则我们设置sa的标志为SA_NOCLDSTOP,表示其子进程终止时候不产生SIGCHLD信号。
22~24:拷贝原来的signal handler到osa,最终返回给caller
25~29:设置新的handler,先调用copyin()将caller 传递来的nsa拷贝到局部变量sa中,然后调用setsigvec()设置它为该signal 新的处理函数

setsigvec()完成实际的设置signal handler工作,代码如下:
void setsigvec(p, signum, sa)
register struct proc *p;//进程描述符,我们将要设置它的signal handler
int signum;//哪个signal?我们将设置这个signal的handler
register struct sigaction *sa;//指向新的handler
{
register struct sigacts *ps = p->p_sigacts;
register int bit;
1bit = sigmask(signum);
/*
 * Change setting atomically.
 */
2(void) splhigh(); //提高中断level,禁止所有中断,同步用,为了保护下面这段代码不被打断
3ps->ps_sigact[signum] = sa->sa_handler;//设置该进程的
4ps->ps_catchmask[signum] = sa->sa_mask &~ sigcantmask;//设置进程的signal mask
5if ((sa->sa_flags & SA_RESTART) == 0)
6ps->ps_sigintr |= bit;//如果该信号没有设置SA_RESTART,则它是属于可中断系统调用的signal,把它加到ps_sigintr
7else
8ps->ps_sigintr &= ~bit;//否则,它是restart syscall类型
9if (sa->sa_flags & SA_ONSTACK)//如果该signal是ONSTACK的,加到ps_sigonstack
10ps->ps_sigonstack |= bit;
11else
12ps->ps_sigonstack &= ~bit;
#ifdef COMPAT_SUNOS
if (sa->sa_flags & SA_USERTRAMP)
ps->ps_usertramp |= bit;
else
ps->ps_usertramp &= ~bit;
#endif
13if (signum == SIGCHLD) {//如果SIGCHLD,做特殊处理
14if (sa->sa_flags & SA_NOCLDSTOP)//如果该信号设置了SA_NOCLDSTOP
15p->p_flag |= P_NOCLDSTOP;//设置p_flag,表示当其子进程exit的时候这个进程不产生SIGCHLD
16else
17p->p_flag &= ~P_NOCLDSTOP;
}
/*
 * Set bit in p_sigignore for signals that are set to SIG_IGN,
 * and for signals set to SIG_DFL where the default is to ignore.
 * However, don't put SIGCONT in p_sigignore,
 * as we have to restart the process.
 */
18if (sa->sa_handler == SIG_IGN ||
19    (sigprop[signum] & SA_IGNORE && sa->sa_handler == SIG_DFL)) {
20p->p_siglist &= ~bit;/* never to be seen again */
21if (signum != SIGCONT)
22p->p_sigignore |= bit;/* easier in psignal */
23p->p_sigcatch &= ~bit;
24} else {
25p->p_sigignore &= ~bit;
26if (sa->sa_handler == SIG_DFL)
27p->p_sigcatch &= ~bit;
28else
29p->p_sigcatch |= bit;
}
30(void) spl0();//降低interrupt level,出临界区
}
1~17在代码注释中解释过了,特别说下4句,这句设置进程mask 哪些signal。sigcantmask定义为:#definesigcantmask(sigmask(SIGKILL) | sigmask(SIGSTOP)),可见它表示SIGKILL和SIGSTOP是不能被mask的。 
18~23处理signal ignore情况,如果一个信号要被ignore,即sa->sa_handler=SIG_IGN或者sigprop[signum]为SA_IGNORE并且sa_handler为default handler,则清除p->p_siglist对应位,p_siglist记录的是这个进程接受到的(但还没有deliver的信号),或者说是这个进程pending的信号,这样这个signal就从进程的就绪signal中去掉了,直接ignore了。如果这个被ignore的信号不是SIGCONT,则把它加到p->p_sigignore,进程描述符的这个标志记录所有被ignore的信号,但是unix对ignore SIGCONT作了特殊处理,如果碰巧这里想ignore的信号是SIGCONT,我们并不把它记录在p->p_sigignore中,这个作用在下面post signal的时候可以看到。
23句把这个signal从p_sigcacth中清除,这样以后该进程不再处理这个signal
24~29处理这个signal没有ignore的情况。 首先清除p_sigignore中对应的位,表示这个信号不被忽略(25句)。如果指定的handler是default handler(26~27句),我们清除p_sigcatch中对应位,表示以后该进程不再catch这个signal,而交给系统默认处理,否则set p_sigcatch对应位。

四.设置进程的signal mask
所谓mask,就是告诉内核哪些signal我不关心,想block掉。4.4bsd提供sigprocmask()系统调用设置进程的mask:
/*
 * Manipulate signal mask.
 * Note that we receive new mask, not pointer,
 * and return old mask as return value;
 * the library stub does the rest.
 */
int sigprocmask(p, uap, retval)
register struct proc *p;
struct sigprocmask_args /* {
syscallarg(int) how;
syscallarg(sigset_t) mask;
} */ *uap;
register_t *retval;
{
int error = 0;

*retval = p->p_sigmask;
(void) splhigh();

switch (SCARG(uap, how)) {
case SIG_BLOCK:
p->p_sigmask |= SCARG(uap, mask) &~ sigcantmask;
break;

case SIG_UNBLOCK:
p->p_sigmask &= ~SCARG(uap, mask);
break;

case SIG_SETMASK:
p->p_sigmask = SCARG(uap, mask) &~ sigcantmask;
break;

default:
error = EINVAL;
break;
}
(void) spl0();
return (error);
}
传递来的参数三个tongue.gif---进程描述符,我们将设置这个进程的mask;uap--一个sigprocmask_args结构,定义为:
struct sigprocmask_args {
syscallarg(int) how;
syscallarg(sigset_t) mask;
};
其中how取值有三种可能:代表的含义分别为:
SIG_BLOCK: 在原来的mask基础上Block signal set
SIG_UNBLOCK: 在原来的mask基础上Unblock signal set
SIG_SETMASK: 设置mask为指定的signal set
另一个域mask就是caller指定的mask。这个函数不多说,很简单,从它的实现可以看到SIGSTOP和SIGKILL是不能被mask的。虽然这里SIGCONT可以被mask(屏蔽),但是下面我们将看到,它resume一个stop进程的功能永远不会被屏蔽。
最后一个参数retval用于返回原先的mask.

五。signal的产生(或者说是post)
内核把signal的处理分成两个阶段:产生阶段(也称post阶段)和deliver阶段。所谓产生阶段就是内核更新对应进程描述符的相关数据结构表明一个signal已经到来
在deliver阶段,内核将强制进程对接受到的信号做出反应,执行signal handler。
下面的情况可以产生Signal:
1.     按下CTRL+C产生SIGINT
2.     硬件中断,如除0,非法内存访问(SIGSEV)等等
3.     Kill函数可以对进程发送Signal
4.     Kill命令。实际上是对Kill函数的一个包装
5.     软件中断。如当Alarm Clock超时(SIGURG),当Reader中止之后又向管道写数据(SIGPIPE),等等

信号产生在4.4bsd中是由psignal()实现的,这个是对单个进程而言的,也称为对单个进程post一个signal(也就是产生一个信号),如果需要对一个进程组post signal,则可以用gsignal()
/*
 * Send the signal to the process.  If the signal has an action, the action
 * is usually performed by the target process rather than the caller; we add
 * the signal to the set of pending signals for the process.
 *
 * Exceptions:
 *   o When a stop signal is sent to a sleeping process that takes the
 *     default action, the process is stopped without awakening it.
 *   o SIGCONT restarts stopped processes (or puts them back to sleep)
 *     regardless of the signal action (eg, blocked or ignored).
 *
 * Other ignored signals are discarded immediately.
 */
void
psignal(p, signum)
register struct proc *p;
register int signum;
{
register int s, prop;
register sig_t action;
int mask;

1if ((u_int)signum >= NSIG || signum == 0)//如果是无效的signal,panic
2panic("psignal signal number");
3mask = sigmask(signum);//获得该信号对应的掩码
4prop = sigprop[signum];//获得该信号对应的default action

/*
 * If proc is traced, always give parent a chance.
 */
5if (p->p_flag & P_TRACED)//如果该进程被traced,则总是采用default action
6action = SIG_DFL;
7else {
/*
 * If the signal is being ignored,
 * then we forget about it immediately.
 * (Note: we don't set SIGCONT in p_sigignore,
 * and if it is set to SIG_IGN,
 * action will be SIG_DFL here.)
 */
8if (p->p_sigignore & mask)//如果该信号被忽略,则返回,什么也不做
9return;
10if (p->p_sigmask & mask)//如果该信号被mask,则只记录这个signal,而不deliver它不处理它
11action = SIG_HOLD;
12else if (p->p_sigcatch & mask)//如果该信号被catch,则我们要调用handler处理它
13action = SIG_CATCH;
14else//否则,应用default action
15action = SIG_DFL;
}

16if (p->p_nice > NZERO && action == SIG_DFL && (prop & SA_KILL) &&
17    (p->p_flag & P_TRACED) == 0)
18p->p_nice = NZERO;

19if (prop & SA_CONT)//如果是一个SIGCONT,则我们把该进程已接受的所有可以导致进程stop的信号从p_siglist中remove
20p->p_siglist &= ~stopsigmask;

21if (prop & SA_STOP) {
/*
 * If sending a tty stop signal to a member of an orphaned
 * process group, discard the signal here if the action
 * is default; don't stop the process below if sleeping,
 * and don't clear any pending SIGCONT.
 */
22if (prop & SA_TTYSTOP && p->p_pgrp->pg_jobc == 0 &&
23    action == SIG_DFL)
24return;
25p->p_siglist &= ~contsigmask;//如果该signal是那几个可以导致进程stop的signal,则这里我们把该进程pending的SIGCONT remove掉
}
26p->p_siglist |= mask;//把该signal加到进程pending signal列表中

/*
 * Defer further processing for signals which are held,
 * except that stopped processes must be continued by SIGCONT.
 */
27if (action == SIG_HOLD && ((prop & SA_CONT) == 0 || p->p_stat != SSTOP))
28return;
29s = splhigh();
30switch (p->p_stat) {//根据进程的当前状态分别处理

31case SSLEEP:
/*
 * If process is sleeping uninterruptibly
 * we can't interrupt the sleep... the signal will
 * be noticed when the process returns through
 * trap() or syscall().
 */
32if ((p->p_flag & P_SINTR) == 0)//如果该进程处于不可中断休眠状态,什么也不做,退出
33goto out;
/*
 * Process is sleeping and traced... make it runnable
 * so it can discover the signal in issignal() and stop
 * for the parent.
 */
34if (p->p_flag & P_TRACED)//如果进程被trace,则设置它为SRUN状态
35goto run;
/*
 * If SIGCONT is default (or ignored) and process is
 * asleep, we are finished; the process should not
 * be awakened.
 */
36if ((prop & SA_CONT) && action == SIG_DFL) {
37p->p_siglist &= ~mask;//如果这是个SIGCONT,并且我们ignore它或者没有catch它,则保持进程处于sleep状态,清除这个signal
38goto out;
}
/*
 * When a sleeping process receives a stop
 * signal, process immediately if possible.
 * All other (caught or default) signals
 * cause the process to run.
 */
39if (prop & SA_STOP) {//如果这是个可以导致进程stop的信号
40if (action != SIG_DFL)//如果被catch了,我们唤醒它进入SRUN状态
41goto runfast;
/*
 * If a child holding parent blocked,
 * stopping could cause deadlock.
 */
42if (p->p_flag & P_PPWAIT)//P_PPWAIT表示其父进程正等待在wait4()上,等着它执行或exit
43goto out;//我们直接退出,什么不做,因为如果这里让该进程stop可能导致死锁
44p->p_siglist &= ~mask;
45p->p_xstat = signum;
46if ((p->p_pptr->p_flag & P_NOCLDSTOP) == 0)
47psignal(p->p_pptr, SIGCHLD);//发送SIGCHLD通知父进程我的状态变换了
48stop(p);//stop这个进程
49goto out;
50} else
51goto runfast;//对于所有其他signal,统统唤醒进程,让它进入SRUN状态
/*NOTREACHED*/

52case SSTOP:
/*
 * If traced process is already stopped,
 * then no further action is necessary.
 */
53if (p->p_flag & P_TRACED)
54goto out;

/*
 * Kill signal always sets processes running.
 */
55if (signum == SIGKILL)
56goto runfast;

57if (prop & SA_CONT) {
/*
 * If SIGCONT is default (or ignored), we continue the
 * process but don't leave the signal in p_siglist, as
 * it has no further action.  If SIGCONT is held, we
 * continue the process and leave the signal in
 * p_siglist.  If the process catches SIGCONT, let it
 * handle the signal itself.  If it isn't waiting on
 * an event, then it goes back to run state.
 * Otherwise, process goes back to sleep state.
 */
58if (action == SIG_DFL)
59p->p_siglist &= ~mask;
60if (action == SIG_CATCH)
61goto runfast;
62if (p->p_wchan == 0)
63goto run;
64p->p_stat = SSLEEP;
65goto out;
}

66if (prop & SA_STOP) {
/*
 * Already stopped, don't need to stop again.
 * (If we did the shell could get confused.)
 */
67p->p_siglist &= ~mask;/* take it away */
68goto out;
}

/*
 * If process is sleeping interruptibly, then simulate a
 * wakeup so that when it is continued, it will be made
 * runnable and can look at the signal.  But don't make
 * the process runnable, leave it stopped.
 */
69if (p->p_wchan && p->p_flag & P_SINTR)
70unsleep(p);
71goto out;

default:
/*
 * SRUN, SIDL, SZOMB do nothing with the signal,
 * other than kicking ourselves if we are running.
 * It will either never be noticed, or noticed very soon.
 */
72if (p == curproc)//如果是当前进程,我们通知它有signal pending
73signotify(p);
74goto out;
}
/*NOTREACHED*/

runfast:
/*
 * Raise priority to at least PUSER.
 */
if (p->p_priority > PUSER)
p->p_priority = PUSER;
run:
setrunnable(p);
out:
splx(s);
}
PARTNER CONTENT

文章评论0条评论)

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