Fungsi Fork() ialah salah satu panggilan sistem yang paling biasa digunakan dalam sistem Linux Ia digunakan untuk mencipta proses baharu, iaitu proses anak proses panggilan. Ciri fungsi fork() ialah ia hanya dipanggil sekali tetapi kembali dua kali, masing-masing dalam proses induk dan proses anak. Nilai pulangan fungsi fork() adalah berbeza dan boleh digunakan untuk membezakan proses induk dan proses anak. Dalam artikel ini, kami akan memperkenalkan prinsip dan penggunaan fungsi fork(), termasuk maksud nilai pulangan, ciri-ciri proses anak, penyegerakan dan komunikasi proses ibu bapa-anak, dsb., dan memberi contoh penggunaan dan langkah berjaga-jaga mereka.
Satu proses, termasuk kod, data dan sumber yang diperuntukkan kepada proses tersebut. Fungsi fork() mencipta proses yang hampir sama dengan proses asal melalui panggilan sistem Iaitu, kedua-dua proses boleh melakukan perkara yang sama, tetapi jika parameter awal atau pembolehubah lulus adalah berbeza, kedua-dua proses itu. juga boleh melakukan perkara yang berbeza.
Selepas proses memanggil fungsi fork(), sistem mula-mula memperuntukkan sumber kepada proses baharu, seperti ruang untuk menyimpan data dan kod. Kemudian salin semua nilai proses asal ke proses baharu, kecuali beberapa nilai yang berbeza daripada nilai proses asal. Ia sama dengan mengklonkan diri sendiri.
Mari kita lihat contoh:
1. /* 2. \* fork_test.c 3. \* version 1 4. \* Created on: 2010-5-29 5. \* Author: wangth 6. */ 7. \#include 8. \#include 9. int main () 10. { 11. pid_t fpid; //fpid表示fork函数返回的值 12. int count=0; 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. printf("我是爹的儿子/n");//对某些人来说中文看着更直白。 19. count++; 20. } 21. else { 22. printf("i am the parent process, my process id is %d/n",getpid()); 23. printf("我是孩子他爹/n"); 24. count++; 25. } 26. printf("统计结果是: %d/n",count); 27. return 0; 28. }
Hasil larian ialah:
saya proses anak, id proses saya ialah 5574
Saya anak ayah saya
Keputusan statistik ialah: 1
saya adalah proses ibu bapa, id proses saya ialah 5573
Saya adalah bapa kanak-kanak itu
Keputusan statistik ialah: 1
Sebelum penyataan fpid=fork(), hanya satu proses melaksanakan kod ini, tetapi selepas penyataan ini, ia menjadi dua proses Kedua-dua proses itu hampir sama Pernyataan seterusnya adalah jika(fpid
Mengapa fpid kedua-dua proses ini adalah berkaitan dengan ciri-ciri fungsi fork.
Salah satu perkara menarik tentang panggilan fork ialah ia hanya dipanggil sekali, tetapi boleh kembali dua kali Ia mungkin mempunyai tiga nilai pulangan yang berbeza:
1) Dalam proses induk, fork mengembalikan ID proses proses anak yang baru dibuat
2) Dalam proses anak, garpu mengembalikan 0;
3) Jika ralat berlaku, garpu mengembalikan nilai negatif
Selepas fungsi fork dilaksanakan, jika proses baru berjaya dibuat, dua proses akan muncul, satu proses anak dan satu lagi proses induk. Dalam proses anak, fungsi garpu mengembalikan 0. Dalam proses induk, garpu mengembalikan ID proses proses anak yang baru dibuat. Kita boleh menggunakan nilai yang dikembalikan melalui garpu untuk menentukan sama ada proses semasa ialah proses anak atau proses induk.
Memetik seorang netizen untuk menjelaskan mengapa nilai fpid berbeza dalam proses bapa dan anak. "Malah, ia bersamaan dengan senarai terpaut. Proses-proses membentuk senarai terpaut. Fpid proses induk (p bermaksud titik) menunjuk kepada id proses proses anak. Kerana proses anak tidak mempunyai proses anak, ia fpid ialah 0.
Ralat garpu mungkin berlaku atas dua sebab:
1) Bilangan proses semasa telah mencapai had atas yang ditentukan oleh sistem Pada masa ini, nilai errno ditetapkan kepada EAGAIN.
2) Sistem mempunyai memori yang tidak mencukupi, dan nilai errno ditetapkan kepada ENOMEM.
Selepas proses baharu berjaya dibuat, dua proses yang pada asasnya serupa akan muncul dalam sistem.
Setiap proses mempunyai pengecam proses (ID proses) yang unik (berbeza), yang boleh diperolehi melalui fungsi getpid(), dan pembolehubah yang merekodkan pid proses induk, dan nilai pembolehubah boleh diperolehi melalui getppid () fungsi.
Selepas garpu dilaksanakan, dua proses muncul,
Sesetengah orang mengatakan bahawa kandungan kedua-dua proses adalah betul-betul sama, tetapi hasil cetakan adalah berbeza. Ini kerana syarat-syarat yang disenaraikan di atas hanyalah kod dan arahan proses, serta pembolehubah .
Selepas melaksanakan fork, pembolehubah proses 1 adalah dikira=0, fpid! =0 (proses induk). Pembolehubah proses 2 adalah kiraan=0 dan fpid=0 (proses kanak-kanak Pembolehubah kedua-dua proses ini adalah bebas dan wujud dalam alamat yang berbeza Ini harus diperhatikan. Boleh dikatakan kami menggunakan fpid untuk mengenal pasti dan mengendalikan proses ibu bapa dan anak.
Sesetengah orang mungkin tertanya-tanya mengapa kod tidak disalin daripada #include Ini kerana fork menyalin situasi semasa proses tersebut, proses telah selesai int count=0;
fork hanya menyalin kod seterusnya untuk dilaksanakan ke proses baharu.
2. Garpu ilmu lanjutan
Mari kita lihat kodnya dahulu:
1. /* 2. \* fork_test.c 3. \* version 2 4. \* Created on: 2010-5-29 5. \* Author: wangth 6. */ 7. \#include 8. \#include 9. int main(void) 10. { 11. int i=0; 12. printf("i son/pa ppid pid fpid/n"); 13. //ppid指当前进程的父进程pid 14. //pid指当前进程的pid, 15. //fpid指fork返回给当前进程的值 16. for(i=0;iif(fpid==0) 19. printf("%d child %4d %4d %4d/n",i,getppid(),getpid(),fpid); 20. else 21. printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid); 22. } 23. return 0; 24. }
Hasil larian ialah:
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
Kod ini agak menarik, mari kita analisa dengan teliti:
Langkah 1: Dalam proses induk, arahan dilaksanakan ke dalam gelung for, i=0, dan kemudian fork dilaksanakan Selepas garpu dilaksanakan, dua proses muncul dalam sistem, iaitu p3224 dan p3225 (saya akan menggunakan pxxxx untuk. mewakili ID proses kemudian) ialah proses xxxx). Anda boleh melihat bahawa proses induk proses induk p3224 ialah p2043 dan proses induk proses anak p3225 adalah p3224. Kami menggunakan senarai terpaut untuk mewakili perhubungan ini:
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系统中最强大的系统调用之一,它可以实现多种复杂的功能和特性,也可以提升程序的灵活性和可扩展性。希望本文能够对你有所帮助和启发。
Atas ialah kandungan terperinci Pengaturcaraan berbilang proses dalam sistem Linux: Penjelasan terperinci tentang fungsi fork().. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!