北邮大三上操作系统进程管理实验报告.docx
- 文档编号:5761966
- 上传时间:2023-01-01
- 格式:DOCX
- 页数:20
- 大小:188KB
北邮大三上操作系统进程管理实验报告.docx
《北邮大三上操作系统进程管理实验报告.docx》由会员分享,可在线阅读,更多相关《北邮大三上操作系统进程管理实验报告.docx(20页珍藏版)》请在冰豆网上搜索。
北邮大三上操作系统进程管理实验报告
(此文档为word格式,下载后您可任意编辑修改!
)
操作系统
实验一
进程管理实验
班级:
学号:
姓名:
schnee
目录
1.实验目的3
2.实验预备内容3
3.环境说明3
4.实验内容4
4.1.进程的创建4
程序1
4.1.1题目要求:
4
4.1.2程序设计说明:
4
4.1.3源代码:
4
4.1.4运行结果:
5
4.1.5分析:
5
4.2.进程的控制6
程序2(a)
4.2.1题目要求:
6
4.2.2程序设计说明:
6
4.2.3源代码:
6
4.2.4运行结果:
7
4.2.5分析:
7
程序2(b)
4.2.1题目要求:
7
4.2.2程序设计说明:
8
4.2.3源代码:
8
4.2.4运行结果:
9
4.2.5分析:
10
4.3.进程的软中断通信11
程序3(a)
4.3.1题目要求:
11
4.3.2程序设计说明:
11
4.3.3源代码:
12
4.3.4运行结果:
14
4.3.5分析:
14
程序3(b)
4.3.1题目要求:
14
4.3.2程序设计说明:
14
4.3.3源代码:
(略)15
4.3.4运行结果及分析:
15
4.4.进程的管道通信17
程序4
4.4.1题目要求:
17
4.4.2程序设计说明:
17
4.4.3源代码:
17
4.4.4运行结果:
19
4.4.5分析:
19
5.思考20
1.实验目的
1)加深对进程概念的理解,明确进程和程序的区别
2)进一步认识并发执行的实质
3)分析进程争用资源的现象,学习解决进程互斥的方法
4)了解Linuxwindows系统中进程通信的基本原理
2.实验预备内容
阅读Linux的sched.-6.5.及kanas-ubuntu-10.10-desktop-i386。
直接编写文件在终端用命令行执行。
虚拟机分配8G内存中的512M。
操作尚未取得root权限。
ubuntu用户名jrayty。
3.实验内容
3.1.进程的创建
程序一:
4.1.1题目要求:
编写一段程序,使用系统调用fork()创建两个子进程。
当此程序运行时,在系统中有一个父进程和两个子进程活动。
让每一个进程在屏幕上显示一个字符:
父进程显示字符“a”,子进程分别显示字符“b”和“c”。
试观察记录屏幕上的显示结果,并分析原因。
4.1.2程序设计说明:
此程序相对简单。
详见源代码。
4.1.3源代码:
#include
#include
#include { pid_tp1,p2; while((p1=fork())==-1);createchildprogress1 if(p1==0) { putchar('b');childprogress1print'b' exit(0); } else { while((p2=fork())==-1);createchildprogress2 if(p2==0) { putchar('c');childprogress2print'c' exit(0); } else { putchar('a');parentprogressprint'a' exit(0); } } return0; } 4.1.4运行结果: 4.1.5分析: 从截图可见运行结果有abc,bac,acb等。 这是因为三个进程间没有同步措施,所以父进程和两个子进程的输出次序带有随机性。 因此实际上三者的各种组合都可能出现。 3.2.进程的控制 程序2.0: 4.2.1题目要求: 修改已经编写的程序,将每个进程输出一个字符改为每个进程输出一句话,再观察程序执行时屏幕上出现的现象,并分析原因。 4.2.2程序设计说明: 直接改自第一个程序。 其间加sleep()是为了规范下格式。 详见源代码。 4.2.3源代码: #include #include #include { pid_tp1,p2; while((p1=fork())==-1);createchildprogress1 if(p1==0) { puts("I'mtheChildProgress1! ! ! "); exit(0); } else { while((p2=fork())==-1);createchildprogress2 if(p2==0) { puts("I'mtheChildProgress2! ! ! "); exit(0); } else { sleep(4); puts("I'mtheParentProgress! ! ! ");parentprogress exit(0); } } return0; } 4.2.4运行结果: 4.2.5分析: 可见运行情况和第一个程序是很像的。 虽然换成了字符串,但同一个进程里puts字符串并不会被中断。 程序2: 4.2.1题目要求: 如果在程序中使用系统调用lockf()来给每一个进程加锁,可以实现进程之间的互斥,观察并分析出现的现象。 4.2.2程序设计说明: 学习lockf()函数学了好久。 。 。 程序中运用了多个sleep()并且让每个进程多输出了几次,从而使结果更明显。 且让父进程休息较久,从而让子进程都执行完。 4.2.3源代码: #include #include #include { pid_tp1,p2; inti; while((p1=fork())==-1);createchildprogress1 if(p1==0) { lockf(1,1,0);lock for(i=0;i<4;i++) { sleep (1); puts("I'mtheChildProgress1! ! ! "); } lockf(1,0,0);unlock exit(0); } else { while((p2=fork())==-1);createchildprogress2 if(p2==0) { lockf(1,1,0);lock for(i=0;i<4;i++) { sleep (1); puts("I'mtheChildProgress2! ! ! "); } lockf(1,0,0);unlock exit(0); } else { puts("I'mtheParentProgress! ! ! "); sleep(10); puts("I'mtheParentProgress! ! ! "); exit(0); } } return0; } 4.2.4运行结果: ******************************************若是去掉lockf(),则运行结果如下 4.2.5分析: 从上面运行结果可以看出。 无论加不加锁,两个子进程运行的次序是随机的。 但是加锁后一个进程会全部输出完了才转给另一个进程。 从而我们可以看出lockf()对两个进程对标准输出流的互斥使用。 lockf(fileno(fp),F_LOCK,0L) 函数的第一个参数是加锁的文件,上述程序用的是1,即标准输出流; 第二个参数是操作,1表示加锁0表示解锁。 第三个参数是文件长度,文中用0表示整个文件。 3.3.进程的软中断通信 程序3: 4.3.1题目要求: (a)使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按DelCTRL+C键).当捕捉到中断信号后,父进程用系统调用kill()向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止: ChildProcess1iskilledbyParent! ChildProcess2iskilledbyParent! 父进程等待两个子进程终止后,输出如下的信息后终止: ParentProcessiskilled! 4.3.2程序设计说明: 1)题目解析和问题解决 一,要求Del后引发父进程的动作。 实际就是触发软中断SIGINT。 二,要求在中断到来前两子进程处于等待状态,中断到来后立刻动作。 对此,我自定义了两个函数my_wait()函数和my_stop()函数,通过对flag标志位的操作来实现。 flag为真时等待,中断到来后通过my_stop()函数使flag改为假,从而退出等待的死循环。 虽然这样效率很低,但是在这个小程序里还是可以的。 三,至于如何控制父进程杀死子进程后再自杀,我用了signal()函数预留给用户自定义的10和12号信号,即SIGUSR1和SIGUSR2。 让两个子进程分别监听这两个信号,父进程被触发后分别向两个子进程发出这两个信号杀死他们,然后再退出即可。 2)知识点 补充学习到的函数的定义。 1)软中断信号预置函数signal(sig,function) sig——系统给定的软中断信号中的序号或名称。 function——与软中断信号关联的函数名,当进程在运行过程中捕捉到指定的软中断信号后,中断当前程序的执行转到该函数执行。 注意: 软中断信号必须提前预置,然后才可以在程序运行中捕获。 2)发送软中断信号函数intkill(pid,sig) pid——表示一个或一组进程的标识符: 当pid>0时,将信号发送给指定pid的进程; 当pid=0时,将信号发送给同组的所有进程; 当pid=-1时,将信号发送给以下所有满足条件的进程: 该进程用户标识符等于发送进程有效用户标识符; sig——软中断信号的序号或名称 功能: 向指定进程标识符pid的进程发软中断信号sig。 本章中用来实现父进程给子进程发终止执行软中断信号。 3)父子父同步的实现 在进程同步中,使用exit()和wait()实现了父进程等子进程终止的同步,但是这种同步方法不能实现子进程对父进程的等待。 要实现子进程对父进程的等待可以使用父进程向子进程发软中断信号,子进程接收信号的方式实现。 这两种同步方式相结合,可以实现父→子→父的同步序列。 实现父→子→父同步的步骤如下: ⑴子进程使用signal()预置软中断处理函数,然后等待父进程发软中断信号; ⑵父进程中使用kill()发软中断信号给子进程,再用wait(0)等待子进程结束; ⑶子进程接收到软中断信号后转去执行中断处理函数 ⑷子进程在中断处理返回后,使用exit(0)终止执行,向父进程发终止信息。 ⑸父进程使用wait(0)接收到子进程的终止信息后结束等待,并终止自己的程序的执行。 4.3.3源代码: #include #include #include { signal(SIGINT,my_stop);DelorCtrl+Ctrigger while((p1=fork())==-1);createchildprogress1 if(p1==0) { flag=true;p1setwaiting signal(SIGUSR1,my_stop);setSIGUSR1tostop my_wait(0);waiting lockf(1,1,0);lock puts("\nChildProcess1iskilledbyParent! "); lockf(1,0,0);unlock sleep (1); exit(0); } else { while((p2=fork())==-1);createchildprogress2 if(p2==0) { flag=true;p2setwaiting signal(SIGUSR2,my_stop);setSIGUSR2tostop my_wait(0);waiting lockf(1,1,0);lock puts("\nChildProcess2iskilledbyParent! "); lockf(1,0,0);unlock sleep (1); exit(0); } else { flag=true;parentsetwaiting my_wait(0); kill(p1,SIGUSR1); kill(p2,SIGUSR2); wait(NULL); wait(NULL); puts("\nParentProcessiskilled! "); exit(0); } } return0; } 4.3.4运行结果: 4.3.5分析: 如上图,可见两个子进程的执行顺序还是随机的。 程序3.2: 4.3.1题目要求: (b)在上面的程序中增加语句signal(SIGINT,SIG_IGN)和signal(SIGQUIT,SIG_IGN),观察执行结果,并分析原因。 4.3.2程序设计说明: signal(SIGTINT,SIG_IGN);后台进程读中断信号,默认挂起 signal(SIGQUIT,SIG_IGN);程序终止信号,默认操作写dump-core文件 4.3.3源代码: (略) 4.3.4运行结果及分析: ***************************signal(SIGINT,my_wait)加到第二个else里面 ***************************加signal(SIGINT,SIG_IGN) **************************加signal(SIGINT,SIGIGN)后signal(SIGINT,my_wait)加到第二个else里面 分析: 1)单纯在程序3的基础上把signal(SIGINT,my_wait)加到第二个else里面后一开始子进程不再输出了。 这是因为SIGINT信号不只向父进程还向子进程发出了信号,而子进程并未对它重定义故就执行默认定义直接被杀死了。 于是子进程实际上是被SIGINT杀死的而不是被父进程kill的。 2)单纯地加了signal(SIGINT,SIGIGN)后虽然表面看起来和不加它没区别,但是本质却完全不同了。 现在子进程确实是被父进程杀死的。 因为SIGINT已经被忽略了。 3)加signal(SIGINT,SIGIGN)并且signal(SIGINT,my_wait)加到第二个else里面后输出也是一样,不会再发生子进程不输出的情况了。 因为现在子进程的SIGINT已经被忽略了。 *******************************加了signal(SIGQUIT,SIG_IGN) 分析: 在程序3的基础上在两个进程的前面加入signal(SIGQUIT,SIG_IGN),运行结果如上图输入ctrl+C时还是不变但是^\后直接退出了,所有的进程都没有输出,并没有被屏蔽。 资料说 signal(SIGTINT,SIG_IGN);后台进程读中断信号,默认挂起 signal(SIGQUIT,SIG_IGN);程序终止信号,默认操作写dump-core文件 但是我还是没有太了解。 3.4.进程的管道通信 程序4: 4.4.1题目要求: 编制一段程序,实现进程的管道通信。 使用系统调用pipe()建立一条管道线,两个子进程P1和P2分别向管道各写一句话: Child1issendingamessage! Child2issendingamessage! 而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。 要求父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息。 4.4.2程序设计说明: 为了避免两个进程抢占管道,我把第二个子进程延时了,并且对管道加锁,从而形成独占,避免冲突产生。 而父进程只需用之前的wait()函数即可确保在两个子进程后执行。 4.4.3源代码: #include #include #include { pid_tp1,p2; intriver[2];1in,0out charrb[PIPE_MAX],wb[PIPE_MAX]; pipe(river);createpipe while((p1=fork())==-1);createchildprogress1 if(p1==0) { lockf(river[1],1,0);lock sprintf(wb,"Child1issendingamessage! \n"); write(river[1],wb,RW_MAX); lockf(river[1],0,0);unlock exit(0); } else { while((p2=fork())==-1);createchildprogress2 if(p2==0) { sleep (1);waitp1 lockf(river[1],1,0);lock sprintf(wb,"Child2issendingamessage! \n"); write(river[1],wb,RW_MAX); lockf(river[1],0,0);unlock exit(0); } else { wait(NULL);waitp1 read(river[0],rb,RW_MAX); printf("%s\n",rb); wait(NULL);waitp2 read(river[0],rb,RW_MAX); printf("%s\n",rb); exit(0); } } return0; } 4.4.4运行结果: 4.4.5分析: 管道通信通过系统调用pipe()初始化一个二元组为管道,1出0进。 4.思考 1)系统是怎么创建进程的? 答: 新进程通过克隆老进程或是当前进程来创建。 系统调用fork或clone可以创建新任务,复制发生在核心状态下地核心中。 在类UNIX系统中,除了根进程之外,如果想要在系统之中创建一个新的进程,唯一的方法就是利用一个已存在的进程通过系统调用fork()函数来实现,fork()函数被调用时,它会将调用它的进程复制出一个副本(一般会共享代码段,其它的数据段和堆栈等会复制个新的),原进程是复制得到进程的父进程,两个进程除了进程号不同,是一模一样,fork 调用结束后将 0 返回给子进程,将子进程的进程号返回给父进程,在调用fork函数之后,父子进程都将从位于fork函数的调用点之后的指令处开始执行,因为是两个不同的进程,它们的执行是并行的,先后次序是不定的。 (参考《Linux内核完全解析》和编程爱好者网站相关文章) 2)可执行文件加载时进行了哪些处理? 答: 当操作系统装载一个可执行文件的时候,首先操作系统判断该文件是否是一个合法的可执行文件。 如果是操作系统将按照段表中的指示为可执行程序分配地址空间。 加载文件最重要的是完成两件事: 加载程序段和数据段到内存,以及进行外部定义符号的重定位。 下面是ELF文件为例的可执行文件加载处理: 1,内核读文件的头部,然后根据文件的数据指示分别读入各种数据结构,找到可加载的段加载到内存中。 2,内核分析出文件对应的动态连接器名称并加载动态连接器。 3,内核在新进程的堆栈中设置一些标记-值对,以指示动态连接器的相关操作。 4,内核把控制传递给动态连接器。 5,动态连接器检查程序对外部文件(共享库)的依赖性,并在需要时对其进行加载。 6,动态连接器对程序的外部引用进行重定位。 7,动态连接器进行程序运行的初始化。 8,动态连接器把控制传递给程序,从文件头部定义的程序进入点开始执行。 (参考execve()的内核代码和网上相关文章) 3)当首次调用新创建进程时,其入口在哪里? 答: 当首次调用新创建进程时,入口是fork()之后的语句。 fork系统调用创建的子进程继承了原进程的context,也就是说fork调用成功后,子进程与父进程并发执行相同的代码。 但由于子进程也继承了父进程的程序指针,所以子进程是从fork()后的语句开始执行(也就是新进程调用的入口)。 另外fork在子进程和父进程中的返回值是不同的。 在父进程中返回子进程的PID,而在子进程中返回0。 所以可以在程序中检查PID的值,使父进程和子进程执行不同的分支。 (参考网上相关文章) 4)进程通信有什么特点? 答: (参考网上文章,个人怀疑“特点”难道就是“分类”吗? ) linux下进程间通信的几种主要手段简介: 管道(Pipe)及有名管道(namedpipe): 管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信; 信号(Signal): 信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数); 报文(Message)队列(消息队列): 消息队列是消息的链接表,包括Posix消息队列systemV消息队列。 有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。 消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。 共享内存: 使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。 是针对其他通信机制运行效率较低而设计的。 往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。 信号量(semaphore): 主要作为进程间以及同一进程不同线程之间的同步手段。 套接口(Socket): 更为一般的进程间通信机制,可用于不同机器之间的进程间通信。 起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 大三 操作系统 进程 管理 实验 报告
![提示](https://static.bdocx.com/images/bang_tan.gif)