转载注明出处http://blog.chinaunix.net/u3/103462/
内核使程序执行的唯一方法是调用一个exec函数。当内核执行C程序时,首先调用exec函数,该函数将调用某个驻留在存储器中的称为加载器的内核代码,将可执行文件从磁盘拷贝到存储器中,这个过程中加载器先创建一个进程的虚拟存储器映像,然后将可执行文件的相关内容拷贝到代码断和数据段,接下来加载器跳转到程序的入口点,这个入口点包含整个程序的启动代码,这是由连接器(ld)在链接阶段在目标文件ctrl.o中定义的。该启动例程做的工作包括:执行一系列.text和.init节中的初始化代码,调用aexit例程(其附加了一系列在exit函数运行时调用的程序),从内核取得命令行参数和环境变量,然后调用main函数开始执行。当程序因为各种原因终止时,启动代码调用_exit将控制交给内核。
下图形象说明了一个c程序的启动和终止:
从用户编程角度看有三个函数用于正常终止一个程序:_exit和_Exit立即进入内核;exit则先执行一些清理处理然后通过
_exit或_Exit进入内核,清理工作主要包括2部分,(a)调用exit
handler,(b)调用fclose等函数关闭与I/O库的联系,其中的exit
handler需要用户自己定义并在退出函数之前用atexit函数注册,执行顺序与注册顺序相反。
#include
void exit(int status);
void _Exit(int status)
#include
void _exit(int status)
使用不同头文件的原因是:exit和_Exit是由ANSIC说明的,而_ezit则是由Posix.1说明的。
很多时候我们需要在程序退出的时候做一些诸如释放资源的操作,但程序退出的方式有很多种,比如main()函数运行结束、在程序的某个地方用
exit()
结束程序,因此需要有一种与程序退出方式无关的方法来进行程序退出时的必要处理。方法就
是用atexit()函数来注册程序正常终止时要被调用的函数,这些注册的程序将在exit中调用(异常终止并不运行exit)。
atexit()函数的参数是一个函数指针,函数指针指向一个没有参数也没有返回值的函数。atexit()的函数原型是:
#include
int atexit (void (*)(void));
在一个程序中最多可以用atexit()注册32个处理函数,这些处理函数的调用顺序与其注册的顺序相反,也即最先注册的最后调用,最后注册的最先调用。
下面是一段代码示
#include<stdlib.h> //使用atexit()函数所必须包含的头文件stdlib.h
#include<stdio.h>
void fn1(void);
void fn2(void)
int main(void)
{
atexit(fn1);
atexit(fn2);
printf("a.\n");
return 0;
}
void fn1()
{
printf("b.\n");
}
void fn2()
{
printf("c.\n");
}
输出结果是:
a.
b.
c.
|
三个函数都带一个整型参数,称之为终止状态(exit status)。如果( a )若调用这些函数时不带终止状态,或( b ) main执行了一个无返回值的return语句,或( c ) main没有声明返回类型为int,则该进程的终止状态是末定义的,随机的。但若将main的返回类型声明为int,并且执行到最后一条语句时候隐式返回,则进程的终止状态是0。
main函数返回(return)一个整型值与用该值调用exit是等价的,这从上面的图可以看出。
进程的正常终止方法有:在main函数内执行return或在程序的任意地方调用exit _Exit _exit中的任何一个。
进程的被异常终止就是接收到信号(该信号没有被捕捉,默认终止进程),包括(a) 调用a b o r t。它产生S I G A B RT信号,所以是下一种异常终止的一种特例。(b) 当进程接收到某个信号时。进程本身(例如调用a b o r
t函数)、其他进程和内核都能产生传送到某一进程的信号。例如,进程越出其地址空间访问存储单元,或者除以0,内核就会为该进程产生相应的信号。不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等等。
对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。在任意一种情况下,该终止进程的父进程都能用w a i t或w a i t p i
d函数取得其终止状态。
#include
#include
pid_t wait(int &statloc);
pid_t waitpid(pid_t pid,int *statloc, int options);
两个函数中的变量statloc是一个指向int型数据的指针。如果此变量不是NULL,则结束的进程的termination status会被保存在statiloc所指向的内存的区域;如果我们不关心termination status,而只是不想让进程成为僵尸进程,则可以把statloc置为NULL。
传统的实现中这两个函数返回的整数中特定的比特位被赋予了特定的含义,这是由实现定义的。其中某些位表示正常返回的的退出状态(传给exit的参数),其他位指示异常返回的信号编号,有一位指示是否产生了core文件等。POSIX.1指定了一些包含在头文件 宏来查看这些termination status
Macro Description
WIFEXITED(status) 如果status是由一个正常结束的进程产生的则值为真, 此时我们可以继续使用宏WEXITSTATUS(status)来获取exit或
_exit的参数
WIFSIGNALED(status) 如果status是由一个异常结束(接受到一个信号的进程产生的则值为真,此时使用宏WTERMSIG(status)来获取信号编号
另外有些实现定义宏WCOREDUMP(status),若已产生终止进程的core文件,则返回真。
见下面的示例程序
#include "apue.h"
#include<sys/types.h>
#include <sys/wait.h>
void
pr_exit(int status)
{
if (WIFEXITED(status))
printf("normal termination, exit status = %d\n",
WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number = %d%s\n",
WTERMSIG(status),
#ifdef WCOREDUMP //在printf语句中使用了预编译命令
WCOREDUMP(status) ? " (core file generated)" : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n",
WSTOPSIG(status));
}
int
main(void)
{
pid_t pid;
int status;
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
exit(7); //正常终止
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
abort(); /*产生 SIGABRT信号,终止子进程 ,该信号的整型值是6*/
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
status /= 0; /* divide by 0 产生SIGFPE信号,终止子进程,该信号的整型值是8*/
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
exit(0);
}
|
编译运行
$ ./a.out
normal termination, exit status =7
abnormal termination, signal number = 6 core file generated
abnormal termination, signal number = 8 core file generated
各个信号的编号是由系统定义的。
exit的整型参数意值由用户自己指定,系统没有定义或指定其意义,通常0表示程序运行完全符合预期;非零表示程序出现某些异常(如系统调用出错返回),无法按预期继续运行下去,我们不得不使程序主动结束。除了在父进程中调用wait函数然后用宏查看退出状态外,在shell下,还可以通过echo $?来查看。
通过在父进程中获取程序的终止状态,我们可以知道程序是如何终止的。如通过查看信号编号确定异常终止的原因;通过查看exit的退出状态,知道程序在何处退出,比如在每个调用exit的地方给exit赋予不同的整型参数,当然更好的办法是在调用exit退出去,调用printf打印出错信息。
阅读(1993) | 评论(0) | 转发(0) |