1、实验题目:进程管理及进程通信 姓名 学号 实验日期 2016/10/12 实验环境: VMware Workstation中的Ubuntu 64位操作系统。 实验目的: 利用Linux提供的系统调用设计程序,加深对进程概念的理解.体会系统进程调度的方法和效果. 了解进程之间的通信方式以及各种通信方式的使用。 实验内容: 用vi 编写使用系统调用的C 语言程序。 操作过程及结果: 实验1: 编写程序.显示进程的有关标识(进程标识、组标识、用户标识等)。经过5 秒钟后,执行另一个程序,最后按用户指示(如:Y/N
2、结束操作. 结果: 实验2: 编写程序。实现父进程创建一个子进程。体会子进程与父进程分别获得不同返回值,进而执行不同的程序段的方法。 结果: 思考:子进程是如何产生的?又是如何结束的?子进程被创建后它的运行环境是怎样建立的? 答:是由父进程用fork()函数创建形成的,通过exit()函数自我结束,子进程被创建后核心将其分配一个进程表项和进程标识符,检查同时运行的进程数目,并且拷贝进程表项的数据,由子进程继承父进程所有文件。 实验3: 编写程序.父进程通过循环语句创建若干子进程。探讨进程的家族树以及子进程继承父进程的资源的关系。 结果: 思考:① 画出进程的家族树.子
3、进程的运行环境是怎样建立的?反复运行此程序看会有什么情况?解释一下。 ② 修改程序,使运行结果呈单分支结构,即每个父进程只产生一个子进程。画出进程树,解释该程序。 答1: 反复运行,子进程的编号在不断变化,但是初始的父进程的编号不会变,因为:父进程始终是该程序在虚拟机操作系统下的进程号,但是子进程是该父进程创建的子进程不断增加而得的,子进程的编号应该是顺序递增的. 答2: #include 〈stdio。h〉 #include 〈sys/types。h〉 #include 〈
4、unistd.h> main() { int i, pid; for (i=1; i<4; i++) if (pid=fork()) break; } 实验4: 编写程序.使用fork( )和exec( )等系统调用创建三个子进程。
5、子进程分别启动不同程序,并结束。反复执行该程序,观察运行结果,结束的先后,看是否有不同次序。 结果: 思考:子进程运行其它程序后,进程运行环境怎样变化的?反复运行此程序看会有什么情况?解释一下。 答:子进程运行其他程序后,这个进程就完全被新程序代替。由于并没有产生新进程所以进程标识号不改变,除此之外的旧进程的其他信息,代码段,数据段,栈段等均被新程序的信息所代替。新程序从自己的main()函数开始进行。反复运行此程序发现结束的先后次序是不可预知的,每次运行结果不一样。原因是当每个子进程运行其他程序时,他们的结束随着其他程序的结束而结束,所以结束的先后次序在改变。 实验5: 编写程序
6、验证子进程继承父进程的程序、数据等资源。如用父、子进程修改公共变量和私有变量的处理结果;父、子进程的程序区和数据区的位置。 结果: 思考:子进程被创建后,对父进程的运行环境有影响吗?解释一下。 答:子进程被创建后,对父进程的运行环境无影响,因为当子进程在运行时,他有自己的代码段和数据段,这些都可以作修改,但是父进程的代码段和数据段是不会随着子进程数据段和代码段的改变而改变。 实验6: 复习管道通信概念,编写一个程序。父进程创建两个子进程,父子进程之间利用管道进行通信。要求能显示父进程、子进程各自的信息,体现通信效果。 结果: 思考:①什么是管道?进程如何利用它进行通信的?解释
7、一下实现方法。 ②修改睡眠时机、睡眠长度,看看会有什么变化。请解释。 ③加锁、解锁起什么作用?不用它行吗? 答1:管道:是指能够连接一个写进程和一个读进程、并允许它们以生产者—消费者方式进行通信的一个共享文件,又称为pipe文件。由于写进程从管道的入端将数据写入管道,而读进程则从管道的出端读出数据.进程利用管道进行通信:将索引结点中的直接地址项作为一个循环队列来管理,为它设置一个读指针,一个写指针,按先进先出的顺序读写.在该例中,利用fd[2]这个数组来确定读写替换。执行进程1时,lockf(fd[1],1,0)使得管道写入端加锁,这样就可以进行写入了,write(fd[1],buf,5
8、0); /*信息写入管道*/ lockf(fd[1],0,0); /*管道写入端解锁*/这样其他进程就能继续进行读写操作了。然后执行进程2的写入,过程同上。最后父进程的进行读,read(fd[0],s,50)。读写之间、写和写之间也是互斥的,多以会加锁. 答2: 修改1号子进程的睡眠时间长度修改为20s后,等待了较长时间,然后出现了下面的结果:2号子进程先执行了,然后父进程执行。原因应该是:父进程进行wait()等待子进程,由于2号子进程的睡眠时间短,于是就先执行完了2号子进程的内容,然后才第二次wait()时才执行1号子进程,最后父进程结尾。 答3:加锁解锁的作用:为了使得读、写进程能够
9、互斥的访问pipe文件,每逢继承访问pipe文件前,都需检查该索引节点是否已经上锁,若已经锁住,进程便睡眠等待;否则,将索引节点上锁,然后再进行读、写操作,操作结束后又将该索引节点解锁,并唤醒因该节点上锁而睡眠的进程。这样就能实现了读、写之间的互斥操作,每次只能有一个进程进行访问pipe文件. 实验7: 编程验证:实现父子进程通过管道进行通信.进一步编程,验证子进程结束,由父进程执行撤消进程的操作。测试父进程先于子进程结束时,系统如何处理“孤儿进程”的。 只要在父进程后加上wait()函数,然后打印“子进程已经结束”,一旦子进程结束,父进 程撤销进程.当父进程先于子进程终止时.所有子
10、进程的父进程改变为init进程称为进程由init进程领养. 实验8: 编写两个程序,一个是服务者程序,一个是客户程序。执行两个进程之间通过消息机制通信。消息标识MSGKEY 可用常量定义,以便双方都可以利用.客户将自己的进程标识(pid)通过消息机制发送给服务者进程。服务者进程收到消息后,将自己的进程号和父进程号发送给客户,然后返回.客户收到后显示服务者的pid 和ppid,结束。 结果: 思考:想一下服务者程序和客户程序的通信还有什么方法可以实现?解释一下你的设想,有兴趣试一试吗。 答:还可以通过管道操作。或者用信号量机制来实现.信号量是一个整形计数器,用来控制多个进程对共享资源
11、的访问。或者通过消息队列信号机制,通过向消息队列发送信息、接收信息来实现进程间的通信。 实验9: 编程实现软中断信号通信.父进程设定软中断信号处理程序,向子进程发软中断信号。子进程收到信号后执行相应处理程序。 结果: 思考:这就是软中断信号处理,有点儿明白了吧?讨论一下它与硬中断有什么区别?看来还挺管用,好好利用它。 答:软中断发生的时间是由程序控制的,而硬中断发生的时间是随机的.软中断是由程序调用发生的,而硬中断是由外设引发的。 实验10:用信号量机制编写一个解决生产者-消费者问题的程序。 研究并讨论: 1。 讨论Linux 系统进程运行的机制和特点,系统通过什么来管理进程?
12、 系统通过进程控制块(PCB)来管理进程.PCB主要分为四部分:(1)进程表项,(2)U区,(3)系统区表,(4)进程区表。 2。C语言中是如何使用Linux 提供的功能的?用程序及运行结果举例说明。 Linux 系统的一种用户接口是程序员用的编程接口,即系统调用。系统调用的目的 是使用户可以使用操作系统提供的有关进程控制、文件系统、输入输出系统、设备管理、 通信及存储管理等方面的功能,而不必涉及系统内部结构和硬件细节,大大减少用户程序 设计和编程难度. Linux 的系统调用以标准实用子程序形式提供给用户在编程中使用。一般使用系统调 用应注意以下三点:(1)函数所在的头文件(2
13、函数调用格式,包括参数格式、类型.(3)返回值格式、类型。 例如:程序:在该程序中调用了系统程序”date”。 #include 〈unistd。h〉 main() { execlp(”date”,”date”,(char *)0); return; } 结果如下: 3.什么是进程?如何产生的?举例说明。 答: 进程实体是程序段、相关的数据段和PCB三部分构成的实体。进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。进程的产生:进程是由创建而产生的,即使进程实体运行时而产生的。例如:父进程通过fork()程序创建一个子进程. 4。进程控制如何实现的?
14、举例说明。 答:进程控制一般由OS的内核中的原语来实现的。例如:用完了I/O设备的进程调用原语weakup(),将等待该事件的进程唤醒. 5.进程通信方式各有什么特点?用程序及运行结果举例说明. 答:通信方式有:(1)共享存储器系统:相互通信的进程共享某些数据结构或共享存储区,进程之间能够通过这些空间进行通信。(2)消息传递系统:源程序可以直接或间接地将消息传送给目标进程.(3)管道通信系统:能够连接一个写进程和一个读进程、并允许它们以生产者—消费者方式 进行通信的一个共享文件,又称为pipe文件。由于写进程从管道的入端将数据写入管道,而读进程则从管道的出端读出数据。 6。管道通信如
15、何实现?该通信方式可以用在何处? 答:实现:(1)互斥,即当一个进程正在对pipe执行读/写操作时其它进程必须等待;(2)同步,指当写(输入)进程把一定数量的数据写入pipe时,便去睡眠等待,知道读(输出)进程取走数据后,再把它唤醒。当读进程读一空pipe时,也应睡眠等待,直至写进程将数据写入管道后,才将之唤醒;(3)确定对方是否存在,只有确定了对方已存在时,才能进行通信。用法:用于链接一个读进程和一个写进程以实现它们之间的通信的一个共享文件。 7。什么是软中断?软中断信号通信如何实现? 答:每个信号读对应一个正整数常量,代表同一用户的诸进程之间传送事先约定的信息的类型,用于通知某进程发
16、生了某一场时间。每个进程在运行时,都要通过信号量机制来检查是否有信号到达。若有,便中断正在执行的程序,转向与该信号对应的处理程序,以完成对该事件的处理,处理结束后再返回到原来的断电继续执行.实质上,信号机制是对中断机制的一种模拟. 体会: 在本次实验中,我利用Linux提供的系统调用设计程序,加深了对进程概念的理解,也体会到了系统进程调度的方法和效果,还了解了进程之间的通信方式以及各种通信方式的使用。虽然在实验的过程中遇到很多困难,但是还好有张涛老师的悉心指点和网上资料的帮助,使我能渡过难关,圆满完成本次实验,在此也向他们说一声感谢。 附录:(源程序) 1、 #include 〈s
17、tdio。h>
#include 〈stdlib。h〉
#include 〈unistd.h>
#include 18、0)
{
exit(1);
}
wait(0);
printf("Do you want to stop the process?\n”);
char x;
scanf(”%c",&x);
if(x == ’y’)
exit(0);
return 0;
}
2、
#include 19、 wait(0);
printf("It is parent process.\n”);
printf(”The child process,ID number %d, is finished.\n”,i);
}
else
{
printf(”It is child process.\n");
sleep(10);
exit(0);
}
}
3、
#include 〈stdio。h〉
#include 〈stdlib。h>
#include 20、std。h〉
#include 21、wait(0);
printf(”%d:The chile %d is finished。\n”,getpid(),j);
}
}
4、
#include 〈stdio。h〉
#include 〈stdlib。h〉
#include 22、d1=fork();
if(child_pid1==0)
{
execlp("echo”,”echo”,"child process 1",(char *)0);
perror(”exec1 error。\n ”);
exit(1);
}
child_pid2=fork();
if(child_pid2==0)
{
execlp(”date”,”date”,(char *)0);
perror(”exec2 err 23、or。\n ”); exit(2);
}
child_pid3=fork();
if(child_pid3==0)
{
execlp(”ls”,"ls",(char *)0);
perror(”exec3 error.\n ”);
exit(3);
}
puts(”Parent process is waiting for chile process return!");
while((pid=wait(&status))!=—1)
24、{
if(child_pid1==pid)
printf("child process 1 terminated with status %d\n”,(status>〉8));
else
{
if(child_pid2==pid)
printf(”child process 2 terminated with status %d\n”,(status〉>8));
else
25、 {
if(child_pid3==pid)
printf(”child process 3 terminated with status %d\n” ,(status>〉8));
}
}
}
puts("All child processes terminated。");
puts(”Parent process terminated.");
exit(0);
}
5、
#include 26、h>
#include 〈stdlib.h>
#include 〈unistd.h〉
#include 27、 {
globa++;
vari——;
printf("Child %d changed the vari and globa.\n”,getpid());
}
else
printf("Parent %d did not changed the vari and globa.\n”,getpid());
printf(”pid=%d, globa=%d, vari=%d\n”,getpid() 28、globa,vari);
exit(0);
}
6、
#include 29、lockf(fd[1],1,0);
sprintf(buf,"Child process P1 is sending messages! \n");
printf(”Child process P1! \n”);
write(fd[1],buf,50); lockf(fd[1],0,0);
sleep(5);
j=getpid();
k=getppid();
printf(”P1 %d is weakup. My parent process 30、 ID is %d.\n”,j,k);
exit(0);
}
else
{
while((p2=fork())==-1);
if(p2==0)
{
lockf(fd[1],1,0);
sprintf(buf,"Child process P2 is sending messages! \n”);
printf(”Child process P2! \n”); write(fd[ 31、1],buf,50); lockf(fd[1],0,0);
sleep(5);
j=getpid();
k=getppid();
printf(”P2 %d is weakup。 My parent process ID is %d.\n”,j,k);
exit(0);
}
else
{
l=getpid();
32、 wait(0);
if(r=read(fd[0],s,50)==-1)
printf(”Can’t read pipe。 \n”);
else
printf("Parent %d: %s \n",l,s);
wait(0);
if(r=read(fd[0],s,50)==—1)
printf(”Can’t read pipe. \n”);
33、
else
printf(”Parent %d: %s \n”,l,s);
exit(0);
}
}
}
7、
8、
服务器程序:
#include 34、Y 75
struct msgform
{
long mtype;
char mtext[256];
}msg;
int msgqid;
int main()
{
int i,pid,* pint;
extern cleanup();
for(i=0;i〈20;i++)
signal(i,cleanup);
msgqid=msgget(MSGKEY,0777|IPC_CREAT);
for(;;)
{
msgrcv(msgqid,&msg,25 35、6,1,0);
pint=(int *)msg.mtext;
pid=*pint;
printf("Server :receive from pid %d\n",pid);
msg。mtype=pid;
*pint=getpid();
msgsnd(msgqid,&msg,sizeof(int),0);
}
}
cleanup()
{
msgctl(msgqid,IPC_RMID,0);
exit(0);
}
客户端程 36、序:
#include 〈stdio.h〉
#include 37、pint;
msgqid=msgget(MSGKEY,0777);
pid=getpid();
pint=(int *)msg.mtext;
*pint=pid;
msg.mtype=1;
msgsnd(msgqid,&msg,sizeof(int),0);
msgrcv(msgqid,&msg,256,pid,0);
printf(”Clint : receive from pid %d\n",* pint);
}
9、
#include 38、b.h>
#include 39、s been sent to child %d,returned %d.\n",i,j);
k=wait(0);
printf(”After wait %d,Parent %d: finished.\n",k,getpid());
}
else
{
sleep(10);
printf(”Child %d: A signal from my parent is recived。\n”,getpid());
}
}
func()
{
int m; 40、
m=getpid();
printf(”I am Process %d: It is signal 18 processing function.\n”,m);
}
10、
#include 〈stdio.h>
#include 〈stdlib.h〉
#include 41、 // 生产者放置产品的位置
int out = 0; // 消费者取产品的位置
int buff[M] = { 0 }; // 缓冲初始化为0,开始时没有产品
sem_t empty_sem; // 同步信号量,当满了时阻止生产者放产品
sem_t full_sem; // 同步信号量,当没产品时阻止消费者消费
pthread_mutex_t mutex; // 互斥信号量,一次只有一个线程访问缓冲
int product_id = 0; //生产者
id int prochase_id = 0; //消费者id
//信号处理函数
void Han 42、dlesignal(int signo){
printf(”程序退出\n”,signo);
exit(0);
}
/* 打印缓冲情况 */
void print() {
inti;
printf("产品队列为");
for(i = 0; i < M; i++)
printf("%d”, buff[i]);
printf(”\n”);
}
/* 生产者方法 */
void *product() {
int id = ++pr 43、oduct_id;
while(1) {//重复进行
//用sleep的数量可以调节生产和消费的速度,便于观察
sleep(2);
sem_wait(&empty_sem);
pthread_mutex_lock(&mutex);
in= in % M;
printf(”生产者%d在产品队列中放入第%d个产品\t”,id, in);
buff[in 44、]= 1;
print();
++in;
pthread_mutex_unlock(&mutex);
sem_post(&full_sem);
}
}
/* 消费者方法 */
void *prochase() {
intid = ++prochase_id;
while(1) {//重复进行
//用sleep的数量可以调节生产和消费的速度,便于观察
45、 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_ 46、unlock(&mutex);
sem_post(&empty_sem);
}
}
int main() {
printf("生产者和消费者数目都为5,产品缓冲为10,生产者每2秒生产一个产品,消费者每5秒消费一个产品,Ctrl+退出程序\n");
pthread_tid1[N];
pthread_tid2[N];
inti;
intret[N];
//结束程序
if(signal(SIGINT,Handlesignal)== 47、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);
}
// 48、初始化互斥信号量
intini3 = pthread_mutex_init(&mutex, NULL);
if(ini3 != 0) {
printf(”线程同步初始化失败!\n");
exit(1);
}
// 创建N个生产者线程
for(i = 0; i 〈 N; i++) {
ret[i]= pthread_create(&id1[i], NULL, product, (void *) (&i));
49、 if(ret[i] != 0) {
printf(”生产者%d线程创建失败!\n”, i);
exit(1);
}
}
//创建N个消费者线程
for(i = 0; i 〈 N; i++) {
ret[i]= pthread_create(&id2[i], NULL, prochase, NULL);
if(ret[i] != 0) {
printf(”消费者%d线程创建失败!\n”, i);
exit(1);
}
}
//等待线程销毁
for(i = 0; i 〈 N; i++) {
pthread_join(id1[i], NULL);
pthread_join(id2[i],NULL);
}
exit(0);
}






