fork与 vfork, exit与 _exit

引言

在linux下,进行多进程相关编程的时候,会使用到fork系列函数。其中fork()vfork()存在哪些区别?

exit()可以终止调用进程,不执行exit()后的代码,那 _exit()exit() 的区别在哪里?为什么建议在fork出的子进程中使用_exit()退出,而非使用exit()退出?

fork 与 vfork

fork

  • 调用方法
1
2
3
4
#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

正确返回:在父进程中返回子进程的进程号,在子进程中返回0

错误返回:-1

子进程是父进程的一个拷贝。即,子进程从父进程得到了数据段和堆栈段的拷贝,这些需要分配新的内存;而对于只读的代码段,通常使用共享内存的方式访问。fork返回后,子进程和父进程都从调用fork函数返回处开始执行。
父进程与子进程的不同之处在于:fork的返回值不同——父进程中的返回值为子进程的进程号,而子进程为0
  • fork函数调用的用途

    ⑴ 一个进程希望复制自身,从而父子进程能同时执行不同段的代码。
    ⑵ 进程想执行另外一个程序

vfork

  • 调用方法
    1
    2
    3
    4
    #include <sys/types.h>
    #include <unistd.h>

    pid_t fork(void);

正确返回:在父进程中返回子进程的进程号,在子进程中返回0

错误返回:-1

  • vfork函数调用的用途

    vfork创建的进程主要目的是用exec系列函数执行另外的程序,与fork的第二个用途相同

fork与vfork的区别

  1. `fork`要拷贝父进程的数据段;而`vfork`则不需要完全拷贝父进程的数据段,在子进程没有调用`exec`或`_exit`之前,子进程与父进程共享数据段。
  2. `fork`不对父子进程的执行次序进行任何限制;而在`vfork`调用中,父进程挂起,直到子进程调用了`exec`系列函数或`_exit`函数,之后两者各自执行。需要注意,子进程不应该`return`或者调用`exit()`。

exit与_exit

exit()_exit()之间的基本区别在于前者执行与用户模式构造相关的清理库,并调用用户提供的清理函数,而后者仅执行进程的内核清理。更加具体的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
exit(3) - Linux man page

Name

exit - cause normal process termination

Synopsis

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

Description

The exit() function causes normal process termination and the value of status & 0377 is returned to the parent (see wait(2)).

All functions registered with atexit(3) and on_exit(3) are called, in the reverse order of their registration. (It is possible for one of these functions to use atexit(3) or on_exit(3) to register an additional function to be executed during exit processing; the new registration is added to the front of the list of functions that remain to be called.) If one of these functions does not return (e.g., it calls _exit(2), or kills itself with a signal), then none of the remaining functions is called, and further exit processing (in particular, flushing of stdio(3) streams) is abandoned. If a function has been registered multiple times using atexit(3) or on_exit(3), then it is called as many times as it was registered.

All open stdio(3) streams are flushed and closed. Files created by tmpfile(3) are removed.

The C standard specifies two constants, EXIT_SUCCESS and EXIT_FAILURE, that may be passed to exit() to indicate successful or unsuccessful termination, respectively.

Return Value
The exit() function does not return.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
exit(2) - Linux man page

Name
_exit, _Exit - terminate the calling process

Synopsis
#include <unistd.h>

void _exit(int status);

#include <stdlib.h>

void _Exit(int status);

Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

_Exit():
_XOPEN_SOURCE >= 600 || _ISOC99_SOURCE || _POSIX_C_SOURCE >= 200112L;
or cc -std=c99

Description
The function _exit() terminates the calling process "immediately". Any open file descriptors belonging to the process are closed; any children of the process are inherited by process 1, init, and the process's parent is sent a SIGCHLD signal.

The value status is returned to the parent process as the process's exit status, and can be collected using one of the wait(2) family of calls.

The function _Exit() is equivalent to _exit().

Return Value
These functions do not return.


Notes
For a discussion on the effects of an exit, the transmission of exit status, zombie processes, signals sent, and so on, see exit(3).

The function _exit() is like exit(3), but does not call any functions registered with atexit(3) or on_exit(3). Whether it flushes standard I/O buffers and removes temporary files created with tmpfile(3) is implementation-dependent. On the other hand, _exit() does close open file descriptors, and this may cause an unknown delay, waiting for pending output to finish. If the delay is undesired, it may be useful to call functions like tcflush(3) before calling _exit(). Whether any pending I/O is canceled, and which pending I/O may be canceled upon _exit(), is implementation-dependent.

In glibc up to version 2.3, the _exit() wrapper function invoked the kernel system call of the same name. Since glibc 2.3, the wrapper function invokes exit_group(2), in order to terminate all of the threads in a process.

可知:

  1. _exit()exit()都会执行的事情: close文件描述符;终止进程;

    _exit()不会执行,但exit()会执行的的事情: 调用所有atexit(3)on_exit(3)中注册的函数; flush 并 close 所有 stdio流; 移除由tmpfile()创建的文件;

  2. exit()是c库函数,所以是在man(3), 而_exit()是系统函数,是在man(2)中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    MANUAL SECTIONS
    The standard sections of the manual include:

    1 User Commands
    2 System Calls
    3 C Library Functions
    4 Devices and Special Files
    5 File Formats and Conventions
    6 Games et. al.
    7 Miscellanea
    8 System Administration tools and Daemons

    Distributions customize the manual section to their specifics,
    which often include additional sections.

不当使用exit()可能造成的问题:

  1. io buffer 被flush 两次,是的输出内容异常,例如:在fork 之后,子进程中调用exec系列函数执行相应的操作,但exec函数调用失败,于是调用exit()退出子进程,此时,会对stdio流进行flush操作,由于子进程中stdout、stderr复制自父进程,如果此时有缓冲,就会将其输出,从而造成两次输出同一缓冲内容,得到异常的输出。
  2. 可能会意外移除由tmpfile()创建的缓冲文件。
  3. 在c++中可能会错误地运行静态对象的析构函数。
  4. 当使用vfork时,错误调用exit()会对父进程的数据状态造成影响。

注意:

  • 在绝大多数情况下适用的基本规则是:对于每个main(),exit()只应该被调用一次。

参考链接