【实验目的】
实验目的:
1、掌握进程的概念,明确进程和程序的区别。
2、认识和了解并发执行的实质。
3、分析进程争用资源的现象,学习解决进程互斥的方法。
实验要求:
用C语言编写程序,模拟实现创建新的进程,掌握进程并发执行的实质,实现创建进程树。
【实验内容】
1、进程的创建
编写一段程序,使用系统调用 fork( )创建两个子进程,在系统中有一个父进程和两个子进程活动。让每个进程在屏幕上显示一个字符;父进程显示字符“a”,子进程分别显示字符“b” 和“c”。试观察记录屏幕上的显示结果,并分析原因。
2、 修改已编写的程序,将每个进程的输出由单个字符改为一句话,再观察程序执行时屏幕上出现的现象,并分析其原因。
3、编写程序创建进程树如图 1 和图 2 所示,在每个进程中显示当前进程识别码和父进程识别码。
【实验环境】(含主要设计设备、器材、软件等)
Pc电脑一台,Linux环境
【实验步骤、过程】(含原理图、流程图、关键代码,或实验过程中的记录、数据等)
1.fork()函数创建一个新的进程,这个新的进程是当前进程的一个复制品。创建一个子进程p1。如果创建成功,p1在父进程中的值为子进程的ID,在子进程中的值为0。然后检查p1的值。如果p1的值为0,说明当前进程是子进程p1,并打印字符’b’。如果p1的值不为0,说明当前进程是p1的父进程。p1的父进程创建另一个子进程p2。同理检查p2的值。如果p2的值为0,说明当前进程是子进程p2,并打印字符’c’。如果p2的值不为0,说明当前进程是p2的父进程,并打印字符’a’。
#include <stdio.h>
#include <unistd.h>
int main() {
int p1, p2;
while ((p1 = fork()) == -1);
if (p1 == 0) {
putchar('b');
} else {
while ((p2 = fork()) == -1);
if (p2 == 0) {
putchar('c');
} else {
putchar('a');
}
}
return 0;
}
2.与第1问类似,只是将每个进程的输出由单个字符改成了一句话。
#include <stdio.h>
#include <unistd.h>
int main() {
int p1, p2;
while ((p1 = fork()) == -1);
if (p1 == 0) {
printf("child process p1 here b\n");
} else {
while ((p2 = fork()) == -1);
if (p2 == 0) {
printf("child process p2 here c\n");
} else {
printf("father process a\n");
}
}
return 0;
}
3.1 图1进程树是由a进程创建b进程,b进程创建c进程,c进程再创建d进程。创建子进程的原理同1类似。首先创建了一个父进程a,a创建子进程b,父进程a等待子进程b执行完毕后输出进程a的信息。接着,子进程b创建子进程c,父进程b等待子进程c执行完毕后输出进程b的信息。最后,子进程c创建子进程d,父进程c等待子进程d执行完毕后输出进程c的信息,而子进程d则直接输出自己的信息。每个进程在完成相应的子进程等待后输出自身的进程ID和父进程ID。
流程图:
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int p_b, p_c, p_d;
while((p_b=fork())==-1) ;
if(p_b>0)
{
wait(0);
printf("process a pid is %d, a ppid is %d\n", getpid(), getppid());
}
else
{
while((p_c=fork()) == -1) ;
if(p_c>0)
{
wait(0);
printf("process b pid is %d, b ppid is %d\n", getpid(), getppid());
}
else
{
while((p_d=fork()) == -1) ;
if(p_d>0)
{
wait(0);
printf("process c pid is %d, c ppid is %d\n", getpid(), getppid());
}
else
{
printf("process d pid is %d, d ppid is %d\n", getpid(), getppid());
}
}
}
return 0;
}
3.2 同理可得图2进程树是由父进程a同时建立b,d子进程,再由b进程创建c子进程,d子进程创建e子进程。首先创建了一个父进程a,a同时创建两个子进程b和d。父进程a等待全部子进程执行完毕后输出进程a的信息。子进程b在创建后,再创建一个子进程c,父进程b等待子进程c执行完毕后输出进程b的信息。子进程c直接输出自身的信息。另一方面,子进程d在创建后,再创建一个子进程e,父进程d等待子进程e执行完毕后输出进程d的信息。子进程e直接输出自身的信息。每个进程在完成相应的子进程等待后输出自身的进程ID和父进程ID。
流程图:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
int p_b, p_d, p_c, p_e, status;
// 创建 b 和 d 两个子进程
while ((p_b = fork()) == -1 || (p_d = fork()) == -1);
if (p_b > 0 && p_d > 0) {
// 父进程 a
waitpid(p_b, &status, 0);
waitpid(p_d, &status, 0);
printf("process a pid is %d, a ppid is %d\n", getpid(), getppid());
} else if (p_b >0) {
// 子进程 b
while ((p_c = fork()) == -1);
if (p_c > 0) {
waitpid(p_c, &status, 0);
printf("process b pid is %d, b ppid is %d\n", getpid(), getppid());
} else {
// c
printf("process c pid is %d, c ppid is %d\n", getpid(), getppid());
}
} else if (p_d >0) {
// d
while ((p_e = fork()) == -1);
if (p_e > 0) {
waitpid(p_e, &status, 0);
printf("process d pid is %d, d ppid is %d\n", getpid(), getppid());
} else {
// 子进程 e
printf("process e pid is %d, e ppid is %d\n", getpid(), getppid());
}
}
return 0;
}
【实验结果或总结】(对实验结果进行相应分析,或总结实验的心得体会,并提出实验的改进意见)
1.程序输出为’bac’,其中’b’为p1的输出,’a’为父进程的输出,’c’为p2的输出。
首先在执行语句p1 = fork()时,创建了子进程p1,此时打印’b’;
然后在执行语句p2 = fork()时,创建了子进程p2,此时打印’c’;
最后父进程运行,打印’a’。
程序运行多次,发现结果不相同,输出有"cab", “abc”, "acb"等。这是因为在执行到fork()之前,只有一个进程在运行,执行到fork()后,父进程产生子进程,父子进程并行执行并没有先后顺序之分,所以使得运行结果不同。
2.输出由第1问的单个字符变成了一句话。程序运行多次,发现结果不相同,进程的执行顺序不同,原理和第1问类似。
3.1 进程d的父进程ppid是388,即进程c;进程c的父进程ppid是387,即进程b;进程b的父进程ppid是386即进程a。由此可以看出进程a是进程b的父进程,进程b是进程c的父进程,进程c是进程d的父进程。
3.2 进程c的父进程ppid是454,即进程b;进程e的父进程ppid是453,即进程d;进程d和进程b的父进程ppid都是452,即进程a;因此进程a是进程b和d的父进程,进程b是进程c的父进程,进程d是进程e的父进程。程序运行多次,结果不相同,进程的执行顺序不同。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
fork调用成功后,子进程与父进程并发执行相同的代码。但由于子进程也继承了父进程的程序指针,所以子进程是从fork()后的语句开始执行(也就是新进程调用的入口)。