Fork() 함수는 Linux 시스템에서 가장 일반적으로 사용되는 시스템 호출 중 하나이며 호출 프로세스의 하위 프로세스인 새 프로세스를 만드는 데 사용됩니다. fork() 함수의 특징은 한 번만 호출되지만 부모 프로세스와 자식 프로세스에서 각각 두 번 반환된다는 것입니다. fork() 함수의 반환 값은 다르며 상위 프로세스와 하위 프로세스를 구별하는 데 사용할 수 있습니다. 이번 글에서는 반환값의 의미, 자식 프로세스의 특징, 부모-자식 프로세스의 동기화와 통신 등을 포함하여 fork() 함수의 원리와 사용법을 소개하고 예제를 제시하겠습니다. 사용법과 주의사항을 알려드립니다.
프로세스에 할당된 코드, 데이터 및 리소스를 포함한 프로세스입니다. fork() 함수는 시스템 호출을 통해 원래 프로세스와 거의 동일한 프로세스를 생성합니다. 즉, 두 프로세스는 완전히 동일한 작업을 수행할 수 있지만 초기 매개변수나 전달된 변수가 다를 경우 두 프로세스는 서로 다른 프로세스를 생성합니다. 다른 일도 할 수 있습니다.
프로세스가 fork() 함수를 호출한 후 시스템은 먼저 데이터 및 코드를 저장할 공간과 같은 리소스를 새 프로세스에 할당합니다. 그런 다음 원래 프로세스의 값과 다른 몇 가지 값을 제외하고 원래 프로세스의 모든 값을 새 프로세스에 복사합니다. 이는 자신을 복제하는 것과 같습니다.
예를 살펴보겠습니다:
으아아아실행 결과는 다음과 같습니다.
나는 자식 프로세스이고 내 프로세스 ID는 5574
입니다.
나는 아버지의 아들이다
통계 결과는 다음과 같습니다: 1
나는 부모 프로세스이고 내 프로세스 ID는 5573
입니다.
나는 아이의 아버지입니다
통계 결과는 다음과 같습니다: 1
fpid=fork() 문 이전에는 하나의 프로세스만 이 코드를 실행하고 있지만 이 문 이후에는 두 프로세스가 거의 동일합니다. 다음으로 실행되는 문은 모두 if(fpid입니다.
두 프로세스의 fpid가 다른 이유는 포크 기능의 특성과 관련이 있습니다.
포크 호출의 멋진 점 중 하나는 한 번만 호출되지만 두 번 반환될 수 있다는 것입니다.
1) 상위 프로세스에서 포크는 새로 생성된 하위 프로세스의 프로세스 ID를 반환합니다.
2) 하위 프로세스에서 포크는 0을 반환합니다.
3) 오류가 발생하면 포크는 음수 값을 반환합니다.
아버지와 아들 프로세스에서 fpid의 값이 다른 이유를 설명하기 위해 네티즌의 말을 인용합니다. "사실 이는 연결 목록과 동일합니다. 프로세스는 연결 목록을 형성합니다. 상위 프로세스의 fpid(p는 포인트를 의미함)는 하위 프로세스의 프로세스 ID를 가리킵니다. 하위 프로세스에는 하위 프로세스가 없기 때문에 fpid는 0입니다.
포크 오류는 두 가지 이유로 발생할 수 있습니다:
1) 현재 프로세스 수가 시스템에서 지정한 상한값에 도달했습니다. 이때 errno 값은 EAGAIN으로 설정됩니다.
2) 시스템에 메모리가 부족하고 errno 값이 ENOMEM으로 설정되어 있습니다.
각 프로세스에는 getpid() 함수를 통해 얻을 수 있는 고유한(다른) 프로세스 식별자(프로세스 ID)와 상위 프로세스의 pid를 기록하는 변수가 있으며, 변수의 값은 getppid를 통해 얻을 수 있습니다. () 기능.
포크가 실행되면 두 가지 프로세스가 나타납니다.
fork 실행 후 프로세스 1의 변수는 count=0, fpid! =0(상위 프로세스). 프로세스 2의 변수는 count=0 및 fpid=0(자식 프로세스)입니다. 이 두 프로세스의 변수는 서로 다른 주소에 존재합니다. fpid를 사용하여 상위 프로세스와 하위 프로세스를 식별하고 운영한다고 할 수 있습니다.
왜 #include에서 코드가 복사되지 않는지 궁금해하시는 분들이 계십니다. 이는 포크가 프로세스의 현재 상황을 복사하기 때문입니다. 프로세스는 이미 실행을 완료했습니다. int count=0;
fork는 실행할 다음 코드만 새 프로세스에 복사합니다.
2. 포크 고급 지식
먼저 코드를 살펴보겠습니다:으아아아
실행 결과는 다음과 같습니다.으아아아
이 코드는 매우 흥미롭습니다. 주의 깊게 분석해 보겠습니다.
1단계: 상위 프로세스에서 명령어는 for 루프(i=0)에서 실행된 다음 포크가 실행됩니다. 포크가 실행된 후 p3224와 p3225라는 두 프로세스가 시스템에 나타납니다(pxxxx를 사용하겠습니다). 나중에 프로세스 ID를 나타내기 위해)는 xxxx)의 프로세스입니다. 상위 프로세스 p3224의 상위 프로세스는 p2043이고 하위 프로세스 p3225의 상위 프로세스는 p3224라는 것을 알 수 있습니다. 우리는 이 관계를 표현하기 위해 연결 리스트를 사용합니다:
p2043->p3224->p3225
第一次fork后,p3224(父进程)的变量为i=0,fpid=3225(fork函数在父进程中返向子进程id),代码内容为:
1. for(i=0;iif(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;iif(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高阶知识”讲。
总结一下,这个程序执行的流程如下:
这个程序最终产生了3个子进程,执行过6次printf()函数。
我们再来看一份代码:
1. /* 2. \* fork_test.c 3. \* version 3 4. \* Created on: 2010-5-29 5. \* Author: wangth 6. */ 7. \#include 8. \#include 9. int main(void) 10. { 11. int i=0; 12. for(i=0;iif(fpid==0) 15. printf("son/n"); 16. else 17. printf("father/n"); 18. } 19. return 0; 20. 21. }
它的执行结果是:
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+……+2N ”,这是错的)
网上有人说N次循环产生2*(1+2+4+……+2N)个进程,这个说法是不对的,希望大家需要注意。
同时,大家如果想测一下一个程序中到底创建了几个子进程,最好的方法就是调用printf函数打印该进程的pid,也即调用printf(“%d/n”,getpid());或者通过printf(“+/n”);
来判断产生了几个进程。有人想通过调用printf(“+”);来统计创建了几个进程,这是不妥当的。具体原因我来分析。
老规矩,大家看一下下面的代码:
1. /* 2. \* fork_test.c 3. \* version 4 4. \* Created on: 2010-5-29 5. \* Author: wangth 6. */ 7. \#include 8. \#include 9. int main() { 10. pid_t fpid;//fpid表示fork函数返回的值 11. //printf("fork!"); 12. printf("fork!/n"); 13. fpid = fork(); 14. if (fpid printf("error in fork!"); 16. else if (fpid == 0) 17. printf("I am the child process, my process id is %d/n", getpid()); 18. else 19. printf("I am the parent process, my process id is %d/n", getpid()); 20. return 0; 21. }
执行结果如下:
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函数。
1. \#include 2. \#include 3. int main(int argc, char* argv[]) 4. { 5. fork(); 6. fork() && fork() || fork(); 7. fork(); 8. return 0; 9. }
问题是不算main这个进程自身,程序到底创建了多少个进程。
为了解答这个问题,我们先做一下弊,先用程序验证一下,到此有多少个进程。
1. \#include 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个分支。
加上前面的fork和最后的fork,总共4*5=20个进程,除去main主进程,就是19个进程了。
三、fork高阶知识
<code style="display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px">这一块我主要就fork函数讲一下操作系统进程的创建、死亡和调度等。因为时间和精力限制,我先写到这里,下次找个时间我争取把剩下的内容补齐。 </code>
通过本文,我们了解了fork()函数的原理和用法,它可以用来实现多进程编程,提高程序的并发性和效率。我们应该根据实际需求选择合适的fork()函数,并遵循一些基本原则,如检查返回值是否正确,处理僵尸进程,使用信号或管道进行同步和通信等。fork()函数是Linux系统中最强大的系统调用之一,它可以实现多种复杂的功能和特性,也可以提升程序的灵活性和可扩展性。希望本文能够对你有所帮助和启发。
위 내용은 Linux 시스템의 다중 프로세스 프로그래밍: fork() 함수에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!