一. 进程退出场景
-
进程正常终止, 结果正确.
-
进程正常终止, 结果不正确.
-
进程异常终止.
二. 进程正常终止
从 main 返回
C 程序总是从 main 函数开始执行, main 函数的原型是:

但实际上 main 函数只是用户程序的入口, 当内核执行 C 程序时(使用一个 exec 函数), 在调用 main 前先调用一个特殊的启动例程. 可执行程序文件将此启动例程指定为程序的起始地址. 启动例程从内核取得命令行参数和环境变量值, 然后为按上述方式调用 main 函数做好准备.
从 main 函数 return
是一种常见的进程终止方法. 启动例程使得 main 返回后立即调用 exit()
, 在 main 中执行 return n
等同于执行 exit(n)
, 因为调用 main 的启动例程会将 main 的返回值当做 exit()
的 status 参数. 如果将启动例程以 C 代码形式表示 (实际上该例程常常用汇编语言编写), 则它调用 main 函数的形式可能是:

通过如下一段代码对上述描述进行验证.
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("hello world\n");
return 0;
}
当运行生成的可执行程序后, 使用 echo $?
打印最近一次进程的退出码, 为 0, 说明该进程确实正常终止了. 在 Linux 中, 0 通常表示成功, 非 0 值表示失败.

进程退出码
C 库中的 strerror 可以通过错误码, 获取对应的错误信息.
c
#include <stdio.h>
#include <string.h>
int main()
{
int i = 0;
for ( ; i < 134; i++) {
printf("%d:%s\n", i, strerror(i));
}
return 0;
}
当运行生成的可执行程序后, 就可以看到各个错误码所对应的错误信息.

注意: 退出码都有对应的错误信息, 帮助用户确认程序执行失败的原因, 而这些退出码具体代表什么含义是人为规定的, 不同环境下相同的退出码的错误信息可能不同.
_exit()

_exit()
会直接终止进程, 并不会在退出进程前会做任何收尾工作.
c
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello world");
_exit(1);
}
以上代码中使用 _exit()
终止进程, 缓冲区当中的数据将不会被打印到显示屏上.

exit()

使用 exit()
终止进程是常用的方法, exit()
可以在任何地方终止进程, 并在终止进程之前会做一系列工作:
-
调用任何由 atexit() 或 on_exit() 注册的函数, 和在系统中注册时顺序相反.
-
清空并关闭所有已经打开的标准 I/O 流 (此过程将所有被缓冲但还没有被写出的数据写出).
-
删除由 tmpfile() 函数创建的所有临时文件.
这些步骤完成了在用户空间需要做的所有工作, 最后 exit()
会调用系统调用 _exit()
, 内核可以处理终止进程的剩余工作.

对以下代码进行测试.
c
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello world");
exit(2);
}
运行结果如下.

三. 进程异常终止
进程一般由于接到一个信号而异常终止, 后期谈论进程信号时再谈论这一话题.
四. exit() 与 _exit() 中的 status
对如下一段代码进行测试.
c
#include <stdio.h>
#include <stdlib.h>
int main()
{
exit(-1);
return 0;
}
当运行生成的可执行程序后, 使用 echo $?
打印最近一次进程的退出码, 发现其为 255, 而非 -1.

出现这样的现象, 是因为父进程通过 wait()
来获取子进程的退出码 时 status 仅有低 8 位可以被父进程所用.

以上面那一段代码为样例进行说明: 对于无符号整型来说, 数据以补码的形式存放在内存中, 所以 status 的二进制表示为 11111111 11111111 11111111 11111111, 而 0377 的二进制表示为 11111111, 两者按位与的结果即为 11111111, 表示为十进制即为 255, 所以使用 echo $?
打印最近一次进程的退出码, 其值为 255, 而非 -1.
