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,表示从文件的当前位置到文件尾。