目录

消息队列的概念

shell命令"ipcs"查看所有ipc对象信息

ftok()生成IPC对象的键值key

参数

返回

注意

创建消息队列msgget()

msgget()

参数

返回

shell命令"ipcmk"

访问消息队列

msgsnd()发送消息

参数

返回

注意

案例

msgrcv()读取消息

参数

返回

案例

demo

控制消息队列"ipcrm和msgctl"

指令删除ipcrm

函数删除msgctl

参数

返回


消息队列的概念

Linux系统中消息队列(Message Queue)是进程间通信的一种方式,这种通信机制的好处是可以传输指定类型(用户可以自行定义)的数据,相同类型的数据根据到达顺序在队列中进行排队。

思考:既然Linux系统中可能存在多条消息队列,那操作系统是如何管理多条消息队列以及进程如何选择某个消息队列来发送消息?

回答:Linux系统每个创建的消息队列都具有一个唯一的键值key进程可以通过指定消息队列的键值来向消息队列发送数据

shell命令"ipcs"查看所有ipc对象信息

Linux系统中提供了一个shell命令:ipcs  -a查看系统中所有的IPC对象的信息

ipcs  -a

ftok()生成IPC对象的键值key

Linux系统中提供了一个名称叫做ftok()的函数接口,利用该接口可以生成IPC对象的键值key,该接口的使用规则如下:

key_t  ftok ( const char  *pathname ,  int proj_id ) ;

ftok()函数可以把一个指定路径的文件一个指定的项目id转换为一个IPC对象使用的键值key。

参数

const char  *pathname : 系统中已经存在并且可以访问的一个文件的路径,用户可以指定一个文件,但是该文件必须存在且可以被访问,其实就是为了得到文件的属性信息中的inode编号和设备编号,因为Linux系统中一个文件的inode编号是唯一的。

int proj_id : 项目ID,可以由用户进行指定,虽然参数proj_id的类型是整型int,但是只会用到最低8it,所以这个参数的范围其实是1~255,因为这个参数的值必须是非0值!!!

返回

函数调用成功后,则返回生成的键值key,

如果调用失败,则返回-1。

注意

在man手册中提到ftok()函数生成的键值key的组成:

proj_id的低8位+ 设备编号的低8位+ inode编号的低16位。

创建消息队列msgget()

该接口可以创建或者打开一个消息队列。

  • msgget()

int  msgget ( key_t  key , int  msgflag ) ;

参数

key_t  key:传入一个key_t类型的值,该值指的是要创建的消息队列的key键值,key也称为密钥。

int  msgflag : 创建消息队列的标志,其中IPC_CREAT指的是如果消息队列不存在则创建,IPC_EXCL指的是如果消息队列存在则表示函数调用失败。

返回

函数调用成功,则返回消息队列的标识符

如果调用失败,则返回-1并设置错误码。

键值类型key_t其实在内核源码中指的是int类型,如下图:

  • shell命令"ipcmk"

ipcmk  -Q                                           【创建消息队列】

ipcmk  -M  字节数                               【创建共享内存】

ipcmk  -S  信号量个数                        【创建信号量组】

访问消息队列

msgsnd()发送消息

msgsnd()系统调用将msgp指向的消息的副本附加到消息队列中,该消息队列的标识符由msqid指定.

通过msg.h可知,用户创建的消息队列是有默认容量的,默认容量是16384字节。用户在向消息队列写入数据的时候要考虑消息队列的容量

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

参数

int msgid 消息队列的标识符,该标识符可以通过msgget函数得到。

const void  *msgp 一个指向struct msgbuf类型的结构体指针,该结构体中有两个成员,其中一个成员mtype指的是消息类型,必须是一个大于0的正整数,另一个成员mtext指的是消息正文,类型可以是数组或者其他结构。

size_t  msgsz消息正文的大小,按字节计算,当然msgsz的值必须是非负整数,可以设置为0,表示消息正文的长度为0

struct msgbuf

{

  long  mtype;        //mtype必须是个严格的正整数值,这个值用于接收消息时进行消息选择

  char  mtext[1];        //消息正文是一个数组(也可以是其他类型)

};

int  msgflg消息队列的标志如果该标志设置为IPC_NOWAIT,则表示不阻塞,此时如果待写入的消息的长度大于消息队列剩余空间,则直接返回并报错。

返回

如果函数调用成功,则返回0,

如果调用失败,则返回-1并设置错误码。

注意

消息队列默认的属性是阻塞的,也就是当待写入的消息的长度大于消息队列剩余空间时,默认阻塞,直到消息队列的容量足够容纳时会解除阻塞

案例

msgrcv()读取消息

系统从msgid指定的队列中删除消息,并将其放置在msgp指向的缓冲区中

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

参数

int msgid :msgqid指的是MSG对象的标识符ID,MSG标识符可以通过msgget()函数获取。

void  *msgp 存放消息的缓存地址,该地址下存储的是struct  msgbuf结构体

size_t msgsz存放消息的缓存的大小,按照字节计算,如果消息正文的大小大于用户设置的缓存大小,则根据msgflg是否为MSG_NOERROR进行判断,如果msgflg设置为MSG_NOERROR ,则可以读取对应字节的消息,如果msgflg没有设置,则无法读取消息并报错。

long msgtyp接收消息的类型,在调用msgsnd函数时构造的消息结构体中有该成员的值。

  1. 等于0:指的是不区分类型,直接读取MSG中的第一个消息
  2. 大于0:读取类型为指定msgtyp的第一个消息(若msgflg被配置了MSG_EXCEPT则读取除了类型为msgtyp的第一个消息)。
  3. 小于0:读取类型小于等于msgtyp绝对值的第一个具有最小类型的消息。例如当MSG对象中有类型为3、1、5类型消息若干条,当msgtyp为-3时,类型为3的第一个消息将被读取。

int msgflgmsgflg指的是接收消息选项,如果msgflg设置为0,指的是默认接收模式,在MSG中无指定类型消息时阻塞。

  1. IPC_NOWAIT  :指的是非阻塞接收模式,当MSG中没有指定类型消息时直接退出函数
  2. MSG_EXCEPT  :指的是读取除msgtyp之外的第一个消息。
  3. MSG_NOERROR:如果待读取的消息尺寸比msgsz大只返回msgsz部分,其余部分丢弃

返回

如果调用成功,返回复制mtext消息正文的字节数。

如果调用失败,则返回-1并设置错误码

案例

demo

前提:已经在当前路径下创建好了有名管道”fifo1“

需求:在进程里打印对方的pid。

processA.c

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
//需求:在进程里打印对方的pid
// 进程A创建一条消息队列之后向进程B发送SIGUSR1信号,
// 进程B收到该信号之后打开消息队列并把进程的PID作为消息写入到消息队列中,要求进程B在写入消息之后,
// 发SIGUSR2信号给进程A,进程A收到该信号则从消息队列中读取消息并输出消息正文的内容
pid_t key = 0;
int ret_msgget = 0;
struct msgbuf
{
    long mtype;
    int mtext;
};
struct msgbuf msgA;
void signal_handlerA(int signum) // 从消息队列中读取消息并输出消息正文的内容
{
    if (signum == SIGUSR2)
    {
        printf("signum == SIGUSR2\n");
        ret_msgget = msgget(key, 0644);
        bzero(&msgA, sizeof(msgA));

        msgrcv(ret_msgget, &msgA, 4, 0, 0);
        printf("MQ mtext:%d\n", msgA.mtext);
    }
}
int main(int argc, char *argv[])
{
    // 获取键值/密匙,打开或创建消息队列
    key = ftok(".", 1);
    ret_msgget = msgget(key, IPC_CREAT | IPC_EXCL | 0644);
    if (-1 == ret_msgget)
    {
        fprintf(stderr, "msgget error,errno:%d,%s\n", errno, strerror(errno));
        ret_msgget = msgget(key, 0644);
    }
    // int ret_fifo = mkfifo("./fifo1", 0644);   // 创建有名管道
    int opfifoA_fd = open("./fifo1", O_RDWR); // 打开有名管道
    int getBpid = 0;
    while (0 == getBpid) // 从管道内读取进程B的pid,读到了,就退出
    {
        read(opfifoA_fd, &getBpid, sizeof(getBpid));
    }
    printf(" Bpid:%d\n", getBpid);
    kill(getBpid, SIGUSR1); // 向进程B发送SIGUSR1信号
    int Apid = getpid();
    write(opfifoA_fd, &Apid, 4);      // 向管道写入进程A的pid
    signal(SIGUSR2, signal_handlerA); // 等待信号
    while (1)
        ;
    return 0;
}

processB.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
//需求:在进程里打印对方的pid
// 进程A创建一条消息队列之后向进程B发送SIGUSR1信号,
// 进程B收到该信号之后打开消息队列并把进程的PID作为消息写入到消息队列中,要求进程B在写入消息之后,
// 发SIGUSR2信号给进程A,进程A收到该信号则从消息队列中读取消息并输出消息正文的内容
struct msgbuf
{
    long mtype;
    int mtext;
};
pid_t keyb = 0;
int ret_msggetb = 0;
struct msgbuf msgB;

void signal_handler(int signum) // 1.打开消息队列    2.进程的PID作为消息写入到消息队列中
{
    if (signum == SIGUSR1)
    {
        // 1.打开消息队列
        keyb = ftok(".", 1);
        ret_msggetb = msgget(keyb, 0644);

        // 2.进程的PID作为消息写入到消息队列中

        msgB.mtype = 1;
        msgB.mtext = getpid();
        printf("msg.mtext:%d\n", msgB.mtext);
        msgsnd(ret_msggetb, &msgB, 4, 0);
    }
}
int main(int argc, char *argv[])
{
    printf("1222\n");
    int opfifoB_fd = open("./fifo1", O_RDWR); // 打开有名管道
    printf("2222222\n");

    int Bpid = getpid();
    printf("222\n");

    write(opfifoB_fd, &Bpid, sizeof(Bpid)); // 向管道写入进程B的pid
    printf("22\n");

    bzero(&msgB, 0);
    printf("2\n");
    while (0 == msgB.mtype)
    {
        signal(SIGUSR1, signal_handler); // 等待信号
    }
    printf("133\n");
    int getApid = 0;
    while (0 == getApid)
    {
        read(opfifoB_fd, &getApid, 4);
    }
    printf(" Apid:%d\n", getApid);
    kill(getApid, SIGUSR2); // 发SIGUSR2信号给进程A
    while (1)
        ;

    return 0;
}

运行结果:

控制消息队列"ipcrm和msgctl"

IPC对象是一种持久性资源,如果没有明确的删除掉IPC对象,则IPC对象是不会自动从内存中消失的。用户除了可以使用命令的方式删除,也可以使用函数来删除。

Linux系统中提供了msgctl的函数接口,该函数实现获取消息队列的属性信息、设置消息队列的属性信息、删除消息队列等操作

  • 指令删除ipcrm

删除IPC资源

ipcrm  [options]

  • 函数删除msgctl

消息控制操作

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

参数

  • int  msqid : 消息队列的标识符,该标识符可以通过msgget函数得到。
  • int  cmd : msgctl() 根据 cmd 指定的内容进行消息控制操作
  • struct  msqid_ds   *buf  : 可以设置为NULL,指的是用户不需要获取消息队列的信息

返回

函数调用成功,则0

如果调用失败,则返回-1并设置错误码。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。