2024年5月31日发(作者:)
实验内容:进程管理
一、实验目的
1、 掌握Linux中进程的创建方法及执行情况;
2、 加深对进程、进程树等概念的理解;
3、掌握Linux中如何加载子进程自己的程序;
4、掌握父进程通过创建子进程完成某项任务的方法;
5.、掌握系统调用exit()和_exit()调用的使用。
6、分析进程竞争资源的现象,学习解决进程互斥的方法;进一步认识并发执行的实质
二、实验内容
(一)进程的创建
1、编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中
有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符。
#include
main()
{
int p,x;
p=fork();
if(p>0)
{
x=fork();
if(x>0)
printf("fathern");
else
printf("child2");
}
else
printf("child1");
}
输出结果:
child1
child2
father
2、
运行以下程序,分析程序执行过程中产生的进程情况。
#include
main()
{
int p,x;
p=fork();
if (p>0)
fork();
else{
fork();
fork();
}
sleep(15);
}
实验步骤:
编译连接 gcc –o forktree forktree.c
后台运行 ./forktree &
使用 pstree –h 查看进程树
运行结果:
├─gnome-terminal─┬─bash─┬─forktree─┬─forktree─┬─forkt
ree───forktree
│ │ │
│ └─forktree
│ │ │
└─forktree
│ │ └─pstree
分析:程序运行,系统首先创建一个进程forktree,执行到p=fork()创建一个子
进程forktree,子进程获得处理机优先执行,父进程等待;执行else,当执行
到第一个fork()函数时,子进程创建了一个进程forktree,称之为孙进程,孙
进程获得处理机往下执行,子进程等待;执行到第二个fork()函数时,孙进程
又创建一个进程forktree,称之为重孙进程,重孙进程很快执行完,将处理机
还给孙进程,孙进程很快执行完,将处理机还给子进程;子进程继续往下执行,
执行到第二个fork()函数,又创建一个进程forktree,称之为第二孙进程,并
获得处理机执行,此进程很快执行完,将处理机还给子进程,子进程也很快执行
完,将处理机还给父进程,父进程P>0执行if语句,运行fork()函数,又创建
一个进程forktree,称之为第二子进程,此进程获得处理机执行很快运行完,
将处理机还给父进程,父进程运行sleep(15)语句,休眠15秒,用pstree命令
查询进程树。
3、运行程序,分析运行结果。
#include
main()
{
int p,x,ppid,pid;
x=0;
p=fork();
if(p>0)
{ printf("parent output x=%dn",++x);
ppid=getpid();
printf("Thi id number of parent is:ppid=%dn",ppid);
}
else
{ printf("child output x=%dn",++x);
pid=getpid();
printf("Thi id number of child is:pid=%dn",pid);
}
}
运行结果:
Parent output x=1
This id number of parent is:ppid=3110
Child output x =1
This is number of child is:pid=3111
分析:fork创建进程的时候子进程与父进程共享代码区,子进程复制父进程的数据区,所以,
两个进程中的数据互不影响都是1。
4、loop.c
#include
main()
{
while(1){ };
{
实验步骤:
编译 gcc loop.c –o loop
后台运行 ./loop &(可多次使用该命令)
多次使用ps命令查看进程状态
结果:
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 2645 2643 0 80 0 - 1444 wait pts/0 00:00:00 bash
0 R 1000 3622 2645 78 80 0 - 403 - pts/0 00:00:18 loop
0 R 1000 3627 2645 39 80 0 - 403 - pts/0 00:00:02 loop
0 R 1000 3628 2645 39 80 0 - 403 - pts/0 00:00:01 loop
0 R 1000 3630 2645 0 80 0 - 625 - pts/0 00:00:00 ps
(二)进程控制
1、设计一程序,父进程创建一子进程,子进程的功能是输出“hello world!”,使用execl
()加载子进程的程序。
程序代码如下:
execl.c程序:
#include
#include
#include
main()
{
int p;
p=fork();
if(p>0)
printf("father is running!n");
else
{
printf("child is running!n");
execl("./hello","hello",0);
printf("child is running!n");
}
}
hello.c程序:
#include
main()
{
printf("hello world!n");
}
输出结果:
child is running!
hello world!
father is running!
2、运行以下程序,分析程序执行结果。
#include
#include
main()
{ int p;
p=fork();
if (p>0)
printf("this is parent ");
else{
printf("this is child firstn”);
printf("this is child second ”);
_exit(0);
}
}
执行结果:
this is child first
this is parent[chun@RedFlagLinux jjz]$
换为exit(0)的执行结果:
this is child first
this is child second this is parent[chun@RedFlagLinux jjz]$
分析:_exit()只是返回进程状态SZOMB,不清除缓冲区内容;
exit(0)表示进程正常终止;
(三)、进程的互斥
1、利用cat to_be_ 查看下面程序的输出结果。
#include
#include
main()
{
}
执行结果:
daughter0
int p1,p2,i;
int *fp;
fp=fopen("to_be_","w+");
if(fp==NULL)
{
printf("Fail to create file");
exit(-1);
}
while((p1=fork())==-1); /*创建子进程p1*/
if(p1==0)
{
lockf(*fp,1,0); /*加锁*/
for(i=0;i<10;i++)
fprintf(fp,"daughter%dn",i);
lockf(*fp,0,0); /*解锁*/
}
else
{
while((p2=fork())==-1); /*创建子进程p2*/
if(p2==0)
{
lockf(*fp,1,0); /*加锁*/
for(i=0;i<10;i++)
fprintf(fp,"son%dn",i);
lockf(*fp,0,0); /*解锁*/
}
else
{
wait(NULL);
lockf(*fp,1,0); /*加锁*/
for(i=0;i<10;i++)
fprintf(fp,"parent%dn",i);
lockf(*fp,0,0); /*解锁*/
}
}
fclose(fp);
daughter1
daughter2
daughter3
daughter4
daughter5
daughter6
daughter7
daughter8
daughter9
son0
son1
son2
son3
son4
son5
son6
son7
son8
son9
parent0
parent1
parent2
parent3
parent4
parent5
parent6
parent7
parent8
parent9
分析:程序开始定义了文件读写指针用于打开指定的文件,当文件不存在时则自动创
建。然后有创建了一个进程p1,p1获得处理机执行,给文件读写指针加锁,这样,即使p1
失去处理机,其他获得处理机的进程也无法访问文件指针指向的文件,当p1再次获得处理
机后继续执行直至进程p1结束并解锁;p1结束后父进程获得处理机执行又创建了进程p2,
p2获得处理机执行,也给文件指针加锁,同理直至p2运行完解锁;p2结束后父进程获得处
理机,父进程也给文件指针加锁,直至父进程执行完毕解锁,程序结束。
2、按以下步骤分析下面的程序:
(1)查看程序执行的结果并估计程序执行所需要时间。
(2)将程序中所有的lockf函数加上注释,再观察程序执行的结果和估算程序执行所需的
时间。
(3)分析这两次执行的结果与时间的区别。
#include
#include
main( )
{
int p1,p2,i;
p1=fork( ); /*创建子进程p1*/
if (p1==0)
{
lockf(1,1,0); /*加锁,这里第一个参数为stdout(标准输出设备的描述符)*/
for(i=0;i<10;i++)
{
printf("child1=%dn",i);
sleep(1);
}
lockf(1,0,0); /*解锁*/
}
else
{
p2=fork(); /*创建子进程p2*/
if (p2==0)
{
lockf(1,1,0); /*加锁*/
for(i=0;i<10;i++)
{
printf("child2= %dn",i);
sleep(1);
}
lockf(1,0,0); /*解锁*/
}
else
{
lockf(1,1,0); /*加锁*/
for(i=0;i<10;i++)
{
printf(" parent %dn",i);
sleep(1);
}
lockf(1,0,0); /*解锁*/
}
}
}
未加注释的执行结果:
child1=0
child1=1
child1=2
child1=3
child1=4
child1=5
child1=6
child1=7
child1=8
child1=9
child2=0
child2=1
child2=2
child2=3
child2=4
child2=5
child2=6
child2=7
child2=8
child2=9
parent0
parent1
parent2
parent3
parent4
parent5
parent6
parent7
parent8
parent9
加上注释的执行结果:
child1=0
child2=0
parent0
child1=1
child2=1
parent1
child1=2
child2=2
parent2
child1=3
child2=3
parent3
child1=4
child2=4
parent4
child1=5
child2=5
parent5
child1=6
child2=6
parent6
child1=7
child2=7
parent7
child1=8
child2=8
parent8
child1=9
child2=0
parent9
分析:未加注释时程序运行时间大约是30秒,注释后的程序运行时间大约只是10秒钟。在
注释掉之前,由于给后面的循环输出语句块加了锁,所以该语句只能被当前进程访问,即使
其他进程获得处理机也无法访问,只有当前进程获得处理机并接着中断的地方继续执行完毕
将锁释放其他的进程才能访问。所以输出结果按照顺序依次输出。由于sleep(1)语句在输出
语句块中,每输出一次的要休眠1秒钟,所以大约需要30秒钟。
注释掉所有lockf()函数后,循环输出语句块没有被加锁,当当前进程失去处理机后,获
得处理机的其它进程也可以继续输出。另外,当进程child1输出一句后进入1秒休眠,此间
进程child2获得处理机并输出一句也进入1秒休眠,此时进程parent获得处理机输出一句后
也进入1秒休眠,由于三进程是依次进入休眠,因此也会依次醒来,依次获得处理机,依次
输出,再依次休眠,直至循环输出完毕。由于一个进程的输出是在其他两个进程休眠期间运
行的,而这两个进程之一也在这个进程运行完一次输出进入休眠期间醒来,因此,只相当于
休眠了10次,故大约需要10秒钟。
三、实验总结
1、fork()函数用于创建进程,返回值是整数。当为0时,表示当前进程是子进程;>0
时,表示父进程。-1时,表示创建进程失败。子进程与父进程共享代码区,子进程复制父
进程的数据区。
2、fork()创建进程的时候,子进程复制父进程的代码,要想改变子进程的代码就可以调
用execl()来实现,调用execl()即可用于新程序的运行;注意exit()和_exit()的不同区别。
3、lockf(files,function,size)
用作锁定文件的某些段或者整个文件,从而实现互斥。头文件为#include "unistd.h"
参数定义:
int lockf(files,function,size)
int files,function;
long size;
其中:files是文件描述符;function是锁定和解锁:1表示锁定,0表示解锁。size是锁定或
解锁的字节数,为0,表示从文件的当前位置到文件尾。


发布评论