进程管理实验.docx
《进程管理实验.docx》由会员分享,可在线阅读,更多相关《进程管理实验.docx(14页珍藏版)》请在冰点文库上搜索。
进程管理实验
第二章进程管理实验
【实验目的】
1、加深对进程概念的理解,明确进程和程序的区别;
2、进一步认识并发执行的实质;
3、分析进程争用资源的现象,学习解决进程互斥的方法;
4、了解Linux系统中进程通信的基本原理
【准备知识】
一.基本概念
1、进程的概念;进程与程序的区别。
2、并发执行的概念。
3、进程互斥的概念。
4、进程通信的基本原理。
二.系统调用
系统调用是一种进入系统空间的办法。
通常,在OS的核心中都设置了一组用于实现各种系统功能的子程序,并将它们提供给程序员调用。
程序员在需要OS提供某种服务的时候,便可以调用一条系统调用命令,去实现希望的功能,这就是系统调用。
因此,系统调用就像一个黑箱子一样,对用户屏蔽了操作系统的具体动作而只是控制程序的执行速度等。
各个不同的操作系统有各自的系统调用,如windowsAPI,便是windows的系统调用,Linux的系统调用与之不同的是Linux由于内核代码完全公开,所以可以细致的分析出其系统调用的机制。
1 系统调用和普通过程的区别
1.1运行于不同的系统状态
用户程序可以通过系统调用进入系统空间,在核心态执行;而普通过程则只能在用户空间当中运行。
1.2通过软中断切换
由于用户程序使用系统调用后要进入系统空间,所以需要调用一个软中断;而普通过程在被调用时没有这个过程。
2系统调用的类型
系统调用的作用与它所在的操作系统有密切关系,根据操作系统的性质不同,它们所提供的系统调用会有一定的差异,不过对于普通操作系统而言,应该具有下面几类系统调用:
(1)进程控制类型;
(2)文件操纵类型;(3)进程通信类型;
(4)信息维护类型。
3系统调用的实现机制
由于操作系统的不同,其系统调用的实现方式可能不同,然而实现机制应该是大致相同的,一般包含下面几个步骤:
3.1设置系统调用号
在系统当中,往往设置多条系统调用命令,并赋予每条系统调用命令一个唯一的系统调用号。
根据分配系统调用号方式的不同分为:
直接方式和参数表方式。
系统调用的参数表方式有分为直接和间接两种方式,如图2.1。
3.2处理系统调用
操作系统中有一张系统调用入口表。
表中的每个表目都对应一条系统调用命令,它包含有该系统调用自带参数的数目、系统调用命令处理程序的入口地址等等。
操作系统内核便是根据所输入的系统调用号在该表中查找到到相应的系统调用,进而转入它的入口地址去执行它。
4Linux的系统调用机制
Linux的系统调用是通过中断机制实现的。
中断,这个概念涉及到计算机系统结构方面的知识,显然它与微处理器等硬件有着密不可分的关系。
中断(Interrupt),是指计算机在执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序,待处理完毕后再返回原来被中断处继续执行的过程。
其发生一般而言是“异步”的,换句话说就是在无法预测何种情况下发生的(比如系统掉电)。
所以计算机的软硬件对于中断的相应反应完全是被动的。
软中断,是对硬中断的一种模拟,发送软中断就是向接收进程的proc结构中的相应项发送一个特定意义的信号。
软中断必须等到接收进程执行时才能生效。
陷阱(Trap),由软件产生的中断,指处理机和内存内部产生的中断,它包括程序运算引起的各种错误,如地址非法、校验错、页面失效等。
它由专门的指令,如X86中的“INTn”,在程序中有意的产生。
所以说陷阱是主动的,“同步”的。
异常(Exception),一般也是异步的,多半是由于不小心造成的,比如在进行除法操作时除数为0,就会产生一次异常。
三.相关函数。
1fork()函数
fork()函数创建一个新进程。
其调用格式为:
intfork();
其中返回int取值意义如下:
正确返回:
等于0:
创建子进程,从子进程返回的ID值;
大于0:
从父进程返回的子进程的进程ID值。
错误返回:
等于-1:
创建失败。
2wait()函数
wait()函数常用来控制父进程与子进程的同步。
在父进程中调用wait()函数,则父进程被阻塞,进入等待队列,等待子进程结束。
当子进程结束时,会产生一个终止状态字,系统会向父进程发出SIGCHLD信号。
当接到信号后,父进程提取子进程的终止状态字,从wait()函数返回继续执行原程序。
其调用格式为:
#include
#include
(pid_t)wait(int*statloc);
正确返回:
大于0:
子进程的进程ID值;
等于0:
其它。
错误返回:
等于-1:
调用失败。
3exit()函数
exit()函数是进程结束最常调用的函数,在main()函数中调用return,最终也是调用exit()函数。
这些都是进程的正常终止。
在正常终止时,exit()函数返回进程结束状态。
其调用格式为:
#include
voidexit(intstatus);
其中status为进程结束状态。
4kill()函数
kill()函数用于删除执行中的程序或者任务。
其调用格式为:
kill(intPID,intIID);
其中:
PID是要被杀死的进程号,IID为向将被杀死的进程发送的中断号。
5signal()函数
signal()函数是允许调用进程控制软中断信号的处理。
其调用格式为:
#include
intsig;
void(*func)();
signal(sig,function);
其中:
(1)sig的值是下列之一:
SIGHUP挂起1
SIGINT键盘按delete键或break键2
SIGQUIT键盘按quit键3
SIGILL非法指令4
SIGTRAP跟踪中断5
SIGIOTIOT指令6
SIGBUS总线错7
SIGFPE浮点运算溢出8
SIGKLL要求终止进程9
SIGUSR1用户定义信号#110
SIGSEGV段违法11
SIGUSR2用户定义信号#212
SIGPIPE向无读者管道上写13
SIGALRM定时器告警,时间到14
SIGTERMkill发出的软件结束信号15
SIGCHLD子进程死17
SIGPWR电源故障30
(2)function的解释如下:
SIG_DFL:
缺省操作。
对除SIGPWR和SIGCLD外所有信号的缺省操作是进程终结。
对信号SIGQUIT、SIGTRAP、SIGILL、SIGIOT、SIGEMT、SIGFPE、SIGBUS、SIGSEGV和SIGSYS,它产生一内存映象文件。
SIG_IGN:
忽视该信号的出现。
Function:
在该进程中的一个函数地址,在核心返回用户态时,它以软件中断信号的序号作为参数调用该函数,对除了信号SIGILL、SIGTRAP和SIGPWR以外的信号,核心自动地重新设置软中断信号处理程序的值为SIG_DFL,一个进程不能捕获SIGKILL信号。
6pipe()函数
pipe()函数用于创建一个管道。
其调用格式为:
#include
pipe(intfp[2]);
其中:
fp[2]是供进程使用的文件描述符数组,fp[0]用于写,fp[1]用于读。
正确返回:
0:
调用成功;
错误返回:
-1:
调用失败。
【实验内容】
任选下列之一进行实验:
1、编制一段程序,实现软中断通信:
使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按Del键),当父进程接受到这两个软中断的其中某一个后,父进程用系统调用kill()向两个子进程分别发送整数值为16和17软中断信号,子进程获得对应软中断信号后,分别输出下列信息后终止:
Childprocess1iskilledbyparent!
!
Childprocess2iskilledbyparent!
!
父进程调用wait()函数等待两个子进程终止后,输出以下信息后终止:
Parentprocessiskilled!
!
多运行几次编写的程序,简略分析出现不同结果的原因。
2、编制一段程序,实现进程的管道通信:
使用系统调用pipe()建立一条管道线,两个子进程分别向管道各写一句话:
Childprocess1issendingamessage!
Childprocess2issendingamessage!
而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。
要求:
父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息。
【实验指导】
1编制一段程序,实现进程的软中断通信,算法流程图:
2编制一段程序,实现进程的管道通信,算法流程图:
【附录】
1进程的软中断通信
1.1参考程序源代码:
#include
#include
#include
#include
intwait_flag;
voidstop();
main(){
intpid1,pid2;
signal(3,stop);//或者signal(14,stop);
while((pid1=fork())==-1);
if(pid1>0){
while((pid2=fork())==-1);
if(pid2>0){
wait_flag=1;
sleep(5);
kill(pid1,16);
kill(pid2,17);
wait(0);
wait(0);
printf(“\nParentprocessiskilled!
!
\n”);
exit(0);
}
else{
wait_flag=1;
signal(17,stop);
printf(“\nChildprocess2iskilledbyparent!
!
\n”);
exit(0);
}
}
else{
wait_flag=1;
signal(16,stop);
printf(“\nChildprocess1iskilledbyparent!
!
\n”);
exit(0);
}
}
voidstop(){
wait_flag=0;
}
1.2运行结果:
Childprocess1iskilledbyparent!
!
Childprocess2iskilledbyparent!
!
Parentprocessiskilled!
!
或者:
(运行多次后会出现如下结果)
Childprocess2iskilledbyparent!
!
Childprocess1iskilledbyparent!
!
Parentprocessiskilled!
!
1.3简要分析:
1、上述程序中,调用函数signal()都放在一段程序的前面部位,而不是在其他接收信号处,这是因为signal()的执行只是为进程指定信号量16和17的作用,以及分配相应的与stop()过程链接的指针。
从而signal()函数必须在程序前面部分执行。
2、该程序段前面部分用了两个wait(0),这是因为父进程必须等待两个子进程终止后才终。
wait()函数常用来控制父进程与子进程的同步。
在父进程中调用wait()函数,则父进程被阻塞。
进入等待队列,等待子进程结束。
当子进程结束时,会产生一个终止状态字,系统会向父进程发出SIGCHLD信号。
当接到信号后,父进程提取子进程的终止状态字,从wait()返回继续执行原程序。
3、该程序中每个进程退出时都用了语句exit(0),这是进程的正常终止。
在正常终止时,exit()函数返回进程结束状态。
异常终止时,则由系统内核产生一个代表异常终止原因的终止状态,该进程的父进程都能用wait()得到其终止状态。
在子进程调用exit()后,子进程的结束状态会返回给系统内核,由内核根据状态字生成终止状态,供父进程在wait()中读取数据。
若子进程结束后,父进程还没有读取子进程的终止状态,则系统就子进程的终止状态置为“ZOMBIE”并保留子进程的进程控制块等信息,等父进程读取信息后,系统才彻底释放子进程的进程控制块。
若父进程在子进程结束之前就结束的话,则子进程就变成了“孤儿进程”,系统进程init会自动“收养”该子进程,成为该子进程的父进程即父进程标识号变为1,当 子进程结束时,init会自动调用wait()读取子进程的遗留数据,从而避免系统中留下大量的垃圾。
4、上述结果中“Childprocess1iskilledbyparent!
” 和“Childprocess2iskilledbyparent!
”的出现,当运行几次后,谁在前谁在后是随机的,这是因为:
从进程调度的角度看,子进程被创建后处于就绪态,此时,父进程和子进程作为两个独立的进程,共享同一个代码段,分别参加调度、执行、直至进程结束。
但是谁会先得到调度,与系统的调度策略和系统当前的资源状态有关,是不确定的,因此,谁先从fork()函数中返回继续执行后面的语句也是不确定的。
2进程的管道通信的参考程序源代码:
2.1参考程序源代码:
#include
#include
#include
intpid1,pid2;
main(){
intfd[2];
charOutPipe[100],InPipe[100];
pipe(fd);
while((pid1=fork())==-1);
if(pid1==0){
lockf(fd[1],1,0);
sprintf(OutPipe,“\nChildprocess1issendingmessage!
\n”);
write(fd[1],OutPipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
}
else{
while((pid2=fork())==-1);
if(pid2==0){
lockf(fd[1],1,0);
sprintf(OutPipe,“\nChildprocess2issendingmessage!
\n”);
write(fd[1],OutPipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
}
else{
wait(0);
read(fd[0],InPipe,50);
printf(“%s\n”,InPipe);
wait(0);
read(fd[0],InPipe,50);
printf(“%s\n”,InPipe);
exit(0);
}
}
}
2.2运行结果:
Childprocess1issendingmessage!
Childprocess2issendingmessage!
2.3简要分析(实验者自己完成)。