深圳大学操作系统实验一《并发程序设计》

一、实验目的

  1. 加深对进程的创建、运行、撤销过程的直观认识;
  2. 掌握通过操作系统的用户接口(命令行和系统函数)控制进程状态的方法;
  3. 了解多进程在多核处理机上的并发执行过程;

二、实验内容与方法

1. 可以使用Linux或其它Unix类操作系统;

2. 学习该操作系统提供的命令行启动、撤销进程的方法;

3. 学习该操作系统提供的系统调用接口(借助于库函数的形式间接调用)启动和撤销进程;

4. 利用该操作系统提供的工具观测这些程序的并发执行过程以及状态转换过程。

三、实验环境

硬件:桌面PC

软件:Linux 或其他操作系统

四、实验步骤及说明

预备部分:

学习top、ps、pstree和kill等命令的使用。能通过top和ps j命令查看进程号、父进程号、可执行文件名(命令)、运行状态信息,能通过pstree查看系统进程树;能通过kill命令杀死制定pid的进程。

学习/proc/PID/maps的输出信息。

了解/porc/PID/status中各项内容的。

操作部分(1/2/3/5/每点完成度各计20%):

  1. 使用fork()创建子进程,形成以下父子关系:
  • /proc文件系统,检查进程的pid和ppid证明你成功创建相应的父子关系,并用pstree验证其关系。
  1. 编写代码实现孤儿进程,用pstree查看孤儿进程如何从原父进程位置转变为init进程所收养的过程;编写代码创建僵尸进程,用ps j查看其运行状态。
  • Linux系统提供的信息,展示并记录上述进程状态及变化
  1. 创建多个线程,在各个线程中打印出堆栈变量的地址。
  • /proc/PID/maps是否相同。检查主线程的堆栈和其他线程堆栈位置在/proc/PID/maps所对应的位置差异。
  1. 分别创建相同数量的进程和线程
  • vma描述符开销的差异,并简要解释原因。
  1. 尝试自行设计一个C语言小程序,完成最基本的shell角色
  • help命令给出用法、exit命令退出shell)、外部命令(即磁盘上的可执行文件)以及无效命令(不是上述两种命令)。

实验报告要求:

  1. 按学校统一格式
  2. 需要给出具体命令和自行编写的程序的源代码
  3. 需要给出实验操作的截图和必要的说明文字

步骤:

1、预备部分:

1、top命令查看进程号等进程相关信息(图3-1-1)

图3-1-1 top指令查看进程

2、ps命令查看当前执行的进程信息快照(图3-1-2)。

图3-1-2 ps命令查看进程

3、编辑HelloWorld.c文件,其中设置fork一个子进程,并且在pid为子进程或父进程时用getchar来使进程保持(图3-1-3)。

图3-1-3 编辑HelloWorld.c文件

4、使用gcc HelloWorld.c –o HelloWorld生成可执行文件(图3-1-4)。

图3-1-4编译生成elf文件

5、使用ps查看运行HelloWorld前后的进程区别,在这其中利用./HelloWorld &使其在后台运行,我们可以看到有两个HelloWorld进程产生,进程号为2513和2514(图3-1-5)。

图3-1-5 运行HelloWorld

6、运行pstree –p 我们可以看到2513的helloworld程序是由bash产生的,并且有一个2514的HelloWorld子进程(图3-1-6)。

图3-1-6 pstree指令结果

7、我们利用kill –kill 2513指令杀死HelloWorld进程,我们发现其子进程也消失了,再次运行后发现进程号换成了2541了(图3-1-7)。

图3-1-7 kill指令杀死进程

8、我们使用cat /../../proc/2541/maps 查看HelloWorld进程详细信息(图3-1-8)。

图3-1-8 查看进程详细信息

9、我们使用cat /../../proc/2541/status 查看HelloWorld进程的详细信息(图3-1-9)。

图3-1-9 查看进程状态

2. 使用fork()创建子进程,形成以下父子关系:

1、如图3-2-1,创建fork1.c 并且循环十次,每次如果是父进程就fork,子进程则跳出循环,然后getchar使进程保持。

图3-2-1 书写进程相关代码

2、接着我们创建elf文件并在后台运行,然后ps可以看到我们生成了2804-2814的进程,其中2804为父进程(图3-2-2)。

图3-2-2 创建elf并执行程序

3、我们通过proc文件系统可以看到,除了2804进程,2805-2814进程的父进程都为2804进程(图3-2-3)。

图 3-2-3 proc查看进程pid与父进程

4、我们通过pstree –p 2804可以看到其确实产生了十个子进程(图3-2-4)。

图3-2-4 pstree查看子进程

5、我们接着实现纵深十个子进程的代码,只用修改父进程与子进程在循环中的动作即可,此外,我们还必须修改getchar函数为pause函数,否则子进程只能产生2个而不是十个(图3-2-5)。

图3-2-5 写下进程相关代码

6、接着我们生成可执行文件并查看进程,发现确实生成了十一个进程,pid从2992-3002(图3-2-6)。

图3-2-6 运行程序

7、我们通过pstree查看,确实生成了纵深子进程图(图3-2-7)。

图3-2-7 pstree查看进程关系

8、利用/proc文件系统,检查得知与预想情况一致(图3-2-8)。

  


图3-2-8 proc文件系统结果

9、接下来我们为实现树状结构的进程树进行编写代码(图3-2-9)。

10、编译生成elf文件并运行,发现生成了15个进程,pid在3102-3116之间(图3-2-10)。

图3-2-10 运行程序

11、用pstree查看进程间关系,得到与题目所示一样的结构(图3-2-11)。

图3-2-11 pstree查看进程关系

3、编写代码实现孤儿进程

1、创建zombie.c文件,写下代码,思路为首先创建一个子进程,然后子进程无限循环不让其结束,父进程睡眠十秒后结束(图3-3-1)。

图3-3-1 编写孤儿程序

2、运行程序后,使用ps j 查看进程运行情况,发现有两个zombie进程,过了10s后在运行ps j,发现只剩一个zombie进程(图3-3-2)。

图3-3-2 ps j查看进程映像

3、如图3-3-3,我们可以看到在运行完程序后,两个zombie进程在bash分支下,然而在稍作等待后我们看到父进程zombie死亡了,然后子进程跟随转入了systemd分支中(图3-3-3)。

图3-3-3 pstree查看孤儿进程结构

4、如图3-3-4,编写僵尸程序,其思路与孤儿程序相反,就是让父进程一直执行,但是其生成的子进程已经结束,此时子进程就会变成僵尸进程(图3-3-4)。

图3-3-4 编写僵尸进程程序

5、我们通过ps j指令查看运行后进程情况,发现正常,3216进程的子进程为3217进程,然而过了一会再用ps j 发现3217进程变成了僵尸进程(图3-3-5)。

图3-3-5 ps j僵尸进程生成前后

4、创建多个线程,在各个线程中打印出堆栈变量的地址。

1、在linux里输入man gettid,可以看到gettid的相关解释(图4-1-1),里面提到gettid()不适合用在程序中,并且说明此为系统调用。

图4-1-1 gettid解释

2、利用 locate unistd_64查找系统调用号,查到gettid是186号(32位为224号)(图4-1-2)。

图4-1-2 32位(右)64位(左)

3、根据上述结论,编写如下代码(图4-1-3)。

图4-1-3 创建线程并获取信息代码

4、运行 gcc ./thread1.c –o thread1 –l pthread ,生成可执行文件(图4-1-4)。

图4-1-4 生成可执行文件

5、首先后台运行thread1,然后发现其堆栈大小都为8388608,即8*1024*1024也就是8M,堆栈地址为每个子线程自己的堆栈地址,通过 ps -mp PID -o THREAD,tid,time 查看该进程的所有线程信息,可以看到确实由一个主线程 + 三个子线程组成(图4-1-5)。

图4-1-5 观察线程堆栈信息

6、运行 cat /../../proc/26589/maps 后可以看到,子线程的堆栈是在主线程堆区申请的“虚拟堆栈”(图4-1-6)。

图4-1-6 maps子堆栈分析

7、从下图中红色框框可以看出,heap区有三个大小相同的堆栈,其有只读+可读可写的内存组成,且为三个子线程的堆栈保护缓冲和堆栈空间(图4-1-7)。

图4-1-7 heap区堆栈空间分析

8、我们计算图4-1-7的第一个红框的可读可写区可以看到,结果为8388608也就是8M(图4-1-8),符合默认堆栈大小。

图4-1-8 计算堆栈大小

9、同理,我们查看主线程的栈空间,计算得到其大小约为132KB(图4-1-9),因此我们可以得出结论:三个子线程具有自己独立的栈空间内存,并且这些内存是位于 heap 区,而主线程的栈空间则不和他们一起存放。

图4-1-9 主线程栈大小

5、分别创建相同数量的进程和线程

1、查阅资料得知(图5-1-1),线程本质仍然是进程,并且线程也有自己的独立PCB,区别在于进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间),而子线程的内存描述符和vma描述符都指向main进程,所以子线程共享一套地址空间。

图5-1-1 线程概念

2、下图分别为创建3个进程(图5-1-2)与3个线程(图5-1-3)的程序,原理与上述程序类似。

图5-1-2 三进程程序

图5-1-3 三线程程序

3、由上述分析可知,创建三个进程需要3个task_struct,3n个vm_area_struct,而线程与其不同在于子线程共享内存空间,所以只需n个vm_area_struct。因此我们需要验证我们的猜想是否正确,但是要想print出来task_struct与vma,需要内核态程序才能做,所以拟写一个module并且插入到linux内核中(图5-1-4)。

图5-1-4 module程序

4、 编写的makefile文件如下图(图5-1-5)。

图5-1-5 Makefile文件

5、make之后编译运行三个进程的程序(图5-1-6),反复插入模块并打印信息,结果发现(图5-1-7),三个进程的 mm_struct 和 vm_area_struct 地址均不同,说明进程之间的地址空间相互独立。

图5-1-6 编译运行且make

图5-1-7 查看三进程的地址信息

6、后台运行生成三个线程的程序后,查明pid=92108,所以执行ps -mp 92108 -o THREAD,tid,time指令,然后将其插入module中,查看发现pid和tid是task_struct获得的,他们都不一样,所以都有不同的PCB,但是线程的mm_struct与vm_area_struct地址都是一样的,说明他们与主进程共享一个地址空间(图5-1-8)。

图5-1-8 三线程info print

7、总的来说,等量的线程与进程相比,他们的PCB开销不变,但是vma描述符进程消耗的比线程多三倍,因为进程的地址空间相互独立。

6、尝试自行设计一个C语言小程序,完成最基本的shell角色

1、其实思路比较简单,但是有个难点在于外部指令,此时需要fork一个新进程,然后使用execlp函数来执行指令,此外并没有啥难点(图6-1-1)。

图6-1-1 shell程序详情

2、我们对程序进行如下演示,首先演示内部指令help与story(图6-1-2)。

图6-1-2 内部指令演示

3、接下来是外部指令演示:ls、ps、pwd(图6-1-3)

图6-1-3 外部指令演示

4、错误指令演示以及退出(图6-1-4)

图6-1-4 错误指令演示及退出

五、实验结论或体会

1、通过这次实验,我脑袋里清晰了进程的创建、运行以及撤销过程,并且明白了进程与线程的区别与相同点,以及在什么情况选择多线程,什么情况适用多进程。

2、熟练掌握了通过操作系统的命令行与系统函数控制进程状态,例如wait等函数。

3、明白了top、ps、pstree和kill指令的使用,并且会使用这些指令去观察进程与线程的状态与信息。

4、学会如何查看/proc/PID/maps以及/proc/PID/status的信息以及利用信息去完成任务。

5、明白如何创建多线程与多进程、僵尸进程、孤儿进程,并且对其有了更加深刻的认识。

6、对module以及内核程序与用户程序的区别有了了解。

  • 21
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值