linux中fork()函数

时间:2022-05-23 05:35:33

man fork:

FORK()                    Linux Programmer's Manual                   FORK(2)

NAME
fork - create a child process SYNOPSIS
#include <unistd.h> pid_t fork(void); DESCRIPTION
fork() creates a new process by duplicating the calling process. The
new process, referred to as the child, is an exact duplicate of the
calling process, referred to as the parent, except for the following
points: * The child has its own unique process ID, and this PID does not match
the ID of any existing process group (setpgid()). * The child's parent process ID is the same as the parent's process
ID. * The child does not inherit its parent's memory locks (mlock(2),
mlockall()). * Process resource utilizations (getrusage()) and CPU time counters
(times()) are reset to zero in the child. * The child's set of pending signals is initially empty (sigpend‐
ing()). * The child does not inherit semaphore adjustments from its parent
(semop()). * The child does not inherit record locks from its parent (fcntl()). * The child does not inherit timers from its parent (setitimer(),
alarm(), timer_create()). * The child does not inherit outstanding asynchronous I/O operations
from its parent (aio_read(), aio_write()), nor does it inherit any
asynchronous I/O contexts from its parent (see io_setup()). The process attributes in the preceding list are all specified in
POSIX.-. The parent and child also differ with respect to the
following Linux-specific process attributes: * The child does not inherit directory change notifications (dnotify)
from its parent (see the description of F_NOTIFY in fcntl()). * The prctl() PR_SET_PDEATHSIG setting is reset so that the child
does not receive a signal when its parent terminates. * Memory mappings that have been marked with the madvise() MADV_DONT‐
FORK flag are not inherited across a fork(). * The termination signal of the child is always SIGCHLD (see
clone()). Note the following further points: * The child process is created with a single thread—the one that
called fork(). The entire virtual address space of the parent is
replicated in the child, including the states of mutexes, condition
variables, and other pthreads objects; the use of pthread_atfork()
may be helpful for dealing with problems that this can cause. * The child inherits copies of the parent's set of open file descrip‐
tors. Each file descriptor in the child refers to the same open
file description (see open()) as the corresponding file descriptor
in the parent. This means that the two descriptors share open file
status flags, current file offset, and signal-driven I/O attributes
(see the description of F_SETOWN and F_SETSIG in fcntl()). * The child inherits copies of the parent's set of open message queue
descriptors (see mq_overview()). Each descriptor in the child
refers to the same open message queue description as the correspond‐
ing descriptor in the parent. This means that the two descriptors
share the same flags (mq_flags). * The child inherits copies of the parent's set of open directory
streams (see opendir()). POSIX.- says that the corresponding
directory streams in the parent and child may share the directory
stream positioning; on Linux/glibc they do not. RETURN VALUE
On success, the PID of the child process is returned in the parent, and
0 is returned in the child. On failure, - is returned in the parent,
no child process is created, and errno is set appropriately. ERRORS
EAGAIN fork() cannot allocate sufficient memory to copy the parent's
page tables and allocate a task structure for the child. EAGAIN It was not possible to create a new process because the caller's
RLIMIT_NPROC resource limit was encountered. To exceed this
limit, the process must have either the CAP_SYS_ADMIN or the
CAP_SYS_RESOURCE capability. ENOMEM fork() failed to allocate the necessary kernel structures
because memory is tight. CONFORMING TO
SVr4, .3BSD, POSIX.-. NOTES
Under Linux, fork() is implemented using copy-on-write pages, so the
only penalty that it incurs is the time and memory required to dupli‐
cate the parent's page tables, and to create a unique task structure
for the child. Since version 2.3., rather than invoking the kernel's fork() system
call, the glibc fork() wrapper that is provided as part of the NPTL
threading implementation invokes clone() with flags that provide the
same effect as the traditional system call. The glibc wrapper invokes
any fork handlers that have been established using pthread_atfork(). EXAMPLE
See pipe() and wait(). SEE ALSO
clone(), execve(), setrlimit(), unshare(), vfork(), wait(), dae‐
mon(), capabilities(), credentials() Linux -- FORK()

f返回值很有意思,  On success, the PID of the child process is returned in the parent, an 0 is returned in the child.

成功时,在父进程中返回子进程的id,在子进程中返回0.

转:

 一、fork入门知识

一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
    一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

我们来看一个例子:

/*
* fork_test.c
* version 1
* Created on: 2010-5-29
* Author: wangth
*/
#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t fpid; //fpid表示fork函数返回的值
int count=;
fpid=fork();
if (fpid < )
printf("error in fork!");
else if (fpid == ) {
printf("i am the child process, my process id is %d/n",getpid());
printf("我是爹的儿子/n");//对某些人来说中文看着更直白。
count++;
}
else {
printf("i am the parent process, my process id is %d/n",getpid());
printf("我是孩子他爹/n");
count++;
}
printf("统计结果是: %d/n",count);
return ;
}

运行结果是:
    i am the child process, my process id is 5574
    我是爹的儿子
    统计结果是: 1
    i am the parent process, my process id is 5573
    我是孩子他爹
    统计结果是: 1
    在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……
    为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
    1)在父进程中,fork返回新创建子进程的进程ID;
    2)在子进程中,fork返回0;
    3)如果出现错误,fork返回一个负值;

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.
    fork出错可能有两种原因:
    1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
    2)系统内存不足,这时errno的值被设置为ENOMEM。
    创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
    每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。
    fork执行完毕后,出现两个进程,

linux中fork()函数

有人说两个进程的内容完全一样啊,怎么打印的结果不一样啊,那是因为判断条件的原因,上面列举的只是进程的代码和指令,还有变量啊。
    执行完fork后,进程1的变量为count=0,fpid!=0(父进程)。进程2的变量为count=0,fpid=0(子进程),这两个进程的变量都是独立的,存在不同的地址中,不是共用的,这点要注意。可以说,我们就是通过fpid来识别和操作父子进程的。
    还有人可能疑惑为什么不是从#include处开始复制代码的,这是因为fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了int count=0;fork只拷贝下一个要执行的代码到新的进程。

二、fork进阶知识

先看一份代码:

/*
* fork_test.c
* version 2
* Created on: 2010-5-29
* Author: wangth
*/
#include <unistd.h>
#include <stdio.h>
int main(void)
{
int i=;
printf("i son/pa ppid pid fpid/n");
//ppid指当前进程的父进程pid
//pid指当前进程的pid,
//fpid指fork返回给当前进程的值
for(i=;i<;i++){
pid_t fpid=fork();
if(fpid==)
printf("%d child %4d %4d %4d/n",i,getppid(),getpid(),fpid);
else
printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
}
return ;
}

运行结果是:
    i son/pa ppid pid  fpid
    0 parent 2043 3224 3225
    0 child  3224 3225    0
    1 parent 2043 3224 3226
    1 parent 3224 3225 3227
    1 child     1 3227    0
    1 child     1 3226    0 
    这份代码比较有意思,我们来认真分析一下:
    第一步:在父进程中,指令执行到for循环中,i=0,接着执行fork,fork执行完后,系统中出现两个进程,分别是p3224和p3225(后面我都用pxxxx表示进程id为xxxx的进程)。可以看到父进程p3224的父进程是p2043,子进程p3225的父进程正好是p3224。我们用一个链表来表示这个关系:
    p2043->p3224->p3225 
    第一次fork后,p3224(父进程)的变量为i=0,fpid=3225(fork函数在父进程中返向子进程id),代码内容为:

  1. for(i=0;i<2;i++){
  2. pid_t fpid=fork();//执行完毕,i=0,fpid=3225
  3. if(fpid==0)
  4. printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);
  5. else
  6. printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
  7. }
  8. return 0;

p3225(子进程)的变量为i=0,fpid=0(fork函数在子进程中返回0),代码内容为:

  1. for(i=0;i<2;i++){
  2. pid_t fpid=fork();//执行完毕,i=0,fpid=0
  3. if(fpid==0)
  4. printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);
  5. else
  6. printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
  7. }
  8. return 0;

所以打印出结果:
    0 parent 2043 3224 3225
    0 child  3224 3225    0
    第二步:假设父进程p3224先执行,当进入下一个循环时,i=1,接着执行fork,系统中又新增一个进程p3226,对于此时的父进程,p2043->p3224(当前进程)->p3226(被创建的子进程)。
    对于子进程p3225,执行完第一次循环后,i=1,接着执行fork,系统中新增一个进程p3227,对于此进程,p3224->p3225(当前进程)->p3227(被创建的子进程)。从输出可以看到p3225原来是p3224的子进程,现在变成p3227的父进程。父子是相对的,这个大家应该容易理解。只要当前进程执行了fork,该进程就变成了父进程了,就打印出了parent。
    所以打印出结果是:
    1 parent 2043 3224 3226
    1 parent 3224 3225 3227 
    第三步:第二步创建了两个进程p3226,p3227,这两个进程执行完printf函数后就结束了,因为这两个进程无法进入第三次循环,无法fork,该执行return 0;了,其他进程也是如此。
    以下是p3226,p3227打印出的结果:
    1 child     1 3227    0
    1 child     1 3226    0 
    细心的读者可能注意到p3226,p3227的父进程难道不该是p3224和p3225吗,怎么会是1呢?这里得讲到进程的创建和死亡的过程,在p3224和p3225执行完第二个循环后,main函数就该退出了,也即进程该死亡了,因为它已经做完所有事情了。p3224和p3225死亡后,p3226,p3227就没有父进程了,这在操作系统是不被允许的,所以p3226,p3227的父进程就被置为p1了,p1是永远不会死亡的,至于为什么,这里先不介绍,留到“三、fork高阶知识”讲。
    总结一下,这个程序执行的流程如下:

linux中fork()函数

这个程序最终产生了3个子进程,执行过6次printf()函数。
    我们再来看一份代码:

/*
* fork_test.c
* version 3
* Created on: 2010-5-29
* Author: wangth
*/
#include <unistd.h>
#include <stdio.h>
int main(void)
{
int i=;
for(i=;i<;i++){
pid_t fpid=fork();
if(fpid==)
printf("son/n");
else
printf("father/n");
}
return ; }

它的执行结果是:
    father
    son
    father
    father
    father
    father
    son
    son
    father
    son
    son
    son
    father
    son 
    这里就不做详细解释了,只做一个大概的分析。
    for        i=0         1           2
              father     father     father
                                        son
                            son       father
                                        son
               son       father     father
                                        son
                            son       father
                                        son
    其中每一行分别代表一个进程的运行打印结果。
    总结一下规律,对于这种N次循环的情况,执行printf函数的次数为2*(1+2+4+……+2N-1)次,创建的子进程数为1+2+4+……+2N-1个。(感谢gao_jiawei网友指出的错误,原本我的结论是“执行printf函数的次数为2*(1+2+4+……+2N)次,创建的子进程数为1+2+4+……+2”,这是错的)
    网上有人说N次循环产生2*(1+2+4+……+2N)个进程,这个说法是不对的,希望大家需要注意。

数学推理见http://202.117.3.13/wordpress/?p=81(该博文的最后)。
    同时,大家如果想测一下一个程序中到底创建了几个子进程,最好的方法就是调用printf函数打印该进程的pid,也即调用printf("%d/n",getpid());或者通过printf("+/n");来判断产生了几个进程。有人想通过调用printf("+");来统计创建了几个进程,这是不妥当的。具体原因我来分析。
    老规矩,大家看一下下面的代码:

/*
* fork_test.c
* version 4
* Created on: 2010-5-29
* Author: wangth
*/
#include <unistd.h>
#include <stdio.h>
int main() {
pid_t fpid;//fpid表示fork函数返回的值
//printf("fork!");
printf("fork!/n");
fpid = fork();
if (fpid < )
printf("error in fork!");
else if (fpid == )
printf("I am the child process, my process id is %d/n", getpid());
else
printf("I am the parent process, my process id is %d/n", getpid());
return ;
}

执行结果如下:
    fork!
    I am the parent process, my process id is 3361
    I am the child process, my process id is 3362 
    如果把语句printf("fork!/n");注释掉,执行printf("fork!");
    则新的程序的执行结果是:
    fork!I am the parent process, my process id is 3298
    fork!I am the child process, my process id is 3299 
    程序的唯一的区别就在于一个/n回车符号,为什么结果会相差这么大呢?
    这就跟printf的缓冲机制有关了,printf某些内容时,操作系统仅仅是把该内容放到了stdout的缓冲队列里了,并没有实际的写到屏幕上。但是,只要看到有/n 则会立即刷新stdout,因此就马上能够打印了。
    运行了printf("fork!")后,“fork!”仅仅被放到了缓冲里,程序运行到fork时缓冲里面的“fork!”  被子进程复制过去了。因此在子进程度stdout缓冲里面就也有了fork! 。所以,你最终看到的会是fork!  被printf了2次!!!!
    而运行printf("fork! /n")后,“fork!”被立即打印到了屏幕上,之后fork到的子进程里的stdout缓冲里不会有fork! 内容。因此你看到的结果会是fork! 被printf了1次!!!!
    所以说printf("+");不能正确地反应进程的数量。
    大家看了这么多可能有点疲倦吧,不过我还得贴最后一份代码来进一步分析fork函数。

#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
fork();
fork() && fork() || fork();
fork();
return ;
}

问题是不算main这个进程自身,程序到底创建了多少个进程。
    为了解答这个问题,我们先做一下弊,先用程序验证一下,到此有多少个进程。

  1. #include <stdio.h>
  2. int main(int argc, char* argv[])
  3. {
  4. fork();
  5. fork() && fork() || fork();
  6. fork();
  7. printf("+/n");
  8. }

答案是总共20个进程,除去main进程,还有19个进程。
    我们再来仔细分析一下,为什么是还有19个进程。
    第一个fork和最后一个fork肯定是会执行的。
    主要在中间3个fork上,可以画一个图进行描述。
    这里就需要注意&&和||运算符。
    A&&B,如果A=0,就没有必要继续执行&&B了;A非0,就需要继续执行&&B。
    A||B,如果A非0,就没有必要继续执行||B了,A=0,就需要继续执行||B。
    fork()对于父进程和子进程的返回值是不同的,按照上面的A&&B和A||B的分支进行画图,可以得出5个分支。

linux中fork()函数

http://blog.csdn.net/jason314/article/details/5640969

http://www.thegeekstuff.com/2012/05/c-fork-function/

http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html

http://itlab.idcquan.com/linux/c/831529.html

一个fork面试题:

一个fork的面试题

前两天有人问了个关于Unix的fork()系统调用的面试题,这个题正好是我大约十年前找工作时某公司问我的一个题,我觉得比较有趣,写篇文章与大家分享一下。这个题是这样的:

题目:请问下面的程序一共输出多少个“-”?

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h> int main(void)
{
int i;
for(i=0; i<2; i++){
fork();
printf("-");
} wait(NULL);
wait(NULL); return 0;
}

如果你对fork()的机制比较熟悉的话,这个题并不难,输出应该是6个“-”,但是,实际上这个程序会很tricky地输出8个“-”。

要讲清这个题,我们首先需要知道fork()系统调用的特性,

  • fork()系统调用是Unix下以自身进程创建子进程的系统调用,一次调用,两次返回,如果返回是0,则是子进程,如果返回值>0,则是父进程(返回值是子进程的pid),这是众为周知的。
  • 还有一个很重要的东西是,在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,包括指令,变量值,程序调用栈,环境变量,缓冲区,等等。

所以,上面的那个程序为什么会输入8个“-”,这是因为printf(“-“);语句有buffer,所以,对于上述程序,printf(“-“);把“-”放到了缓存中,并没有真正的输出(参看《C语言的迷题》中的第一题),在fork的时候,缓存被复制到了子进程空间,所以,就多了两个,就成了8个,而不是6个。

另外,多说一下,我们知道,Unix下的设备有“块设备”和“字符设备”的概念,所谓块设备,就是以一块一块的数据存取的设备,字符设备是一次存取一个字符的设备。磁盘、内存都是块设备,字符设备如键盘和串口。块设备一般都有缓存,而字符设备一般都没有缓存

对于上面的问题,我们如果修改一下上面的printf的那条语句为:

printf("-\n");

或是

 printf("-");
fflush(stdout);

就没有问题了(就是6个“-”了),因为程序遇到“\n”,或是EOF,或是缓中区满,或是文件描述符关闭,或是主动flush,或是程序退出,就会把数据刷出缓冲区。需要注意的是,标准输出是行缓冲,所以遇到“\n”的时候会刷出缓冲区,但对于磁盘这个块设备来说,“\n”并不会引起缓冲区刷出的动作,那是全缓冲,你可以使用setvbuf来设置缓冲区大小,或是用fflush刷缓存。

(

printf输出函数,每执行一个printf输出函数,输出的数不是“肯定立刻”打印到屏幕上的,只有遇到一下几种情况时,printf输出的数据(执行了printf,但还没有打印到屏幕的数据)才会全部打印到屏幕上:

1、有输入请求的时候,会立马输出到屏幕
2、输出有换行符的时候,也会马上输出到屏幕上
3、程序结束的时候也会马上输出到屏幕上
4、输出缓冲区满的时候

)

我估计有些朋友可能对于fork()还不是很了解,那么我们把上面的程序改成下面这样:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
int i;
for(i=0; i<2; i++){
fork();
//注意:下面的printf有“\n”
printf("ppid=%d, pid=%d, i=%d \n", getppid(), getpid(), i);
}
sleep(10); //让进程停留十秒,这样我们可以用pstree查看一下进程树
return 0;
}

于是,上面这段程序会输出下面的结果,(注:编译出的可执行的程序名为fork)

ppid=8858, pid=8518, i=0
ppid=8858, pid=8518, i=1
ppid=8518, pid=8519, i=0
ppid=8518, pid=8519, i=1
ppid=8518, pid=8520, i=1
ppid=8519, pid=8521, i=1 $ pstree -p | grep fork
|-bash(8858)-+-fork(8518)-+-fork(8519)---fork(8521)
| | `-fork(8520)

面对这样的图你可能还是看不懂,没事,我好事做到底,画个图给你看看:

linux中fork()函数

注意:上图中的我用了几个色彩,相同颜色的是同一个进程。于是,我们的pstree的图示就可以成为下面这个样子:(下图中的颜色与上图对应)

linux中fork()函数

这样,对于printf(“-“);这个语句,我们就可以很清楚的知道,哪个子进程复制了父进程标准输出缓中区里的的内容,而导致了多次输出了。(如下图所示,就是我阴影并双边框了那两个子进程)

linux中fork()函数

转自:http://coolshell.cn/articles/7965.html

下面的程序并不见得会输出 hello-std-out,你知道为什么吗?

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <unistd.h>
int main() 
{
    while(1)
    {
        fprintf(stdout,"hello-std-out");
        fprintf(stderr,"hello-std-err");
        sleep(1);
    }
    return 0;
}

参考答案:stdout和stderr是不是同设备描述符。stdout是块设备,stderr则不是。对于块设备,只有当下面几种情况下才会被输入,1)遇到回车,2)缓冲区满,3)flush被调用。而stderr则不会。