1. 实验目的
- 通过基础实验,基本掌握共享内存的程序设计。
- 通过编写程序,使读者掌握消息队列的设计方法。
- 通过编写程序,掌握通道通信的设计方法
2. 实验内容
- 共享内存程序设计:创建两个进程,在A进程中创建一个共享内存,并向其写入数据,通过B进程从共享内存中读出数据。
- 消息队列程序设计:创建一个消息队列,如何使用消息队列进行两个进程(发送端和接受端)之间的通信,包括消息队列的创建、消息发送与读取、消息队列的撤销和删除等多种操作。
- 管道通信程序设计:编写程序实现进程的管道通信,掌握有名管道的创建和读写方式,并熟悉LINUX支持的有名管道通信方式
3. 实验要求
理解进程通信的不同方法,要求仿真实现共享内存方式或消息传递方式或管道方式。运行程序并分析实验结果。
实验报告中给出程序中使用的数据结构及流程图。
4. 实验预习内容
参阅相关资料,熟悉共享内存、消息队列、管道通信策略及实现技术。
5. 实验指导
(一)共享内存程序设计
- 函数说明:共享内存的实现分为两个步骤,第一步是创建共享内存,这里用到的函数是shmget(),也就是从内存中获得一段共享内存区域。第二步映射共享内存,也就是把这段创建的共享内存映射到具体的进程空间中,这里使用的函数是shmat()。到这里,就可以使用这段共享内存了,也就是可以使用不带缓冲的I/O读写命令对其进行操作。除此之外,当然还有撤销映射的操作,其函数为shmdt()。
- 共享内存的用法:使用共享内存进行进程间通信一般要经历下面几个步骤:
- 分配:进程通过调用shmget来分配一个共享内存块。
- 映射:要让一个进程获取对一块共享内存的访问,这个进程必须先调用shmat映射共享内存。
- 脱离与释放:当进程结束使用共享内存时,使用shmdt使共享内存脱离进程。当不再需要共享内存时,使用shmctl(sid,IPC_RMID,0)删除它。
实验步骤及代码:
1)自己建立文件夹,然后分别编辑shm_com.h、shm1.c、shm2.c.
/*shm_com.h*/
#define TEXT_SZ 2048
struct shared_use_st
{
int written_by_you;
char some_text[TEXT_SZ];
};
功能描述:本程序申请和分配共享内存,然后轮询并读取共享内存中的数据,直至读到“end”
/*shm1.c*/
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include "shm_com.h"
int main(void)
{
int running=1;
void *shared_memory=(void *)0;
struct shared_use_st *shared_stuff;
int shmid;
/*创建共享内存*/
shmid=shmget((key_t) 1234,sizeof(struct shared_use_st),
0666|IPC_CREAT);
if(shmid==-1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
/*映射共享内存*/
shared_memory=shmat(shmid,(void *)0,0);
if(shared_memory==(void *)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n",(int)shared_memory);
/*让结构体指针指向这块共享内存*/
shared_stuff=(struct shared_use_st *)shared_memory;
/*控制读写顺序*/
shared_stuff->written_by_you=0;
/*循环地从共享内存中读数据,直到读到“end”为止*/
while(running)
{
if(shared_stuff->written_by_you)
{
printf("You wrote:%s",shared_stuff->some_text);
/*读进程睡眠1秒,同时会导致写进程睡眠1秒,做到先读后写 */
sleep(1);
shared_stuff->written_by_you=0;
if(strncmp(shared_stuff->some_text,"end",3)==0)
{
running=0;//结束循环
}
}
}
/*删除共享内存*/
if(shmdt(shared_memory)==-1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
功能描述:本程序申请了上一段程序相同的共享内存,然后循环地向共享内存中写数据,直至写入“end”
/*shm2.c*/
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include "shm_com.h"
int main(void)
{
int running=1;
void *shared_memory=(void *)0;
struct shared_use_st *shared_stuff;
char buffer[BUFSIZ];
int shmid;
/*创建共享内存*/
shmid=shmget((key_t) 1234,sizeof(struct shared_use_st),
0666|IPC_CREAT);
if(shmid==-1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
/*映射共享内存*/
shared_memory=shmat(shmid,(void *)0,0);
if(shared_memory==(void *)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n",(int)shared_memory);
/*让结构体指针指向这块共享内存*/
shared_stuff=(struct shared_use_st *)shared_memory;
/*循环地向共享内存中写入数据,直到写入的为“end”为止*/
while(running)
{
while(shared_stuff->written_by_you==1)
{
sleep(1); //等到读进程读完之后再写
printf("waiting for client...\n");
}
printf("ENter some text:");
fgets(buffer,BUFSIZ,stdin);
strncpy(shared_stuff->some_text,buffer,TEXT_SZ);
shared_stuff->written_by_you=1;
if(strncmp(buffer,"end",3)==0)
{
running=0; //结束循环
}
}
/*删除共享内存*/
if(shmdt(shared_memory)==-1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
2)使用gcc shm1.c –o shm1和gcc shm2.c –o shm2 分别编译shm1.c和shm2.c
3)在一个终端运行shm1,在另一个终端运行shm2。当shm1运行起来后,由于共享内存中没有数据可读,会处于等待状态。在向shm2运行的终端中输入字符串后,shm1能够逐个从共享内存中把它们读出来,直到双方遇到字符串“end”后,两个程序都退出。
运行shm1
运行shm2并输入字符串
shm1读出共享内存中的数据
(二)消息队列程序设计
编写一个应用程序,创建一个消息队列,如何使用消息队列进行两个进程(发送端和接受端)之间的通信,包括消息队列的创建、消息发送与读取、消息队列的撤销和删除等多种操作。
(1)消息队列发送端的代码
/* msgsnd.c */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 512
struct message
{
long msg_type;
char msg_text[BUFFER_SIZE];
};
int main()
{
int qid;
key_t key;
struct message msg;
/*根据不同的路径和关键表示产生标准的key*/
if ((key = ftok(".", 'a')) == -1)
{
perror("ftok");
exit(1);
}
/*创建消息队列*/
if ((qid = msgget(key, IPC_CREAT|0666)) == -1)
{
perror("msgget");
exit(1);
}
printf("Open queue %d\n",qid);
while(1)
{
printf("Enter some message to the queue(enter 'quit' to exit):");
if ((fgets(msg.msg_text, BUFFER_SIZE, stdin)) == NULL)
{
puts("no message");
exit(1);
}
msg.msg_type = getpid();
/*添加消息到消息队列*/
if ((msgsnd(qid, &msg, strlen(msg.msg_text), 0)) < 0)
{
perror("message posted");
exit(1);
}
if (strncmp(msg.msg_text, "quit", 4) == 0)
{
break;
}
}
exit(0);
}
(2)消息队列接收端的代码
/* msgrcv.c */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 512
struct message
{
long msg_type;
char msg_text[BUFFER_SIZE];
};
int main()
{
int qid;
key_t key;
struct message msg;
/*根据不同的路径和关键表示产生标准的key*/
if ((key = ftok(".", 'a')) == -1)
{
perror("ftok");
exit(1);
}
/*创建消息队列*/
if ((qid = msgget(key, IPC_CREAT|0666)) == -1)
{
perror("msgget");
exit(1);
}
printf("Open queue %d\n", qid);
do
{
/*读取消息队列*/
memset(msg.msg_text, 0, BUFFER_SIZE);
if (msgrcv(qid, (void*)&msg, BUFFER_SIZE, 0, 0) < 0)
{
perror("msgrcv");
exit(1);
}
printf("The message from process %d : %s", msg.msg_type, msg.msg_text);
} while(strncmp(msg.msg_text, "quit", 4));
/*从系统内核中移走消息队列 */
if ((msgctl(qid, IPC_RMID, NULL)) < 0)
{
perror("msgctl");
exit(1);
}
exit(0);
}
运行程序,输入quit两个程序都结束。
#./msgsnd
(三)管道通信程序设计
1、了解什么是管道,并熟悉LINUX支持的管道通信方式
编写程序实现进程的管道通信。用系统调用pipe( )建立一管道,二个子进程P1和P2分别向管道各写一句话:
Child 1 is sending a message!
Child 2 is sending a message!
父进程从管道中读出二个来自子进程的信息并显示(要求先接收P1,后P2)。
参考代码:
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
int pid1,pid2;
main( )
{
int fd[2];
char outpipe[100],inpipe[100];
pipe(fd); /*创建一个管道*/
while ((pid1=fork( ))= =-1);
if(pid1= =0)
{
lockf(fd[1],1,0);
sprintf(outpipe,"child 1 process is sending message!");
/*把串放入数组outpipe中*/
write(fd[1],outpipe,50); /*向管道写长为50字节的串*/
sleep(5); /*自我阻塞5秒*/
lockf(fd[1],0,0);
exit(0);
}
else {
while((pid2=fork( ))= =-1);
if(pid2= =0)
{ lockf(fd[1],1,0); /*互斥*/
sprintf(outpipe,"child 2 process is sending message!");
write(fd[1],outpipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
}
else
{ wait(0); /*同步*/
read(fd[0],inpipe,50); /*从管道中读长为50字节的串*/
printf("%s/n",inpipe);
wait(0);
read(fd[0],inpipe,50);
printf("%s/n",inpipe);
exit(0);
}
}
}
2、掌握有名管道的创建和读写方式,并熟悉LINUX支持的有名管道通信方式
- 创建有名管道
- 本进程执行循环等待数据被写入到管道中并读有名管道
- 打开有名管道并写数据到名管道
参考代码://read_fifo.c
//read_fifo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#define BUFFER_SIZE 1024
int main(int argc, char **argv)
{
int fd;
if (argc < 2)
{
fprintf(stdout, "Usage: %s <filename>\n", argv[0]);
exit(1);
}
//int open(const char *path, int oflag, ...);
if ((fd = open(argv[1], O_RDONLY)) < 0)
{
fprintf(stderr, "open fifo %s for reading failed: %s\n", argv[1], strerror(errno));
exit(1);
}
fprintf(stdout, "open fifo %s for reading successed.\n", argv[0]);
char buffer[BUFFER_SIZE];
ssize_t n;
while (1)
{
again:
//ssize_t read(int fd, void *buf, size_t count);
if ((n = read(fd, buffer, BUFFER_SIZE)) < 0)
{
if (errno == EINTR)
{
goto again;
}
else
{
fprintf(stderr, "read failed on %s: %s\n", argv[1], strerror(errno));
exit(1);
}
}
else if (n == 0)
{
fprintf(stderr, "peer closed fifo.\n");
break;
}
else
{
buffer[n] = '\0';
fprintf(stdout, "read %d bytes from fifo: %s\n", n, buffer);
}
}
return 0;
}
write_fifo.c
// write_fifo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#define BUFFER_SIZE 1024
void signal_handler(int s);
int main(int argc, char **argv)
{
int fd;
if (argc < 2)
{
fprintf(stdout, "Usage: %s <filename>\n", argv[0]);
exit(1);
}
signal(SIGPIPE, signal_handler);
//int open(const char *path, int oflag, ...);
if ((fd = open(argv[1], O_WRONLY)) < 0)
{
fprintf(stderr, "open fifo %s for writting failed: %s\n", argv[1], strerror(errno));
exit(1);
}
fprintf(stdout, "open fifo %s for writting successed.\n", argv[0]);
char buffer[BUFFER_SIZE];
ssize_t n;
//char *fgets(char *s, int size, FILE * stream);
while (fgets(buffer, BUFFER_SIZE, stdin))
{
again:
//ssize_t write(int fd, const void *buf, size_t count);
if ((n = write(fd, buffer, strlen(buffer))) < 0)
{
if (errno == EINTR)
{
goto again;
}
else
{
fprintf(stderr, "write() failed on fifo: %s\n", strerror(errno));
// FIXME:
break;
}
}
}
return 0;
}
void signal_handler(int s)
{
fprintf(stdout, "Caught signal %d\n", s);
}
// create_fifo.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
int main(int argc, char **argv)
{
if (argc < 2)
{
fprintf(stdout, "Usage: %s <filename>\n", argv[0]);
exit(1);
}
//int mkfifo(const char *path, mode_t mode);
if (mkfifo(argv[1], 0644) < 0)
{
fprintf(stderr, "mkfifo() failed: %s\n", strerror(errno));
exit(1);
}
return 0;
}