我们早些时候提到函数abort能够造成程序的非正常终止。
#include <stdlib.h>
void abort(void);
This function never returns.
该函数会发送一个信号SIGABRT到调用进程。(进程不应该忽略这一信号),ISO C指出调用函数abort将会通过调用函数raise(SIGABRT)向主机环境发送一个不成功的终止通知。(ISO C states that calling abort will deliver an successful termination notification to the host environment by calling raise(SIGABRT)).
ISO C要求如果信号SIGABRT被捕获并且信号处理函数返回,abort仍然不会返回到其调用进程。如果该信号被捕获,如果该信号被捕获,信号处理函数不返回的唯一的方法是调用函数exit,_exit,_Exit,longjmp,siglongjmp.POSIX.1也指出abort将会重写信号的阻塞或者是忽略处理。
让进程捕获信号SIGABRT的目的是允许进程在终止之前执行清理工作,如果进程在信号处理函数中不终止自身进程,那么当信号处理函数返回的时候,abort将会终止进程。
ISO C将输出流是否被清空以及临时文件是否被删除交给了实现来处理,POSIX.1对此作了改变,其允许实现调用函数fclose在进程终止之前关闭已经打开的标准IO流。
早期的System V版本在abort函数中产生SIGIOT信号,进程可以捕获或者忽略该信号,并且可以从信号处理函数中返回,在这种情况下abort将会返回到其调用进程中去。
4.3BSD产生SIGILL信号,在发出信号之前,4.3BSD解除了该信号的阻塞并将其处理修改为SIG_DFL(终止并保存core文件)。这可以防止进程忽略或者是捕获该信号。
历史上,abort函数的实现对于标准IO流的处理在不同实现上有差异,为了提升程序的防御能力以及改善其可移植性,如果我们想要标准IO流被清空,我们需要在调用函数abort之前执行,我们将在err_dump函数中实现这一处理。
因为很多UNIX系统的函数tmpfile的实现在创建文件之后会立即调用unlink函数,ISO C对于临时文件的提醒我们并不需要担心。
Example
图10.25展示了POSIX.1指定的abort函数的一个实现:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void abort(void) /*POSIX-style abort() function */
{
sigset_t mask;
struct sigaction action;
/*Caller can‘t ignore SIGABRT, if so reset to defualt */
sigaction(SIGABRT, NULL, &action);
if(action.sa_handler == SIG_IGN)
{
action.sa_handler = SIG_DFL;
sigaction(SIGABRT, &action, NULL);
}
if(action.sa_handler == SIG_DFL)
{
fflush(NULL); /* flush all open stdio streams */
}
/* Caller can‘t block SIGABRT; make sure it‘s unblocked. */
sigfillset(&mask);
sigdelset(&mask, SIGABRT); /*mask has only SIGABRT turned off .*/
sigprocmask(SIG_SETMASK, &mask, NULL);
kill(getpid(), SIGABRT); /*send the signal*/
/*If we‘re here,process caught SIGABRT and returned */
fflush(NULL); /*flush all open stdio streams.*/
action.sa_handler = SIG_DFL;
sigaction(SIGABRT, &action, NULL); /*reset to default*/
sigprocmask(SIG_SETMASK, &mask, NULL); /*just in case ... */
kill(getpid(), SIGABRT); /*and one more time*/
exit(1); /*this should never be excuted*/
}
Figure 10.25 POSIX.1的abort函数的实现
首先检查信号是不是默认处理函数,如果是的话就flush所有IO流,注意这与fclose不同,因为fflush只是flush文件流但是并不关闭它们,当进程终止的时候,系统会关闭所有已经打开的文件。如果进程捕获到信号并且返回到abort函数中来,我们需要再一次flush文件流,因为进程可能再次产生了更多的输出内容,我们不需要处理的确情况是:进程捕获到信号以后调用了函数_exit或者是_Exit.在这种情况下,内存中所有没有flush的缓冲区将会被丢弃,我们假设这样做的用户是不想要缓冲区中的内容被flush的。
在10.9中我们已经了解到kill函数会产生信号,并且如果信号没有被阻塞(图10.25中的程序我们可以保证没有被阻塞),那么信号将在kill函数返回之前被发送到进程,因为我们阻塞了所有的信号除了SIGABRT,因此我们知道如果kill调用返回到abort,那么进程就已经捕获到了信号并且信号处理函数已经返回了。