【Linux】Linux进程间通信——共享内存/消息队列/守护进程


进程间通信——共享内存/守护进程

共享内存是效率最高的进程间通信方式。

一, 共享内存

1. 共享内存概念

共享内存允许两个或多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会成为一个进程用户空间的一部分,因此这种IPC机制无需内核介入。
我们需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其它所有共享同一个段的进程可用。

与管道等要求 发送进程将数据从用户空间的缓冲区复制进内核内存 和 接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种IPC技术速度更快。

但共享内存也会带来一些问题,比如,几个进程共享内存,那么可能进程A在写数据,进程B也在写数据,进程C读数据,那么就会出现冲突,进程C最终读出来的数据到底是什么呢?涉及到进程同步的问题。

共享内存存储在用户区的共享库所在空间,如下:
在这里插入图片描述

2. 共享内存使用

1. 共享内存使用步骤
  1. 创建shmget:调用shmget()创建一个新的共享内存段或取得一个既有共享内存段的标识符(由其它进程创建的共享内存段)。这个调用将返回后续调用中需要用到的共享内存标识符。
  2. 链接shmat:使用shmat()附加共享内存段到当前的调用进程,使该段成为调用进程的虚拟内存的一部分。
  3. 操作:此刻在程序中可以像对待其它可用内存那样对待这个共享内存段。为引用这块共享内存,程序需要使用由shmat()调用返回的addr值,它是一个指向进程的虚拟地址空间中该共享内存段的起点的指针。
  4. 分离shmdt:调用shmdt()来分离共享内存段,在这个调用之后,进程就无法再引用这块共享内存了。这一步是可选的,并且在进程终止时会自动完成这一步。
  5. 删除shmctl():调用shmctl()来删除共享内存段。只有当当前所有附件内存段的进程都与之分离之后内存段才会销毁,只有一个进程需要执行这一步。
2. 共享内存操作函数

共享内存操作函数有如下:

int shmget(key_t key, size_t size, int shmflg);  // 创建或获取共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);  // 共享内存链接到当前调用进程
int shmdt(const void *shmaddr);   // 将当前进程与共享内存分离
int shmctl(int shmid, int cmd, struct shmid_ds *buf);  // 对共享内存操作,设置/获取/删除共享内存
key_t ftok(const char *pathname, int proj_id);  // 生成共享key,创建或获取共享内存时会使用该函数生成的key

shmget()函数说明:

头文件#include <sys/ipc.h>
#include <sys/shm.h>
函数声明int shmget(key_t key, size_t size, int shmflg);
功能创建一个新的共享内存段,或者获取一个既有的共享内存段的标志
新创建的内存段中的数据都会被初始化为0
参数keykey_t类型,通过该参数找到或创建一个共享内存。
一般使用16进制表示,非0值
参数size共享内存的大小,以分页大小创建
参数shmflg共享内存的属性,如访问权限、附加属性(创建/判断共享内存是不是存在)等
IPC_CREATE:创建共享内存
IPC_EXCL:判断共享内存是否存在,需要和IPC_CREATE一起使用,如:IPC_EXCL
返回值成功返回>0的值,表示共享内存标识符,失败返回-1并设置errno

shmat()函数说明:

头文件#include <sys/ipc.h>
#include <sys/shm.h>
函数声明void *shmat(int shmid, const void *shmaddr, int shmflg);
功能将共享内存与当前调用进程相关联
参数shmid共享内存标识符,由shmget()返回
参数shmaddr申请的共享内存的起始地址,设置为NULL,由内核指定
参数shmflg对共享内存的操作
SHM_RDONLY:读权限,必须要有读权限
0:读写权限
返回值成功返回共享内存的首地址,失败返回(void*)-1并设置errno

shmdt()函数说明:

头文件#include <sys/ipc.h>
#include <sys/shm.h>
函数声明int shmdt(const void *shmaddr);
功能解除当前进程和共享内存的关联
参数shmaddr共享内存的首地址
返回值成功返回0,失败返回-1并设置errno

shmctl()函数说明:

头文件#include <sys/ipc.h>
#include <sys/shm.h>
函数声明int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能对共享内存的操作(获取状态,设置状态、标记销毁),创建共享内存的进程被销毁对共享内存没有影响
参数shmid共享内存标识符ID
参数cmd对共享内存的操作
IPC_STAT:获取共享内存的当前状态
IPC_SET:设置共享内存的状态
IPC_RMID:标记共享内存被销毁
参数buf需要设置或者获取的共享内存的属性,buf设置与参数cmd有关,当cmd为:
IPC_STAT:buf存储数据
IPC_SET:buf需要初始化数据,设置到内核中
IPC_RMID:buf没有用,设置NULL即可
返回值成功返回0,失败返回-1并设置errno

当参数cmd设置为IPC_STAT或IPC_SET时,用到第三个参数buf,是结构体指针struct shmid_ds*,该结构体声明如下:

        struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };

ftok()函数说明:

头文件#include <sys/ipc.h>
#include <sys/types.h>
函数声明key_t ftok(const char *pathname, int proj_id);
功能根据指定的路径名和proj_id值生成一个共享内存key
参数pathname指定一个存在的路径
参数proj_id系统调用只使用其中的1个字节,一般指定一个字符,例如可以传递 'a'
返回值成功返回生成的key,失败返回-1并设置errno
3. 共享内存常用操作命令
  • ipc命令

    • ipcs -a:打印当前系统中所有的进程键通信方式的信息
    • ipcs -m:打印使用共享内存进行进程间通信的信息
    • ipc -q:打印使用消息队列进行进程间通信的信息
    • ipc -s:打印使用信号量进行进程间通信的信息
  • ipcrm命令

    • ipcrm -M shmkey:移除用shmkey创建的共享内存段
    • ipcrm -m shmid:移除用shmid标识的共享内存段
    • ipcrm -Q msgkey:移除用msqkey创建的消息队列
    • ipcrm -q msgid:移除用msqid标识的消息队列
    • ipcrm -S semkey:移除semkey创建的信号量
    • ipcrm -s semid:移除用semid标识的信号量
4. 共享内存使用示例:

示例1:创建两个进程,一个向共享内存中写数据write_shm.c,一个从共享内存中读数据read_shm.c。

read_shm.c

/**
 * @file read_shm.c
 * @author zoya (2314902703@qq.com)
 * @brief 从共享内存中读取数据
 * @version 0.1
 * @@date: 2022-10-03
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main()
{
    // 获取key
    key_t key = ftok("/home/zoya/Linux/chap2/lesson14/key", 'a');
    if (key == -1)
    {
        perror("ftok");
        exit(-1);
    }

    // 获取共享内存
    int shmid = shmget(key, 0, IPC_CREAT);
    if (shmid == -1)
    {
        perror("shmget");
        exit(-1);
    }

    //  共享内存与当前进程关联
    void *ptr = shmat(shmid, NULL, SHM_RDONLY);
    if (ptr == (void *)-1)
    {
        perror("shmat");
        exit(-1);
    }

    // 操作共享内存中的数据,即读取共享内存数据
    while (1)
    {
        if(ptr == NULL)
            continue;
        printf("read : %s\n", (char *)ptr);
        sleep(2);
    }

    //  分离当前进程与共享内存的关联
    int ret = shmdt(ptr);
    if (ret == -1)
    {
        perror("shmdt");
        exit(-1);
    }

    // 删除共享内存
    ret = shmctl(shmid, IPC_RMID, NULL);
    if (ret == -1)
    {
        perror("shmctl");
        exit(-1);
    }

    return 0;
}

write_shm.c

/**
 * @file read_shm.c
 * @author zoya (2314902703@qq.com)
 * @brief 向共享内存中写数据
 * @version 0.1
 * @@date: 2022-10-03
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main()
{
    // 获取key
    key_t key = ftok("/home/zoya/Linux/chap2/lesson14/key", 'a');
    if (key == -1)
    {
        perror("ftok");
        exit(-1);
    }

    // 获取共享内存
    int shmid = shmget(key, 4096, IPC_CREAT | 0664);
    if (shmid == -1)
    {
        perror("shmget");
        exit(-1);
    }

    //  共享内存与当前进程关联
    void *ptr = shmat(shmid, NULL, 0); // 读写模式与当前进程关联
    if (ptr == (void *)-1)
    {
        perror("shmat");
        exit(-1);
    }

    // 操作共享内存中的数据,即读取共享内存数据
    char str[1024] = {0};
    long int num = 0;
    while (1)
    {
        memset(str, 0, sizeof(str));
        sprintf(str, "hello, %ld", num++);
        memcpy((char *)ptr, str, strlen(str));
        printf("按任意键继续\n");
        getchar();
    }

    //  分离当前进程与共享内存的关联
    int ret = shmdt(ptr);
    if (ret == -1)
    {
        perror("shmdt");
        exit(-1);
    }

    // 删除共享内存
    ret = shmctl(shmid, IPC_RMID, NULL);
    if (ret == -1)
    {
        perror("shmctl");
        exit(-1);
    }

    return 0;
}

程序运行:
在这里插入图片描述
示例2:命令ipcs -q查看进程间使用共享内存通信的个数,发现有很多,并且键为0。要删除这些用不到的共享内存,思路:

  • 首先获取是哪些进程用到了共享内存,根据shmid使用shmctl()函数可以获取进程pid;
  • 然后使用kill -9 pid杀死进程即可;
    在这里插入图片描述
    参考代码:
/**
* @file rmshmid.c
* @author zoya (2314902703@qq.com)
* @brief 根据指定的shmid杀死对应的进程
* @version 0.1
* @@date: 2022-10-03
* 
* @copyright Copyright (c) 2022
* 
*/

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main(int argc, char *argv[]){
    // 根据shmid获取进程id
    int shmid = atoi(argv[1]);
    struct shmid_ds buf;
    shmctl(shmid,IPC_STAT,&buf);

    printf("shmid=%d 对应的 pid=%d, last oppid=%d , uid=%d\n",shmid,buf.shm_cpid,buf.shm_lpid,buf.shm_perm.cuid);
    return 0;
}

获取到进程pid后使用kill命令杀死对应的进程即可。

5. 共享内存使用总结

1. 操作系统如何知道共享内存被多少个进程关联?
从内核来看,共享内存维护结构体struct shmid_ds,该结构体中有一个成员shm_nattach,shm_attach记录了关联的进程个数。
2. 是否可以对共享内存进行多次删除 shmctl
可以,shmctl只是标记共享内存,不是直接删除;
当和共享内存关联的进程数为0时,共享内存被真正删除;
当共享内存的key变为0时,共享内存被标记删除了;
如果一个进程和共享内存取消关联,该进程就不能继续操作共享内存,也不能进行再次关联;
3. 共享内存和内存映射的区别

  1. 共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外);
  2. 共享内存效率更高;
  3. 内存:
    共享内存:所有进程操作的是同一块共享内存;
    内存映射:每个进程在自己的虚拟地址空间有一个独立的空间;
  4. 数据安全
    1. 进程突然退出:共享内存还存在;内存映射区小时
    2. 运行进程的电脑宕机:数据存在共享内存中,就没有了;内存映射:由于磁盘文件中的数据还在,所以内存映射区中的数据还存在
  5. 生命周期
    1. 内存映射:进程退出,内存映射区销毁
    2. 共享内存:进程退出,共享内存还在,需要手动删除(所有关联的进程数为0也可以删除)或者关机;如果进程退出,会自动和共享内存取消关联。

二, 守护进程

先了解下终端、进程组、会话的概念。

1. 终端

UNIX/Linux中,用户通过终端登录系统后得到一个shell进程,这个终端称为shell进程的控制终端。进程中,控制终端是保存在PCB中的信息,而fork()会复制PCB中的信息,因此由shell()进程启动的其它进程的控制终端也是这个终端。

默认情况下,每个进程的标准输入、标准输出和标准错误都指向控制终端,进程从标准输入读出就是读用户的键盘输入,进程标准输出或标准错误输出是输出到显示器上。

在控制终端输入一些特殊的控制键可以给前台进程发信号,例如 CTRL+C 会产生SIGINT信号,Ctrl+\会产生SIGQUIT信号。

echo $$ 可以查看当前终端的进程id
.\a.out & 以后台进程运行

2. 进程组

进程组:很多进程的集合。

进程组和会话在进程之间形成了一种两级层次关系;进程组是一组相关进程的集合,会话是一组相关进程组的集合。
进程组和会话是为支持shell作业控制而定义的抽象概念,用户通过shell能够交互式地在前台或后台运行命令。

进程组由一个或多个共享同一进程组标识符PGID的进程组成。一个进程组拥有一个进程组首进程,该进程是创建该组的进程,其进程ID为该进程组的ID,新进程会继承其父进程所属的进程组ID。

进程组拥有一个生命周期,其开始时间为首进程创建组的时刻,结束时间为最后一个成员进程推出组的时刻。一个进程可能会因为终止而退出进程组,也可能会因为加入了另外一个进程组而退出进程组。进程组首进程无需是最后一个离开进程组的成员。

3. 会话

会话是一组进程组的集合。会话首进程是创建该新会话的进程,其进程ID称为会话ID,新进程会继承其父进程的会话ID(SID)。

一个会话中的所有进程共享单个控制终端。控制终端在会话首进程首先打开一个终端设备时被建立,一个终端最多可能会称为一个会话的控制终端。

在任一时刻,会话中的其中一个进程组会成为终端的前台进程组,其它进程组会成为后台进程组。只有前台进程组中的进程才能从控制终端中读取输入。当用户在控制终端中输入终端字符生成信号后,该信号会被发送到前台进程组中的所有成员。

当控制终端的连接建立起来之后,会话首进程会成为该终端的控制进程。

find / 2 > /dev/null | wc -l & >是重定向,&表示是后台运行
sort < longlist | uniq -c

4. 进程组、会话等相关操作函数

pid_t getpgrp(void);  // 获取当前调用进程的进程组id
int setpgid(pid_t pid,pid_t pgid);  // 设置pid的进程组id为pgid;如果pid=0,使用当前调用进程的id为pid;如果pgid=0,则pid指定的pgid与pid一致
// 建议使用上面两个函数设置/获取进程组id
pid_t getpgid(pid_t pid);  // 获取指定的进程组ID

pid_t getsid(pid_t pid);  // 获取指定pid的会话id
pid_t setsid(void);  // 如果当前调用进程不是进程组首进程,则创建会话;默认创建的会话没有控制终端

5. 守护进程

守护进程,也就是Daemon进程/精灵进程,是Linux中的后台服务进程,是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件,一般采用以d结尾的名字。

守护进程具备下列特征:

  • 生命周期很长,守护进程会在系统启动的时候被创建并一直运行直至系统被关闭。
  • 它在后台运行并且不拥有控制终端。没有控制终端确保了内核永远不会为守护进程自动生成任何控制信号以及终端相关的信号。

Linux的大多数服务器就是用守护进程实现的。比如,internet服务器inetd,web服务器httpd等。

5.1 创建守护进程

创建守护进程步骤(其中后面打√表示该步骤是必须操作的):

  1. 执行一个fork(),然后父进程退出,子进程继续执行; √
    目的是为了
    • 防止父进程结束后显示~指示符 ;
    • 使用fork()可以确保子进程ID不是所在进程组的进程组ID;
  2. 子进程调用setsid()开启一个新会话; √
    • 避免创建有冲突的会话;
  3. 清除进程的umask以确保当守护进程创建文件和目录时拥有所需的权限;
  4. 修改进程的当前工作目录;
  5. 关闭守护进程从其父进程继承来的所有打开的文件描述符;
  6. 在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null,并使用dup2()使所有这些文件描述符指向这个设备;
  7. 核心业务逻辑 √
5.2 创建守护进程示例
/**
 * @file daemon.c
 * @author zoya (2314902703@qq.com)
 * @brief 创建一个守护进程,每隔2s打印当前时间
 * @version 0.1
 * @@date: 2022-10-04
 *
 * @copyright Copyright (c) 2022
 *
 */

#define _XOPEN_SOURCE

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <time.h>

void my_alarm(int signum)
{
    // 打印时间
    time_t t1 = time(NULL);
    struct tm *t = localtime(&t1);
    char *str = asctime(t);

    int fd = open("time.txt", O_CREAT | O_RDWR | O_APPEND, 0664);
    write(fd, str, strlen(str));
    close(fd);
}

int main()
{

    // 创建子进程,父进程退出,子进程继续执行
    pid_t pid = fork();
    if (pid > 0)
    {
        printf("父进程,退出!\n");
        exit(0);
    }

    // 子进程开启一个新会话
    pid_t sid = setsid();
    if (sid == (pid_t)-1)
    {
        perror("setsid");
        exit(-1);
    }

    // 清除进程的umask
    umask(022);

    //  修改进程的当前工作目录
    chdir("/home/zoya");

    // 关闭守护进程从其父进程继承来的所有打开的文件描述符
    // 父进程没有打开文件描述符
    // 重定向到/dev/null
    int fd = open("/dev/null", O_RDWR);
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);

    // 核心业务逻辑

    // 捕捉定时器信号
    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = my_alarm;
    sigemptyset(&act.sa_mask);
    sigaction(SIGALRM, &act, NULL);

    // 设置定时器,1s后每隔2s定时一次
    struct itimerval new;
    new.it_interval.tv_sec = 2;
    new.it_interval.tv_usec = 0;
    new.it_value.tv_sec = 1;
    new.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, &new, NULL);

    // 进程持续运行
    while (1)
    {
        sleep(5);
    }

    return 0;
}

三, 消息队列

1. 什么是消息队列

  • 消息队列也称为报文队列,也是Linux的一种通信机制。消息队列传递的数据具有某种结构,不是简单的字节流;每个数据结构被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构
  • Linux使用宏MSGMAX和MSGMNB限制一条消息的最大长度和一个队列的最大长度。
  • 消息队列的本质是一个内核提供的链表,内核基于这个链表,实现了一个数据结构;
  • 向消息队列中写数据,实际是向这个数据结构中插入了一个新结点;从消息队列中读数据,实际是从这个数据结构中删除一个结点;
  • 消息队列提供了从一个进程向另外一个进程发送一块数据的方法;
  • 消息队列每个数据块的最大长度是有上限的,系统上的全体队列的最大总长度也有一个上限;

进程间使用消息队列进行通信时通过key队列标识码建立连接。

无论是发送数据的进程还是接收数据的进程,都需要在进程空间中用消息缓冲区来暂存消息。该消息缓冲区的结构定义如下:

struct msgbuf{
   long mtype;  // 消息的类型
   char mtext[1];  // 消息正文
};

mtype用来区分数据类型,同时判断mtext是否为需要接收的数据;
mtext为存放消息正文的数组,可以根据消息的大小定义该数组的长度;

2. 消息队列相关操作函数

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key,int msgflg);
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msgid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
int msgctl(int msgid,int command,struct msgid_ds *buf);

msgget()函数说明

头文件#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数声明int msgget(key_t key,int msgflg);
函数功能创建消息队列
参数key根据key创建消息队列,key可以指定或者由ftok()函数获取
参数msgflg创建的消息队列的权限,与文件的访问权限一样
返回值成功返回非负整数,是创建的消息队列的标识码
失败返回-1

msgsnd()函数说明:

头文件#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数声明int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);
函数功能发送消息队列
参数msgidmsgget函数返回的消息队列标识码
参数msg_ptr指针,指向准备发送的消息,必须是以一个长整型成员变量开始的结构体,接收函数将用这个成员确定消息的类型,如上面的结构体msgbuf
参数msg_szmsg_ptr指向的消息长度,消息缓冲区结构体中text的大小,不包括数据的类型
参数msgflg控制当前消息队列满或到达上限时将要发生的事情
IPC_NOWAIT表示队列满不等待,返回EAGAIN错误
返回值成功返回0,消息数据会被放到消息队列中,失败返回-1

msgrcv()函数说明:

头文件#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数声明ssize_t msgrcv(int msgid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
函数功能从消息队列中获取消息
参数msgidmsgget函数返回的消息队列标识码
参数msg_ptr指针,指向准备接收的消息
参数msgszmsg_ptr指向的消息长度,消息缓冲区结构体中text的大小,不包括数据的类型
参数msgtyp可以实现接收优先级的简单形式
=0 获取消息队列中的第一个消息
>0 获取具有相同消息类型的第一个信息
<0 获取类型等于或小于msgtype的绝对值的第一个消息
参数msgflg控制队列中没有相应类型的消息可供接收时将要发生的事情
IPC_NOWAIT表示对于没有可读消息不等待,返回ENOMSG错误
MSG_NOERROR表示消息大小超过msgsz时被截断
返回值成功返回接收的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息
失败返回-1

msgctl()函数说明:

头文件#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数功能控制消息队列,与共享内存的shmctl类似
函数声明int msgctl(int msgid, int command, struct msgid_ds *buf);
参数msgidmsgget函数返回的消息队列标识码
参数command对消息队列要采取的动作
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值
IPC_SET:在进程有足够权限的前提下,把消息队列的当前关联值设置为msgid_ds数据结构中给出的值
IPC_RMID:删除消息队列,如果选择删除消息队列,第三个参数传递NULL
参数buf指向msgid_ds结构的指针,表示消息队列模式和访问权限,msgid_ds结构如下
返回值成功返回0,失败返回-1
struct msqid_ds {
               struct ipc_perm msg_perm;     /* Ownership and permissions */
               time_t          msg_stime;    /* Time of last msgsnd(2) */
               time_t          msg_rtime;    /* Time of last msgrcv(2) */
               time_t          msg_ctime;    /* Time of last change */
               unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
               msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
               msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
               pid_t           msg_lspid;    /* PID of last msgsnd(2) */
               pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
           };
struct ipc_perm {
               key_t          __key;       /* Key supplied to msgget(2) */
               uid_t          uid;         /* Effective UID of owner */
               gid_t          gid;         /* Effective GID of owner */
               uid_t          cuid;        /* Effective UID of creator */
               gid_t          cgid;        /* Effective GID of creator */
               unsigned short mode;        /* Permissions */
               unsigned short __seq;       /* Sequence number */
           };

查看消息队列:
使用命令ipcs -q查看已经创建的消息队列,包括key值信息,id信息,拥有者信息,文件权限信息,已使用的字节数和消息条数。
使用命令ipcrm -Q加消息队列的key值用来删除一个消息队列。

3. 消息队列使用示例

read_msgqueue.c

/**
 * @file read_msgqueue.c
 * @author zoya (2314902703@qq.com)
 * @brief 从消息队列中读取消息
 * @version 0.1
 * @@date: 2022-10-04
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>

struct MSG
{
    long mtype;
    char mtext[2048];
};

int main()
{

    // 获取key
    key_t key = ftok("/home/zoya/Linux/chap2/lesson16/test", 'b');
    printf("key : 0x%x\n", key);

    // 创建消息队列
    int msgid = msgget(key, O_RDONLY);
    if (msgid == -1)
    {
        perror("msgget");
        exit(-1);
    }

    // 从消息队列中读取消息
    struct MSG msgbuf;
    msgbuf.mtype = 1; // 消息类型
    while (1)
    {
        msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgbuf.mtype, 0);
        printf("receive msg : [%s]\n", msgbuf.mtext);
        printf("按enter键继续,按0退出\n");
        if ('0' == getchar())
            break;
        msgbuf.mtype++;
        msgbuf.mtype %= 4;
        if (msgbuf.mtype == 0)
            msgbuf.mtype++;
    }

    //  删除消息队列
    msgctl(msgid, IPC_RMID, NULL);

    return 0;
}

write_msgqueue.c

/**
 * @file read_msgqueue.c
 * @author zoya (2314902703@qq.com)
 * @brief 向消息队列发送消息
 * @version 0.1
 * @@date: 2022-10-04
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>

struct MSG
{
    long mtype;
    char mtext[2048];
};

int main()
{

    // 获取key
    key_t key = ftok("/home/zoya/Linux/chap2/lesson16/test", 'b');
    printf("key : 0x%x\n", key);

    // 创建消息队列
    int msgid = msgget(key, IPC_CREAT | O_WRONLY | 0777);
    if (msgid == -1)
    {
        perror("msgget");
        exit(-1);
    }

    // 向消息队列发送消息
    struct MSG msgbuf;
    msgbuf.mtype = 1; // 消息类型
    char buf[2048];
    while (1)
    {
        memset(buf,0,sizeof(buf));
        sprintf(buf, "key=0x%x, msgid=%d, type=%ld, msg : hello, i am writer!",
                key, msgid, msgbuf.mtype);
        printf("send msg: [%s]\n", buf);
        strcpy(msgbuf.mtext, buf);
        msgsnd(msgid, &msgbuf, sizeof(msgbuf.mtext), 0);

        printf("按enter键继续,按0退出\n");
        if ('0' == getchar())
            break;
        
        msgbuf.mtype++;
        msgbuf.mtype %= 4;
        if (msgbuf.mtype == 0)
            msgbuf.mtype++;
    }

    //  删除消息队列
    msgctl(msgid, IPC_RMID, NULL);

    return 0;
}

程序运行:

在这里插入图片描述

文章参考:

  1. https://blog.csdn.net/qq_42425882/article/details/105105209
  2. https://www.nowcoder.com/study/live/504/2/28
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值