Linux 下的进程通信手段基本上是从Unix 平台上的进程通信手段继承而来的。而对Unix 发展做出重大贡献的两大主力AT&T 的贝尔实验室以及BSD(加州大学伯克利分校的伯克利软件发布中心),他们在进程间通信方面的侧重点有所不同;

  • 前者对Unix 早期的进程间通信手段进行了系统的改进和扩充,形成了“system-V
    IPC”,通信进程局限在单个计算机内(同一个设备的不同进程间通讯);

  • 而后者则跳过了该限制,形成了基于套接字(socket)的进程间通信机制(多用于不同设备的进程间通讯)。Linux则把两者继承了下来,所以说Linux 才是最成功的,既有“system-VIPC”,又支持“socket”。

消息队列、共享内存和信号量被统称为system-V IPC,V 是罗马数字5,是Unix 的AT&T 分支的其中一个版本,一般习惯称呼他们为IPC 对象,这些对象的操作接口都比较类似,在系统中他们都使用一种叫做key 的键值来唯一标识,而且他们都是“持续性”资源——即他们被创建之后,不会因为进程的退出而消失,而会持续地存在,除非调用特殊的函数或者命令删除他们。

Linux 的IPC 对象(包括消息队列、共享内存和信号量)在内核内部使用链表维护,不同的对象使用IPC 标识符来标识,如消息队列标识符msqid、共享内存标识符shmid,信号量标识符semid。

对于用户来说,内核提供了简洁的接口,不同的进程通过IPC 关键字(key)即可访问具体的对象。

通过如下命令可以查看系统当前的IPC 对象,没有使用的情况下可能为空:

# 查询系统当前的IPC 对象
ipcs# 以下是示例输出,没有使用的情况下可能为空
--------- 消息队列-----------
键           msqid   拥有者     权限  已用字节数   消息
0x000004d2    98345     flyleaf     666        0            0------------ 共享内存段--------------
键   shmid   拥有者 权限  字节  连接数 状态--------- 信号量数组-----------
键   semid   拥有者 权限  nsems

消息队列的基本概念

消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。

消息队列与信号管道的对比

消息队列与信号的对比:

  • 信号承载的信息量少,而消息队列可以承载大量自定义的数据。

消息队列与管道的对比:

  • 消息队列跟命名管道有不少的相同之处,它与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write(),接收数据用read(),则在消息队列中,发送数据用msgsnd(),接收数据用msgrcv(),消息队列对每个数据都有一个最大长度的限制。

  • 消息队列也可以独立于发送和接收进程而存在,在进程终止时,消息队列及其内容并不会被删除。

  • 管道只能承载无格式字节流,消息队列提供有格式的字节流,可以减少了开发人员的工作量。

  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级,接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的顺序接收,也可以按消息的类型接收。

消息队列的实现包括创建或打开消息队列、发送消息、接收消息和控制消息队列这4 种操作。

消息队列函数说明

Linux 内核提供了一系列函数来使用消息队列:

  • 其中创建或打开消息队列使用的函数是msgget(),这里创建的消息队列的数量会受到系统可支持的消息队列数量的限制;

  • 发送消息使用的函数是msgsnd() 函数,它把消息发送到已打开的消息队列末尾;

  • 接收消息使用的函数是msgrcv(),它把消息从消息队列中取走,与FIFO 不同的是,这里可以指定取走某一条消息;

  • 最后控制消息队列使用的函数是msgctl(),它可以完成多项功能。

msgget() 获取函数

收发消息前需要具体的消息队列对象,msgget() 函数的作用是创建或获取一个消息队列对象,并返回消息队列标识符。函数原型如下:

int msgget(key_t key, int msgflg);

若执行成功返回队列ID,失败返回-1。它的两个输入参数说明如下:

  • key:消息队列的关键字值,多个进程可以通过它访问同一个消息队列。例如收发进程都使用同一个键值即可使用同一个消息队列进行通讯。其中有个特殊值IPC_PRIVATE,它用于创建当前进程的私有消息队列。

  • msgflg:表示创建的消息队列的模式标志参数,主要有IPC_CREAT,IPC_EXCL 和权限mode,

    – 如果是IPC_CREAT 为真表示:如果内核中不存在关键字与key 相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,返回此消息队列的标识符。

    – 而如果为IPC_CREAT | IPC_EXCL 表示如果内核中不存在键值与key 相等的消息队列,则新建一个消息队列;如果存在这样的消息队列则报错。

    – mode 指IPC 对象存取权限,它使用Linux 文件的数字权限表示方式,如0600,0666等。

这些参数是可以通过“|”运算符联合起来的,因为它始终是int 类型的参数。如msgflag使用参数IPC_CREAT | 0666 时表示,创建或返回已经存在的消息队列的标识符,且该消息队列的存取权限为0666,即消息的所有者,所属组用户,其他用户均可对该消息进行读写。

注意:

  • 选项msgflg 是一个位掩码,因此IPC_CREAT、IPC_EXCL 和权限mode 可以用位或的方式叠加起来,比如:msgget(key, IPC_CREAT | 0666); 表示如果key 对应的消息队列不存在就创建,且权限指定为0666,若已存在则直接获取消息队列ID,此处的0666 使用的是Linux 文件权限的数字表示方式。

  • 权限只有读和写,执行权限是无效的,例如0777 跟0666 是等价的。

  • 当key 被指定为IPC_PRIVATE 时,系统会自动产生一个未用的key 来对应一个新的消息队列对象,这个消息队列一般用于进程内部间的通信。

  • 该函数可能返回以下错误代码:

    – EACCES:指定的消息队列已存在,但调用进程没有权限访问它

    – EEXIST:key 指定的消息队列已存在,而msgflg 中同时指定IPC_CREAT 和IPC_EXCL标志

    – ENOENT:key 指定的消息队列不存在同时msgflg 中没有指定IPC_CREAT 标志

    – ENOMEM:需要建立消息队列,但内存不足

    – ENOSPC:需要建立消息队列,但已达到系统的限制

发送消息与接收消息

msgsnd() 发送函数

这个函数的主要作用就是将消息写入到消息队列,俗称发送一个消息。函数原型如下:

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数说明:

  • msqid:消息队列标识符。

  • msgp:发送给队列的消息。msgp 可以是任何类型的结构体,但第一个字段必须为long 类型,即表明此发送消息的类型,msgrcv() 函数则根据此接收消息。msgp 定义的参照格式如下:

/*msgp 定义的参照格式*/
struct s_msg{long type; /* 必须大于0, 消息类型*/char mtext[]; /* 消息正文,可以是其他任何类型*/
} msgp;

– msgsz:要发送消息的大小,不包含消息类型占用的4 个字节,即mtext 的长度。

– msgflg:如果为0 则表示:当消息队列满时,msgsnd() 函数将会阻塞,直到消息能写进消息队列;如果为IPC_NOWAIT 则表示:当消息队列已满的时候,msgsnd() 函数不等待立即返回;如果为IPC_NOERROR:若发送的消息大于size 字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。

  • 返回值:如果成功则返回0,如果失败则返回-1,并且错误原因存于error 中。错误代码:

    – EAGAIN:参数msgflg 设为IPC_NOWAIT,而消息队列已满。

    – EIDRM:标识符为msqid 的消息队列已被删除。

    – EACCESS:无权限写入消息队列。

    – EFAULT:参数msgp 指向无效的内存地址。

    – EINTR:队列已满而处于等待情况下被信号中断。

    – EINVAL:无效的参数msqid、msgsz 或参数消息类型type 小于0。

msgsnd() 为阻塞函数,当消息队列容量满或消息个数满会阻塞。若消息队列已被删除,则返回EIDRM 错误;若被信号中断返回E_INTR 错误。

如果设置IPC_NOWAIT 消息队列满或个数满时会返回-1,并且置EAGAIN 错误。

msgsnd() 解除阻塞的条件有以下三个条件:

  • 消息队列中有容纳该消息的空间。

  • msqid 代表的消息队列被删除。

  • 调用msgsnd 函数的进程被信号中断。

msgrcv() 接收函数

msgrcv() 函数是从标识符为msqid 的消息队列读取消息并将消息存储到msgp 中,读取后把此消息从消息队列中删除,也就是俗话说的接收消息。函数原型:

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数说明:

  • msqid:消息队列标识符。

  • msgp:存放消息的结构体,结构体类型要与msgsnd() 函数发送的类型相同。

  • msgsz:要接收消息的大小,不包含消息类型占用的4 个字节。

  • msgtyp 有多个可选的值:如果为0 则表示接收第一个消息,如果大于0 则表示接收类型等于msgtyp 的第一个消息,而如果小于0 则表示接收类型等于或者小于msgtyp 绝对值的第一个消息。

  • msgflg 用于设置接收的处理方式,取值情况如下:

    – 0: 阻塞式接收消息,没有该类型的消息msgrcv 函数一直阻塞等待

    – IPC_NOWAIT:若在消息队列中并没有相应类型的消息可以接收,则函数立即返回,此时错误码为ENOMSG

    – IPC_EXCEPT:与msgtype 配合使用返回队列中第一个类型不为msgtype 的消息

    – IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size 字节,则把该消息截断,截断部分将被丢弃

  • 返回值:msgrcv() 函数如果接收消息成功则返回实际读取到的消息数据长度,否则返回-1,错误原因存于error 中。错误代码:

    – E2BIG:消息数据长度大于msgsz 而msgflag 没有设置IPC_NOERROR

    – EIDRM:标识符为msqid 的消息队列已被删除

    – EACCESS:无权限读取该消息队列

    – EFAULT:参数msgp 指向无效的内存地址

    – ENOMSG:参数msgflg 设为IPC_NOWAIT,而消息队列中无消息可读

    – EINTR:等待读取队列内的消息情况下被信号中断

msgrcv() 函数解除阻塞的条件也有三个:

  • 消息队列中有了满足条件的消息。

  • msqid 代表的消息队列被删除。

  • 调用msgrcv() 函数的进程被信号中断。

msgctl() 操作消息队列

消息队列是可以被用户操作的,比如设置或者获取消息队列的相关属性,那么可以通过msgctl()函数去处理它。函数原型:

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数说明:

  • msqid:消息队列标识符。

  • cmd 用于设置使用什么操作命令,它的取值有多个:

    – IPC_STAT 获取该MSG 的信息,获取到的信息会储存在结构体msqid_ds 类型的buf 中。

    – IPC_SET 设置消息队列的属性,要设置的属性需先存储在结构体msqid_ds 类型的buf中,可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode 以及msg_qbytes,储存在结构体msqid_ds 中。

    – IPC_RMID 立即删除该MSG,并且唤醒所有阻塞在该MSG 上的进程,同时忽略第三个参数。

    – IPC_INFO 获得关于当前系统中MSG 的限制值信息。

    – MSG_INFO 获得关于当前系统中MSG 的相关资源消耗信息。

    – MSG_STAT 同IPC_STAT,但msgid 为该消息队列在内核中记录所有消息队列信息的数组的下标,因此通过迭代所有的下标可以获得系统中所有消息队列的相关信息。

  • buf:相关信息结构体缓冲区。

    – 返回值:

    – 成功:0

    – 出错:-1,错误原因存于error 中,错误代码:

     ∗ EACCESS:参数cmd 为IPC_STAT,确无权限读取该消息队列。∗ EFAULT:参数buf 指向无效的内存地址。∗ EIDRM:标识符为msqid 的消息队列已被删除。∗ EINVAL:无效的参数cmd 或msqid。∗ EPERM:参数cmd 为IPC_SET 或IPC_RMID,却无足够的权限执行。
    

消息队列示例

接下来通过示例来讲解消息队列的使用,使用方法一般是:

发送者:

  1. 获取消息队列的ID

  2. 将数据放入一个附带有标识的特殊的结构体,发送给消息队列。

接收者:

  1. 获取消息队列的ID

  2. 将指定标识的消息读出。

当发送者和接收者都不再使用消息队列时,及时删除它以释放系统资源。

本次实验主要是两个进程(无血缘关系的进程)通过消息队列进行消息的传递,一个进程发送消息,一个进程接收消息,并将其打印出来。

发送进程

本示例的发送进程代码如下:

列表1: 消息队列发送进程

#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 512struct message
{long msg_type;char msg_text[BUFFER_SIZE];
};
int main()
{int qid;struct message msg;/* 创建消息队列*/if ((qid = msgget((key_t)1234, IPC_CREAT|0666)) == -1){perror("msgget\n");exit(1);}printf("Open queue %d\n",qid);while(1){printf("Enter some message to the queue:");if ((fgets(msg.msg_text, BUFFER_SIZE, stdin)) == NULL){printf("\nGet message end.\n");exit(1);}msg.msg_type = getpid();/* 添加消息到消息队列*/if ((msgsnd(qid, &msg, strlen(msg.msg_text), 0)) < 0){perror("\nSend message error.\n");exit(1);}else{printf("Send message.\n");          }if (strncmp(msg.msg_text, "quit", 4) == 0){printf("\nQuit get message.\n");break;} }
}

本代码重点说明如下:

  • 第22 行,调用msgget() 函数创建/获取了一个key 值为1234的消息队列,该队列的属性“0666”表示任何人都可读写,创建/获取到的队列ID 存储在变量qid 中。

  • 第47 行,调用msgsndb() 函数把进程号以及前面用户输入的字符串,通过msg 结构体添加到前面得到的qid 队列中。

  • 第51 行,若用户发送的消息为quit,那么退出循环结束进程。

接收进程

接收进程示例如下:

列表2: 消息队列接收进程

#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 512struct message
{long msg_type;char msg_text[BUFFER_SIZE];
};int main()
{int qid;struct message msg;/* 创建消息队列*/if ((qid = msgget((key_t)1234, 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 %ld : %s", msg.msg_type, msg.msg_text);} while(strncmp(msg.msg_text, "quit", 4));/* 从系统内核中删除消息队列*/{perror("msgctl");exit(1);}else{printf("Delete msg qid: %d.\n", qid);}exit(0);
}

本代码重点说明如下:

  • 第23 行,调用msgget() 函数创建/获取队列qid。可以注意到,此处跟发送进程是完全一样的,无论哪个进程先运行,若key值为1234 的队列不存在则创建,把以实验时两个进程并没有先后启动顺序的要求。

  • 第36 行,在循环中调用msgrcv() 函数接收qid 队列的msg 结构体消息,此处使用阻塞方式接收,若队列中没有消息,会停留在本行代码等待。

  • 第47 行,若前面接收到用户的消息为quit,会退出循环,在本行代码调用msgctl() 删除消息队列并退出本进程。

编译及测试

实验现象如下:

发送进程

在发送消息进程运行的时候,会提示让你输入要发送的消息,随便什么消息都可以的,使用回车完成消息的输入。输入quit 或使用Ctrl+D、Ctrl+C 可结束进程。

# 以下操作在system_programing/msg/msg_send 代码目录进行
# 编译X86 版本程序发送进程
make
# 运行X86 版本程序发送进程
./build_x86/msg_send_demo# 输入消息测试,
Open queue 98345
Enter some message to the queue:embedfire
Send message.
Enter some message to the queue:test
Send message.
Enter some message to the queue:hello world
Send message.
# 发送quit 消息并结束进程
Enter some message to the queue:quit
Send message.Quit get message.

查看消息队列

可以通过ipcs -q 命令来查看系统中存在的消息队列,若以上队列没有关闭,它的查看结果如下:

# 查询系统当前存在的队列
ipcs -q# 以下为输出:
--------- 消息队列-----------
键           msqid   拥有者     权限  已用字节数   消息
0x000004d2    98345     flyleaf     666       0         0# 可查看到key 键值0x04d2(1234),qid 98345 与进程中创建的一致。

接收进程

打开一个新终端,编译及运行接收消息进程,当你从发送消息进程输入消息时(按下回车键发送),接收消息进程会打印出你输入的消息,若无消息则接收进程会阻塞等待,接收到quit 消息会退出进程。

# 以下操作在system_programing/msg/msg_recv 代码目录进行
# 编译X86 版本程序发送进程
make
# 运行X86 版本程序发送进程
./build_x86/msg_recv_demo# 接收到的消息
Open queue 98345
The message from process 21023 : embedfire
The message from process 21023 : test
The message from process 21023 : hello world
The message from process 21023 : quit
Delete msg qid: 98345.

小技巧: 在本例子中,若发送进程不是通过quit 消息退出(如Ctrl+C 或Ctrl+D),则不会触发接收进程主动删除消息队列,在这种情况下可通过ipcs -q 命令查看到该消息队列依然存在,通过ipcrm -q [消息队列qid] 即可删除。



参考资料:Linux 基础与应用开发实战指南——基于STM32MP1 系列

第二十三节 消息队列相关推荐

  1. Python编程基础:第二十三节 嵌套函数调用Nested Functions Calls

    第二十三节 嵌套函数调用Nested Functions Calls 前言 实践 前言 当一个函数的返回结果是另一个函数的输入的时候,我们就可以采用嵌套调用的方式来书写代码.举例来说,y=f(x)=x ...

  2. 嵌入式Linux系统编程学习之二十四消息队列

    文章目录 前言 一.msgget 函数 二.msgsnd 函数 三.msgctl 函数 补充 前言   消息队列与 FIFO 很相似,都是一个队列结构,都可以有多个进程往队列里面写信息,多个进程从队列 ...

  3. hadoop离线阶段(第二十三节)oozie简介、安装和使用及oozie与hue的整合

    目录 oozie简介 oozie的介绍 oozie的架构 oozie的执行流程 oozie的组件介绍 oozie安装 第一步:修改core-site.xml 第二步:上传oozie的安装包并解压 第三 ...

  4. 【java】兴唐第二十三节课作业

    已知如下: 下表为某班级四次考试成绩单, 要求使用HashMap<String, Integer>存储每次考试的成绩(key键为姓名,value为成绩). 要求使用LinkedList存储 ...

  5. 【java】兴唐第二十三节课(暑期第一节TreeSet)

    预警:进入暑期培训的博主即将高产似母猪,敬请博友期待. 1.给类添加构造方法 alt + shift + s 选择Generate Construct using Fields 2.map两种遍历方法 ...

  6. Scala入门到精通——第二十三节 高级类型 (二)

    本节主要内容 中置类型(Infix Type) 存在类型 函数类型 抽象类型 关于语法糖的问题,在讲解程序语言时,我们常常听到"语法糖"这个术语,在百度百科中,它具有如下定义: 语 ...

  7. 第二十三节: EF性能篇(三)之基于开源组件 Z.EntityFrameWork.Plus.EF6解决EF性能问题

    一. 开篇说明 EF的性能问题一直以来经常被人所吐槽,究其原因在于"复杂的操作在生成SQL阶段耗时长,且执行效率不高",但并不是没有办法解决,从EF本身举几个简单的优化例子: ①: ...

  8. 第二十二课php注入,php第二十三节课

    XML XML:页面之间传递数据,跨平台传递 HTML:超文本标记语言,核心标签 XML特点: 1.标签名可以自己定义 2.有且只有一个根 3.大小写敏感 4.标签必须完整 无标题文档 $(docum ...

  9. 消息队列面试经典十连问

    前言 金三银四即将来临,整理了十道十分经典的消息队列面试题,看完肯定对面试有帮助的,大家一起加油哈~ 什么是消息队列 消息队列的应用场景 消息队列如何解决消息丢失问题 消息队列如何保证消息的顺序性. ...

  10. SpringCloud 微服务 (十) 消息队列MQ 基础

    2019独角兽企业重金招聘Python工程师标准>>> 壹 之前学习了SpringCloud Bus结合MQ,没有多学习MQ,本次学习相关内容,先了解异步,同步就不说了 异步: 客户 ...

最新文章

  1. 失业后跑摩的985高校硕士,被质疑学历、深“扒”论文...
  2. 告别Heatmap!人体姿态估计表征新方法SimDR
  3. Xcode真机调试(有证书)
  4. 一个大型网游需要哪些代码块_你会因为网游的非公平性,而转投单机游戏阵营吗?...
  5. 【Tomcat Eclipse】8080端口被占用问题记录
  6. 基于sympy的python实现三层BP神经网络算法
  7. 2,数据类型,约束条件
  8. 如何在前端中使用protobuf(vue篇)
  9. Kafka技术资料总结(不断更新中)
  10. 推荐一款数据恢复软件(迅龙数据恢复)
  11. 欢迎关注异贝!今天与您一起分享服装行业异贝引客方案设计!
  12. 深信服虚拟服务器设置ip,深信服网关怎么设置端口映射
  13. andserver FileBrowser 图片浏览
  14. 英文邮件寻求帮助的礼貌用语
  15. 王道408思维导图 marginnote 【4门科齐全】Xmind+脑图原件可下载 - 在线分享
  16. IOS按钮排列自动换行
  17. 360杯-实训平台安全入门 · 课程测试
  18. 如何在您的香港主机帐户上注册多个域名
  19. 为什么要学Markdown?有什么用?
  20. 【Java核心技术卷】深入理解Java近似数(近似存储)与有效数字、精确度、最大有效位数的概念

热门文章

  1. 2023年8月京东空调行业品牌销售排行榜(京东数据报告)
  2. 如何进入训练模式_为何孩子学习痛苦又低效?巧用这2个训练方法,孩子学习更高效...
  3. 救命!!这到底是蛇魔错误!真的会疯!
  4. php将数组的下标从新定义自增 array_values
  5. 监管文件显示英特尔将放弃Tick-Tock芯片开发模式
  6. IDEA中Maven项目快速导入本地jar包
  7. 看40W程序员评论他们的真实生活是怎样的!
  8. 私有化部署,给企业知识管理上把安全锁
  9. 自学51单片机 - 静态数码管实验
  10. 是“谁”占用了你的带宽?6个工具帮助你实时监控网络情况