一.实验目的
(1)初步了解Linux环境下进程创建和进程间通信的机制
(2)掌握如何利用消息和共享内存进行通信的原理
(3)利用POSIX API函数编写实例程序
(4)实现父子进程间通过消息传递和共享内存方式进行通信。
二.实验内容
编制两个程序P1、P2。当这程序P1运行后,它创建一个子进程P2(也就是在P1中调用P2),其中P1是父进程,P2由P1创建,是P1的子进程。
P1和P2利用两种机制进行通信:消息和共享内存。P1输入4个数字并指定通讯模式,通过消息或共享内存方式传送给P2;P2接收到后计算这四个数字和加减乘除四个运算符结合后是否可以算出24;把结果(包括所有可能的计算方式)通过消息机制返回P1并显示。
三.实验步骤和结果
1、编写进程通信代码shmmsg.c。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#define size 32 //共享内存大小
#define MSGKEY 75 //消息队列键值
char op[5] = { '#', '+', '-', '*', '/', };
int flag = 0; //标志位,记录是否能算出24点
int shmid; //共享内存id
char buff[8000];
//消息队列结构体
struct msgform{
long mtype; //消息类型
char mtext[8192]; //消息内容
};
float cal(float x, float y, int op)
{
switch (op) //将运算符初始化为1,2,3,4四个数字
{
case 1:return x + y;
case 2:return x - y;
case 3:return x * y;
case 4:return x / y;
}
}
//对应表达式((AoB)oC)oD
float m1(float i, float j, float k, float t, int op1, int op2, int op3)
{
float r1, r2, r3;
r1 = cal(i, j, op1);
r2 = cal(r1, k, op2);
r3 = cal(r2, t, op3);
return r3;
}
//对应表达式(Ao(BoC))oD
float m2(float i, float j, float k, float t, int op1, int op2, int op3)
{
float r1, r2, r3;
r1 = cal(j, k, op2);
r2 = cal(i, r1, op1);
r3 = cal(r2, t, op3);
return r3;
}
//对应表达式Ao(Bo(CoD))
float m3(float i, float j, float k, float t, int op1, int op2, int op3)
{
float r1, r2, r3;
r1 = cal(k, t, op3);
r2 = cal(j, r1, op2);
r3 = cal(i, r2, op1);
return r3;
}
//对应表达式Ao((BoC)oD)
float m4(float i, float j, float k, float t, int op1, int op2, int op3)
{
float r1, r2, r3;
r1 = cal(j, k, op2);
r2 = cal(r1, t, op3);
r3 = cal(i, r2, op1);
return r3;
}
//对应表达式(AoB)o(CoD)
float m5(float i, float j, float k, float t, int op1, int op2, int op3)
{
float r1, r2, r3;
r1 = cal(i, j, op1);
r2 = cal(k, t, op3);
r3 = cal(r1, r2, op2);
return r3;
}
void get24(int i, int j, int k, int t)
{
int op1, op2, op3;
for (op1 = 1; op1 <= 4; op1++) { //通过一个三重循坏列举了符号的变化情况
for (op2 = 1; op2 <= 4; op2++) {
for (op3 = 1; op3 <= 4; op3++) {
if (m1(i, j, k, t, op1, op2, op3) == 24)
{
char mes[24];
sprintf(mes,"((%d%c%d)%c%d)%c%d=24\n", i, op[op1], j, op[op2], k, op[op3], t);
strcat(buff,mes);
flag = 1;
}
if (m2(i, j, k, t, op1, op2, op3) == 24)
{
char mes[24];
sprintf(mes,"(%d%c(%d%c%d))%c%d=24\n", i, op[op1], j, op[op2], k, op[op3], t);
strcat(buff,mes);
flag = 1;
}
if (m3(i, j, k, t, op1, op2, op3) == 24)
{
char mes[24];
sprintf(mes,"%d%c(%d%c(%d%c%d))=24\n", i, op[op1], j, op[op2], k, op[op3], t);
strcat(buff,mes);
flag = 1;
}
if (m4(i, j, k, t, op1, op2, op3) == 24)
{
char mes[24];
sprintf(mes,"%d%c((%d%c%d)%c%d)=24\n", i, op[op1], j, op[op2], k, op[op3], t);
strcat(buff,mes);
flag = 1;
}
if (m5(i, j, k, t, op1, op2, op3) == 24)
{
char mes[24];
sprintf(mes,"(%d%c%d)%c(%d%c%d)=24\n", i, op[op1], j, op[op2], k, op[op3], t);
strcat(buff,mes);
flag = 1;
}
}
}
}
}
int main()
{
//共享内存
key_t key = ftok("./shm", 'b');
shmid = shmget(key, size, 0644 | IPC_CREAT);
if (shmid == -1)
{
perror("shm get error!\n");
exit(1);
}
else
{
char *ptr = (char *)shmat(shmid, NULL, 0);
ptr[0]=1;
printf("start\n");
}
//消息队列
int id;
struct msgform msg;
id=msgget(MSGKEY,0777|IPC_CREAT);
if (id==-1)
{
perror("msg get error!\n");
exit(1);
}
//创建子进程
pid_t pid2 = fork();
if (pid2 < 0)
{
perror("fork error!");
exit(1);
}
//-----------------------------子进程代码--------------------------------
if (pid2 == 0)
{
int i,j,k,t=0;
char *p2 = (char *)shmat(shmid, NULL, 0);
while (p2[0]!=0)
{
sleep(1);
}
printf("this is the child process P2!\n");
strcpy(buff,"the result is:\n");
if (p2[2]==0)
{
i=p2[3];
j=p2[4];
k=p2[5];
t=p2[6];
}
else if (p2[2]==1)
{
msgrcv(id,&msg,sizeof(msg),1,0); //接收父进程消息
sscanf(msg.mtext,"%d %d %d %d",&i,&j,&k,&t);
}
int a[4] = { i,j,k,t };
for (int b = 0; b < 4; b++)
{
for (int c = 0; c < 4; c++)
{
if (c != b)
{
for (int d = 0; d < 4; d++)
{
if (d != b && d != c)
{
for (int e = 0; e < 4; e++)
{
if (e != b && e != c && e != d)
{
get24(a[b], a[c], a[d], a[e]);
}
}
}
}
}
}
}
printf("I have count the 4 numbers if they can get 24,the result is sent by msg\n");
if (flag == 0)
{
msg.mtype=2;
strcpy(msg.mtext,"这四个数算不出来24\n");
msgsnd(id,&msg,sizeof(char)*8192,0);
}
else
{
msg.mtype=2;
strcpy(msg.mtext,buff);
msgsnd(id,&msg,sizeof(char)*8192,0);
}
p2[0]=2; //信号量控制
}
//----------------------------父进程代码-----------------------------
else if (pid2 > 0)
{
int num1,num2,num3,num4=0;
int mode=0;
char *p1 = (char *)shmat(shmid, NULL, 0);
//------------父进程向子进程通信---------------
while (p1[0]!=1)
{
sleep(1);
}
printf("this is the parent process P1!\n");
printf("please select a communication mode(0 represent shm,1 represent msg):\n");
scanf("%d",&mode);
printf("please input 4 numbers:\n");
scanf("%d %d %d %d",&num1,&num2,&num3,&num4);
p1[2]=mode; //指定通讯模式
if (p1[2]==0) //共享内存
{
p1[3]=num1;
p1[4]=num2;
p1[5]=num3;
p1[6]=num4;
printf("4 numbers are sent by shm\n");
p1[0]=0; //信号量控制
}
else if (p1[2]==1) //消息队列
{
msg.mtype=1;
sprintf(msg.mtext,"%d %d %d %d",num1,num2,num3,num4);
msgsnd(id,&msg,sizeof(char)*8192,0);
printf("4 numbers are sent by msg\n");
p1[0]=0;
}
//------------------父进程接收子进程消息-------------------
while (p1[0]!=2)
{
sleep(1);
}
if (msgrcv(id,&msg,sizeof(char)*8192,2,0) == -1) {
printf("father process failed to receive!\n");
}
printf("this is the parent process P1!\n");
printf("%s",msg.mtext);
//删除消息队列和共享内存
msgctl(id,IPC_RMID,0);
if (shmctl(shmid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
}
return 0;
}
2、使用命令cc -o shmmsg shmmsg.c编译源文件,使用命令./shmmsg执行文件。
(1)P1通过共享内存向P2发送四个数字1 9 10 11,P2返回“这四个数算不出来24”。
(2)P1通过消息队列向P2发送数字2 5 6 9,P2返回所有可能情况:
四.实验问题分析
1、本次实验稍有难度,主要在于创建子进程的方法上,我使用的是fork来创建子进程,它会将父进程之前的代码都复制和执行一遍,导致一些全局变量并没有起到全局的作用,所以在前期的时候,一直在不停的测试fork子进程的执行顺序,花了大量时间。后来转念一想,直接就用共享内存来存放全局变量不是就可以了吗,单独分配一块区域用来进行子进程和父进程之间通信顺序的控制。
2、一开始的时候,我只写了共享内存的通信方式,还一切顺利,后来加了一个消息队列进来后,发现只要是涉及到消息队列的语句都执行不了,光标就停在了消息队列发送接收执行语句之前,但是消息队列已经创建成功了。
我找了好久之后才发现是msgsnd、msgrcv这两个消息队列函数使用的有问题,这两个函数都有一个传入参数msgsz,他表示要发送或接收消息的大小,不含消息类型占用的4个字节,即mtext的长度,而我在写的时候传的参数是sizeof(msgform),是整个消息结构体的大小,没有去掉消息类型占用的四个字节。
3、在解决了上面的问题之后发现,子进程可以接收到父进程发来的消息了,但是父进程却收不到子进程发回来的消息,这个就是之前第一个问题里讲到的父子进程之间的执行顺序问题。
我的销毁共享内存和消息队列的语句一开始是写在main函数的最末尾,独立于子进程和父进程的分支,我一开始以为这样就可以在父子进程执行完毕后释放内存了。但其实,在子进程处理完24点,将相应的消息再发送给父进程后,他就去执行后面的销毁语句了,并不会等待父进程接收到相应的消息,所以父进程就会接收不到消息。
解决办法就是将销毁语句放在父进程代码的最末尾处,因为按照顺序父进程应该是最后执行的,等到父进程接收完相应的消息后,再释放相应的内存空间就可以了。
五.实验总结
本次实验我通过利用POSIX API函数编写实例程序,实现了父子进程间通过消息传递和共享内存方式进行通信。初步了解了Linux环境下进程创建和进程间通信的机制,掌握了如何利用消息和共享内存进行通信的原理。