一、 实验目的
- 掌握无名管道与有名管道的进程通信;
- 掌握消息队列的读写操作;
- 掌握共享内存的通信机制。
二、 实验任务与要求
- 管道读写程序的编写与应用;
- 消息队列的发送和接收程序的编写和应用;
- 共享内存的创建、连接和分离编程和应用。
三、 实验工具和环境
PC机、Linux Ubuntu操作系统。
四、 实验内容与结果
- 利用无名管道通信编写程序实现命令cat的功能。
7.2.1-1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
// printf("%d", argc);
if (argc < 2)
{
printf("argv lost\n");
exit(0);
}
int fd[2];
int err = pipe(fd);
if (err == -1)
{
printf("pipe err\n");
exit(0);
}
pid_t pid = fork();
if (pid == -1)
exit(0);
else if (pid == 0)
{
close(fd[1]);
char buf[256] = {0};
int size = read(fd[0], buf, 256);
if (size > 0)
printf("son --- %s\n", buf);
else
printf("son read err\n");
close(fd[0]);
exit(0);
}
else if (pid > 0)
{
close(fd[0]);
int fd2, size2;
char buf2[256];
fd2 = open(argv[1], O_RDONLY);
if (fd2)
{
size2 = read(fd2, buf2, 256);
write(fd[1], buf2, 256);
}
close(fd2);
sleep(5);
close(fd[1]);
wait(NULL);
exit(0);
}
}
这段代码实现了一个简单的管道通信,父进程通过读取文件内容,将数据写入管道,子进程从管道中读取数据并打印。其中使用了fork创建子进程,pipe创建管道,open函数打开文件,read和write函数进行读写操作。程序在父进程中使用wait函数等待子进程退出。
- 设计两个程序:有名管道的读程序和写程序,要求利用有名管道实现聊天程序,每次发言后自动在后面增加当前系统时间。增加结束字符,比如最后输入“886”后结束进程。
写程序
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <time.h>
int main(void)
{
int fd;
int len;
char buf[PIPE_BUF];
time_t tp;
printf("I am %d\n", getpid());
if ((fd = open("fifo1", O_RDWR)) < 0)
{
perror("open");
exit(EXIT_FAILURE);
}
while (1)
{
time(&tp);
printf("\n[%d]请输入文字:", getpid());
char text[256];
fgets(text, (sizeof text / sizeof text[0]), stdin);
if (strcmp(text, "886\n") == 0)
{
printf("\n886!\n");
close(fd);
exit(EXIT_SUCCESS);
}
len = sprintf(buf, "-[%d]: %s%s", getpid(), text, ctime(&tp));
if ((write(fd, buf, len)) < 0)
{
perror("write");
close(fd);
exit(EXIT_FAILURE);
}
sleep(3);
}
close(fd);
exit(EXIT_SUCCESS);
}
读程序
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <time.h>
int main(void)
{
int fd;
int len;
char buf[PIPE_BUF];
mode_t mode = 0666;
system("rm fifo1 > null");
if ((mkfifo("fifo1", mode)) < 0)
{
perror("mkfifo");
exit(EXIT_FAILURE);
}
if ((fd = open("fifo1", O_RDONLY)) < 0)
{
perror("open");
exit(EXIT_FAILURE);
}
while ((len = read(fd, buf, PIPE_BUF)) > 0)
{
printf("%s", buf);
}
close(fd);
exit(EXIT_SUCCESS);
}
这两段代码是一个进程间通信的例子,使用了命名管道(FIFO)来实现。第一个程序是写程序,不断从命令行读取用户输入的文字,将其和当前时间一起发送到命名管道中。第二个程序是读程序,不断从命名管道中读取数据并输出到控制台。通过命名管道,实现了两个进程之间的通信。其中,mkfifo函数用于创建命名管道,open函数用于打开命名管道,read和write函数用于读写数据,close函数用于关闭文件描述符。
- 设计一个程序,要求用函数msgget创建消息队列,从键盘输入的字符串添加到消息队列,然后应用函数msgrcv读取队列中的消息并在计算机屏幕上输出。程序先调用msgget函数创建、打开消息队列,接着调用msgsnd函数,把输入的字符串添加到消息队列中,然后调用msgrcv函数,读取消息队列中的消息并打印输出,最后调用msgctl函数,删除系统内核中的消息队列。
// q3reader.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct my_msg_st
{
long int my_msg_type;
char some_text[BUFSIZ];
};
int main(void)
{
int running = 1;
int msgid;
struct my_msg_st some_data; // 定义消息变量
long int msg_to_receive = 0;
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if (msgid == -1)
{
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
/*创建消息队列*/
/*循环从消息队列中接收消息*/
while (running)
{
/*读取消息*/
if (msgrcv(msgid, (void *)&some_data, BUFSIZ, msg_to_receive, 0) == -1)
{
fprintf(stderr, "msgrcv failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
printf("You wrote: %s", some_data.some_text);
/*接收到的消息为“end”时结束循环*/
if (strncmp(some_data.some_text, "end", 3) == 0)
{
running = 0;
}
}
/*从系统内核中移走消息队列*/
if (msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
// q3writer.c
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MAX_TEXT 512
struct my_msg_st
{
long int my_msg_type;
char some_text[MAX_TEXT];
};
int main(void)
{
int running = 1;
struct my_msg_st some_data;
int msgid;
char buffer[BUFSIZ];
/*创建消息队列*/
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if (msgid == -1)
{
fprintf(stderr, "msgget failed with error:%d\n", errno);
exit(EXIT_FAILURE);
}
while (running)
{ /*循环向消息队列中添加消息*/
printf("Enter some text:");
fgets(buffer, BUFSIZ, stdin); // 从标准输入文件读取字符串赋给buffer
some_data.my_msg_type = 1;
strcpy(some_data.some_text, buffer); // buffer的内容复制给消息
/*添加消息*/
if (msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1)
{
fprintf(stderr, "msgsed failed\n");
exit(EXIT_FAILURE);
}
/*用户输入的为“end”时结束循环*/
if (strncmp(buffer, "end", 3) == 0)
{
running = 0;
}
}
exit(EXIT_SUCCESS);
}
这两段代码分别实现了消息队列的读和写操作。其中,q3reader.c创建了一个消息队列,并通过循环从中接收消息,如果接收到的消息为“end”,则结束程序;而q3writer.c循环向消息队列中添加消息,如果用户输入的消息为“end”,则结束程序。两段代码都使用了结构体my_msg_st来定义消息,其包含了消息类型my_msg_type和消息内容some_text。在创建/添加消息的时候,需要使用msgsnd/mssgrcv函数,并将my_msg_st作为参数传递进去。同时,需要使用msgget函数获取消息队列的ID,并使用msgctl函数移走消息队列。
- 设计两个程序要求用消息队列实现简单的聊天功能。
// q3reader.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct my_msg_st
{
long int my_msg_type;
char some_text[BUFSIZ];
};
int main(void)
{
int running = 1;
int msgid;
struct my_msg_st some_data; // 定义消息变量
long int msg_to_receive = 0;
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if (msgid == -1)
{
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
/*创建消息队列*/
/*循环从消息队列中接收消息*/
while (running)
{
/*读取消息*/
if (msgrcv(msgid, (void *)&some_data, BUFSIZ, msg_to_receive, 0) == -1)
{
fprintf(stderr, "msgrcv failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
printf("You wrote: %s", some_data.some_text);
/*接收到的消息为“end”时结束循环*/
if (strncmp(some_data.some_text, "end", 3) == 0)
{
running = 0;
}
}
/*从系统内核中移走消息队列*/
if (msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
// q3writer.c
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MAX_TEXT 512
struct my_msg_st
{
long int my_msg_type;
char some_text[MAX_TEXT];
};
int main(void)
{
int running = 1;
struct my_msg_st some_data;
int msgid;
char buffer[BUFSIZ];
/*创建消息队列*/
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if (msgid == -1)
{
fprintf(stderr, "msgget failed with error:%d\n", errno);
exit(EXIT_FAILURE);
}
while (running)
{ /*循环向消息队列中添加消息*/
printf("Enter some text:");
fgets(buffer, BUFSIZ, stdin); // 从标准输入文件读取字符串赋给buffer
some_data.my_msg_type = 1;
strcpy(some_data.some_text, buffer); // buffer的内容复制给消息
/*添加消息*/
if (msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1)
{
fprintf(stderr, "msgsed failed\n");
exit(EXIT_FAILURE);
}
/*用户输入的为“end”时结束循环*/
if (strncmp(buffer, "end", 3) == 0)
{
running = 0;
}
}
exit(EXIT_SUCCESS);
}
- 在主程序中先调用shmget函数创建一个共享内存,得到共享内存的id,然后利用shmat函数将创建的共享内存连接到一个进程的地址空间,返回值为该内存空间的地址指针,利用地址指针对共享内存进行访问;最后利用shmdt函数分离进程和共享内存。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#define BUFSZ 4096
int main(int argc, char *argv[])
{
int shm_id;
char *shm_buf;
key_t key;
system("touch shmfile");
// 生成一个共享内存段的唯一键
key = ftok("shmfile", 65);
if (key == -1)
{
perror("ftok");
exit(1);
}
// 用键和共享内存段的大小创建一个共享内存段并获取它的 ID
shm_id = shmget(key, BUFSZ, 0666 | IPC_CREAT);
if (shm_id < 0)
{
perror("shmget");
exit(1);
}
printf("successfully created segment: %d \n", shm_id);
// 将共享内存段连接到进程地址空间并获取一个指向它的指针
if ((shm_buf = shmat(shm_id, NULL, 0)) == (char *)-1)
{
perror("shmat");
exit(1);
}
printf("segment attached at %p\n", shm_buf);
system("ipcs -m");
sleep(3); /*休眠*/
// 将共享内存段从进程地址空间分离
if ((shmdt(shm_buf)) < 0)
{
perror("shmdt");
exit(1);
}
printf("segment detached \n");
system("ipcs -m "); /*再次查看系统IPC状态*/
// 删除共享内存段
if (shmctl(shm_id, IPC_RMID, NULL) == -1)
{
perror("shmctl");
exit(1);
}
printf("segment removed \n");
system("ipcs -m "); /*再次查看系统IPC状态*/
exit(0);
}
这段代码演示了创建、连接、分离、删除共享内存段的过程。首先使用ftok函数生成一个共享内存段的唯一键,然后使用shmget函数创建一个共享内存段并获取它的ID。接着使用shmat函数将共享内存段连接到进程地址空间并获取一个指向它的指针。然后可以使用shm_buf指针来读写共享内存段。当不需要使用共享内存段时,可以使用shmdt函数将它从进程地址空间分离。最后使用shmctl函数删除共享内存段并释放它的系统资源。在代码中还使用了system函数调用ipcs命令来查看系统中的IPC状态。
- 编写生产者、消费者程序。
(1) 消费者程序中创建一个共享内存段,并将其中的内容显示出来;
消费者程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024 // 共享内存大小
#define SHM_KEY 1234 // 共享内存键值
int main()
{
int shmid;
char *shmaddr;
// 连接到已有的共享内存段并获取其地址
shmid = shmget(SHM_KEY, SHM_SIZE, 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (char *)(-1)) {
perror("shmat");
exit(1);
}
printf("Content of shared memory:\n%s", shmaddr); // 显示共享内存中的内容
// 断开共享内存连接
shmdt(shmaddr);
return 0;
}
(2) 生产者连接到一个已有的共享内存段,并允许向其中写入数据。
生产者程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024 // 共享内存大小
#define SHM_KEY 1234 // 共享内存键值
int main()
{
int shmid;
char *shmaddr;
char buffer[256];
// 创建共享内存段
shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
// 连接到共享内存段并获取其地址
shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (char *)(-1)) {
perror("shmat");
exit(1);
}
while (1) {
printf("Enter message: ");
fgets(buffer, sizeof(buffer), stdin);
strncpy(shmaddr, buffer, SHM_SIZE); // 写入共享内存
}
// 断开共享内存连接
shmdt(shmaddr);
// 删除共享内存段
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
生产者程序使用 shmget()
函数创建了一个大小为 SHM_SIZE
的共享内存段,并使用 shmat()
函数将其连接到进程的虚拟地址空间,从而获取其地址指针 shmaddr
。然后通过 fgets()
函数从标准输入读取字符串,再使用 strncpy()
将其写入共享内存中。这个过程循环执行,直到程序结束。最后使用 shmdt()
断开共享内存连接,使用 shmctl()
删除共享内存段。
消费者程序先使用 shmget()
函数连接到已有的共享内存段,并使用 shmat()
函数将其连接到进程的虚拟地址空间,从而获取其地址指针 shmaddr
。然后通过 printf()
打印共享内存中的内容。最后使用 shmdt()
断开共享内存连接。
五、 实验总结