操作系统实验2进程管理报告.docx
《操作系统实验2进程管理报告.docx》由会员分享,可在线阅读,更多相关《操作系统实验2进程管理报告.docx(16页珍藏版)》请在冰豆网上搜索。
操作系统实验2进程管理报告
操作系统实验2进程管理报告
实验一进程管理
一、实验目的:
1.加深对进程概念的理解,明确进程和程序的区别;
2.进一步认识并发执行的实质;
3.分析进程争用资源的现象,学习解决进程互斥的方法;
4.了解Linux系统中进程通信的基本原理;
二、实验预备内容:
1.阅读Linux的sched.h源码文件,加深对进程管理概念的理解;
2.阅读Linux的fork()源码文件,分析进程的创建过程;
三、实验环境说明:
1.此实验采用的是Win7(32bit)下虚拟机VMware-workstation-10.0.4build-2249910;
2.ubuntu版本3.19.0;
3.直接编写c文件在终端用命令行执行;
4.虚拟机分配8G内存中的1024M;
5.虚拟机名称knLinux;
6.ubuntu用户名kn;
四、实验内容:
1.进程的创建:
a)题目要求:
编写一段程序,使用系统调用fork()创建两个子进程。
当此程序运行时,在系统中有一个父进程和两个子进程活动。
让每一个进程在屏幕上显示一个字符:
父进程显示字符“a”,子进程分别显示字符“b”和“c”。
试观察记录屏幕上的显示结果,并分析原因。
b)程序设计说明:
一个父进程,两个子进程,分别输出显示a,b,c。
c)源代码:
d)运行结果:
e)分析:
由输出结果可知,运行结果不唯一,可以是abc,acb,bca等多种情况。
因为在程序中,并没有三个进程之间的同步措施,所以父进程和子进程的输出顺序是随机的。
在试验次数足够大的情况下,6中顺序都有可能出现:
abc,acb,bac,bca,cab,cba。
2.进程的控制:
a)修改已经编写的程序,将每个进程输出一个字符改为每个进程输出一句话,再观察程序执行时屏幕上出现的现象,并分析原因。
i.程序设计说明:
将第一个程序中输出字符的语句改为输出parentprocess和childprocess1&2的语句。
ii.源代码:
iii.运行结果:
iv.分析:
发现在结果中,运行结果同第一个程序,但是在一个进程输出语句的中途不会被打断,语句都是完整的。
但是三条语句的先后顺序随机,不能够确定。
b)如果在程序中使用系统调用lockf()来给每一个进程加锁,可以实现进程之间的互斥,观察并分析出现的现象。
i.程序设计说明:
在程序中加入lockf(1,1,0)和lockf(1,0,0)语句用于加锁和去锁,实现进程之间的互斥。
在程序中加入sleep()函数让父进程休息,多循环进行子进程的输出,可观察到加锁前后的差距。
ii.源代码:
iii.运行结果:
若去掉lockf(),结果如下:
iv.分析:
通过上网查阅资料,了解到lockf(fileno(fp),F_LOCK,0L)函数中,fileno(fp)是要加锁的文件,其中1则为标准输出流;F_LOCK为1则加锁,0则解锁;0L为文件长度,0则为整个文件。
由输出结果观察到,加锁的函数使得程序结果发生了变化。
在加锁后,根据结果显示,一个进程全部输出完毕后才交给另一个进程,表现为连续的1和连续的2;而没有加锁的话,每一个进程输出完毕一次就随机选择一个进程进行输入,直到全部输出完毕,导致每一次输出的进程都是随机的。
从而看出lockf()对两个进程的输出表现出互斥作用。
进程互斥是指两个或两个以上的进程,不能同时进入关于同一组共享变量的临界区域,否则可能发生与时间有关的错误。
而lockf()函数作为监视锁,对锁定的进程控制访问。
而在多次输出后,三个进程有可能同时进入同一组共享变量的临界区域,所以结果发生改变。
3.进程的软中断:
a)编写一段程序,使其实现进程的软中断通信;
i.题目要求:
使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按DEL键);当捕捉到中断信号后,父进程用系统调用Kill()向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
ChildProcess1iskilledbyParent!
ChildProcess2iskilledbyParent!
父进程等待两个子进程终止后,输出如下的信息后终止:
ParentProcessiskilled
ii.程序设计说明:
利用Del触发软中断SIGINT。
要求在中断来临前,两个子进程都处于等待状态,中断来临后立即响应。
因此加入了my_wait()和my_stop()两个函数,并通过flag来实现子进程对中断信号的屏蔽。
父进程在杀死子进程后自杀,则引入SIGUSR1和SIGUSR2两个信号,分别让两个子进程监听这个信号,父进程收到中断信号后通过这两个信号对子进程发出kill信号,再自杀。
在程序的实现过程中,我们需要父进程等待子进程终止后再自杀,则需要子进程引入signal预置软中断处理函数,等待父进程向他发出软中断信号,而不是直接从键盘上自行接受,再自行终止。
而父进程使用kill向子进程发送软中断信号,并且wait直到子进程处理中断,使用exit(0)终止执行并且向父进程返回终止信息。
父进程一旦收到子进程发送来的终止信息,则结束等待,终止自己的进程。
iii.源代码:
iv.运行结果:
v.分析:
ctrl+C信号并发传到所有进程中,而子进程中屏蔽掉了中断信号,所以信号只发给了父进程,父进程终止,将kill信号发给两个子进程,从而显示出如上图的结果。
b)在上面的程序中增加语句signal(SIGINT,SIG-IGN)和signal(SIGQUIT,SIG-IGN),观察执行结果,并分析原因;
i.程序设计说明:
在a)代码基础上加入以下两种信号:
signal(SIGTINT,SIG_IGN);//后台进程读中断信号,默认挂起;
signal(SIGQUIT,SIG_IGN);//程序终止信号,默认操作写dump-core文件
ii.源代码:
参照a)中源代码。
iii.运行结果:
加上signal(SIGINT,SIG-IGN)后程序不再继续进行:
加上signal(SIGQUIT,SIG-IGN)后:
加上signal(SIGINT,SIG-IGN)和signal(SIGQUIT,SIG-IGN)后:
iv.分析:
只加上signal(SIGINT,SIG-IGN),则父程序不再继续输出,程序处于wait状态,因为在父进程收到信号时已默认为被杀死,程序停留在wait,不再继续进行,也没有父进程的输出。
而当只加上signal(SIGQUIT,SIG-IGN)时,输出结果不变。
加上signal(SIGINT,SIG-IGN)和signal(SIGQUIT,SIG-IGN)后,输出结果依旧不变。
但是后两种情况的含义是不同的。
当只加了signal(SIGQUIT,SIG-IGN)时,子进程收到键盘上的ctrl+C中断信号从而被杀死,输出killed语句。
而同时加上这两句话,屏蔽了从键盘上传来的中断信号,因此子进程接收到父进程传来的软中断信号,所以被杀死,输出killed语句。
4.进程的管道通信:
a)题目要求:
编制一段程序,实现进程的管道通信。
使用系统调用pipe()建立一条管道线;两个子进程P1和P2分别向管道各写一句话:
Child1issendingamessage!
Child2issendingamessage!
而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。
要求父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息
b)程序设计说明:
两个子进程分别向管道写一句话,为了避免两个进程抢占管道,利用一个sleep()函数将两个进程有效的时间区分开来,并且在第一个进程写进数据时,对管道进行加锁,写完后解锁,第二个进程继续写数据,以此避免冲突的产生。
而父进程则在子进程全部进行完毕后再执行。
c)源代码:
d)运行结果:
e)分析:
若是多次进行管道写入,并想保证一个进程完整运行,则必须加锁;若单次,不一定需要lockf(),因为一个标准输出流一定会完整的写完不被中断,然后再进行其他操作。
在管道通信中,系统调用pipe()函数初始化一个二元组作为管道,1为出,0为进,以此来保证管道通信不发生冲突。
五、思考:
1.系统是怎样创建流程的?
操作系统在创建流程时,首先申请空白的PCB(进程控制块),再为新进程分派资源,然后初始化PCB,最终将新进程插入就绪队列中。
而在我们编程的过程中,系统通过调用fork()函数来创建进程。
而当一个进程中调用了fork()函数时,则系统会为这个进程创建一个子进程。
这个子进程和父进程不同的地方只有他的进程ID,其他部分都一样。
也就是说新进程通过克隆老进程或是当前进程来创建。
其中系统调用fork()或clone()函数可以创建新任务,复制发生在核心状态下地核心中。
复制完成后,Linux允许两个进程共享资源而不是复制各自的拷贝(包括文件、信号处理过程和虚拟内存)。
而在创建后,先执行父进程还是子进程则取决于内核的调度算法。
子进程被创建后,父子进程相互竞争系统的资源,并行执行。
若想子进程先继续执行,则需要父进程阻塞至子进程完成任务,即用wait()系统调用来完成。
2.可执行文件加载时进行了哪些处理?
在编译执行源代码的过程中,由程序,到编译预处理,到编译,到优化程序,到汇编程序,到链接程序,最后到可执行文件。
在加载可执行文件时首先操作系统应该判断该文件是否为一个合法的执行文件,若是则按照段表中的指示为可执行文件分配地址空间。
在加载文件的过程中,主要是需要加载程序段和数据段到内存,并且进行外部定义符号的重定位。
任何一个可执行文件,启动时都需要调用Ntdll.dll中的NtCreateProcess()函数。
3.当首次调用新创建进程时,
其入口在哪里?
在进程队列ready的状态下,由离自己最近的父进程执行调度,即入口在最近的父进程处,是fork()之后的语句。
4.进程通信有什么特点?
linux下进程间通信的几种主要方式:
管道(Pipe)及有名管道(namedpipe):
管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
信号(Signal):
信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
报文(Message)队列(消息队列):
消息队列是消息的链接表,包括Posix消息队列systemV消息队列。
有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。
消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享内存:
使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。
是针对其他通信机制运行效率较低而设计的。
往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
信号量(semaphore):
主要作为进程间以及同一进程不同线程之间的同步手段。
套接口(Socket):
更为一般的进程间通信机制,可用于不同机器之间的进程间通信。
起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:
Linux和SystemV的变种都支持套接字
总的来说,进程间的通信根据通信内容可以划分为控制信息的传送(低级通信)和大批数据的传送(高级通信)。
在消息队列中信息的复制需要额外消耗CPU时间,不适宜于信息量大或操作频繁的场合;共享内存针对消息缓冲的缺点改而利用内存缓冲区直接交换信息,无需复制,快捷、信息量大就是又是。
但是不同的进程通信方式有不同的优缺点,因此对于不同应用问题,需要根据问题本身情况来选择进程间的通信方式。
六、总结:
本次实验中我遇到了很多大大小小的问题,首先就是linux虚拟机安装的问题。
先是下载了virtualbox软件但却因为版本问题一直不能安装linux系统成功。
于是转而用VMware软件。
安装上后,仍须安装gcc、g++等编译工具,于是上网搜索命令行安装的方法。
利用apt-getupdate、apt-getupgrade、apt-getinstallgcc语句安装成功,但g++编译器一直提示:
这次的实验通过进程创建、进程互斥、进程软中断、管道进程四个小程序让我们对进程管理这一章有了更深的理解。
在听课时,由于没有将书本跟实际动手操作联系在一起,对知识的印象不是很深,但这次为了编程,将书本上的内容完完整整过了好几遍,对进程中fork()、kill()、signal()、lockf()、pipe()等函数有了更深的了解。
因为管道进程在书上并没有怎么涉及,知识在ppt上有补充,因此通过上网查阅了许多资料才了解如何进行操作。
总体来说,这一次的实验,我们真正通过程序,了解到了进程管理、进程间通信的过程,加深了印象,也学习到了很多书本上没有的知识,还学会了linux环境下的编程、调试,受益匪浅。