实验二:《操作系统》之进程控制

“小忙”解释一下,三天没更博 是因为有事情在忙,据Keep APP不完整计程 + 不完整计时,三天骑行一百多公里,第一次觉得骑车真的太累了……
不过我又回来啦 今日份继续更博咯,开启正文叭!

Part2. 进程控制

往期回顾:
Part0. 实验环境
Part1-1.熟悉UKylin环境
Part1-2.熟悉UKylin环境

一、实验目的

  1. 掌握进程的概念,进一步理解进程和程序的区别。
  2. 认识和了解并发执行的实质。
  3. 掌握fork()、wait()、exit()函数。

二、实验内容

  1. 运行下面程序,查看程序运行结果,了解并掌握fork()函数调用的结果。
#include
#include
int main()
{
	int pid;
	
	pid = fork();
	if(pid < 0)
		printf("返回值为:pid = %d. Failed to create process. \n",pid);
	else if(pid == 0)
		printf("返回值为:pid = %d. I'm the child process. My Process ID is:%d. \n", pid, getpid());
	else 
	{
		wait(0);
		printf("返回值为:pid = %d. I'm the parent process. My Process ID is:%d. \n", pid, getpid());
	}
	
	return 0;
}

运行结果:
实验二:《操作系统》之进程控制_第1张图片
分析:fork()函数创建一个子进程,该子进程复制父进程的PCB。当返回值为0时代表子进程,当返回值是大于0是代表父进程,返回值为负数时表示创建进程失败。上述代码创建进程的父进程的进程号为5252,子进程的进程号为5253。

fork( )
功能:创建一个新的子进程。其子进程会复制父进程的数据与堆栈空间,并继承父进程的用户代码、组代码、环境变量、已打开的文件代码、工作目录和资源限制。
系统调用格式:
int fork()
如果fork调用成功则在父进程会返回新建立的子进程标识符(PID),而在新建立的子进程中则返回0。如果fork失败则直接返回-1。

修改后代码:

#include
#include   //fork()  getpid()
#include   //exit()
#include //wait()
int main()
{
	int pid;
	
	pid = fork();
	if(pid < 0)
		printf("返回值为:pid = %d. Failed to create process. \n",pid);
	else if(pid == 0)
	{
		printf("返回值为:pid = %d. I'm the child process. My Process ID is:%d. \n", pid, getpid());
		exit(0);
	}
	else
	{ 
		wait(0);
		printf("返回值为:pid = %d. I'm the parent process. My Process ID is:%d. \n", pid, getpid());
	}
	return 0;
}

分析:调用了wait函数和exit函数,父进程等待子进程执行完再执行。

1、wait()
等待子进程运行结束。如果子进程没有完成,父进程一直等待。wait( )将调用进程挂起,直至其子进程因暂停或终止而发来软中断信号为止。如果在wait( )前已有子进程暂停或终止,则调用进程做适当处理后便返回。
2、exit()
终止进程的执行。通常父进程在创建子进程时,应在进程的末尾安排一条exit( )语句,使子进程自我终止。exit(0)表示进程正常终止,exit(1)表示进程运行有错,异常终止。
如果调用进程在执行exit( )时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。操作系统须为exit( )完成以下操作:
(1)关闭软中断
(2)回收资源
(3)写记帐信息
(4)置进程为“僵死状态”

1.进程的创建
编写一段程序,使用系统调用fork( )创建两个子进程,在系统中有一个父进程和两个子进程活动。让每个进程在屏幕上显示一个字符;父进程显示字符“f”,两个子进程分别显示字符“s” 和“d”。多次运行可执行程序,观察记录屏幕上的显示结果,并分析原因。画出进程树的结构图。

#include
#include
#include
#include
int main()
{
	int p1,p2;
	while((p1 = fork()) == -1);   // 创建成功为止
	if(p1 == 0)	                  //如果是子进程
	{
		putchar('s');putchar('\n');
		exit(0);
	}
	else             // p1 > 0    父进程
	{
		while((p2 = fork()) == -1);  // 创建新的进程-成功为止
		if(p2 == 0)  //p1 > 0 && p2 = 0 
		{
			putchar('d');putchar('\n');
			exit(0);
		}
		else         //p1 > 0 && p2 >0
		{
			wait(0);
			sleep(0);
			putchar('f');putchar('\n');
		}
	}
	return 0;
}

进程树:
实验二:《操作系统》之进程控制_第2张图片
2.观察程序执行时终端出现的现象,并分析其原因

#include
#include
#include
#include
int main()
{	
	int p1,p2;
	while((p1=fork()) == -1);
	if(p1 == 0)
		while(1) printf("--");
	else
	{
		while((p2=fork()) == -1);
		if(p2 == 0)
			while(1) printf("l");
		else
			while(1) printf("f");
	}
	exit(0);
}

运行结果分析:
代码中创建了两个子进程,三个进程(父子进程)并发执行,由于调度顺序的不同,导致输出的结果是随机的。并发执行涉及到三个进程抢占处理机问题,只有占有CPU资源才能执行相应输出。

3.修改下面程序,将每个进程的输出由单个字符改为一句话,再观察程序执行时屏幕上出现的现象,并分析其原因。多次执行程序,观察结果是否一致;将循环次数增加到1000、1500,观察结果是否一致,并分析原因。画出进程树的结构图。

#include
#include
#include
#include
int main()
{	
	int p1,p2,i;
	while((p1=fork()) == -1);
	if(p1 == 0)
		for(i = 0; i < 500; i++)
			printf("Daughter.");
			//printf("Daughter %d.\n",i);
	else
	{
		while((p2=fork()) == -1);
		if(p2 == 0)
			for(i = 0; i < 500; i++)
				printf("Son.");
				//printf("Son %d.\n",i);
		else
			for(i = 0; i < 500; i++)
				printf("Parent.");
				//printf("Parent %d.\n",i);
	}
	return 0;
}

注:请自行修改参数为1000、1500,并观察执行结果。
运行结果分析:
由于输出行数很多,终端一页无法完全看到所有输出。在多次运行后,可以观察到,两个子进程(输出分别为“Daughter”和“Son”)的输出是随机的,在并发执行中,对进程的调度不确定。多次执行,结果不一定一致。
进程树:
实验二:《操作系统》之进程控制_第3张图片
4.编写程序创建进程树如图1和图2所示,在每个进程中显示当前进程标识符PID号和父进程标识符。
实验二:《操作系统》之进程控制_第4张图片

Note:程序设计思路和方法:
图1和图2 是两个结构不同的进程树,在进行代码编写时,需要依次进行以下几步:
①首先确定需要创建几个子进程,可以根据节点数,也可以根据边数(我们用的后者设计的),以图2为例,有5条边,需要创建5个子进程pi(i=1…5);
②进行逻辑划分:准确确定左右分支上的进程,左孩子划分在上一个进程调用fork函数返回值为0的分支中进行创建,右孩子划分在上一个进程调用fork函数返回值为大于0的分支中进行创建。
③以图2的a、b、d节点进程为例,调用fork()函数(p1 = fork()),如果返回值为0(p1=0),代表子进程b节点;否则(p1>0),调用fork()函数创建一个新的进程(p2 = fork()),如果返回值为0(p1>0&&p2 = 0),代表进程d节点,否则(p1 > 0 && p2 > 0),代表父进程a节点。
④每个节点代表的进程调用getpid()函数,显示当前进程标识符PID;
⑤每个节点的父进程标识符处理:在调试代码的过程中终端结果可能会出现多个父进程PID =1的情况,而不是预期的父进程PID,这是因为在代码执行过程中,提前终止了父进程,导致子进程变为“僵尸程序”或“孤儿程序”,由操作系统接管。
⑥输出正确的父进程的PID,通过调用getppid()函数实现,可以通过调用wait()函数或sleep()函数控制父子进程的先后输出顺序。

Point1: PID=1的情况:父进程先退出了,造成子进程被init(ID=1)接管,所以用getppid出来的是1.
最后在执行父进程的时候加了sleep就能保证父进程后退出。
附:https://blog.csdn.net/happyguys12345/article/details/52550505/

Point2: 一般进程、孤儿进程和僵尸进程 1)一般进程
正常情况下:子进程由父进程创建,子进程再创建新的进程。父子进程是一个异步过程,父进程永远无法预测子进程的结束,所以,当子进程结束后,它的父进程会调用wait()或waitpid()取得子进程的终止状态,回收掉子进程的资源。
2)孤儿进程 孤儿进程:父进程结束了,而它的一个或多个子进程还在运行,那么这些子进程就成为孤儿进程(father
died)。子进程的资源由init进程(进程号PID = 1)回收。 3)僵尸进程
僵尸进程:子进程退出了,但是父进程没有用wait或waitpid去获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵死进程。

附:https://blog.csdn.net/a13568hki/article/details/103851388

(1)图1代码+进程树

//代码一:利用wait()函数,等待子进程先执行,最后输出父进程,依次显示当前进程的PID和父进程的PID。
#include
#include
#include
#include
int main()
{	
	int p1,p2,p3;
	printf("My PID is %d.\n",getpid());
	while((p1=fork()) == -1);
	if(p1 == 0)
	{
		while((p2=fork()) == -1);
		if(p2 == 0)
		{
			while((p3=fork()) == -1);
			if(p3 == 0)
			{
				printf("PID = %d. I'm the third child. My parent's PID is %d.\n",getpid(),getppid());
				exit(0);
			}
			else   //p2 == 0 && p3 > 0
			{
				wait(0);
				printf("PID = %d. I'm the second child. My parent's PID is %d.\n",getpid(),getppid());
				exit(0);
			}
		}
		else    //p1 == 0 && p2 >0  
		{
			wait(0);
			printf("PID = %d. I'm the first child. My parent's PID is %d.\n",getpid(),getppid());
			exit(0);
		}
	}
	else
	{
		wait(0);
		printf("PID = %d. I'm parent.\n",getpid());
	}
	return 0;
}
//代码二:利用sleep函数,设置不同参数,使得依次输出父子进程,并显示当前进程PID和父进程的PID。
#include
#include
#include
#include
int main()
{	
	int p1,p2,p3;
	printf("My PID is %d.\n",getpid());
	while((p1=fork()) == -1);
	if(p1 == 0)
	{
		while((p2=fork()) == -1);
		if(p2 == 0)
		{
			while((p3=fork()) == -1);
			if(p3 == 0)
			{
				sleep(0);
				printf("PID = %d. I'm the third child. My parent's PID is %d.\n",getpid(),getppid());
				exit(0);
			}
			else   //p2 == 0 && p3 > 0
			{
				sleep(1);
				printf("PID = %d. I'm the second child. My parent's PID is %d.\n",getpid(),getppid());
				exit(0);
			}
		}
		else    //p1 == 0 && p2 >0  
		{
			sleep(2);
			printf("PID = %d. I'm the first child. My parent's PID is %d.\n",getpid(),getppid());
			exit(0);
		}
	}
	else
	{
		sleep(3);
		printf("PID = %d. I'm parent.\n",getpid());
	}
	return 0;
}

进程树:
实验二:《操作系统》之进程控制_第5张图片
(2)图二代码+进程树

#include
#include
#include
#include
int main()
{	
	int p1,p2,p3,p4,p5;
	printf("My PID is %d.\n",getpid());
	while((p1=fork()) == -1);
	if(p1 == 0) 
	{
		while((p3=fork()) == -1);
		if(p3 == 0) 
		{
			printf("PID = %d. C stands for me.My parent's PID is %d.\n",getpid(),getppid());
		}
		else 
		{
			wait(0);
			printf("PID = %d. B stands for me.My parent's PID is %d.\n",getpid(),getppid());
		}
	}
	else 
	{
		while((p2=fork()) == -1);
		if(p2 == 0)
		{
			while((p4=fork()) == -1);
			if(p4 == 0) 
			{
				printf("PID = %d. E stands for me.My parent's PID is %d.\n",getpid(),getppid());
			}
			else 
			{
				while((p5=fork()) == -1);
				if(p5 == 0) 
				{	
					printf("PID = %d. F stands for me.My parent's PID is %d.\n",getpid(),getppid());
				}
				else 
				{
					wait(0);
					sleep(1);
					printf("PID = %d. D stands for me.My parent's PID is %d.\n",getpid(),getppid());
				}
			}
		}
		else   
		{
			wait(0);
			sleep(2);
			printf("PID = %d. I'm parent. A stands for me.\n",getpid());
		}
	}
	return 0;
}
//注:也可以用sleep设置不同延时  进行输出

进程树:
实验二:《操作系统》之进程控制_第6张图片
5.在Linux系统中运行下面的程序,最多可产生多少个进程,试画出进程家族树。
(1)main(){ fork();fork(); }
实验二:《操作系统》之进程控制_第7张图片
详细过程的理解,附:https://www.jianshu.com/p/1327c51a4a99

(2)main(){ fork();fork();fork() }
实验二:《操作系统》之进程控制_第8张图片
当每个进程都创建子进程时,满足如下规律:
实验二:《操作系统》之进程控制_第9张图片
——————
The End!

那写看似毫无波澜的日复一日,会在某一天 让你突然发现努力的意义。 “小忙”加油!
无悔昨天 & 感谢今天 & 喜欢明天~

你可能感兴趣的:(操作系统——奕曼体风格,操作系统)