Fork函数:调用系统的fork()指令,即父进程调用fork()从而创建一个新的子进程。
关于系统的fork()指令:
一次调用,两次返回。
返回值为0,说明此时在子进程中;返回值大于0,说明在父进程中,返回值为子进程的进程号(PID)
接下来看看其他系统指令:
getpid():
返回当前进程的PID
getppid():
返回当前进程父进程的PID
*waitpid(pid_t pid,int statusp, int options):
主要是等待子进程终止或者停止,并返回要等待并要被系统回收的子进程的PID
@若pid>0,等待集合是一个单独的子进程,进程ID等于pid
@若pid=-1,等待集合是由父进程所有的子进程组成
@@ 当options=0时,waitpid挂起调用进程的执行,直到它的等待集合(wait set)中的一个子进程终止
@@@如果调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD。
@@@如果waitpid函数被一个信号中断,那么它返回-1,并设置errno为EINTR
wait(&status):
相当于调用waitpid(-1,&status,0)
WIFEXITED(status):
如果子进程通过调用exit或者一个返回(return)正常终止,就返回真
WEXITSTATUS(status):
当WIFEXITED( )为真时,才可使用,并返回一个正常终止的子进程的退出状态
接下来用ubantu运行几个程序来理解Fork函数
前期准备:
1.http://csapp.cs.cmu.edu/public/code.html 下载.tar文件解压,包含了CSAPP书上的所有代码
2.在终端中键入文件移动命令:sudo mv csapp.h /usr/include和 sudo mv csapp.c /usr/include 更多的命令 mv – help
3.在gcc的时候最后加上-lpthread就可以
由于csapp.h里没有#include <csapp.c> 导致编译失败,应在文件的末端#endif 前加上即可
现在运行以下代码:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void fork1()
{
int x = 1;
pid_t pid = fork();
if (pid == 0) {
printf("Child has x = %d\n", ++x);
}
else {
printf("Parent has x = %d\n", --x);
}
printf("Bye from process %d with x = %d\n", getpid(), x);
}
int main(int argc, char *argv[])
{
fork1();
return 0;
}
运行结果:
父进程的输出是:x = 0
子进程的输出是:x = 2
并且能够看到父进程和子进程的进程编号,一个是18354,一个是18355
接下来是另一段代码:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void fork2()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("Bye\n");
}
int main(int argc, char *argv[])
{
fork2();
return 0;
}
运行结果:
为什么会有这样的运行结果呢?
现在通过思维导图的方式进行演示:
由此可以看出fork之后产生新的进程,并且子进程和父进程同时进行下一步的操作。
再来看一段代码:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void fork4()
{
printf("L0\n");
if (fork() != 0) {
printf("L1\n");
if (fork() != 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
int main(int argc, char *argv[])
{
fork4();
return 0;
}
运行结果为:
对于输出结果是否会产生好奇?
不急,咱们看看思维导图:
对于Fork()函数而言,产生的子进程的pid为0(由前文的fork1可知),而原父进程的pid则是大于0的,故结合前文的fork2可得到相应的思维导图供理解。
接下来是另一段比较有趣的代码:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void fork7()
{
if (fork() == 0) {
/* Child */
printf("Terminating Child, PID = %d\n", getpid());
exit(0);
} else {
printf("Running Parent, PID = %d\n", getpid());
while (1)
; /* Infinite loop */
}
}
int main(int argc, char *argv[])
{
fork7();
return 0;
}
其运行结果如图:
实际上这个程序不会结束,会陷入一个死循环,那么在Linux中我还想进行其他操作怎么办呢?
这个时候使用ctrl+z对其进行挂起或者ctrl+c强行终止,即可进行其他操作,如图中所示,我对他进行挂起。
用ps命令即可查看进程的信息,如图:
可以看到进程任在进行,但是这个时候子进程已经死了,形成一个僵死进程,但父进程还在运行,此时子进程无法被收回,并且由于父进程一直在运行,导致shell命令行不能够出现,若此时不进行进程挂起或强制退出则无法进行其他操作。
接下来看一段好玩的程序:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void){
int i;
for(i=0;i<2;i++){
fork();
printf("*");
}
return 0;
}
运行结果为:
那么为什么会输出8个呢?
看看流程图:
其罪魁祸首就在于标准I/O并未在fork时就输出,而存在于缓冲区中。在fork时,子进程自动继承了父进程的缓冲区,导致比预期多输出两个
通过这几个小程序可以帮助我们理解fork函数的有关知识,掌握fork函数是掌握异常控制流必备的一步,鄙人知识浅薄,分享就到这啦~ 我们下次再见!