fork复制进程

进程的描述

  • 进程是一个正在运行的程序
  • 每个进程都有一个进程控制块,英文缩写PCB
  • 进程控制块是一个用结构体struct task_struct来实现

操作系统如何管理进程

fork复制进程_第1张图片
进程控制块是进程存在的唯一标志,pid是一个整型,它属于是进程的身份证号码,由于pid是由整型表示的,所以pid的范围也就是一个整型的大小范围

fork复制进程

fork复制进程_第2张图片
首先会将原来的进程(父进程)PCB复制一块出来,并且给他申请一个pid,来唯一标识子进程,接下来将整个进程的实体复制一份给子进程
复制出来的子进程与父进程是相同的,唯一不同的是fork()的返回值不同,父进程fork()返回子进程的pid,子进程fork()返回0

我们写一段代码来看一下

通过帮助手册看一下fork
fork复制进程_第3张图片

#include
#include
#include
#include
#include

int main()
{
	char *s = NULL;
	int n = 0;
	
	pid_t  id = fork();//pid_t 相当于 int
	assert(id != -1);
	
	if(id == 0)
	{
		s = "child";
		n = 3;
	}
	else
	{
		s = "parent";
		n = 7;
	}

	int i = 0;
	for(;i<n;i++)
	{
		printf("s = %s pid = %d\n",s,getpid());
		sleep(1);
	}

	exit(0);
}

fork复制进程_第4张图片
fork()执行之后,子进程会从fork()执行到的地方开始执行,而不是从新执行一遍程序

fork的写时拷贝技术

fork复制进程_第5张图片
在物理内存的管理中,我们会将物理内存划分为一个一个的页面,一个页面有4k或者8k大小
在这里插入图片描述
而这个是一个进程的逻辑页面,也同样是4k或者8k一个页面大小,即使页面使用只有一部分但是还会分给他一个完整的页面

我们通过页表来描述,逻辑页与物理页之间的联系,根据上表进程复制,会把4号页面的内容复制给7号一份,10号页面的内容复制给14号一份以此类推,但是假如我们2号3号页面我们不去进行修改,我们将其复制,子进程物理页面上与父进程物理页面上存放的内容是一模一样的,这样不光复制开销大,也占用了内存空间
fork复制进程_第6张图片

继而我们引入了写时拷贝技术,假如我们的0号页面与1号页面会发生修改,我们就将这两个页面进行复制,而后面的2号3号页面不会进行修改,那么我们父子进程共享这两个页面

进程的逻辑地址与物理地址

fork复制进程_第7张图片
我们的32位系统,4g内存的使用情况,上面的1g是有内核占用的

进程中使用的是逻辑地址,即两个程序会使用相同的地址(逻辑),而会映射到不同的物理地址上

我们修改这一块的代码

printf("s = %s pid = %d %x\n",s,getpid(),&n);
来打印 n 的地址,运行程序

fork复制进程_第8张图片
我们发现父进程与子进程打印的n地址都是一样,我们知道父子进程的n值不一样,为什么地址会相同呢,这就是进程使用逻辑地址造成的,他们虽然逻辑地址相同,但是在物理上映射的地址是不同的

僵死进程

僵死进程的产生:

  • 当子进程先于父进程结束,父进程没有获取子进程的退出码,此时子进程变成僵死进程

我们的程序退出时,无论是return 0还是exit(0)都会有一个退出码,退出码会存放在进程的PCB中,希望父进程能够获得这个退出码,来观察子进程的退出状态
fork复制进程_第9张图片

子进程结束,进程实体会消失,但是PCB会暂时保留,等待父进程获取PCB的退出码,否则会一直存在,所以当父进程先于子进程结束,PCB等待不到父进程的获取,继而会进入僵死状态

写一段代码来看一下僵死进程

if(id == 0)
	{
		s = "child";
		n = 3;
	}
	else
	{
		s = "parent";
		n = 7;
	}

将这一段修改,让子进程先与父进程结束
fork复制进程_第10张图片
子进程结束,父进程没有获得子进程的退出码,变成僵死进程

如何处理僵死进程

  • 父进程调用wait()方法获取子进程的退出码
  • 父进程先结束

僵死进程的影响:当不断有僵死进程产生,会一直消耗PCB编号,以及占用一定的内存空间,会对系统软硬件资源进行损耗

父进程先于子进程结束,子进程会变成孤儿进程,孤儿进程会被init进程进行收养,获取它的退出码

if(id == 0)
	{
		s = "child";
		n = 7;
	}
	else
	{
		s = "parent";
		n = 3;
	}

	int i = 0;
	for(;i<n;i++)
	{
		printf("s = %s pid = %d ppid = %d\n",s,getpid(),getppid());
		sleep(1);
	}


我们这次这样修改代码,让父进程先结束,子进程后结束并显示他们的父进程pid
fork复制进程_第11张图片
子进程被init收养

另外一种方法就是使用wait()方法来调用等待获取子进程退出码
wait()会将父进程阻塞住,等待子进程结束获取到退出码,进程才会继续执行

#include
#include
#include
#include
#include

int main()
{
	char *s = NULL;
	int n = 0;
	
	pid_t  id = fork();
	assert(id != -1);
	
	if(id == 0)
	{
		s = "child";
		n = 3;
	}
	else
	{
		s = "parent";
		n = 7;
		int val = 0;
		wait(&val);//接收子进程的退出码 
		if(WIFEXITED(val))//是否正常结束 获取退出码
		{
			printf("val = %d\n",WEXITSTATUS(val));
		}
		
	}

	int i = 0;
	for(;i<n;i++)
	{
		printf("s = %s pid = %d ppid = %d\n",s,getpid(),getppid());
		sleep(1);
	}

	exit(3);
}

你可能感兴趣的:(Linux,bash,linux,c语言,多进程)