一个进程,包括分配给该进程的代码、数据和资源。 fork()函数通过系统调用创建一个与原进程几乎一模一样的进程,即两个进程可以做一模一样的事情,但如果初始参数或传入的变量不同,则两个进程还可以做不同的事情。 。
进程调用fork()函数后,系统首先为新进程分配资源,例如存储数据和代码的空间。 然后将原进程的所有值复制到新进程中,除了少数与原进程的值不同的值。 相当于克隆自己。
让我们看一个例子:
/* * fork_test.c * version 1 * Created on: 2010-5-29 * Author: wangth */ #include#include int main () { pid_t fpid; //fpid表示fork函数返回的值 int count=0; fpid=fork(); if (fpid < 0) printf("error in fork!"); else if (fpid == 0) { 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 0; }
运行结果为:
我是孩子,我的id是5574
我是我父亲的儿子
统计结果为:1
我是,我的ID是5573
我是孩子的父亲
统计结果为:1
在fpid=fork()语句之前,只有一个进程在执行这段代码,但是在这条语句之后,就变成了两个进程。 这两个过程几乎相同。 下一条要执行的语句都是if(fpid
为什么两个进程的fpid不一样? 这和fork函数的特性有关。 fork 调用的美妙之处之一是它只被调用一次,但可以返回两次。 它可能有三个不同的返回值:
1)在父进程中,fork返回新创建的子进程的进程ID;
2)在子进程中,fork返回0;
3)如果发生错误,fork返回负值;
fork函数执行后,如果新进程创建成功,会出现两个进程,一个是子进程,一个是父进程。 在子进程中,fork函数返回0。在父进程中,fork返回新创建的子进程的进程ID。 我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
引用一位网友的解释,为什么父子进程中fpid的值不同。 》其实就相当于一个链表,进程之间形成一个链表,父进程的fpid(p表示点)指向子进程的进程ID,因为子进程没有子进程,所以它的fpid 为 0。
出现 Fork 错误的原因有两个:
1)当前进程数已达到系统规定的上限,errno的值被设置为。
2)系统内存不足,errno的值设置为。
新进程创建成功后,系统中会出现两个基本相同的进程。 这两个进程的执行顺序没有固定的顺序。 哪个进程先执行取决于系统的进程调度策略。
每个进程都有一个唯一的(不同的)进程标识符(ID),可以通过()函数获得,还有一个记录父进程pid的变量,并且可以通过()函数获得该变量的值。
fork执行完成后,出现两个进程,
有人说,两个过程的内容一模一样,但打印的结果不同。 那是因为判断条件的原因。 上面列出的只是进程的代码和指令,以及变量。
执行fork后,进程1的变量为count=0,fpid! =0(父进程)。 进程2的变量是count=0和fpid=0(子进程)。 这两个进程的变量是独立的,存在于不同的地址。 它们不被共享。 这点应该注意。 可以说,我们使用fpid来识别和操作父子进程。
可能有人还会奇怪,为什么不从#位置复制代码呢。 这是因为 fork 复制了进程的当前情况。 执行fork时,进程已经执行完int count=0; fork 只复制下一个要执行的代码。 到一个新的过程。
2.分叉高级知识
我们先看一下代码:
/* * fork_test.c * version 2 * Created on: 2010-5-29 * Author: wangth */ #include#include int main(void) { int i=0; printf("i son/pa ppid pid fpid/n"); //ppid指当前进程的父进程pid //pid指当前进程的pid, //fpid指fork返回给当前进程的值 for(i=0;i<2;i++){ pid_t fpid=fork(); if(fpid==0) printf("%d child %4d %4d %4d/n",i,getppid(),getpid(),fpid); else printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid); } return 0; }
运行结果为:
我儿子/pa ppid pid fpid
0 2
0 儿童 3224 3225 0
1 2
1 3224 3225 3227
1 名儿童 1 3227 0
1 名儿童 1 3226 0
这段代码挺有趣的,我们仔细分析一下:
步骤1:在父进程中,执行指令进入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),代码内容为:
for(i=0;i<2;i++){ pid_t fpid=fork();//执行完毕,i=0,fpid=3225 if(fpid==0) printf("%d child %4d %4d %4d/n",i,getppid(),getpid(),fpid); else printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid); } return 0;
p3225(子进程)的变量为i=0,fpid=0(子进程中fork函数返回0),代码内容为:
for(i=0;i<2;i++){ pid_t fpid=fork();//执行完毕,i=0,fpid=0 if(fpid==0) printf("%d child %4d %4d %4d/n",i,getppid(),getpid(),fpid); else printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid); } return 0;
所以打印出结果:
0 2
0 儿童 3224 3225 0
步骤2:假设先执行父进程p3224。 进入下一个循环时,i=1,则执行fork,系统中添加一个新进程p3226。 对于此时的父进程来说,p2043->p3224(当前进程)->p3226(创建的子进程)。
对于子进程p3225,执行完第一个循环后,i=1,则执行fork,系统中添加一个新进程p3227。 对于此进程,p3224->p3225(当前进程)->p3227(创建的子进程)。 从输出中我们可以看到p3225原本是p3224的子进程,现在变成了p3227的父进程。 父子是相对的,这一点大家应该都很容易理解。 只要当前进程执行fork,该进程就成为父进程并打印出来。
所以打印的结果是:
1 2
1 3224 3225 3227
步骤3:在第二步中,创建了两个进程p3226和p3227。 这两个进程执行完函数后就结束了,因为这两个进程无法进入第三次循环,无法fork。 是时候执行0了;,其他进程也是如此。
以下是p3226和p3227打印的结果:
1 名儿童 1 3227 0
1 名儿童 1 3226 0
细心的读者可能会注意到,p3226和p3227的父进程不应该是p3224和p3225。 怎么可能是1呢? 这里就不得不说一下进程创建和死亡的过程。 p3224和p3225执行第二个循环后,main函数应该退出,也就是说,进程应该死掉,因为它已经完成了所有事情。 p3224和p3225死亡后,p3226和p3227将没有父进程。 这在操作系统中是不允许的,因此将p3226和p3227的父进程设置为p1。 P1永远不会死。 至于为什么,这里就不介绍了,留给“3. 叉高级知识”。
总结一下,该程序的执行流程如下:
该程序最终产生了 3 个子进程并执行了 () 函数 6 次。
我们看另一段代码:
/* * fork_test.c * version 3 * Created on: 2010-5-29 * Author: wangth */ #include#include int main(void) { int i=0; for(i=0;i<3;i++){ pid_t fpid=fork(); if(fpid==0) printf("son/n"); else printf("father/n"); } return 0; }
其执行结果为:
儿子
儿子
儿子
儿子
儿子
儿子
儿子
这里不做详细解释,只是粗略分析一下。
每一行代表一个进程的运行打印结果。
总结一下规则,对于这种N次循环的情况,函数执行次数为2*(1+2+4+...+2N-1)次,创建的子进程数为1+2+4+...+2N-1。 (感谢网友指出错误,我原来的结论是“函数执行次数为2*(1+2+4+...+2N)次,创建的子进程数是 1+2+4+...+2N”,这是错误的)
网上有人说N个循环生成2*(1+2+4+...+2N)个进程。 这种说法是错误的。 希望大家关注。
同时,如果想测试一个程序中创建了多少个子进程,最好的方法是调用一个函数来打印进程的pid,即call("%d/n",( )); 或者通过("+/n");来判断生成了多少个进程。有些人想通过调用("+");来统计创建的进程数,这是不合适的,我来分析一下具体原因。
像往常一样,看一下下面的代码:
/* * fork_test.c * version 4 * Created on: 2010-5-29 * Author: wangth */ #include#include int main() { pid_t fpid;//fpid表示fork函数返回的值 //printf("fork!"); printf("fork!/n"); fpid = fork(); if (fpid < 0) printf("error in fork!"); else if (fpid == 0) 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 0; }
执行结果如下:
叉!
我是,我的id是3361
我是孩子,我的id是3362
如果注释掉语句("fork!/n");,则执行("fork!");
新程序的执行结果为:
fork!我是,我的id是3298
fork!我是孩子,我的id是3299
程序之间的唯一区别是 /n 回车符。 为什么结果如此不同?
这与缓冲机制有关。 当有某些内容时,操作系统只是将内容放入缓冲队列中,并没有真正将其写入屏幕。 不过,只要看到/n,就会立即刷新,所以可以立即打印。
运行后(“叉子!”),“叉子!” 仅放置在缓冲区中。 当程序运行到fork时,“fork!” 缓冲区中的内容由子进程复制。 所以,还有fork! 在子级缓冲区中。 所以,你最终看到的是 fork! 2次! ! ! !
运行后(“fork!/n”),“fork!” 立即打印到屏幕上,并且不会出现分叉! 之后分叉的子进程中缓冲区中的内容。 因此,你看到的结果将会是fork! 被封一次! ! ! !
所以(“+”); 无法正确反映进程数。
看了这么多你可能有点累了,但是我还是要贴出最后一段代码来进一步分析fork函数。
#include#include int main(int argc, char* argv[]) { fork(); fork() && fork() || fork(); fork(); return 0; }
问题是,不计算主进程本身,程序创建了多少个进程。
为了回答这个问题,我们先做一些错误的事情,使用程序来验证到目前为止有多少个进程。
#includeint main(int argc, char* argv[]) { fork(); fork() && fork() || fork(); fork(); printf("+/n"); }
答案是一共有20个进程,除去主进程,还有19个进程。
我们仔细看看为什么还有19个进程。
第一个fork和最后一个fork肯定会被执行。
主要是中间的三个叉子,可以画个图来描述一下。
这里需要注意&&和|| 运营商。
A&&B,如果A=0,则不需要继续执行&&B; 如果A不为0,则需要继续执行&&B。
A||B,如果A不为0,则无需继续执行||B。 如果A=0,则需要继续执行||B。
fork()的返回值对于父进程和子进程是不同的。 按照上面的分支A&&B和A||B画图,可以得到5个分支。
包括上一次fork和最后一次fork,总共有4*5=20个进程。 除去主进程,共有19个进程。
3.分叉高级知识
本节主要讲一下基于fork函数的操作系统进程的创建、死亡和调度。 由于时间和精力有限,先写到这里,剩下的等下次有时间再补上。
以上就是本文的全部内容。 希望对大家的学习有所帮助。 也希望大家支持 Home。