【北邮大三上操作系统】第三组 进程通信——消息队列通信与共享内存通信

发布于 2023-07-21  288 次阅读


第三组 进程通信之基于消息队列的进程通信与共享内存通信

1 题目

1. 基于消息队列的进程通信

Linux 消息队列包括 POSIX 消息队列、System V 消息队列。发送者进程向消息队列中写入数据,接收者进程从队列中接收数据,实现相互通信。消息队列克服了信号 signal 承载信息量少,管道 pipe 只能承载无格式字节流以及缓冲区大小受限等缺点。

要求:编程实现发送者和接收者两个并发进程:

(1)发送者和接收者使用 msgget(key_t, key, int msgflg)、msgctl(int msgid, int command, struct msgid_ds *buf)创建、管理消息队列。只有接收者在接收完最后一个消息之后,才删除消息。

(2)发送者使用msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg)向消息队列不断写入数据,并打印提示信息;

(3)接收者使用msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg)从消息队列中接收消息,并打印提示信息。

2. 共享内存通信

Linux 内核支持多种共享内存方式,如 mmap()系统调用,POSIX 共享内存,以及 System V 共享内存。本实验采用 System V 共享内存实现方法。

要求:编程实现写者进程 Writer 和读者进程 Reader,

(1)写者进程和读者进程使用 shmget(key_t key, int size, int shmflg)创建在内存中创建用于两者间通信的共享内存,使用 shmat(int shmid, char*shmaddr, int flag)将共享内存映射到进程地址空间中,以便访问共享内存内容;

(2)写者进程向共享内存写入多组数据,读者进程从共享内存 d 读出数据;

(3)进程间通信结束后,写者进程和读者进程使用 shmdt(char*shmaddr)解除共享内存映射,使用 shmctl(int shmid, int cmd, struct shmid_ds*buf)删除共享内存。

2 实验目的

  • 加深对进程概念的理解,明确进程和程序的区别;进一步认识并发执行的实质;
  • 了解并熟悉 Linux 系统中消息机制的基本概念及方法;
  • 熟悉 Linux 提供的有关消息机制的系统调用函数/库函数,并能使用这些函数。

3 实验内容

3.1 实验一

函数 传入参数 返回值 作用
msgget (key_t key, int msgflg)
key:键值, 标记唯一消息队列,多个进程可通key值来访问同一个消息队列。
msgflag: 权限标志位。msgflg可以与IPC_CREAT 做或操作,表示当 key 所命名的消息队列不存在时创建一个消息队列,如果 key所命名的消息队列存在时,IPC_CREAT 标志会被忽略,而只返回一个标识符。
返回值:返回一个以 key 命名的消息队列的标识符(非零整数),失败时返回-1 得到消息队列标识符或创建一个消息队列对象并返回消息队列标识符
msgsnd (int msgid, const void *msg_ptr, size_t msg_sz, int msgflg)
msgid:消息队列标识符;
msg_ptr:发送的消息;
msg_sz:发送消息的长度;
msgflg:控制消息传送的行为
调用成功返回 0,失败时返回-1 向消息队列发送消息
msgrcv (int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg)
msgtype:1.msgtype = 0,就获取队列中的第一个消息。2.msgtype>0,将获取具有相同消息类型的第一个信息。3.msgtype<0,就获取类型等于或小于 msgtype 的绝对值的第一个消息;
获取消息队列里的信息
msgctl (int msgid, int command, struct msgid_ds *buf)
command: 将要采取的动作
1.IPC_STAT:用消息队列的当前关联值覆盖 msgid_ds 的值
2.IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为 msgid_ds 结构中给出的值;
3.IPC_RMID:删除消息队列。buf:指向消息队列模式和访问权限的结构
成功时返回 0,失败时返回-1. 控制消息队列

3.2 实验二

函数 传入参数 返回值 作用
shmget (key_t key, int size, int shmflg)
key是标识共享内存的键值,可以取非负整数或IPC_PRIVATE。当key是IPC_PRIVATE时,shmflg不需要IPC_CREAT。当key是非负整数时,shmflg需要IPC_CREAT。
size是共享内存的大小,以字节为单位。
shmflg是权限标志,与文件的读写权限一样。
如果成功,返回共享内存表示符,如果失败,返回-1。 创建共享内存
shmat (int shmid, char*shmaddr, int flag)
shmid:shmget函数返回的共享存储标识符,
shmaddr:共享内存映射地址(若为 NULL 则由系统自动指定),
flag:决定以什么样的方式来确定映射的地址(通常为0)
如果成功,则返回共享内存映射到进程地址空间中的地址;如果失败,则返回-1。 映射共享内存

4 实验设计原理

4.1实验一

“消息队列”是在消息的传输过程中保存消息的容器,队列的主要目的是提供路由并保证消息的传递。如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。

RECEIVER进程和SENDER进程并发执行

由RECEIVER进程创造消息队列

3e07ce4b4a2f9bb307a373d53a6d88ea.png

创建消息队列使用函数msgget

从消息队列接收数据使用函数msgrcv

发送消息到消息队列使用函数msgsnd

最后由RECEIVER进行消息队列的删除

4.2 实验二

进程间需要共享的数据被放在一个称为 IPC共享内存区域的地方,需要访问该共享区域的进程需要将该共享区域映射到本进程的地址空间中。系统V共享内存通过shmget获得或创建一个IPC共享内存区域,并返回相应的标识符。内核执行 shmget创建一个共享内存区,初始化该共享内存区对应的shmid_kernel结构,同时在特殊文件系统shm中创建并打开一个同名文件,并在内存中建立起该文件对应的dentryinode结构。新打开的文件不属于任何一个特定进程,任何进程都可以访问该共享内存区。

共享内存是被多个进程共享的一部分物理内存。一个进程向共享内存写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。

如下图所示,共享内存的地址在进程A中的地址可能是0x5000,在进程B中的地址可能是0x7000,这是因为共享内存映射到不同进程当中的不同位置。

共享内存的实现分为2个步骤:

  1. 使用shmget函数创建共享内存。
  2. 使用shmat函数将创建的共享内存映射到具体的进程空间。

5 实验步骤

5.1 实验一

在vim文本编辑器里编写代码

msg_send.c部分代码

msg_recv.c部分代码

编译

使用命令gcc -o msg_send.exe msg_send.c -lmgcc -o msg_recv.exe msg_recv.c -lm

运行

./msg_recv.exe &创建进程msg_recv

./msg_send.exe并发执行两个进程

5.2 实验二

在vim文本编辑器里编写代码

write.c部分代码

cf0e87971ba5ec823d356c3cc846dbb5.png

read.c部分代码

编译

使用命令gcc -g write.c -o write gcc -g read.c -o read

运行

先运行write程序将数据写入,接着运行read将写入的数据进行读取

6 实验结果及分析

6.1 实验一

实验结果

分析:两个进程之间可以进行稳定的通信,符合预期结果

6.2 实验二

分析:write程序写入三个学生的数据,输入结束后运行read程序可以正确得到输入的三个学生的姓名学号,程序运行正确

7 程序代码

7.1 实验一

msg_recv.c

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
typedef struct info
{
    long int msg_type;
    int number;
    char text[BUFSIZ];
}info;
int main()
{
    int flag = 1;
    int msgid = -1;
    info data;
    long int msgtype = 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 (flag)    //从队列中获取消息,直到遇到 end 消息为止
    {
        if (msgrcv(msgid, (void*)&data, BUFSIZ+4, msgtype, 0) == -1)
        {
            fprintf(stderr, "msgrcv failed with errno: %d\n", errno);
            exit(EXIT_FAILURE);
        }
        printf("no.%d receive data successfully: %s\n", data.number,data.text);
        if (strncmp(data.text, "end", 3) == 0)        //遇到 end 结束
            flag = 0;
    }

    if (msgctl(msgid, IPC_RMID, 0) == -1)//删除消息队列
    {
        fprintf(stderr, "msgctl(IPC_RMID) failed\n");
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

msg_send.c

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
#define MAX_TEXT 512
typedef struct info
{
    long int msg_type;
    int number;
    char text[MAX_TEXT];
}info;

int main()
{
    int flag = 1;
    int i = 0;
    info data;
    char buffer[BUFSIZ];
    int msgid = -1;

    msgid = msgget((key_t)1234, 0666| IPC_CREAT);   //建立消息队列,返回队列标识符
    if (msgid == -1)
    {
        fprintf(stderr, "msgget failed with error: %d\n", errno);
        exit(EXIT_FAILURE);
    }

    while (flag)    //向消息队列中写消息,直到写入 end
    {
        i++;
        printf("The text you want to send: ");//输入转杯传送的数据数据
        fgets(buffer, BUFSIZ, stdin);
        data.msg_type = 1; //消息类型设置为1
        data.number = i;
        strcpy(data.text, buffer);

        if (msgsnd(msgid, (void*)&data, MAX_TEXT+4, 0) == -1)       //向队列发送数据
        {
            fprintf(stderr, "msgsnd failed\n");
            exit(EXIT_FAILURE);
        }
        printf("no.%d send data successfully\n", data.number);
        if (strncmp(buffer, "end", 3) == 0)//输入 end 结束输入
            flag = 0;
        sleep(1);//需要进程休眠,否则输出错位
    }
    exit(EXIT_SUCCESS);
}

7.2 实验二

read.c

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct student {
    int id;
    char name[8];
}student;
int main(int argc, char** argv)
{
    int shm_id, i;
    key_t key;
    student* school;
    char pathname[30];
    strcpy(pathname, "/tmp");
    key = ftok(pathname, 0x03);
    if (key == -1)
    {
        perror("ftok error");
        return-1;
    }
    printf("key=%d\n", key);
    shm_id = shmget(key, 0, 0);//得到共享内存
    if (shm_id == -1)
    {
        perror("shmget error");
        return -1;
    }
    printf("shm_id=%d\n", shm_id);
    school = (student*)shmat(shm_id, NULL, 0);
    for (i = 0; i < 3; i++)
    {
        printf("student's name:%s\n", (*(school + i)).name);
        printf("student's id:%d\n", (*(school + i)).id);
    }
    if (shmdt(school) == -1)
    {
        perror("detach error");
        return -1;
    }
    shmctl(shm_id, IPC_RMID, 0);//删除共享内存
    return 0;
}

write.c

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct student {
    int id;
    char name[8];
}student;
int main(int argc, char** argv) {
    int shm_id;
    int i = 0;
    key_t key;
    char buffer[BUFSIZ];
    student* school;
    char pathname[30];
    strcpy(pathname, "/tmp");
    /*ftok系统IPC键值转化函数,根据路径名,提取文件信息, 再根据这些文件信息及project ID合成key
    pathname参数:  必须是一个已经存在且程序可范围的文件 。
    */
    key = ftok(pathname, 0x03);
    if (key == -1)//错误
    {
        perror("ftok error");
        return -1;
    }
    printf("key=%d\n", key);
    /*IPC_CREAT | IPC_EXCL时, 如果没有该块共享内存,则创建,并返回共享内存ID。*/
    shm_id = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0600);

    if (shm_id == -1)//shmid是共享内存标识符
    {
        perror("shmgct error");
        return -1;
    }
    printf("shm_id=%d\n", shm_id);
    school = (student*)shmat(shm_id, NULL, 0); //映射共享内存
    for (i = 0; i < 3; i++)
    {
        printf("please input student name: ");
        fgets(buffer, BUFSIZ, stdin);
        (school + i)->id = 0 + i;
        strncpy((school + i)->name, buffer, 5);
    }
    shmdt(school); //用来断开与共享内存附加点的地址, 禁本进程访问此片共享内存。

    return 0;
}