一个进程调用fork()函数创建一个新的运行的子进程。
使用 fork() 函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间:包括代码,数据段,进程上下文、进程堆栈、打开的文件描述符等。
简单来说, 一个进程调用 fork() 函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中。
需要注意的是**fork()函数被调用一次,却会返回两次。**两次返回的区别是,子进程的返回值的是0,而父进程返回的是子进程的ID。若调用出错则返回-1.
以以下程序为例
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);
}
当在Linux系统上运行该代码,我们会得到如下结果
` 执行到fork()函数后,就创建了一个子进程,然后根据父进程和子进程返回值的不同,执行相应代码段。系统把原来的进程的所有代码,数据都复制到新的新进程中,所以子进程和父进程都会继续执行fork()调用之后的代码,最后一句输出会打印两次(其中getpid()函数是返回调用进程的ID)
例2.然后可以看看有多个循环的例子
void fork2()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("Bye\n");
}
可以先尝试画一下它的进程图
按照进程图,它会输出L0 L1 Bye Bye L1 Bye Bye
但一般来说,在 fork() 之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
在Linux上运行之后
而提示符会在子进程之前运行是因为父进程是当前shell命令执行的,父进程结束后就返回了shell。之后才是子进程继续打印的信息。
例3
void doit()
{
if (fork()==0){
fork();
printf("hello\n");
exit(0);
}
return;
}
可先尝试画进程图
运行结果:
子进程符合返回值等于0,运行语句块,再调用fork()之后该进程和他的子进程都运行后续代码,所以打印出两个hello。
例4:关于waitpid函数
int main()
{
if (fork()==0){
printf("a");fflush(stdout);
exit(0);
}
else{
printf("b");fflush(stdout);
waitpid(-1,NULL,0);
}
printf("c");fflush(stdout);
exit(0);
}
运行结果如下
关于waitpid: 当子进程退出时, 父进程需要wait/waitpid系统调用来读取子进程的退出状态, 然后子进程被系统回收.
如果父进程没有wait的话, 子进程将变成一个"僵尸进程", 内核会释放这个子进程所有的资源,包括打开的文件占用的内存等, 但在进程表中仍然有一个PCB, 记录进程号和退出状态等信息, 并导致进程号一直被占用, 而系统能使用的进程号数量是有限的
如果产生大量僵尸进程的话, 将因为没有可用的进程号而导致系统不能产生新的进程
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束
waitpid()共有3个参数,在这个程序中第一个参数pid=-1表示waitpid就等待任何一个子进程结束,第二个参数statusp用于返回回收子进程的退出状态,第三个参数option提供了一些额外的选项来控制waitpid,值为0表示没有这些选项。
在这个程序先运行父进程,输出b,然后调用waitpid等待子进程结束,输出a,子程序正常退出后,输出c;