fork

说明

  • fork()函数通过系统调用创建一个与原来进程几乎完全相同的新进程,新进程为子进程,原进程为父进程。特点是:调用一次,返回两次,它可能有三种不同的返回值:
    1)在父进程中,fork返回新创建子进程的进程ID;
    2)在子进程中,fork返回0;
    3)如果出现错误(创建新进程失败),fork返回一个负值;
    我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
  • 进程调用fork,当控制转移到内核后,内核将做以下事情:
    1)分配新的内存块和内核数据结构给子进程
    2)将父进程的部分数据结构内容拷贝至子进程(写时拷贝)
    3)添加子进程到系统进程列表当中
    4)fork返回,开始调度器调度

举例

下面用一些例子详细解析fork函数

例0

0:
void fork0() 
{
    if (fork() == 0) {
 printf("Hello from child\n");
    }
    else {
 printf("Hello from parent\n");
    }
}
         

运行结果
fork产生了一个新的子进程,根据fork返回值的结果不同,明确区分执行不同代码

coral@coral-VirtualBox:~/homework/fork$ ./forks 0
Hello from parent
Hello from child

进程结构图

          
         ------------------------->  printf("Hello from child\n")   
         |
         |
         |            
         |            
         |            
         |       
---------+-----------------------> printf("Hello from parent\n")  
	fork       

例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);
}

运行结果
据fork返回值的结果不同,明确区分执行不同代码,同时还有一条两个进程中都存在的语句,两个进程都会执行它。

coral@coral-VirtualBox:~/homework/fork$ ./forks 1
Parent has x = 0
Bye from process 3354 with x = 0
Child has x = 2
Bye from process 3355 with x = 2

进程结构图

     		    printf,x=2	    	
         ---------------->------------------->printf(getpid()),x=2   
         |
         |
         |            
         |            
         |            
         | 	    printf,x=0	
---------+---------------->------------------>printf(getpid()),x=0    
       fork 
	x=1      

例2

void fork2()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("Bye\n");
}

运行结果

第一次
coral@coral-VirtualBox:~/homework/fork$ ./forks 2
L0
L1
Bye
coral@coral-VirtualBox:~/homework/fork$ L1
Bye
Bye
Bye

第二次
coral@coral-VirtualBox:~/homework/fork$ ./forks 2
L0
L1
Bye
coral@coral-VirtualBox:~/homework/fork$ Bye
L1
Bye
Bye


由于父、子进程运行的顺序不是确定的,取决于系统的调度,所以输出结果也是不确定的
进程结构图

				         
                                   ------------------> printf("Bye\n")
                                   |
                                   |
 		   printf("L1\n")  |  
                --------->---------+------------------>printf("Bye\n")   
                |		  Fork
                |                    
                |                  ------------------->printf("Bye\n")  
                |                  |
                |                  |
printf("L0\n")  |  printf("L1\n")  |  
+---------------+--------->--------+--------------------> printf("Bye\n") 
              Fork                Fork

例3

void fork3()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("L2\n");    
    fork();
    printf("Bye\n");
}

运行结果
同例2

coral@coral-VirtualBox:~/homework/fork$ ./forks 3
L0
L1
L2
Bye
coral@coral-VirtualBox:~/homework/fork$ Bye
L2
Bye
L1
L2
Bye
Bye
Bye
L2
Bye
Bye

进程结构图


 			
   						                                 -----------------> printf("Bye\n")
                                        printf("L2\n")   |fork  
                                      --------->---------+---------------->printf("Bye\n")
                                      |                  --------------->printf("Bye\n")
        	     printf("L1\n")       |   printf("L2\n") |fork  
                  -------->-----------+--------->--------+---------------->printf("Bye\n")    
                  |                 fork                  
                  |     				                 ----------------> printf("Bye\n")
                  |                    printf("L2\n")    | fork 
                  |                   -------->----------+--------------->printf("Bye\n")  
                  |            	      |                  --------------->printf("Bye\n")
 printf("L0\n")   |   printf("L1\n")  |  printf("L2\n")  |
------------------+---------->-------+--------->---------+--------------->printf("Bye\n")   
                fork                 fork              fork

例4

void fork4()
{
    printf("L0\n");
    if (fork() != 0) {
    printf("L1\n");    
    	if(fork() != 0) {
     	printf("L2\n");
 }
    }
    printf("Bye\n");
}

运行结果
第一次fork,父进程执行括号内的语句,打印L1,子进程打印Bye,第二次fork,同理,子进程执行仅括号外的语句,打印Bye,而父进程执行完括号内语句后,再执行括号外语句,同例1

coral@coral-VirtualBox:~/homework/fork$ ./forks 4
L0
L1
L2
Bye
coral@coral-VirtualBox:~/homework/fork$ Bye
Bye

进程结构图

                           
                                            
                       Bye     
         -------------->+
         |           
         |	                     Bye
         |            ---------------->+                      
  L0     |    L1      |              L2               Bye      |            |
+--------+----->------+-------------->+--------------->+   
       Fork          Fork
      

例5

void fork5()
{
    printf("L0\n");
    if (fork() == 0) {
 printf("L1\n");    
 if (fork() == 0) {
     printf("L2\n");
 }
    }
    printf("Bye\n");
}

运行结果

coral@coral-VirtualBox:~/homework/fork$ ./forks 5
L0
Bye
coral@coral-VirtualBox:~/homework/fork$ L1
Bye
L2
Bye

进程结构图

                            L2                        Bye
                       ------->+------->-------------->+   
                       |
                       | 
              L1       |             Bye
         ------>+------+-------------->+   
         |	    Fork
         |
         |            
         |            
   L0    |            Bye       
+--------+------------>+   
       Fork       

例6

void cleanup(void) {
    printf("Cleaning up\n");
}
void fork6()
{
    atexit(cleanup);
    fork();
    exit(0);
}

atexit函数是一个特殊的函数,它是在正常程序退出时调用的函数,我们把他叫为登记函数
函数原型:int atexit (void (*)(void))):
⼀个进程可以登记若⼲个函数,这些函数由exit⾃动调⽤,这些函数被称为终⽌处理函数(即atexit函数内的参数), atexit函数可以登记这些函数。 exit调⽤终⽌处理函数的顺序和atexit登记的顺序相反,如果⼀个函数被多次登记,也会被多次调⽤。
运行结果

coral@coral-VirtualBox:~/homework/fork$ ./forks 6
Cleaning up
coral@coral-VirtualBox:~/homework/fork$ Cleaning up

进程结构图

                    
         -------------->+cleanup
         |           
         |	                     
         |                                
         |    
+--------+-------------->+cleanup  
       Fork          

6-1

#include<stdio.h> 

#include<stdlib.h>  //atexit函数所属头文件

   

void func1() 

{ 

    printf("The process is done...\n"); 

} 

void func2() 

{ 

    printf("Clean up the processing\n"); 

} 

void func3() 

{ 

    printf("Exit sucessful..\n"); 

} 

int main() 

{ 

  //其作用是注册某一个函数,当进程执行结束时,会自动调用注册的函数

  //注册几次,就执行几次

    atexit(func1); 

    atexit(func2); 

    atexit(func3); 

    exit(0); 

} 

运行结果

coral@coral-VirtualBox:~/homework/fork$ ./test6
func3
func2
func1

例7

void fork7()
{
    if (fork() == 0) {
	 printf("Terminating Child, PID = %d\n", getpid());
	 exit(0);
    } else {
 	printf("Running Parent, PID = %d\n", getpid());
	while (1) ; 
    }
}

运行结果
由于在父进程中存在死循环,而子进程停止后,只有等到他的父进程来回收他,否则将一直存在于内存中,成为僵尸进程。可通过命令top来查看所有僵尸进程。

coral@coral-VirtualBox:~/homework/fork$ ./forks 7 &
[1] 4387
coral@coral-VirtualBox:~/homework/fork$ Running Parent, PID = 4387
Terminating Child, PID = 4388
ps
  PID TTY          TIME CMD
 4375 pts/4    00:00:00 bash
 4387 pts/4    00:00:04 forks
 4388 pts/4    00:00:00 forks <defunct>
 4389 pts/4    00:00:00 ps

例8

void fork8()
{
    if (fork() == 0) {
 	printf("Running Child, PID = %d\n",
        getpid());
 	while (1)  ; 
    } else {
 	printf("Terminating Parent, PID = %d\n",
        getpid());
 	exit(0);
    }
}

运行结果
子进程成为僵尸进程

coral@coral-VirtualBox:~/homework/fork$ ./forks 8
Terminating Parent, PID = 4644
coral@coral-VirtualBox:~/homework/fork$ Running Child, PID = 4645
ps
  PID TTY          TIME CMD
 4631 pts/4    00:00:00 bash
 4645 pts/4    00:00:29 forks
 4653 pts/4    00:00:00 ps

例9

请问下面的程序一共输出多少个“-”?
#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;
}

运行结果

coral@coral-VirtualBox:~/homework/fork$ ./test1
--coral@coral-VirtualBox:~/homework/fork$ ------

程序结构图

		                        printf("-")
                       --------------------->+   输出两个“-”
                       |
                       | 
           printf("-") |                printf("-")
         ------>+------+-------------------->+   
         |		 fork
         |          i=1                printf("-")
         |            --------------------->+  输出两个“-” 
         |            |
         |            |
         | printf("-")|                 printf("-")
+--------+------>+-----+-------------------->+   
       Fork          Fork
       i = 0         i = 1

答案为8个。在这之前我们需要知道:

  1. fork函数会把它所在语句以后的语句复制到一个子进程,单独执行。
  2. 在fork的调用处,整个父进程空间会原封不动地复制到子进程中,包括指令,变量值,程序调用栈,环境变量,缓冲区等。
  3. 程序遇到“\n",或是EOF,或是缓冲区满,或是文件描述符关闭,或是主动flush,或是程序退出,就会把数据刷出缓冲区。
    而在这里,由于标准输出是行缓冲,遇到“\n"才会把数据刷出缓冲区。printf("-")把“-”放在缓存中,并没有真正输出,在fork的时候将复制到子进程空间中。因此有两个子进程将多输出1个“-”。

对于上面的问题,我们如果修改一下上面的printf的那条语句为:
printf("-\n");

printf("-");
fflush(stdout);
就没有问题了(就是6个“-”了),因为程序遇到“\n”,就会把“-”刷出缓冲区,不会再复制到子进程空间。

例10

#include "stdio.h"
#include "sys/types.h"
#include "unistd.h" 
int main()
{    
	pid_t pid1;    
	pid_t pid2;     
	pid1 = fork();    
	pid2 = fork();     
	printf("pid1:%d, pid2:%d\n", pid1, pid2);
}

已知从这个程序执行到这个程序的所有进程结束这个时间段内,没有其它新进程执行。
1、请说出执行这个程序后,将一共运行几个进程。
2、如果其中一个进程的输出结果是“pid1:1001, pid2:1002”,写出其他进程的输出结果(不考虑进程执行顺序)。
运行结果

coral@coral-VirtualBox:~/homework/fork$ ./test2
pid1:4754, pid2:4755
coral@coral-VirtualBox:~/homework/fork$ pid1:4754, pid2:0
pid1:0, pid2:4756
pid1:0, pid2:0

例11

int main(){    
	return fork()&&fork()||fork();
} 
问题:1.一共产生几个进程  2.返回值为1的概率为多少?

进程结构图
在这里插入图片描述

例12

int main(int argc, char* argv[]){
   fork();   
   fork() && fork() || fork();   
   fork();
   }
不算main这个进程自身,到底创建了多少个进程啊?

进程结构图
在这里插入图片描述
例13

#include <unistd.h>  
#include <stdio.h>  
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;  
}  

运行结果

进程结构图
同例9

例14

#include "csapp.h"
void doit()
{
 if (fork()==0){
  fork();
  printf("hello\n")exit(0);
 }
 return;
}
 int main()
 {
  doit()printf("hello\n");
  exit(0);
 }  

运行结果

coral@coral-VirtualBox:~/homework/fork$ ./test3
hello
coral@coral-VirtualBox:~/homework/fork$ hello
hello

进程结构图
在这里插入图片描述
例15
将doit函数中exit(0)改为return
运行结果

coral@coral-VirtualBox:~/homework/fork$ ./test3
hello
coral@coral-VirtualBox:~/homework/fork$ hello
hello
hello
hello

进程结构图
在这里插入图片描述

  • exit(0):exit 是一个函数,正常运行程序并退出程序,是系统调用级别的,exit它表示了一个进程的结束;
    exit(1):非正常运行导致退出程序;
    return():返回函数值,是关键字。

  • return是函数的退出(返回);exit是进程的退出。return用于结束一个函数的执行,将函数的执行信息传出给其他调用函数使用;exit函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给OS或其父进程,这个状态标识了应用程序的一些运行信息,这个信息和机器和操作系统有关,一般是 0 为正常退出, 非0 为非正常退出。
    所以在例14的doit函数中,子进程直接结束,不会再返回到主函数中,并执行最后一条打印指令。而在例15中,子进程也会返回到主函数,故会打印最后一条指令。

  • 另外
    在stdlib.h中exit函数是这样子定义的:void exit(int status)。这个系统调用是用来终止一个进程的,无论在程序中的什么位置,只要执行exit,进程就会从终止进程的运行。而另一个和它很相似的函数_exit()也位于unistd.h中,相比于exit(),_exit()函数的功能最为简单,直接终止进程的运行,释放其所使用的内存空间,并销毁在内存中的数据结构,而exit()在于在进程退出之前要检查文件的状态,将文件缓冲区中的内容写回文件。

例16

#include "csapp.h"
void end(void)
{
	printf("2");fflush(stdout);
}

int main()
{
	if(fork()==0)
		atexit(end);
	if(fork()==0){
		printf("0");fflush(stdout);
	}
	else{
		printf("1");fflush(stdout);
	}
	exit(0);
}
coral@coral-VirtualBox:~/homework/fork$ ./test5
1coral@coral-VirtualBox:~/homework/fork$ 01202
coral@coral-VirtualBox:~/homework/fork$ ./test5
1coral@coral-VirtualBox:~/homework/fork$ 01202
coral@coral-VirtualBox:~/homework/fork$ ./test5
1coral@coral-VirtualBox:~/homework/fork$ 01202
coral@coral-VirtualBox:~/homework/fork$ ./test5
1coral@coral-VirtualBox:~/homework/fork$ 01202
                   +-->printf("0")+--->printf("2")
                   |
    +----->atexit+-+-->printf("1")+--->printf("2")
    |              Fork
    |              Fork
+---+--------------+-->printf("1")
   Fork            |
                   +-->printf("0")
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值