#define _BSD_SOURCE     /* Get on_exit() declaration from <stdlib.h> */
#include <stdlib.h>
#include "tlpi_hdr.h"

static void
atexitFunc1(void)
{
    printf("atexit function 1 called\n");
}

static void
atexitFunc2(void)
{
    printf("atexit function 2 called\n");
}

static void
onexitFunc(int exitStatus, void *arg)
{
    printf("on_exit function called: status=%d, arg=%ld\n",
                exitStatus, (long) arg);
}

int
main(int argc, char *argv[])
{
    if (on_exit(onexitFunc, (void *) 10) != 0)
        fatal("on_exit 1");
    if (atexit(atexitFunc1) != 0)
        fatal("atexit 1");
    if (atexit(atexitFunc2) != 0)
        fatal("atexit 2");
    if (on_exit(onexitFunc, (void *) 20) != 0)
        fatal("on_exit 2");

    exit(2);
}

执行结果:
on_exit function called: status=2, arg=20
atexit function 2 called
atexit function 1 called
on_exit function called: status=2, arg=10
经过atexit()注册的退出处理程序会受到两种限制。
其一,退出处理程序在执行时无法获知传递给exit()的状态。特别是退出程序依据进程状态而执行不同动作时。
其二,无法给退出处理程序指定参数。
如同atexit() 一样通过on_exit()可以注册多个退出处理程序。使用atexit()和on_exit()函注册的函数位于同一函数列表。如果在程序中同时用到了这两种方式,则会按照使用这两个方法注册的相反顺序来执行相应的退出处理程序。

/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2017.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* Listing 25-2 */

#include "tlpi_hdr.h"

int
main(int argc, char *argv[])
{
    printf("Hello world\n");
    write(STDOUT_FILENO, "Ciao\n", 5);

    if (fork() == -1)
        errExit("fork");

    /* Both child and parent continue execution here */

    exit(EXIT_SUCCESS);
}

执行结果,标准输出定向到终端时,结果正常
./fork_stdio_buf
Hello world
Ciao
当重定向标准输出到一个文件时结果异常:
Ciao
Hello world
Hello world
原因分析如下:
在进程的用户空间内存中维护stdio缓冲区,因此通过fork()创建子进程时会复制这些缓冲区。当标准输出重定向到终端时,因为缺省为行缓冲,所以立即显示函数printf()输出的包含换行符的字符串。
当标准输出重定向到文件时,由于缺省为块缓冲,所以在本例中当调用fork()时printf()输出的字符串仍在父进程的stdio缓冲区中,并随子进程的创建而产生一份副本。父子进程调用exit()时刷新各自的stdio韩冲去,从而导致重复的输出结果。
write()的输出并未产生两次,这是因为write()会将数据直接传给内核缓冲区,fork()不会复制这一缓冲区。而printf()的输出则需要等到调用exit()刷新stdio缓冲区时。
解决方法:
1.在fork()前使用fflush()来刷新stdio缓冲区。
2.使用setvbuf()和setbuf()来关闭stdio流的缓冲功能
3.子进程调用_exit()而非exit(),以便不在刷新stdio缓冲区。
exit()会在系统调用_exit()前执行如下动作:
1.调用退出处理程序(通过atexit()和on_exit()函数注册),执行顺序与注册的顺序相反
2.刷新stdio流缓冲区
3.使用由status提供的值执行_exit()系统调用