荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: bstone (Back to real world!), 信区: Hacker
标  题: unix环境高级编程--第10章 信号 (下)
发信站: BBS 荔园晨风站 (Wed Mar 29 08:51:59 2000), 转信

发信人: scircle (yuanyuan), 信区: Security
标  题: unix环境高级编程--第10章 信号 (下)
发信站: BBS 水木清华站 (Mon Mar 27 16:03:15 2000)

1012〓sigprocmask函数
在108节中曾说明一个进程的信号屏蔽字规定了当前阻塞而不能传送给该进程的
信号集。
调用函数sigprocmask可以检测或更改(或两者)进程的信号屏蔽字。
#include<signalh>
int sigprocmask(int how,const sigset 迹茫模*常病絫 *set,sigset 迹茫模?
2〗t *
oset);
返回:若成功为0,出错为-1 Returns:0 if OK,-1 on error
首先,oset是非定指针,进程的当前信号屏蔽字通过oset返回。其次,若set是一
个非定指
针,则参数how指示如何修改当前信号屏蔽字。图104说明了how可选用的值。SI
G-BLOCK是
或操作,而SIG-SETMASK则是赋值操作。
〖HT5"SS〗图104 用sigprocmask更改当前信号屏蔽字的方法〖HT5〗

如果set是个定指针,则不改变该进程的信号屏蔽字,how的值也无意义。
如果在调用sigprocmask后有任何未决的,不再阻塞的信号,则在sigprocmask返回
的至少将
的至少将
其中之一传送给该进程。
实例
程序1010是一个函数,它打印调用进程的信号屏蔽字所阻塞的信号的名称。从程
序1014
和1015中调用此函数。为了节省空间,没有对图101中列出的每一种信号测试
该屏蔽字
。(见练习109)
程序1010 为进程打印信号 帘为?
1013〓sigpending函数
sigpending返回对于调用进程被阻塞不能传送和当前未决的信号集。该信号集通过
set参数
返回。
#include<signalh>
int sigpending(sigset 迹茫模*常病絫 *set);
返回:若成功为0,出错为-1
实例
程序1011使用了很多前面说明过的信号功能。进程阻塞了SIGQUIT信号,保存了
当前信
号屏蔽字(以便以后恢复),然后睡眠5秒钟。在此期间所产生的退出信号都被阻塞
,不传送
至该进程,直到该信号不再被阻塞。在5秒睡眠结束后,检查是否有信号未决,然
后将SIGQU
后将SIGQU
IT设置为不再阻塞。
注意,在设置SIGQUIT为阻塞时,我们保存了老的屏蔽字。为了解除对该信号的阻
塞,我们
用老的屏蔽字重新设置了进程信号屏蔽字(SIG-SETMAS)。可以使用的另一种方法是
用SIG-U
NBLOCK使以前阻塞的信号不再阻塞。但是,应当了解如果我们编写了一个可能由其
他人使用
的函数,而且我们需要在我们的函数中阻塞一个信号,则不能用SIG-UNBLOCK解除
对此信号
的阻塞,这是因为此函数的调用者在调用本函数之前可能也阻塞了此信号。在这种
情况下我
们必须使用SIG-SETMASK将信号屏蔽字恢复为原先值。在1018节的system函数部
分有这
样的一个例子。
在睡眠期间如果产生了退出信号,那么程序运行到这一点则该信号是未决的,但是
不再受阻
塞,所以在sigprocmask返回之前,它就被传送到本进程。从程序的输出中我们可
以看到这
一点:在SIGQUIT处理程序(sig-quit)中的printf语句先执行,然后再执行sigpro
cmask之后
的printf语句。
〖HT5"〗程序1011 信号设置和sigprocmask的实例〖HT5〗
〖HT5"〗程序1011 信号设置和sigprocmask的实例〖HT5〗
然后该进程再睡眠5秒钟。如果在此期间再产生退出信号,那么它就会使该进程终
止,因为
在上次捕捉到该信号时,已将其处理方式设置为默认动作。此时如果键入终端退出
字符Cont
rol-\,则输出"QUIT(coredump)"信息,表示进程因接到SIGQUIT而终止,但是在
core文
件中保存了与进程有关的信息(该信息是由shell发现其子进程异常终止时打印的。
)
$ aout
^\ 产生信号一次(在5秒之内)
SIGQUIT pending 从sleep返回后
caught SIGQUIT 在信号处理程序中
SIGQUIT unblocked 从sigprocmask返回后
^\Quit(coredump) 再次产生信号
$ aout
^\^\^\^\^\^\^\^\^\^\ 产生信号10次(在5秒之内)
SIGQUIT pending
caught SIGQUIT 只产生信号一次
SIGQUIT unblocked
^\Quit(coredump) 再产生信号
注意,在第二次运行该程序时,在进程睡眠期间我们使SIGQUIT信号产生了10次,
但是解除
但是解除
了对该信号的阻塞后,只向进程传送一次SIGQUIT。从中可以看出在此系统上没有
将信号进
行排队。
1014〓sigaction函数
sigaction函数的功能是检查或修改(或两者)与指定信号相关联的处理动作。此函
数取代了U
nix早期版本使用的signal函数。在本市末尾我们用sigaction函数实现了signal。

#include<signalh>
int sigaction(int signo,const struct sigaction *act,
struct sigaction *oact);
返回:若成功为0,出错为-1
其中,参数signo是要检测或修改具体动作的信号的编号数。若act指针非定,则要
修改其动
作。如果oact指针非定,则系统返回该信号的原先动作。此函数使用下列结构:

struct sigaction {
void (*sa 迹茫模*常病絟andler) (); /* 信号处理程序的地址,或SIG-IGN,
或SIG-DFL
,
sigset 迹茫模*常病絫 sa 迹茫模*常病絤ask; /*添加的要阻塞的信号
int sa 迹茫模*常病絝lags; /*信号选项,见图105
int sa 迹茫模*常病絝lags; /*信号选项,见图105
};
当更改信号动作时,如果sa-handler指向一个信号捕捉函数(不是常数SIG-IGN或S
IG-DFL)
,则sa-mask字段说明了一个信号集,在调用信号捕捉函数之前,该信号集要加到
进程的信
号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字恢复为原先值。这
样,在调
用执行信号处理程序时就能阻塞某些信号。在信号处理程序被调用时,系统建立的
新信号屏
蔽字会自动包括正被传送的信号。因此就保证了在处理一个给定的信号时,如果这
种信号再
次发生,那么它会被阻塞到对前一个信号的处理结束为止。回忆108节,若同一
种信号多
次发生,通常并不将它们排队,所以如果在某种信号被阻塞时,它发生了五次,那
么对这种
信号解除阻塞后,其信号处理函数通常只会被调用一次。
一旦对一个给定的信号设置了一个动作,那么在用sigaction改变它之前,该设置
就一直有
效。这与早期的不可靠信号机制不同,而符合了POSIX1在这方面的要求。
act结构的sa-flags字段包含了对信号进行处理的各个选择项。图105详细列出了
这些选择
项的意义。
项的意义。
图105 信号处理的选择项标志(sa-flags)
实例〖CD2〗signal函数
现在让我们用sigaction实现signal函数。43+BSD也是这样做的(POSIX1的原理
阐述中分
也说明这里POSIX所希望的)。SVR4则提供老的不可靠信号语义的signal函数。除非
为了向后
兼容面使用老的语义,在SVR4之下,你也应使用下面的signal实现,或者直接调用
sigactio
n(在SVR4下,可以在调用sigaction时指定SA-RESETHAND和SA-NODEFER选项以实现
老的语义
的signal函数)。
在本书中所有调用signal的实例都是调用程序1012中所实现的该函数。
〖HT5"〗程序1012 用sigaction所实现的signal函数〖HT5〗
注意,我们必须用sigemptyset函数初始化act结构的成员。不能保证
actsa-mask=0;
会做同样的事情。
我们对除SIGALRM以外的所有信号都企图设置SA-RESTART标志,于是被这些信号中
断的系统
调用都能再起动。不希望再起动由SIGALRM信号中断的系统调用的原因是:我们希
望对I/
O操作可以设置时间限制。(请回忆与程序107有关的讨论。)
某些系统定义了SA-INTERRUPT标志。这些系统的默认方式是重新起动被中断的系统
某些系统定义了SA-INTERRUPT标志。这些系统的默认方式是重新起动被中断的系统
调用,
而指定此标志则使系统调用被中断后不再重起动。
实例〖CD2〗signal 迹茫模*常病絠ntr函数
程序1013是signal函数的另一种版本,它阻止被中断的系统调用的再起动。
程序1013 signal-intr函数
如果系统定义了SA-INTERRUPT,则在sa-flags中增加该标志,这样也就阻止了被中
断的系统
调用的再起动。
1015〓sigsetjmp和siglongjmp函数
在710节中说明了用于非局部转移的setjmp和longjmp函数。在信号处理程序中经
常调用lo
ngjmp函数以返回到程序的主循环中,而不是从该处理程序返回。确实,ANSIC标准
说明一个
信号处理程序可以或者返回,或者调用abort,exit或longjmp。在程序105和10
8中已经
有了这种情况。
调用longjmp有一个问题。当捕捉到一个信号时,进入信号捕捉函数,此时当前信
号被自动
地加到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断此信号处理程序。
如果用lo
ngjmp跳出此信号处理程序,则对此进程的信号屏蔽字会发生什么呢?
在43+BSD下,setjmp和longjmp保存和恢复信号屏蔽字。但是,SVR4并不做这种
在43+BSD下,setjmp和longjmp保存和恢复信号屏蔽字。但是,SVR4并不做这种
操作。4
3+BSD提供函数-setjmp和-longjmp,它们也不保存和恢复信号屏蔽字。
为了允许两种形式并存,POSIX1并没有说明setjmp和longjmp对信号屏蔽字的作
用。作为
替代,POSIX1定义了两个新函数sigsetjmp和siglongjmp。在信号处理程序中作
非局部转
移时应当使用这两个函数。
#include <setjmph>
int sigsetjmp(sigjmp 迹茫模*常病絙uf env,int savemask);
void siglongjmp bufenv int val;
返回:若直接调用为0,若从siglongjmp调用返回则为非0。
这两个函数和setjmp,longjmp之间的唯一区别是sigsetjmp增加了一个参数。如果
savemask
非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。
在调用siylongjmp时,如果带非0 savemask的sigsetjmp调用已经保存了env,则si
glongjmp
从其中恢复保存的信号屏蔽字。
实例
程序1014显示了在信号处理程序被调用时,系统所设置的信号屏蔽字如何自动地
包括刚被
捕捉到的信号。它也例示了如何使用sigsetjmp和siglongjmp函数。
程序1014 信号屏蔽、sigsetjmp和siglongjmp的实例
程序1014 信号屏蔽、sigsetjmp和siglongjmp的实例
此程序例示了另一种技术,只要在信号处理程序中调用siglongjmp就应使用这种技
术。在调
用sigsetjmp之后将变量canjump设置为非0。在信号处理程序中检测此变量,仅当
它为非0值
时才调用siglongjmp。这提供了一种保护机制,使得若在jmpbuf(跳转缓存)尚未由
sigsetjm
p初始化时,调用信号处理程序,则不执行其处理动作就返回。(在本程序中,sig
longjmp之
后程序很快就结束,但是在较大的程序中,在siglongjmp之后,信号处理程序可能
仍旧被设
置)。在一般的C代码中(不是信号处理程序),对于longjmp并不需要这种保护措施
。但是,
因为信号可能在任何时候发生,所以在信号处理程序中,需要这种保获措施。
在程序中使用了数据类型sig-atomic-c,它是ANSI C定义的在写时不会被中断的变
量类型。
它意味着这种变量在具有虚存的系统上不会跨越页的边界,可以用一条机器指令对
其进行存
取。对于这种类型的变量总是带ANSI C的类型修饰符vlatile,其原因是:该变量
将由二个
不同的控制线-main函数和异步执行的信号处理程序存取。
图106显示了此程序的执行时间顺序。可将图106分成三部分:左面部分(对应
于main),
于main),
中间部分(sig-usr1)和右面部分(sig-alrm)。在进程执行左面部分时,信号屏蔽字
是0(没有
信号是阻塞的)。而执行中间部分时,其信号屏蔽字是SIGUSR1。执行右面部分时,
信号屏蔽
字是SIGUSR1|SIGALRM。
〖HT5"〗图106 处理两个信号的实例程序的时间顺序〖HT5〗
执行程序1014得到下面的输出:
$ aout & 在后台启动进程
starting main:
[1] 531 作业控制shell打印其进程ID
$ kill -USR1 531 向该进程发送SIGUSR1
starting sig 迹茫模*常病絬sr1:SIGUSR1
$ in sig 迹茫模*常病絘lrm:SIGUSR1 SIGALRM
finishing sig 迹茫模*常病絬sr1:SIGUSR1
ending main:
[1]+Done aout & 键入回车
这与我们所期望的相同:当调用一个信号处理程序时,被捕捉到的信号加到了进程
的当前信
号屏蔽字。当从信号处理程序返回时,原来的屏蔽字被恢复。另外,siglongjmp恢
复了由si
gsetjmp所保存的信号屏蔽字。
如果将程序1014中的sigsetjmp和siglongjmp分别代换成-setjmp和-longjmp,则
如果将程序1014中的sigsetjmp和siglongjmp分别代换成-setjmp和-longjmp,则
在43+B
SD下运行此程序,最后一行输出变成:
ending main:SIGUSR1
这忌味着在调用-setjmp之后执行main函数时,其SIGUSR1是阻塞的。这多半不是我
们所希
望的。
1016〓sigsuspend函数
上面已经说明,更改进程的信号屏蔽字可以阻塞或解除阻塞所选择的信号。使用这
种技术可
以保获不希望由信号中断的代码临界区。如果希望对一个信号解除阻塞,然后pau
ser以等待以前被阻塞的信号发生,则将又如何呢?假定信号是SIGINT,实现这一点
的不正确方法是:

sigset 迹茫模*常病絫 newmask,oldmask;
sigemptyset(&newmask);
sigaddset(&newmask,SIGINT);
/* 阻塞SIGINT,保存当前信号屏蔽 */
if(sigprocmask(SIG 迹茫模*常病紹LOCK,&newmask,&oldmask)<0)
err 迹茫模*常病絪ys("SIG 迹茫模*常病紹LOCK error");
/* 代码临界区 */
/* 恢复信号屏蔽,它不阻塞SIGINT
if(sigprocmask(SIG 迹茫模*常病絊ETMASK,&oldmask,NULL)<0)
if(sigprocmask(SIG 迹茫模*常病絊ETMASK,&oldmask,NULL)<0)
err 迹茫模*常病絪ys("SIG 迹茫模*常病絊ETMASK error");
pause();/* 等待信号发生 */
/* 继续处理 */
如果在解除对SIGINT的阻塞和pause之间发生了SIGINT信号,则此信号就被丢失。
这是早期的不可靠信号机制的另一个问题。
为了纠正此问题,需要在一个原子操作中实现恢复信号屏字,然后使进程睡眠这种
功能是由sigsuspend函数所提供的。
#include<signalh>
int sigsuspend(const sigset 迹茫模*常病絫 *sigmask);
返回:-1 errno设置为EINTR
进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会
终止该进
程的信号之前,该进程也被挂起。如果捕捉到一个信号而且从该信号处理程序返回
,则sigs
uspend返回,并且该进程的信号屏蔽字设置为调用sigsuspend之前的值。
注意,此函数没有成功返回值。如果它返回到调用者,则总是返回-1,并且errno
设置为EIN
TR(表示一个被中断的系统调用)。
实例
程序1015显示了保护临界区,使其不被指定的信号中断的正确方法。
程序1015 保护临界区不被信号中断
注意,当sigsuspend返回时,它将信号屏蔽字设置为调用它之前的值。在本例中,
注意,当sigsuspend返回时,它将信号屏蔽字设置为调用它之前的值。在本例中,
SIGINT信
号将被阻塞。因此将信号屏蔽复置为早先保存的值(oldmask)。
运行程序1015得到下面的输出:
$ aout
in critical region:SIGINT
^? 键入我们的中断字符
in sig 迹茫模*常病絠nt:SIGINT
after return from sigsuspend:SIGINT
从中可见,在sigsuspend返回时,它将信号屏蔽字恢复为调用它之前的值。
实例
sigsuspend的另一种应用是等待一个信号处理程序设置一个全局变量。在程序10
16中,捕
捉中断信号和退出信号两者,但是希望只有在捕捉到退出信号时再继续执行main程
序。此程
序的样本输出是:
$ aout
^? 键入我们的中断学符
interrupt
^? 再次键入我们的中断字符
interrupt
^? 再一次
interrupt
interrupt
^/$ 用退出符终止
考虑到ANSI C的非POSIX系统,以及POSIX系统两者之间的可移植性,在一个信号处
理程序中
我们唯一应当做的是赋一个值给类型为sig-atomic-t的变量。POSIX1规定得更多
一些,它
说明了在一个信号处理程序中可以安全地调用的函数表(图103),但是如果这样
来编写代
码,则它可能不会正确地在非POSIX系统上运行。
〖HT5"〗程序1016 用sigsuspend等待一个全局变量被设置〖HT5〗
实例
可以用信号实现父子进程之间的同步,这是信号应用的另一个实例。程序1017实
现了88
节中提到的五个例程:TELL-WAIT、TELL-PARENT、TELL-CHILD、WAIT-PARENT和WA
IT-CHILD

其中使用了两个用户定义的信号:SIGUSR1由父进程发送给子进程;SIGUSR2由子进
程发送给
父进程。在程序143中,我们说明了用管道的这五个函数的另一种实现。
程序1017 父子进程可用来实现同步的例程
如果在等待信号发生时希望去睡眠,则Sigsuspend函数可以满足此种要求(正如在
前面两个
例子中所示),但是如果在等待信号期间希望调用其它系统函数则均如何呢?不幸的
interrupt
^/$ 用退出符终止
考虑到ANSI C的非POSIX系统,以及POSIX系统两者之间的可移植性,在一个信号处
理程序中
我们唯一应当做的是赋一个值给类型为sig-atomic-t的变量。POSIX1规定得更多
一些,它
说明了在一个信号处理程序中可以安全地调用的函数表(图103),但是如果这样
来编写代
码,则它可能不会正确地在非POSIX系统上运行。
〖HT5"〗程序1016 用sigsuspend等待一个全局变量被设置〖HT5〗
实例
可以用信号实现父子进程之间的同步,这是信号应用的另一个实例。程序1017实
现了88
节中提到的五个例程:TELL-WAIT、TELL-PARENT、TELL-CHILD、WAIT-PARENT和WA
IT-CHILD

其中使用了两个用户定义的信号:SIGUSR1由父进程发送给子进程;SIGUSR2由子进
程发送给
父进程。在程序143中,我们说明了用管道的这五个函数的另一种实现。
程序1017 父子进程可用来实现同步的例程
如果在等待信号发生时希望去睡眠,则Sigsuspend函数可以满足此种要求(正如在
前面两个
例子中所示),但是如果在等待信号期间希望调用其它系统函数则均如何呢?不幸的
handle 迹茫模*常病絠ntr();
}else
/* 某个其它错 */
}
在调用select之前测试各全局附标志,如果select返回一个中断的系统调用错,则
再次进行

试。如果在前两个if语句和后过的select调用之间捕捉到两个信中的任一个,则问
题就发生
了。正如代码中的注释所指出的,在此处发生的信号丢失了。调用了相应的信号处
理程序,
它们设置了相应的全局变量,但是select决不会返回(除非某些数据已准备好可读
)。
我们想要能够做的是下列步骤序列:
1阻塞SIGINT和SIGALRM
2测试两个全局变量以判别是否发生了一个信号,如果已发生则处理此条件。

3调用select(或任何其它系统调用,例如read)并解除对这两个信号的阻塞,这
两个操作
要作为一个原子操作。
sigsuspend函数仅当第3步是一个pause操作时才帮助我们。
1017〓abort函数
前面已提及abort函数的功能是使程序异常终止。
前面已提及abort函数的功能是使程序异常终止。
#include<stdlibh>
void abort(void);
此函数不返回
此函数将SIGABRT信号发送给调用进程。一个进程不应忽略此信号。
ANSIC要求若捕捉到此信号而且相应信号处理程序返回,abort仍不会返回到其调用
者。如果
捕捉到信号,则信号处理程序不能返回的唯一方法是它调用exit、exit、longjmp
或siglong
jmp。(1015节讨论了在longjmp和siglongjmp之间的区别)。POSIX1也说明abo
rt复盖了
进程对此信号的阻塞和忽略。
让进程捕捉SIGABRT的意图是:在进程终止之前由其执行所需的清除操作。如果进
程并不在
信号处理程序中终止自己,POSIX1说明当信号处理程序返回时,abort终止该进
程。
ANSI C对此函数的规格说明将这一问题留由实现决定,而不管输出流是否刷新以及
不管临时
文件(513节)是否删除。POSIX1的要求则进了一步,它要求如果abort调用终止
进程,
则它应该有对所有打开的标准I/O流调用fclose的效果。但是如果abort调用并
不终止进
程,则它对打开流也不应有影响。正如我们将在后面所看到的,这种要求是很难实
前面已提及abort函数的功能是使程序异常终止。
#include<stdlibh>
void abort(void);
此函数不返回
此函数将SIGABRT信号发送给调用进程。一个进程不应忽略此信号。
ANSIC要求若捕捉到此信号而且相应信号处理程序返回,abort仍不会返回到其调用
者。如果
捕捉到信号,则信号处理程序不能返回的唯一方法是它调用exit、exit、longjmp
或siglong
jmp。(1015节讨论了在longjmp和siglongjmp之间的区别)。POSIX1也说明abo
rt复盖了
进程对此信号的阻塞和忽略。
让进程捕捉SIGABRT的意图是:在进程终止之前由其执行所需的清除操作。如果进
程并不在
信号处理程序中终止自己,POSIX1说明当信号处理程序返回时,abort终止该进
程。
ANSI C对此函数的规格说明将这一问题留由实现决定,而不管输出流是否刷新以及
不管临时
文件(513节)是否删除。POSIX1的要求则进了一步,它要求如果abort调用终止
进程,
则它应该有对所有打开的标准I/O流调用fclose的效果。但是如果abort调用并
不终止进
程,则它对打开流也不应有影响。正如我们将在后面所看到的,这种要求是很难实
不等价于
对所有打开的流调用fclose(因为只刷新它们,并不关闭它们),但是当进程终止时
,系统会
关闭所有打开文件。如果进程捕捉此信号并返回,则我们刷新所有的流。(如果进
程捕捉此
信号,并且不返回,则我们也不会触及标准I/O流。)我们没有处理的唯一条件
是如果进
程捕捉此信号,然后调用-exit。在这种情况下,任何未刷新的在存储器中的标准
I/O缓存
都被丢弃。我们假定捕捉此信号,并特地调用-exit的调用者并不想要刷新缓存。

回忆109节,如果调用kill使为调用者产生信号,并且如果该信号是不被阻塞的
(在程序10
18中保证做出了这一点),则在kill返回前该信号就被传送给了该进程。这样我
们就可确
知如果对kill的调用返回了,则该进程一定已捕捉到该信号,并且也从该信号处理
程序返回
。
程序1018 abort的POSIX1实现
1018〓system函数
在812节已经有了一个system函数的实现。但是该版本并不做任何信号处理。PO
SIX2要
求system忽略SIGINT和SIGQUIT,阻塞SIGCHLD。在给出一个正确地处理这些信号的
求system忽略SIGINT和SIGQUIT,阻塞SIGCHLD。在给出一个正确地处理这些信号的
一个版本
之前,先说明为什么要考虑信号处理。
实例
程序1019使用812节中的system版本,用其调用ed(1)编辑程序。(ed很久以来
就是Unix
的组成部分。在这里使用它的原因是:它是一个交互式的捕捉中断和退出信号的程
序。若从
shell调用ed,并按中断字符,则它捕捉中断信号并打印问号符。它也将对退出符
的处理方
式设置为忽略。)
程序1019〓用syetem调用ed编辑程序
程序1019捕捉SIGINT和SIGCHLD。若调用它则可得:
$ aout
a 将正文添加至编辑器缓存
Here is one line of text
and another
行首的点停止添加方式
1,$p 打印第1行至最后1行,以便观察缓存中的内容
Here is one line of text
and another 将缓存写至一文件
w tempfoo 编辑器称写了37个字节
37 离开编辑器
37 离开编辑器
q
caught SIGCHLD
当编辑程序终止时,对父进程(aou十进程)产生SIGCHLD信号。父进程捕捉它,执
行其处理
程序sig-chid,然后从其返回。但是若父进程正捕捉SIGCHLD信号,因为它已创建
自己的子
进位,所以它应当这样做,使其了解何时它的子进程已终止。在system函数执行时
,父进程
中该信号的传送应当阻塞。确实,这就是POSIX2所说明的。否则,当system创建
的子进程
结束时,system的调用者可能错误地认为,它自己的一个子进程结束了。
如果我们再次执行该程序,在这次运行时将一个中断信号传送给编辑程序,则可得
:
$ aout 
a 将正文添加至编辑器缓存
hello,world
 行首的点停止添加方式
1,$p 打印第1街至最后1行
hello,world
w etmpfoo 将缓存写至一文件
13 编辑器称写了13个字节
^? 键入中断符
^? 键入中断符
? 编辑器捕捉信号,打印问号
caught SIGINT 父进程执行同一操作
q 离开编辑器
caught SIGCHLD
回忆96节可知,按中断字符使中断信号传送给前台进程组中的所有进程。图10
7显示了
编辑程序正在进行时的进程安排。
图107 程序1019运行时的前、后台进程组
在这一实例中,SIGINT被送给三个前台进程。(shell进程忽略此信号)从输出中可
见,Qou
t进程和ed进程捕捉该信号。但是,当我们用system运行另一道程序(例如ed)时,
不应使父
、子进程两者都捕捉终端一产生的两个信号:中断和退出。这两个信号只应送给正
在运行的
程序:子进程。因为由system执行的命令可能是交互作用命令(为本例中的ed),以
及因为sy
stem的调用者在程序执行时放弃了控制,等待该执行程序的结束,所以system的调
用者就不
应接收这两个终端一产生的信号。这就是为什么POSIX2规定system的调用者应当
忽略这两
个信号的原因。
实例
实例
程序1020是system函数的另一个实现,它进行了所要求的信号处理。
程序1020是ystem函数的POSIX2实现
很多较早的文献中使用下列程序段忽略中断和退出信号:
if((pid=fork())<0)
err 迹茫模*常病絪ys("fork error");
else if (pid==0){ /* 子进程 */
execl(…);
 迹茫模*常病絜xit(127);
}
/* parent */
old 迹茫模*常病絠ntr=signal(SIGINT,SIG 迹茫模*常病絀GN);
old 迹茫模*常病絨uit=signal(SIGQUIT,SIG 迹茫模*常病絀GN);
waitpid(pid,&status,0)
signal(SIGINT,old 迹茫模*常病絠ntr);
signal(SIGQUIT,old 迹茫模*常病絨uit);
这段代码的问题是:在fork之后不能保证父进程还是子进程先运行。如果子进程先
运行,父
进程在一段时间后再运行,那么在父进程将中断信号的配置改变为忽略之前,就可
能产生这
种信号。由于这种原因在程序1020中在fork之前就改变对该信号的配置。
注意,子进程在调用execl之前要先恢复这两个信号的配置。这就允许在调用者配
置的基础
置的基础
上,execl可将它们的配置更改为默认值。
system的返回值
system的返回值是shell的终止状态,它不总是执行命令字符串进程的终止状态。
在程序8
13中有一些例子,其结果正是我们所期望的;如果执行一条如date那样的简单命令
,则其终
止状态是0。执行shell命令exit44,则得终止状态44。在信号方面又如何呢?
让我们运行程序814,并向正执行的命令发送一些信号。
$ tsys "sleep 30"
^?normal termination,exit status=130 wetype our interrupt key键入中断

$ tsys "sleep 30"
^\sh:946 Quit 退出
normal termination,exit status=131
当用中断信号终止sleep时,pr-exit函数(程序83)认为它正常终止。当用退出键
消sleep
进程时,发生同样的事情。终止状态130,131又是怎样得到的呢?原来Bourne she
ll有一个
在其文档中没有清楚地说明的特性,其终止状态是128加上它所执行的命令由一个
信号终止
时的该信号编号数。用交互方式使用shell可以看到这一点。
$ sh 确保运行Bourne shell
$ sh 确保运行Bourne shell
$ sh -c "sleep 30"
^? 按中断键
$ echo $? 打印最后一条命令的终止状态
130
$ sh -c "sleep 30"
^\sh:962 Quit-core dumped 按退出键
$ echo $?
131
$ exit 离开Bourne shell
在所使用的系统中,SIGINT的值为2,SIGQUIT的值为3,于是给出shell终止状态1
30,131
。
再做几个类似的例子,这一次将一个信号直接送给shell,然后观察system返回什
么。
$ tsys "sleep 30" ^ 这一次在后台启动它
[1] 980
$ ps 查看进程ID
PID TT STAT TIME COMMAND
980 p3 S 0:00 tsys sleep 30
981 p3 S 0:00 sh -c sleep 30
982 p3 S 0:00 sleep 30
985 p3 R 0:00 ps
985 p3 R 0:00 ps
$ kill -KILL 981
kill shell
abnormal termination,signal number=9
[1]+Done tsys "sleep 30" &
从中可见仅当shell本身异常终止时,system的返回值再报告一个异常终止。
在编写使用system函数的程序时,一定要正确地解释返回值。如果直接调用fork,
exec和wa
it,则终止状态与调用system是不同的。
1019〓sleep函数
在本市的很多例子中都已使用了sheep函数,在程序104和105中有两个sleep的
很完善的
实现。
# include<unistdh>
unsigned int sleep(unsigned int seconds);
返回:0或未睡的秒数
此函数使调用进程被挂起直到:
1已经过了seconds所指定的墙上时钟时间,或者
2该进程捕捉到一个信号并从信号处理程序返回。
如同alarm信号一样,由于某些系统活动,实际返回时间比所要求的会迟一些。

在第一种情形,返回值是0。当由于捕捉到某个信号,sleep提早返回时(第二种情
形),返回
形),返回
值是末睡是的秒数(所要求的时间减实际睡眠时间)。
sleep可以用alarm函数(1010节)实现,但这并不是一定要求的。但是如果使用a
larm,则
这两个函数之间可以有交互作用。POSIX1标准对这些交互作用并未作任何说明。
例如,若
先调用alarm(10),过了3秒后又调用sleep(5),则如何呢?sleep将在5秒后返回(假
定在这
段时间内没有捕捉到另一个信号),但是否在2秒后又产生另一个SIGALRM信号呢?这
种细节依
赖于实现。
SVR4用alarm实现sleep。sleep(3)手册页中说明以前按排的闹钟仍被正常处理。例
如,在前
面的例子中,在sleep返回之前,它安排在2秒后再次到达闹钟时间。在这种情况下
,sleep
返回0。(很明显,sleep必须保存SIGALRM信号处理程序的地址,在返回前重新设置
它)。另
外,如果先做一次alarm(6),3秒钟之后又做一次sleep(5),则在3秒后sleep返回,
而不是5
秒钟。而sleep的返回值则是未睡定的时间2秒。
43+BSD则使用另一种技术:由setitimer(2)提供间隔计时器。该计时器独立于a
larm函数
,但在以前设置的间隔计时器和sleep之间仍能有相互作用。另外,即使闹钟计时
,但在以前设置的间隔计时器和sleep之间仍能有相互作用。另外,即使闹钟计时
器(alarm)
和间隔计时器(setitimer)是分开的,但是不幸它们使用同一SIGALRM信号。因为s
leep暂时
将该信号的处理程序改变为它自己的函数,所以在alarm和sleep之间仍可能有不所
希望的相
互作用。
如果混合调用sleep和其它与时间有关的函数,它们之间有相互作用,则你应当清
楚地了解
你所使用的系统是如何实现sleep的。
以前贝克莱类的sleep实现不提供任何有用的返回信息。这在43+BSD中已经解决
。
实现
程序1021是一个POSIX1 sleep函数的实现。此函数是程序104的修改版,它
可靠地处
理信号,避免了早期实现中的竞态条件,但是仍未处理与以前设置的闹钟的相互作
用。(正
如前面提到的POSIX1并未对这些交互作用进行定义)。
程序1021 sleep的可靠实现
与程序104相比为了可靠的实现sleep,程序1021的代码比较长。程序中没有使
用任何形
式的非局部转移(如程序105为了避免在alarm和pause之间的竞态条件所做的那样
),所以
),所以
对处理SIGALRM信号期间可能执行的其它信号处理程序没有影响。
1020〓作业控制信号
在图101中有六个POSIX1认为是与作业控制有关的信号。
SIGCHLD 要进程已停止或终止。
SIGCONT 如果进程已停止,则使其继续运行。
SIGSTOP 停止信号(不能被捕促或忽略)。
SIGTSTP交互停止信号。
SIGTTIN 一个后台进程组的成员读控制终端。
SIGTTOU 一个后台进程组的成员写控制终端。
虽然仅当系统支持作业控制时,POSIX1才要求它支持SIGCHLD,但是几乎所有Un
ix版本,都支持这种信号。我们已经说明了在子进程终止时这种信号是如何产生的。
大多数应用程序并不处理这些信号〖CD2〗交互式shell通常做处理这些信号的所有
工作。当我们按挂起字符(通常是Control-Z)时,SIGTSTP被送至后台进程组的所有
进程。当我们通知shell在前台或后台恢复一个作业时,该shell向该作业中的所有
进程发送SIGCONT信号。相类似,如果向一个进程传送了SIGTTIN或SIGTTOU信号,
则按系统默认,此进程就停止,作业控制shell了解到这一点后就通知我们。
一个例外是管理终端的进程〖CD2〗例如,vi(1)编辑程序。当用户要挂起它时,它
需要能了解到这一点,这样它就能将终端状态恢复到vi起动时的情况。另外,当
在前台恢复它时,它需
要将终端状态设置回所希望的状态,它需要重画终端屏幕。我们可以在下面的例子
中观察到
vi这样的程序是如何处理这种情况的。
vi这样的程序是如何处理这种情况的。
在作业控制信号间有某种相互作用。当对一个进程产生四种停止信号(SIGTSTP,S
IGSTOP,S
IGTTIN或SIGTTOU)中的任何一种时,对该进程的任一末决的SIGCONT信号就被丢弃
。相类似
,当对一个进程产生SIGCONT信号时,对该同一进程的任一末决的停止信号被丢弃
。
注意,SIGCONT的默认动作是继续一个进程,如果该进程是停止的,否则忽略此信
号。通常
,我们对该信号无需做任何事情。当对一个停止的进程产生一个SIGCONT信号时,
该进程就
继续,即使该信号是被阻塞或忽略也是如此。
实例
程序1022例示了当一道程序处理作业控制时所使用的通常的代码序列。这道程序
只是将其
标准输入复制到其标准输出,但是在信号处理程序中以注释形式给出了管理屏幕的
程序所执
行的典型操作。当程序1022起动时,仅当SIGTSTP信号的配置是SIG-DFL,它再安
排捕捉该
信号。其理由是:当此程序由不支持作业控制的shell(例如/bin/sh)所起动时,
此信号的

置应当设置为SIG-IGN。实际上,shell并不显式地忽略此信号,而是init将这三个
vi这样的程序是如何处理这种情况的。
在作业控制信号间有某种相互作用。当对一个进程产生四种停止信号(SIGTSTP,S
IGSTOP,S
IGTTIN或SIGTTOU)中的任何一种时,对该进程的任一末决的SIGCONT信号就被丢弃
。相类似
,当对一个进程产生SIGCONT信号时,对该同一进程的任一末决的停止信号被丢弃
。
注意,SIGCONT的默认动作是继续一个进程,如果该进程是停止的,否则忽略此信
号。通常
,我们对该信号无需做任何事情。当对一个停止的进程产生一个SIGCONT信号时,
该进程就
继续,即使该信号是被阻塞或忽略也是如此。
实例
程序1022例示了当一道程序处理作业控制时所使用的通常的代码序列。这道程序
只是将其
标准输入复制到其标准输出,但是在信号处理程序中以注释形式给出了管理屏幕的
程序所执
行的典型操作。当程序1022起动时,仅当SIGTSTP信号的配置是SIG-DFL,它再安
排捕捉该
信号。其理由是:当此程序由不支持作业控制的shell(例如/bin/sh)所起动时,
此信号的

置应当设置为SIG-IGN。实际上,shell并不显式地忽略此信号,而是init将这三个
置应当设置为SIG-IGN。实际上,shell并不显式地忽略此信号,而是init将这三个
作业控制
信号SIGTSTP、SIGTTIN和SIGTTOU设置为SIG-IGN。然后,这种配置由所有登录she
ll继承。
只有作业控制shell才应将这三个信号重新设置为SIG-DFL。
当我们键入挂起字符时,进程接到SIGTSTP信号,然后该信号处理被调用。在此点
上,我们
应当进行与终端有关的处理:将光标移到左下角,恢复终端工作方式,等等。在将
SIGTSTP
重新设置为默认值(停止该进程),并且解除了对此信号的阻塞之后,进程向自己发
送同一信
号SIGTSTP。
因为现在正处理SIGTSTP信号,而在捕捉到该信号期间系统自动地阻塞它,所以我
们应当解

对此信号的阻塞。仅当某个进程(通常是正响应一个交互式fg命令的作业控制shel
l)向该进
程发送一个SIGCONT信号时,该进程再继续。我们不捕捉SIGCONT信号。该信号的默
认配置是
继续停止的进程,当此发生时,此程序如同从kill函数返回一样继续运行。当此程
序继续运
行时,将SIGTSTP信号再设置为捕捉,并且做我们所希望做的终端处理。(例如可以
重画屏幕
者是一个定指针,或者是一个指向siginfo结构的指针。(第三个参数提供在一个进
程内不同
控制线程的有关信息,我们在此不对它进行讨论)。
struct siginfo {
int si 迹茫模*常病絪igno; /* 信号编号 */
int si 迹茫模*常病絜rrno; /* 若非0,则为<errnoh>中的errno值
int si 迹茫模*常病絚ode; /* 附加的info(取决于信号)
pid 迹茫模*常病絫 si 迹茫模*常病絧id; /* 发送进程ID */
uid 迹茫模*常病絫 si 迹茫模*常病絬nid; /* 发送进程实际用户ID */
/* 其它字段 */
};
对于由硬件产生的信号,例如SIGFPE,si-code值给出附加的信息;FPE-INTDIV表
示整数除
以0,FPE-FLTDIV表示浮点数除以0等等。如若si-code小于或等于0,则表示信号是
由调用kill(2)的用户进程产生的。在此情况下,si-pid和si-uid给出了发透此信号的
进程的有关信息。依赖于正被捕捉的信号,还有一些信息可用,见SVR4 siginfo(5)手
册页。

43+BSD信号处理程序的附加参数
43+BSD总是用三个参数调用信号处理程序:
handler(int signo,int code,struct sigcontext *scp);
参数signo是信号编号,code给出某些信号的附加信息。例如,对于SIGFPE的code
值FPE-INT
以0,FPE-FLTDIV表示浮点数除以0等等。如若si-code小于或等于0,则表示信号是

DIV-TRAP表示整数除以0。第三个参数SCP是与硬件有关的。

1022〓摘要
信号用于很多比较复杂的应用程序中。对进行信号处理的原因和方式有较好理解对
高级Unix程序设计是极其重要的。本章对Unix信号进行详细而且比较深入的介绍。
开始时先说明以前的信号实施的问题以及它们又是如何显现出来的。然后介绍POSIX1
的可靠信号概念以及所有相关的函数。在此基础上接着提供了abort、system和sleep
函数的POSIX1实现。最后以观察分析作业控制信号结束。


--
※ 来源:·BBS 水木清华站 smth.org·[FROM: 162.105.8.213]

--
☆ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: bbs@192.168.28.23]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店