实验二-进程管理.doc

上传人:b****1 文档编号:150367 上传时间:2022-10-04 格式:DOC 页数:39 大小:111KB
下载 相关 举报
实验二-进程管理.doc_第1页
第1页 / 共39页
实验二-进程管理.doc_第2页
第2页 / 共39页
实验二-进程管理.doc_第3页
第3页 / 共39页
实验二-进程管理.doc_第4页
第4页 / 共39页
实验二-进程管理.doc_第5页
第5页 / 共39页
点击查看更多>>
下载资源
资源描述

实验二-进程管理.doc

《实验二-进程管理.doc》由会员分享,可在线阅读,更多相关《实验二-进程管理.doc(39页珍藏版)》请在冰豆网上搜索。

实验二-进程管理.doc

实验二进程管理

实验目的

通过进程的创建、撤销和运行加深对进程概念和进程并发执行的理解,明确进程与程序的区别。

实验内容

1、了解系统调用fork()、exec()、exit()和waitpid()的功能和实现过程。

2、编写一段程序,使用系统调用fork()创建两个子进程。

当此程序运行时,在系统中有一个父进程和两个子进程活动。

让每一个进程在屏幕上显示一个字符:

父进程显示字符“a”;子进程分别显示字符“b”和“c”。

试观察记录屏幕上的显示结果,并分析原因。

3、编写一段程序,使用系统调用fork()来创建一个子进程,子进程通过系统调用exec()更换自己的执行代码,显示新的代码“newprogram.”后,调用exit()结束。

而父进程则调用waitpid()等待子进程结束,并在子进程结束后显示子进程的标识符,然后正常结束。

实验指导

一、所涉及的系统调用

1、getpid

在2.4.4版内核中,getpid是第20号系统调用,其在Linux函数库中的原型是:

#include/*提供类型pid_t的定义*/

#include/*提供函数的定义*/

pid_tgetpid(void);

getpid的作用很简单,就是返回当前进程的进程ID,请大家看以下的例子:

/*getpid_test.c*/

#include

main()

{

printf("ThecurrentprocessIDis%d\n",getpid());

}

这个程序的定义里并没有包含头文件sys/types.h,这是因为我们在程序中没有用到pid_t类型,pid_t类型即为进程ID的类型。

事实上,在i386架构上(就是我们一般PC计算机的架构),pid_t类型是和int类型完全兼容的,我们可以用处理整形数的方法去处理pid_t类型的数据,比如,用"%d"把它打印出来。

编译并运行程序getpid_test.c:

$gccgetpid_test.c-ogetpid_test

$./getpid_test

ThecurrentprocessIDis1980

(你自己的运行结果很可能与这个数字不一样,这是很正常的。

再运行一遍:

$./getpid_test

ThecurrentprocessIDis1981

正如我们所见,尽管是同一个应用程序,每一次运行的时候,所分配的进程标识符都不相同。

2、fork

在2.4.4版内核中,fork是第2号系统调用,其在Linux函数库中的原型是:

#include/*提供类型pid_t的定义*/

#include/*提供函数的定义*/

pid_tfork(void);

创建一个新进程。

系统调用格式:

pid=fork()

fork()返回值意义如下:

0:

在子进程中,pid变量保存的fork()返回值为0,表示当前进程是子进程。

>0:

在父进程中,pid变量保存的fork()返回值为子进程的id值(进程唯一标识符)。

-1:

创建失败。

如果fork()调用成功,它向父进程返回子进程的PID,并向子进程返回0,即fork()被调用了一次,但返回了两次。

此时OS在内存中建立一个新进程,所建的新进程是调用fork()父进程(parentprocess)的副本,称为子进程(childprocess)。

子进程继承了父进程的许多特性,并具有与父进程完全相同的用户级上下文。

父进程与子进程并发执行。

核心为fork()完成以下操作:

(1)为新进程分配一进程表项和进程标识符

进入fork()后,核心检查系统是否有足够的资源来建立一个新进程。

若资源不足,则fork()系统调用失败;否则,核心为新进程分配一进程表项和唯一的进程标识符。

(2)检查同时运行的进程数目

超过预先规定的最大数目时,fork()系统调用失败。

(3)拷贝进程表项中的数据

将父进程的当前目录和所有已打开的数据拷贝到子进程表项中,并置进程的状态为“创建”状态。

(4)子进程继承父进程的所有文件

对父进程当前目录和所有已打开的文件表项中的引用计数加1。

(5)为子进程创建进程上、下文

进程创建结束,设子进程状态为“内存中就绪”并返回子进程的标识符。

(6)子进程执行

虽然父进程与子进程程序完全相同,但每个进程都有自己的程序计数器PC(注意子进程的PC开始位置),然后根据pid变量保存的fork()返回值的不同,执行了不同的分支语句。

例:

…..

pid=fork();

if(pid==0)

printf("I'mthechildprocess!

\n");

elseif(pid>0)

printf("I'mtheparentprocess!

\n");

else

printf("Forkfail!

\n");

……

PC

fork()调用前

PC

fork()调用后

…..

PC

pid=fork();

if(pid==0)

printf("I'mthechildprocess!

\n");

elseif(pid>0)

printf("I'mtheparentprocess!

\n");

else

printf("Forkfail!

\n");

……

…..

pid=fork();

if(pid==0)

printf("I'mthechildprocess!

\n");

elseif(pid>0)

printf("I'mtheparentprocess!

\n");

else

printf("Forkfail!

\n");

……

3、exit

在2.4.4版内核中,exit是第1号调用,其在Linux函数库中的原型是:

#include

voidexit(intstatus);

不像fork那么难理解,从exit的名字就能看出,这个系统调用是用来终止一个进程的。

无论在程序中的什么位置,只要执行到exit系统调用,进程就会停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。

请看下面的程序:

/*exit_test1.c*/

#include

main()

{

printf("thisprocesswillexit!

\n");

exit(0);

printf("neverbedisplayed!

\n");

}

编译后运行:

$gccexit_test1.c-oexit_test1

$./exit_test1

thisprocesswillexit!

我们可以看到,程序并没有打印后面的"neverbedisplayed!

\n",因为在此之前,在执行到exit(0)时,进程就已经终止了。

exit系统调用带有一个整数类型的参数status,我们可以利用这个参数传递进程结束时的状态,比如说,该进程是正常结束的,还是出现某种意外而结束的,一般来说,0表示没有意外的正常结束;其他的数值表示出现了错误,进程非正常结束。

我们在实际编程时,可以用wait系统调用接收子进程的返回值,从而针对不同的情况进行不同的处理。

exit和_exit

_exit在Linux函数库中的原型是:

#include

void_exit(intstatus);

_exit()函数的作用最为简单:

直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit()函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。

exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件。

在Linux的标准函数库中,有一套称作“高级I/O”的函数,我们熟知的printf()、fopen()、fread()、fwrite()都在此列,它们也被称作“缓冲I/O(bufferedI/O)”,其特征是对应每一个打开的文件,在内存中都有一片缓冲区,每次读文件时,会多读出若干条记录,这样下次读文件时就可以直接从内存的缓冲区中读取,每次写文件的时候,也仅仅是写入内存中的缓冲区,等满足了一定的条件(达到一定数量,或遇到特定字符,如换行符\n和文件结束符EOF),再将缓冲区中的内容一次性写入文件,这样就大大增加了文件读写的速度,但也为我们编程带来了一点点麻烦。

如果有一些数据,我们认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时我们用_exit()函数直接将进程关闭,缓冲区中的数据就会丢失,反之,如果想保证数据的完整性,就一定要使用exit()函数。

请看以下例程:

/*exit2.c*/

#include

main()

{

printf("outputbegin\n");

printf("contentinbuffer");

exit(0);

}

编译并运行:

$gccexit2.c-oexit2

$./exit2

outputbegin

contentinbuffer

/*_exit1.c*/

#include

main()

{

printf("outputbegin\n");

printf("contentinbuffer");

_exit(0);

}

编译并运行:

$gcc_exit1.c-o_exit1

$./_exit1

outputbegin

在一个进程调用了exit之后,该进程并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构。

在Linux进程的5种状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集。

/*zombie.c*/

#include

#include

main()

{

pid_tpid;

pid=fork();

if(pid<0) /*如果出错*/

printf("erroroccurred!

\n");

elseif(pid==0)/*如果是子进程*/

exit(0);

else /*如果是父进程*/

sleep(60); /*休眠60秒,这段时间里,父进程什么也干不了*/

wait(NULL); /*收集僵尸进程*/

}

sleep的作用是让进程休眠指定的秒数,在这60秒内,子进程已经退出,而父进程正忙着睡

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 军事

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1