实验二-进程管理Word格式文档下载.doc

上传人:wj 文档编号:831823 上传时间:2023-04-29 格式:DOC 页数:39 大小:111KB
下载 相关 举报
实验二-进程管理Word格式文档下载.doc_第1页
第1页 / 共39页
实验二-进程管理Word格式文档下载.doc_第2页
第2页 / 共39页
实验二-进程管理Word格式文档下载.doc_第3页
第3页 / 共39页
实验二-进程管理Word格式文档下载.doc_第4页
第4页 / 共39页
实验二-进程管理Word格式文档下载.doc_第5页
第5页 / 共39页
实验二-进程管理Word格式文档下载.doc_第6页
第6页 / 共39页
实验二-进程管理Word格式文档下载.doc_第7页
第7页 / 共39页
实验二-进程管理Word格式文档下载.doc_第8页
第8页 / 共39页
实验二-进程管理Word格式文档下载.doc_第9页
第9页 / 共39页
实验二-进程管理Word格式文档下载.doc_第10页
第10页 / 共39页
实验二-进程管理Word格式文档下载.doc_第11页
第11页 / 共39页
实验二-进程管理Word格式文档下载.doc_第12页
第12页 / 共39页
实验二-进程管理Word格式文档下载.doc_第13页
第13页 / 共39页
实验二-进程管理Word格式文档下载.doc_第14页
第14页 / 共39页
实验二-进程管理Word格式文档下载.doc_第15页
第15页 / 共39页
实验二-进程管理Word格式文档下载.doc_第16页
第16页 / 共39页
实验二-进程管理Word格式文档下载.doc_第17页
第17页 / 共39页
实验二-进程管理Word格式文档下载.doc_第18页
第18页 / 共39页
实验二-进程管理Word格式文档下载.doc_第19页
第19页 / 共39页
实验二-进程管理Word格式文档下载.doc_第20页
第20页 / 共39页
亲,该文档总共39页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

实验二-进程管理Word格式文档下载.doc

《实验二-进程管理Word格式文档下载.doc》由会员分享,可在线阅读,更多相关《实验二-进程管理Word格式文档下载.doc(39页珍藏版)》请在冰点文库上搜索。

实验二-进程管理Word格式文档下载.doc

,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

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

再运行一遍:

ThecurrentprocessIDis1981

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

2、fork

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

pid_tfork(void);

创建一个新进程。

系统调用格式:

pid=fork()

fork()返回值意义如下:

0:

在子进程中,pid变量保存的fork()返回值为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)

mtheparentprocess!

\n"

else

printf("

Forkfail!

……

PC

fork()调用前

fork()调用后

if(pid==0)

\n"

3、exit

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

stdlib.h>

voidexit(intstatus);

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

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

请看下面的程序:

/*exit_test1.c*/

thisprocesswillexit!

exit(0);

neverbedisplayed!

编译后运行:

$gccexit_test1.c-oexit_test1

$./exit_test1

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

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

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

其他的数值表示出现了错误,进程非正常结束。

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

exit和_exit

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

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*/

outputbegin\n"

contentinbuffer"

编译并运行:

$gccexit2.c-oexit2

$./exit2

outputbegin

contentinbuffer

/*_exit1.c*/

_exit(0);

$gcc_exit1.c-o_exit1

$./_exit1

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

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

/*zombie.c*/

#include<

pid_tpid;

pid=fork();

if(pid<

0) /*如果出错*/

printf("

erroroccurred!

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

exit(0);

else /*如果是父进程*/

sleep(60);

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

wait(NULL);

/*收集僵尸进程*/

sleep的作用是让进程休眠指定的秒数,在这60秒内,子进程已经退出,而父进程正忙着睡觉,不可能对它进行收集,这样,我们就能保持子进程60秒的僵尸状态。

编译这个程序:

$gcczombie.c-ozombie

后台运行程序,以使我们能够执行下一条命令

$./zombie&

[1]1577

列一下系统内的进程

$ps-ax

......

1177pts/0S0:

00-bash

1577pts/0S0:

00./zombie

1578pts/0Z0:

00[zombie<

defunct>

]

1579pts/0R0:

00ps-ax

系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁。

僵尸进程虽然对其他进程几乎没有什么影响,不占用CPU时间,消耗的内存也几乎可以忽略不计,但有它在那里呆着,还是让人觉得心里很不舒服。

而且Linux系统中进程数目是有限制的,在一些特殊的情况下,如果存在太多的僵尸进程,也会影响到新进程的产生。

那么,我们该如何来消灭这些僵尸进程呢?

僵尸进程的概念是从UNIX上继承来的。

僵尸进程中保存着很多对程序员和系统管理员非常重要的信息,首先,这个进程是怎么死亡的?

是正常退出呢,还是出现了错误,还是被其它进程强迫退出的?

其次,这个进程占用的总系统CPU时间和总用户CPU时间分别是多少?

发生页错误的数目和收到信号的数目。

这些信息都被存储在僵尸进程中,试想如果没有僵尸进程,进程一退出,所有与之相关的信息都立刻归于无形,而此时程序员或系统管理员需要用到,就不行了。

如何收集这些信息,并终结这些僵尸进程呢?

就要靠waitpid调用和wait调用。

这两者的作用都是收集僵尸进程留下的信息,同时使这个进程彻底消失。

4、wait和waitpid

wait的函数原型是:

#include<

sys/wait.h>

pid_twait(int*status)

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;

如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。

但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,我们就可以设定这个参数为NULL,就象下面这样:

pid=wait(NULL);

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1。

/*wait1.c*/

pid_tpc,pr;

pc=fork();

if(pc<

0) /*如果出错*/

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

Thisischildprocesswithpidof%d\n"

sleep(10);

/*睡眠10秒钟*/

}

else{ /*如果是父进程*/

pr=wait(NULL);

/*在这里等待*/

Icatchedachildprocesswithpidof%d\n"

),pr);

}

编译并运行:

$ccwait1.c-owait1

$./wait1

Thisischildprocesswithpidof1508

Icatchedachildprocesswithpidof1508

5、waitpid

waitpid系统调用在Linux函数库中的原型是:

pid_twaitpid(pid_tpid,int*status,intoptions)

从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options:

pid

从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。

但当pid取不同的值时,在这里有不同的意义。

pid>

0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。

pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。

options

options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"

|"

运算符把它们连接起来使用,比如:

ret=waitpid(-1,NULL,WNOHANG|WUNTRACED);

如果我们不想使用它们,也可以把options设为0,如:

ret=waitpid(-1,NULL,0);

如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

而WUNTRACED参数,由于涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多费笔墨了。

返回值和错误

waitpid的返回值比wait稍微复杂一些,一共有3种情况:

当正常返回的时候,waitpid返回收集到的子进程的进程ID;

如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

如果调用中出错,则返回-1;

当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回

/*waitpid.c*/

pid_tpc,pr;

0) /*如果fork出错*/

Erroroccurredonforking.\n"

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

/*睡眠10秒*/

/*如果是父进程*/

do{

pr=waitpid(pc,NULL,WNOHANG);

/*使用了WNOHANG参数,waitpid不会在这里等待*/

if(pr==0){ /*如果没有收集到子进程*/

printf("

Nochildexited\n"

sleep

(1);

}

}while(pr==0);

/*没有收集到子进程,就回去继续尝试*/

if(pr==pc)

successfullygetchild%d\n"

pr);

else

someerroroccurred\n"

$ccwaitpid.c-owaitpid

$./waitpid

Nochildexited

successfullygetchild1526

父进程经过10次失败的尝试之后,终于收集到了退出的子进程。

因为这只是一个例子程序,不便写得太复杂,所以我们就让父进程和子进程分别睡眠了10秒钟和1秒钟,代表它们分别作了10秒钟和1秒钟的工作。

父子进程都有工作要做,父进程利用工作的简短间歇察看子进程的是否退出,如退出就收集它。

6、exec

说是exec系统调用,实际上在Linux中,并不存在一个exec()的函数形式,exec指的是一组函数,一共有6个,分别是:

intexecl(constchar*path,constchar*arg,...);

intexeclp(constchar*file,constchar*arg,...);

intexecle(constchar*path,constchar*arg,...,char*constenvp[]);

intexecv(constchar*path,char*constargv[]);

intexecvp(constchar*file,char*constargv[]);

intexecve(constchar*path,char*constargv[],char*constenvp[]);

其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。

exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。

这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,看上去还是旧的躯壳,却已经注入了新的灵魂。

只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。

每当有进程认为自己不能为系统和用户做出任何贡献了,他就调用任何一个exec,让自己以新的面貌重生;

或者,更普遍的情况是,如果一个进程想执行另一个程序,它就可以fork出一个新进程,然后调用任何一个exec,这样看起来就好像通过执行应用程序而产生了一个新进程一样。

事实上第二种情况被应用得如此普遍,以至于Linux专门为其作了优化,我们已经知道,fork会将调用进程的所有内容原封不动的拷贝到新产生的子进程中去,这些拷贝的动作很消耗时间,而如果fork完之后我们马上就调用exec,这些辛辛苦苦拷贝来的东西又会被立刻抹掉,这看起来非常不划算,于是人们设计了一种"

写时拷贝(copy-on-write)"

技术,使得fork结束后并不立刻复制父进程的内容,而是到了真正使用的时候才复制,这样如果下一条语句是exec,它就不会白白作无用功了,也就提高了效率。

上面6条函数看起来似乎很复杂,但实际上无论是作用还是用法都非常相似,只有很微小的差别。

在学习它们之前,先来了解一下我们习以为常的main函数。

intmain(intargc,char*argv[],char*envp[])

参数argc指出了运行该程序时命令行参数的个数,数组argv存放了所有的命令行参数,数组envp存

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

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2