1《操作系统》实验报告指导老师:赵正德2实验三进程管理及进程通信一.实验目的利用Linux提供的系统调用设计程序,加深对进程概念的理解。体会系统进程调度的方法和效果。了解进程之间的通信方式以及各种通信方式的使用。二.实验准备复习操作系统课程中有关进程、进程控制的概念以及进程通信等内容(包括软中断通信、管道、消息队列、共享内存通信及信号量概念)。熟悉本《实验指导》第五部分有关进程控制、进程通信的系统调用。它会引导你学会怎样掌握进程控制。阅读例程中的程序段。三.实验方法用vi编写c程序(假定程序文件名为prog1.c)编译程序$gcc-oprog1.oprog1.c或$cc-oprog1.oprog1.c运行$./prog1.o四.实验内容及步骤用vi编写使用系统调用的C语言程序。1.编写程序。显示进程的有关标识(进程标识、组标识、用户标识等)。经过5秒钟后,执行另一个程序,最后按用户指示(如:Y/N)结束操作。3编译运行结果:2.编写程序。实现父进程创建一个子进程。体会子进程与父进程分别获得不同返回值,进而执行不同的程序段的方法。编译运行:思考:子进程是如何产生的?又是如何结束的?子进程被创建后它的运行环境是怎样建立的?答:子进程由fork()函数创建,通过exit()函数自我结束,子进程被创建后核心将为其分配一个进程表项和进程标识符,检查同时运行的进程数目,并且拷贝进程表项的数据,由子进程继承父进程的所有文件。43.编写程序。父进程通过循环语句创建若干子进程。探讨进程的家族树以及子进程继承父进程的资源的关系。程序如下:编译运行:思考:①画出进程的家族树。子进程的运行环境是怎样建立的?反复运行此程序看会有什么情况?解释一下。1839224722482252225422492251225322505每一次运行返回的进程号都不相同,但是都符合家族进程树,出现这样的情况是由于系统本身就是随机分配进程号的。②修改程序,使运行结果呈单分支结构,即每个父进程只产生一个子进程。画出进程树,解释该程序。用一个break;语句使父进程在子进程结束后跳出循环,运行结果如下:进程家族树如下:200424012402240324044.编写程序。使用fork()和exec()等系统调用创建三个子进程。子进程分别启动不同程序,并结束。反复执行该程序,观察运行结果,结束的先后,看是否有不同次序。编译代码如下:6编译运行:7思考:子进程运行其它程序后,进程运行环境怎样变化的?反复运行此程序看会有什么情况?解释一下。答:子进程运行其他程序后,这个进程就完全被新程序代替。由于并没有产生新进程所以进程标识号不改变,除此之外的旧进程的其他信息,代码段,数据段,栈段等均被新程序的信息所代替。新程序从自己的main()函数开始进行。反复运行此程序发现结束的先后次序是不可预知的,每次运行结果不一样。原因是当每个子进程运行其他程序时,他们的结束随着其他程序的结束而结束,所以结束的先后次序在改变。5.编译程序,验证子进程继承父进程的程序、数据等资源。如用父、子进程修改公共变量和私有变量的处理结果;父、子进程的程序区和数据区的位置。编译源代码如下:编译运行结果:8思考:子进程被创建后,对父进程的运行环境有影响吗?解释一下。答:子进程被创建后,对父进程的运行环境无影响,因为当子进程在运行时,他有自己的代码段和数据段,这些都可以作修改,但是父进程的代码段和数据段是不会随着子进程数据段和代码段的改变而改变。6.参照《实验指导》第五部分中“管道操作的系统调用”。复习管道通信概念,编写一个程序。父进程创建两个子进程,父子进程之间利用管道进行通信。要求能显示父进程、子进程各自的信息,体现通信效果。源代码:9编译运行:思考:①什么是管道?进程如何利用它进行通信的?解释一下实现方法。②修改睡眠时机、睡眠长度,看看会有什么变化。请解释。③加锁、解锁起什么作用?不用它行吗?答:1.管道是指能够连接一个写进程和一个读进程,并允许他们以生产者-消费者方式进行通信的一个共享文件,又称pipe文件。由写进程从管道的入端将数据写入管道,而读进程则从管道出端读出数据来进行通信。2.修改睡眠时机和睡眠长度都会引起进程被唤醒的时间不一,因为睡眠时机决定进程在何时睡眠,睡眠长度决定进程何时被唤醒。3.加锁、解锁是为了解决临界资源的共享问题。不用它将会引起无法有效管理数据,即数据会被修改导致读错了数据。7.编程验证:实现父子进程通过管道进行通信。进一步编程,验证子进程结束,由父进程执行撤消进程的操作。测试父进程先于子进程结束时,系统如何处理“孤儿进程”的。把上题中的父进程中的wait()都去掉,运行结果如下:10思考:对此作何感想,自己动手试一试?解释一下你的实现方法。答:只要在父进程后加上wait()函数,然后打印“子进程已经结束”,一旦子进程结束,父进程撤销进程。把父进程中的wait()去掉,父进程先于子进程终止时.所有子进程的父进程改变为init进程,称为进程由init进程领养。8.编写两个程序一个是服务者程序,一个是客户程序。执行两个进程之间通过消息机制通信。消息标识MSGKEY可用常量定义,以便双方都可以利用。客户将自己的进程标识(pid)通过消息机制发送给服务者进程。服务者进程收到消息后,将自己的进程号和父进程号发送给客户,然后返回。客户收到后显示服务者的pid和ppid,结束。11运行结果:服务者:客户程序:思考:想一下服务者程序和客户程序的通信还有什么方法可以实现?解释一下你的设想,有兴趣试一试吗。答:还可以用信号量机制来实现。信号量是一个整形计数器,用来控制多个进程对共享资源的访问。或者通过消息队列信号机制,通过向消息12队列发送信息、接收信息来实现进程间的通信。9.这部分内容涉及《实验指导》第五部分中“有关信号处理的系统调用”。编程实现软中断信号通信。父进程设定软中断信号处理程序,向子进程发软中断信号。子进程收到信号后执行相应处理程序。源代码如下:编译运行:思考:这就是软中断信号处理,有点儿明白了吧?讨论一下它与硬中断有什么区别?看来还挺管用,好好利用它。答:硬中断是由外部硬件产生的,而软中断是CPU根据软件的某条指令或者软件对标志寄存器的某个标志位的设置而产生的。10.怎么样,试一下吗?用信号量机制编写一个解决生产者—消费者问13题的程序,这可是受益匪浅的事。本《实验指导》第五部分有关进程通信的系统调用中介绍了信号量机制的使用。#includestdio.h#includestdlib.h#includeunistd.h#includepthread.h#includesemaphore.h#includesignal.h#defineN5//消费者或者生产者的数目#defineM10//缓冲数目//intM=10;intin=0;//生产者放置产品的位置intout=0;//消费者取产品的位置intbuff[M]={0};//缓冲初始化为0,开始时没有产品sem_tempty_sem;//同步信号量,当满了时阻止生产者放产品sem_tfull_sem;//同步信号量,当没产品时阻止消费者消费pthread_mutex_tmutex;//互斥信号量,一次只有一个线程访问缓冲intproduct_id=0;//生产者idintprochase_id=0;//消费者id//信号处理函数voidHandlesignal(intsigno){printf(程序退出\n,signo);exit(0);}/*打印缓冲情况*/voidprint(){inti;printf(产品队列为);for(i=0;iM;i++)printf(%d,buff[i]);printf(\n);}/*生产者方法*/void*product(){intid=++product_id;while(1){//重复进行//用sleep的数量可以调节生产和消费的速度,便于观察sleep(2);14sem_wait(&empty_sem);pthread_mutex_lock(&mutex);in=in%M;printf(生产者%d在产品队列中放入第%d个产品\t,id,in);buff[in]=1;print();++in;pthread_mutex_unlock(&mutex);sem_post(&full_sem);}}/*消费者方法*/void*prochase(){intid=++prochase_id;while(1){//重复进行//用sleep的数量可以调节生产和消费的速度,便于观察sleep(5);sem_wait(&full_sem);pthread_mutex_lock(&mutex);out=out%M;printf(消费者%d从产品队列中取出第%d个产品\t,id,out);buff[out]=0;print();++out;pthread_mutex_unlock(&mutex);sem_post(&empty_sem);}}intmain(){printf(生产者和消费者数目都为5,产品缓冲为10,生产者每2秒生产一个产品,消费者每5秒消费一个产品,Ctrl+退出程序\n);pthread_tid1[N];pthread_tid2[N];15inti;intret[N];//结束程序if(signal(SIGINT,Handlesignal)==SIG_ERR){//按ctrl+C产生SIGINT信号printf(信号安装出错\n);}//初始化同步信号量intini1=sem_init(&empty_sem,0,M);//产品队列缓冲同步intini2=sem_init(&full_sem,0,0);//线程运行同步if(ini1&&ini2!=0){printf(信号量初始化失败!\n);exit(1);}//初始化互斥信号量intini3=pthread_mutex_init(&mutex,NULL);if(ini3!=0){printf(线程同步初始化失败!\n);exit(1);}//创建N个生产者线程for(i=0;iN;i++){ret[i]=pthread_create(&id1[i],NULL,product,(void*)(&i));if(ret[i]!=0){printf(生产者%d线程创建失败!\n,i);exit(1);}}//创建N个消费者线程for(i=0;iN;i++){ret[i]=pthread_create(&id2[i],NULL,prochase,NULL);if(ret[i]!=0){printf(消费者%d线程创建失败!\n,i);exit(1);}}//等待线程销毁for(i=0;iN;i++){pthread_join(id1[i],NULL);pthread_join(id2[i],NULL);}exit(0);五.研究并讨论161.讨论Linux系统进程运行的机制和特点,系统通过什么来管理进程?在Linux中,每个进程在创建时都会被分配一个数据结构,称为进程控制块(ProcessControlBlock,简称PCB)。PCB中包含了很多重要的信息,供系统调度和进程本身执行使用。所有进程的PCB都存放在内核空间中。PCB中最重要的信息就是进程PID,内核通过这个PID来唯一标识一个进程。PID可以循环使用,最大值是32768。init进程的pid为1,其他进程都是init进程的后代。除了进程控制块(PCB)以外,每个进程都有独立的内核堆栈(8k),一个进程描述符结构,这些数据都作为进程的控制信息储存在内核空间中;而进程的用户空间主要存储代码和数据。Linux操作系统使用一些系统调用(如:fork()、wait、exit等)来实现对进程的管理。2.C语言中是如何使用Linux提供的功能的?用程序及运行结果举例说明。C语言是通过在.c文件中添加函数调用的.h文件,来调用进程管理的相关函数,并通过getpid()和g