一、实验目的
1、掌握进程的概念,理解进程和程序的区别。
2、认识和了解并发执行的实质。
3、学习使用系统调用fork()创建新的子进程方法,理解进程树的概念。
4、学习使用系统调用wait()或waitpid()实现父子进程同步。
5、学习使用getpid()和getppid()获得当前进程和父进程的PID号。
6、掌握使用exec簇函数实现进程映像更换的方法。
7、了解系统编程,学习父进程如何通过创建一个子进程来完成某项特定任务的方法。
二、实验内容
1.进程的创建
编写一段程序,使用系统调用fork( )创建两个子进程,在系统中有一个父进程和两个子进程活动。让每个进程在屏幕上显示一个字符;父进程显示字符“a”,子进程分别显示字符“b” 和“c”。试观察记录屏幕上的显示结果,并分析结果。(1分)
<参考程序>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{ int p1, p2;
while((p1=fork())==-1);
if(p1==0)
printf("b ");
else
{ while((p2=fork())==-1);
if(p2==0)
printf("c ");
else
printf("a ");
}
return 0;
}
执行结果及结果分析:
首先父进程会fork()创建一个新的子进程也就是b进程,当b进程运行到if(p1==0)语句时就会打印出“b ”,然后父进程会进入else语句,再创建一个子进程c,由于在父进程中p2>0所以进入else语句,打印“a ”,在创建出来的c进程中,p2=0打印出“c ”。
修改上题,在父进程中显示当前进程识别码,在每个子进程中显示当前进程识别码和父进程识别码,运行程序查看结果,分析运行结果。(1分)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{ int p1, p2;
while((p1=fork())==-1);
if(p1==0) //p1子进程
printf("b: pid=%d ppid=%d\n",getpid(),getppid());
else //父进程
{ while((p2=fork())==-1);
if(p2==0)//p2子进程
printf("c: pid=%d ppid=%d\n",getpid(),getppid());
else //父进程
printf("a: pid=%d\n",getpid());
}
return 0;
}
父进程a首先创建出一个子进程p1,在p1进程中p1=0,打印出p1的pid和ppid,可以看出b的父进程为a,然后主进程运行到else语句中,再次创建一个子进程p2,在p2进程中p2=0,打印出c的pid和ppid,可以看出,c的父进程为a,主进程运行到else语句,打印出自己的pid。
改进上题,使父进程等待两个子进程结束之后再结束。(1分)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
int p1, p2;
while ((p1 = fork()) == -1);
if (p1 == 0) { // 子进程p1
printf("b: pid=%d ppid=%d\n", getpid(), getppid());
} else { // 父进程
while ((p2 = fork()) == -1);
if (p2 == 0) { // 子进程p2
printf("c: pid=%d ppid=%d\n", getpid(), getppid());
} else { // 父进程
printf("a: pid=%d\n", getpid());
// 等待两个子进程结束
}
}
return 0;
}
2.编写程序创建进程树如图1所示,在每个进程中显示当前进程识别码和父进程识别码。(1分)
图1进程树的参考程序:
#include<stdio.h>
#include<unistd.h>
int main()
{
int p1,p2,p3;
while((p1=fork())== -1);
if(p1==0)
{
while((p2=fork())==-1);
if(p2==0)
{
while((p3=fork())==-1);
if(p3==0) //p3子进程
printf(" d,Mypid=%d, myppid=%d\n", getpid(), getppid());
else //p2子进程
printf(" c,Mypid=%d, myppid=%d\n", getpid(), getppid());
}
else //p1子进程
printf(" b,Mypid=%d, myppid=%d\n", getpid(), getppid());
}
else //主进程
printf(" a,Mypid is %d\n", getpid());
getchar();
}
编译及执行程序:
结果截屏:
3.模仿第2题,按图2进程树编写程序,给出编译及执行过程和结果截屏。(1分)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
int p1, p2, p3, p4, p5;
while ((p2 = fork()) == -1 );
if(p2 > 0){
waitpid(p2,NULL, 0);
printf("a: pid=%d\n", getpid());
while ((p4 = fork()) == -1);
if(p4 == 0){
waitpid(p4, NULL, 0);
printf("d: pid=%d, ppid=%d\n", getpid(), getppid());
while ((p5 = fork()) == -1);
if(p5 == 0){
waitpid(p5, NULL, 0);
printf("d: pid=%d, ppid=%d\n", getpid(), getppid());
}
exit(0);
}
waitpid(p4, NULL, 0);
}
else if(p2 == 0){
waitpid(p2, NULL, 0);
printf("b: pid=%d, ppid=%d\n", getpid(), getppid());
while ((p3 = fork()) == -1);
if (p3 == 0) {
waitpid(p3, NULL, 0);
printf("c: pid=%d, ppid=%d\n", getpid(), getppid());
exit(0);
}
waitpid(p3, NULL, 0);
}
return 0;
}
4.分析程序,给出编译及执行过程和结果截屏。(2分)
(1)
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
main()
{ int child,p;
while((child=fork())==-1);
if(child==0) //子进程下
{ printf("In child: sleep for 10 seconds and then exit. \n");
sleep(10);
exit(0);
}
else //父进程下
{ do
{ p=waitpid(child,NULL,WNOHANG); //非阻塞式等待子进程结束
if(p==0)
{ printf("In father: The child process has not exited.\n");
sleep(1);
}
}while(p==0);
if(p==child)
{ printf("Get child exitcode then exit!\n");}
else
{ printf("Error occured!\n");}
}
exit(0);
}
编译及执行过程和运行结果截屏:
分析程序功能:
这段程序的功能是创建一个子进程,然后父进程非阻塞地等待子进程结束。父进程调用fork()创建子进程。如果fork()成功,子进程会打印一条消息并休眠10秒,然后退出。如果fork()失败,父进程会继续尝试创建子进程,直到成功。父进程在一个循环中使用waitpid()函数以非阻塞方式等待子进程结束。这意味着父进程会定期检查子进程的状态而不会被阻塞。如果waitpid()返回0,表示子进程尚未退出,父进程会打印一条消息,并休眠1秒。循环直到waitpid()返回的进程ID等于子进程的ID,表示子进程已经退出。最后,父进程根据waitpid()返回的值来判断是否成功等待子进程退出,并打印相应的消息。综上所述,该程序实现了父进程非阻塞地等待子进程退出,并在子进程退出后打印相应的消息。
(2)
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
main()
{ int child,p;
while((child=fork())==-1);
if(child==0) //子进程下
{ execl("/home/student/welcome.out","",NULL);
exit(0);
}
else //父进程下
{ p=waitpid(child,NULL,0); //阻塞式等待子进程结束
if(p==child)
printf("Get child exitcode then exit!\n");
else
printf("Error occured!\n");
}
exit(0);
}
子进程要加载程序的源程序welcome.c:
#include<stdio.h>
main()
{ printf("Hello! This is another process.\n");}
编译及执行过程和运行结果截屏:
分析程序功能:
这段程序的功能是创建一个子进程,然后父进程阻塞地等待子进程结束,接着根据waitpid()返回的值判断子进程是否成功退出,并打印相应的消息。父进程调用fork()创建子进程。如果fork()成功,子进程会调用execl()函数执行另一个程序/sy/sy1/welcome.out,然后子进程会退出。如果fork()失败,父进程会继续尝试创建子进程,直到成功。父进程使用waitpid()函数以阻塞方式等待子进程结束,其中参数NULL表示不获取子进程的退出状态,参数0表示等待任何子进程退出。当子进程退出后,waitpid()会返回子进程的进程ID。父进程根据waitpid()返回的值来判断是否成功等待子进程退出,并打印相应的消息。综上所述,该程序实现了父进程阻塞地等待子进程退出,并在子进程退出后打印相应的消息。
5. 编程创建2个子进程,子进程1运行指定路径下的可执行文件(如:/home/student/welcome.out),子进程2暂停10s之后退出,父进程先用阻塞方式等待子进程1的结束,然后用非阻塞方式等待子进程2的结束,待收集到二个子进程结束的信息,父进程就返回。(2分)
参考程序框架:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
int child1, child2, p;
while ((child1 = fork()) == -1);
if (child1 == 0) {
execl("/sy/sy1/welcome.out", "", NULL);
exit(0);
} else {
while ((child2 = fork()) == -1);
if (child2 == 0) {
// 子进程2暂停10秒后退出
sleep(10);
exit(0);
} else {
// 父进程等待子进程1结束
p = waitpid(child1, NULL, 0);
if (p == child1)
printf("Get child1 exitcode then exit!\n");
else
printf("Error occurred!\n");
// 父进程非阻塞方式等待子进程2结束
do {
p = waitpid(child2, NULL, WNOHANG);
if (p == 0) {
printf("In father: The child2 process has not exited.\n");
sleep(1);
}
} while (p == 0);
if (p == child2)
printf("Get child2 exitcode then exit!\n");
else
printf("Error occurred!\n");
}
}
exit(0);
}
编译及执行过程:
结果截屏:
6.编写一个简易的shell解释程序。其运行原理是:当命令行上有命令需要执行时,shell进程获得该命令,然后创建子进程,让子进程执行该命令,shell进程等待子进程退出,之后继续等待命令行上的命令周而复始。(附加题)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#define MAX_COMMAND_LENGTH 100
int main() {
char command[MAX_COMMAND_LENGTH];
pid_t pid;
int status;
while (1) {
// 打印提示符
printf("shell> ");
// 读取命令行输入
if (fgets(command, sizeof(command), stdin) == NULL) {
printf("Error reading command\n");
continue;
}
// 移除命令行输入中的换行符
command[strcspn(command, "\n")] = '\0';
// 创建子进程
pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 在子进程中执行命令
execlp(command, command, NULL);
// 如果execlp返回,说明命令执行失败
perror("execlp");
exit(EXIT_FAILURE);
} else {
// 父进程等待子进程退出
waitpid(pid, &status, 0);
}
}
return 0;
}
三、实验总结和体会(1分)
通过本次实验,我学到了以下几点:
学习了如何使用fork()函数创建子进程,以及父子进程之间的关系。
理解了使用wait()和waitpid()函数等待子进程结束,并获取子进程的退出状态。
掌握了使用execl()函数在子进程中执行外部命令。
加深了对进程间通信和进程控制的理解,例如父进程等待子进程退出的过程。
熟悉了在C语言中使用系统调用来实现基本的shell解释器功能。
总的来说,通过实验,我加深了对进程管理、进程通信和操作系统底层的理解,并且了解了基本的Linux系统