1. 基础知识

进程退出场景

  • 程序异常退出:即被一个信号终止。我们知道大部分标准信号的默认处理操作都是杀死进程,以及可能产生核心转储文件,而只有少部分信号是执行一些不会杀死进程的操作。
  • 程序正常退出:即通过 exit() 或 _exit() 或 return 退出程序。而其中又根据退出返回值的不同将之分成了结果符合预期和结果不符合预期两种。按照惯例,返回值为0表示进程“功成身退”,而非0则表示进程运行结果不符合预期。

进程正常退出方法

  • main 函数中 return,刷新缓冲区、执行用户自定义的清理函数(atexit)、释放资源
  • _exit() 系统调用,刷新缓冲区、执行用户自定义的清理函数(atexit)、释放资源
  • exit() 库函数,释放资源

_exit()

_exit() 即为正常终止进程。

#include <unistd.h>
void _exit(int status);

status 参数定义了进程的终止状态(termination status),父进程可调用 wait() 获取该状态。其虽然定义为8位,但仅有低8位可为父进程所用。按照惯例,终止状态为0表示进程运行结果符合预期,而非0值则表示运行结果不符合预期。

exit()

程序一般不会直接调用 _exit() ,而是调用库函数 exit(),它会在内部调用 _exit(),但是在这之前会执行各种动作。

#include <stdlib.h>
void exit(int status);

exit() 会执行的动作如下:

  • 调用退出处理函数(通过 atexit() 和 on_exit() 注册的函数),其执行顺序与注册顺序相反。
  • 刷新 stdio 缓冲区。
  • 使用由 status 提供的值调用 _exit() 系统调用。

return

程序的另一种终止方法是从 main() 函数中返回(return),或者走到没有 return 语句的main函数末尾。这同样会导致 mian() 函数的调用者执行 exit() 函数。

2. 一个关于fork()、exit()与stdio缓冲区的有趣程序

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    printf("Hello World!\n");
    write(1, "Ding\n", 5);

    fork();
    exit(0);
}

看起来是一段目的明确的代码,程序显而易见的会输出 Hello World!和 Ding。但如果使用shell重定向来将输出重定向到文件,结果就会出人意料。

$ ./a.out > output.txt
$ cat output.txt
Ding
Hello World!
Hello World!

可以看到 Hello World!出人意料的写入了两次,而且是在写入 Ding 之后,这是为何?

首先知道在shell中使用输出重定向,相当于在运行程序之前就已经把标准输出重定向到了打开文件 output.txt 的文件描述符上。而在程序内部向文件描述符 1 写入其实就是向打开文件写入数据。而最关键的是printf 发生了什么?我们知道 printf 默认为行缓冲模式,即等到收到一个换行符或者缓冲区被填满才会调用 write() 系统调用。但由于标准输出被重定向到了文件,所以 printf() 的缓冲模式变成了全缓冲,所以在创建子进程之前,Hello World! 还保存在stdio缓冲区中,而我们知道这个缓冲区是C标准库 stdio 提供的功能并负责维护的,所以其实是存在用户虚拟空间中的,所以在创建子进程的时候子进程连这个缓冲区的内容也一块复制了。所以等到父子进程各自调用 exit() 的时候,各自清理自己的缓冲区,从而写了两条 Hello World! 在文件中。

最后修改:2019 年 11 月 10 日
如果觉得我的文章对你有用,请随意赞赏