当前位置:   article > 正文

Linux——fork进程复制,fork的写时拷贝技术_子进程复制了父进程的fork 它会重新调用fork吗

子进程复制了父进程的fork 它会重新调用fork吗

前言

进程是一个运行中的程序,每个进程都有一个进程控制块,英文缩写PCB,Linux系统中的进程控制块是一个结构体strut task_struct实现(PCB是进程存在的唯一标志)
数据结构中定义的内容是为后面的管理提供支持的,所以不同的操作系统根据自己的特点又对PCB的内容做了一些调整。
在这里插入图片描述

一般情况下,PCB中包含以下内容:

  • 进程标识符(内部,外部)
  • 处理机的信息(通用寄存器,指令计数器,PSW,用户的栈指针)。
  • 进程调度信息(进程状态,进程的优先级,进程调度所需的其它信息,事件)
  • 进程控制信息(程序的数据的地址,资源清单,进程同步和通信机制,链接指针)

当线程调用fork时,就为子进程创建了整个进程地址空间的副本,子进程和父进程是完全不同的进程,只要两者都没有对内存作出改动,父进程和子进程之间还可以共享内存页的副本。
子进程通过继承整个地址空间的副本、也从父进程那里继承了所有互斥量、读写锁和条件变量的状态、如果父进程包含多个线程、子进程在fork返回之后,如果紧接着不是马上调用exec的话,就需要清理锁状态。

事实上,子进程执行的代码和父进程一模一样,只是fork的返回值不同

  • 父进程的fork的返回值是子进程的PID(大于0)
  • 子进程的fork的返回值等于0(标志)

在这里插入图片描述

注:

  • getpid():获取当前进程的PID
  • getppid():获取当前进程父进程的PID

执行逻辑:只有当fork执行完成后,子进程才会被复制出来,子进程不会再去fork,并且子进程也不会从头开始执行,而是从返回值处开始执行。
在这里插入图片描述
bash就是我们“父进程”的“父进程”

也许有的时候父子进程的执行顺序不一样,是因为父子进程是并发运行的,执行顺序并不一定固定。

注意并发和并行的区别:
在这里插入图片描述

多线程中某个线程调用了fork创建子进程,在子进程中线程的运行情况是怎样的?

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <assert.h> 
#include <unistd.h>

#include <pthread.h>

void *fun(void *arg) 
{    
	printf("fun start\n");
	pid_t pid = fork();
	assert(pid != -1);
	
    if(pid == 0)
    {
      	int i = 0;
        for(; i < 3; ++i)        
        {            
        	printf("child: mypid = %d\n", getpid());
        	sleep(1);        
        }    
     }    
     
     else    
     {        
     int i = 0;        
     for(; i < 3; ++i)        
	     {            
	     	printf("father: mypid = %d\n", getpid());            	
	     	sleep(1);        
	     }   
     } 
}
int main() 
{    
	pthread_t id;
	int res = pthread_create(&id, NULL, fun, NULL);
	assert(res == 0);
    int i = 0;
    for(; i < 5; ++i)    
    {        
    	printf("main and  mypid = %d\n", getpid());        
    	sleep(1);    
    }
    pthread_join(id, NULL);
    exit(0);
 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

执行结果及结论
在这里插入图片描述

多线程中调用fork的锁的继承问题

示例代码

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h>
#include <assert.h>
#include <unistd.h>

#include <pthread.h>

pthread_mutex_t mutex;

void *fun(void *arg) 
{    
	sleep(1);
	printf("fun start\n");


    // pthread_mutex_lock(&mutex);    
    pid_t pid = fork();  // 调用fork时,互斥锁的状态是加锁状态    
    assert(pid != -1);    
    // pthread_mutex_unlock(&mutex);  // 父子进程都会执行
    
    if(pid == 0)    
    {       
    	 pthread_mutex_lock(&mutex);
    	 int i = 0;
    	 for(; i < 3; ++i)        
    	 {            
    	 	printf("child: mypid = %d\n", getpid());            
    	 	sleep(1);        
    	 }        
    	 pthread_mutex_unlock(&mutex);    
    }    
    else    
    {        
    	pthread_mutex_lock(&mutex);
    	int i = 0;        
    	for(; i < 3; ++i)       
        {            
        	printf("father: mypid = %d\n", getpid());
        	sleep(1);        
        }        
        pthread_mutex_unlock(&mutex);    
        } 
     }
int main() 
{    
	pthread_mutex_init(&mutex, NULL);    
	pthread_t id;    
	int res = pthread_create(&id, NULL, fun, NULL);   
    assert(res == 0);
    
    pthread_mutex_lock(&mutex);
    int i = 0;    
    for(; i < 5; ++i)    
    {        
    	printf("main and  mypid = %d\n", getpid());        
    	sleep(1);    
    }    
    pthread_mutex_unlock(&mutex);
    
    pthread_join(id, NULL);
    pthread_mutex_destroy(&mutex);    
    exit(0); 
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

在这里插入图片描述
结论
调用fork时,子进程会复制父进程的锁,(子进程会继承父进程互斥锁的状态)
解决方案
使用互斥锁对fork()调用做保护,使fork在调用过程中的锁是被自己加锁的,线程库提供了一个注册方法pthread_atfork()
注册方法

  1. prepare这个方法——对所有的锁执行加锁操作,他会在fork调用之前调用
  2. parent这个方法——对所有的锁执行解锁操作,他会在fork调用之后在父进程中执行
  3. child这个方法——对所有的锁执行解锁操作,他会在fork调用之后在子进程中执行。
int pthread_atfork(void (*prepare)();void (*parent),void (*child));
  • 1

fork的写时拷贝技术

一种推迟或者免除拷贝的技术。

内核fork()时并不复制整个进程地址空间,而是让父子进程共享一个地址空间——>只有在需要写入时,数据才会被复制,从而使各个进程拥有各自的拷贝数据。

也就是说,只有在需要写入的时候才复制资源,在此之前,以只读方式共享。
在这里插入图片描述
在进程实体的逻辑页中,假如子进程对0号页和1号页进行了修改,那么就单独按照页表将父子进程的这两个页表单独复制出来,2号页和3号页未被修改,其实是可以共享的,只有需要修改这两个页面时,才单独拷贝出来,相当于将页面的复制时间推迟(这也就是写时拷贝技术的含义),这一点用户也不会感受到。

  • 注意:写时拷贝技术是以“”为单位
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/883026
推荐阅读
相关标签
  

闽ICP备14008679号