fork/vfork详解


父子进程的关系

使用fork(或者其他能创建子进程的API)函数得到的子进程从父进程的继承了整个进程的地址空间,包括:进程上下文、进程堆栈、代码段、数据段、以及PCB和内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。

getpid / getppid

getpid 获取当前进程id
getppid 获取当前进程的父进程的id

fork

pid_t fork()

  1. 为什么fork有两个返回值?
    因为这两个返回值是由不同的进程return出来的,而不是由一个fork函数返回两个数。(fork后,进程由一个变成两个,两个进程分别有一个返回值)
    返回值:
      - 成功创建一个子进程,对于父进程来说返回子进程ID
      - 成功创建一个子进程,对于子进程来说返回值为0
      - 如果为-1表示创建失败
  2. 子进程创建成功后,代码的执行的开始位置?
    fork代码段的位置
  3. 父子进程的执行顺序?
    不一定谁先谁后(看谁抢到CPU资源)
示例代码1:子进程在fork之后,才开始执行
#include <stdio.h>   
#include <stdlib.h>  
#include <unistd.h>  
#include <sys/stat.h>
#include <sys/types.h>
int main(){    
  for(int i=0;i<4;i++)   //仅仅在父进程中执行一次
    printf("---------%d\n",i);   

  pid_t pid=fork();  
  if(pid<0){
    perror("fork fail");
  }      
  if(pid>0){ //parent process    
    printf("parent process,pid=%d\n",getpid()); 
  }      

  if(pid==0){  //child process   
   printf("child process,pid=%d\n",getpid());
  }      

 for(int i=0;i<4;i++) //在父进程和子进程中各执行一次  
   printf("%d\n",i); 
}
[gjw@localhost 1-fork]$ gcc fork.c -o fork --std=c99  #编译代码
[gjw@localhost 1-fork]$ ./fork     #执行结果
---------0
---------1
---------2
---------3
parent process,pid=117802
0
1
2
3
child process,pid=117803
0
1
2
3
示例代码2:循环创建number个子进程
					父进程
子进程1	子进程2	子进程3	......	子进程number			
	
#include <stdio.h>   
#include <stdlib.h>  
#include <unistd.h>  
#include <sys/stat.h>
#include <sys/types.h>  
int main(){ 
  int i;  
  int number=5;      

  pid_t pid;
  for(i=0;i<number;i++){
    pid=fork();      
    if(pid<0){       
      perror("fork fail");       
      exit(1);       
    }    
    if(pid==0)       
      break;
  }      

  for(int j=0;j<number;j++){    //判断子进程是第几个孩子 
    if(j==i)
      printf("%d process,pid=%d\n",j,getpid()); 
  }      
  if(i==number) //判断父进程是哪一个:(父进程退出for循环时,i==number)
    printf("parent process,pid=%d\n",getpid());      
}  
[gjw@localhost 1-fork]$ ./fork 
0 process,pid=70113
1 process,pid=70114
2 process,pid=70115
parent process,pid=70112
4 process,pid=70117
3 process,pid=70116
示例代码3:循环创建process_num个子进程,每个进程循环打印rollnum次
int main(){
  int process_num;    
  int rollnum;       
  scanf("%d",&process_num);       
  scanf("%d",&rollnum); 

  pid_t pid;
  int i; 
  for(i=0;i<process_num;i++){     
    pid=fork();      
    if(pid<0){       
      perror("fork");
      exit(1);       
    }    
    else if(pid==0){ //子进程
      break;
    }    
  }      

  for(int j=0;j<process_num;j++){  //判断是第几个子进程  
    if(i==j){        
      for(int k=0;k<rollnum;k++){ //每个进程打印rollnum次  
        usleep(500); 
        printf("%dth pid ,%d,____%d\n",j,getpid(),k);    
      }  
    }    
  }  
  if(i==process_num){ //父进程:释放子进程资源   
    int status;      
    pid_t ret;       
    while((ret=waitpid(-1,&status,WNOHANG))!=-1){        
      if(ret==0)     
        continue;    
      printf("wait %d pid",ret); 
      if(WIFEXITED(status))      
        printf("exit,status=%d\n",WEXITSTATUS(status));  
      else if(WIFSIGNALED(status))  
        printf("signal,status=%d\n",WTERMSIG(status));   
    }    
  }      
  return 0; 
}   

写时拷贝原则write-on-copy

  1. 读时共享
    刚fork出来之后,两个地址空间用户区数据完全相同,父子进程都指向同一块共享区域,父子进程中都映射到共享区域中的变量(int num)
  2. 写时拷贝
    当后续父子进程对共享区域中的变量进行不同的操作时(父进程对num++,子进程对num–),—>发生写时拷贝原则,父子进程各自拷贝出来int大小的空间存放自己的num,因此父子进程中的num是相互独立,互不影响的====>因此父子进程之间不能够使用全局变量进行通信。

在这里插入图片描述


vfork

  1. 引出vfork的背景:
      在fork还没实现copy on write之前。Unix设计者很关心fork之后立刻执行exec所造成的地址空间浪费,所以引入了vfork系统调用。
  2. vfork和fork的区别/联系:
    vfork() 函数和 fork() 函数一样都是在已有的进程中创建一个新的进程,但它们创建的子进程是有区别的。
  • 父子进程的执行顺序
    fork(): 父子进程的执行次序不确定。
    vfork():保证子进程先运行,在它调用 exec/exit之后,父进程才执行
  • 是否拷贝父进程的地址空间
    fork(): 子进程拷贝父进程的地址空间,子进程是父进程的一个复制品。
    vfork():子进程共享父进程的地址空间
  • 调用vfork函数,是为了执行exec函数;如果子进程没有调用 exec/exit, 程序会出错
vfork示例代码
验证1:vfork父子进程执行顺序
	子进程先执行完exec或exit后
	父进程才开始执行
int main(int argc, char *argv[]){
	pid_t pid;
	pid = vfork();	// 创建进程
	if(pid < 0){ // 出错
		perror("vfork");
	}
	if(0 == pid){  
		sleep(3); // 延时 3 秒
		printf("i am son\n");
		
		_exit(0); // 退出子进程,必须
	}
	else if(pid > 0){ // 父进程
		printf("i am father\n");
	}
}
执行结果:已经让子进程延时 3 s,结果还是子进程运行结束后,父进程才执行

验证2:vfork后,父子进程共享内存空间
int a = 10;
int main(int argc, char *argv[]){
	pid_t pid;
	int b = 20;
	pid = vfork();	// 创建进程
	if(pid < 0){ // 出错
		perror("vfork");
	}
	if(0 == pid){ // 子进程
		a = 100, b = 200;
		printf("son: a = %d, b = %d\n", a, b);
		_exit(0);  
	}
	else if(pid > 0){ 
		printf("father: a = %d, b = %d\n", a, b);	
	}
}
执行结果:子进程先执行,修改完a,b的值后,由于父子进程共享内存空间,因此会影响父进程
	son: a = 100, b = 200
	son: a = 100, b = 200
  • 8
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值