【Linux】进程间通信之System V共享内存

news2024/9/9 5:16:52

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 一、共享内存的工作原理
  • 二、系统调用接口
      • 2.1 shmget函数 --- 创建共享内存
      • 2.2 补充:key值和shmid的区别
      • 2.3 shmctl函数 --- 释放
      • 2.4 shmat函数 --- 进程挂接共享内存
      • 2.5 shmdt函数 --- 手动解除进程和共享内存挂接
  • 三、使用以上接口让两个进程通信
  • 四、共享内存的特性
  • 五、共享内存的属性
  • 六、解决共享内存没有同步和互斥保护机制问题
  • 七、本篇博客源代码

一、共享内存的工作原理

请添加图片描述

Linux操作系统除了要为进程创建结构体对象task_struct(表示进程的数据结构,包含了进程的所有属性,如进程标识符PID);除此之外,操作系统还会为每个进程创建进程地址空间结构体对象mm_struct(存储了进程的地址空间信息,包括堆、栈等)。该进程要如何找到自己的进程地址空间呢?因此task_struct结构体还会有该进程对应的mm_struct结构体指针字段,可以通过task_struct对象找到对应的进程地址空间。

另外,操作系统还需为每个进程创建页表Linux操作系统系统会通过分页机制来管理虚拟地址和物理地址之间的映射关系,用于将虚拟地址映射到物理地址。当程序访问虚拟地址时,操作系统会根据页表将虚拟地址转换为物理地址。

而进程间通信的本质是:让不同的进程看到同一份资源。因此,共享内存的原理就是:因为进程具有独立性,无法自己提供资源给对方,因此操作系统会在物理内存中开辟一块内存(“共享内存”由操作系统提供),再通过页表映射到两个不同进程的虚拟地址空间中的共享区,此时两个独立的进程看到同一块空间,那么就可以进行通信了。(整个过程类似于动态库的加载)

除此之外,因为共享内存是由程序员向操作系统申请的,当然还需要正确地释放共享内存资源,以避免内存泄漏和资源占用。释放共享内存的过程包括两个主要步骤:

  • 解除所有与要释放的共享内存有关系的进程(去关联)
  • 最后释放共享内存,通常由最后一个使用该共享内存段的进程来执行(释放共享内存)

因此,System V共享内存通信的整个过程总结如下:

  • 创建共享内存段

  • 进程挂接共享内存

  • 通信

  • 解除共享内存挂接

二、系统调用接口

共享内存的创建、进程间建立映射和释放都是由操作系统完成的。对应的操作系统也要为用户提供访问和管理共享内存的接口,允许用户在程序中使用共享内存来实现进程间通信。

2.1 shmget函数 — 创建共享内存

shmget 函数是一个用于创建共享内存或者访问已存在共享内存段,通常用于在进程之间共享数据而无需通过文件系统。它的函数原型如下:

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

int shmget(key_t key, size_t size, int shmflg);

参数说明:

  • key:可以唯一标识一个共享内存段(内核层使用)。这个值是多少并不重要,关键在于它必须具有唯一性,能够让不同的进程看到同一个共享内存(两个进程传入相同的key值即看到同一块内存)。就这样说吧,第一个进程通过key值创建共享内存,后面的进程要用看到同一个共享内存通信,只需拿同一个key值即可!注意:这个key值在struct shmid_ds对象中。那么接下来只有最后一个问题:如何获取设置key?一般使用 ftok 函数生成唯一key值(常用)。(查看函数用法:点击跳转)

  • size指定要创建的共享内存段的大小,以字节为单位。如果创建新的共享内存段,建议分配4096的整数倍(如果你创建的大小是4097,实际上操作系统分配的大小是4096 * 2)。如果只是获取一个已存在的共享内存段的标识符,这个参数可以设置为0来表示忽略。

  • shmflg:这是一个标志参数,用于指定操作模式和权限。可以用操作符'|'进行组合使用。它可以是以下几个标志的组合:

    • IPC_CREAT这个选项单独使用的话,如果申请的共享内存不存在,则创建一个新的共享内存;如果存在,获取已存在的共享内存
    • IPC_EXCL: 一般配合 IPC_CREAT 一起使用(不单独使用)。他主要是检测共享内存是否存在,如果存在,则出错返回;如果不存在就创建。确保申请的共享内存一定是新的。
    • 权限标志:以与文件权限类似的方式指定共享内存段的访问权限(例如 0666 表示所有用户可读写)。
    • 但在获取已存在的共享内存时,可以设置为 0
  • 返回值:

    • 成功时返回共享内存段的标识符shmid。(操作系统内部分配的,提供给用户层使用,类似于文件描述符fd

    • 失败时返回 -1,并设置 errno 以指示错误原因。

  • 返回值常见错误errno如下:

    • EACCES:对于给定的键值没有足够的权限。

    • EEXIST: 设置了 IPC_CREAT | IPC_EXCL,但具有给定键值的共享内存段已经存在。

    • EINVAL: 请求的大小无效,或者给定的键值无效。

    • ENOENT: 没有设置 IPC_CREAT,而且具有给定键值的共享内存段不存在。

    • ENOMEM: 没有足够的内存来满足请求。

ftok函数是专门用于生成唯一键值,通常用于进程间通信的共享内存、信号量和消息队列。它通过将文件路径(路径本身就具有唯一性)项目标识符ID 结合起来生成一个唯一的键值。其函数原型如下:

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

key_t ftok(const char *pathname, int proj_id);
  • pathname: 指向一个现有文件的路径。这个文件可以是任何文件,但在实际应用中,通常选择一个固定存在且不会被删除的文件。

  • proj_id: 项目标识符,一个用户指定的整数值。该值一般用于扩展唯一性,如果冲突该次参数就行。

  • 返回值:

    • 成功时返回生成的键值 (key_t 类型)。可以用于后续的函数调用(如shmget()等)。

    • 失败时返回 -1,并设置 errno 以指示错误原因。

常见的 errno 值包括:

  • EACCES: 文件不可访问。
  • ENOENT: 文件不存在。
  • ENAMETOOLONG: 路径名过长。

代码样例:

resource.hpp中封装创建共享内存段的接口,来供其他进程使用。

#pragma once

#include <iostream>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string>
#include <cstring>
#include "log.hpp"

using namespace std;

const string pathname = "/home/wj"; // ftok函数的第一个参数
int proj_id = 'A';                  // ftok函数的第二个参数
log l;                              // 日志对象

key_t Getkey()
{
    key_t key = ftok(pathname.c_str(), proj_id);
    if (key == -1)
    {
        l.logmessage(Fatal, "ftok error: %s", strerror(errno));
        exit(1);
    }
    l.logmessage(Info, "ftok success, key is: 0x%x", key);
    return key;
}

int GetShareMem()
{
    key_t key = Getkey();
    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL);
    if (shmid == -1)
    {
        l.logmessage(Fatal, "create share memory error: %s", strerror(errno));
    }
    l.logmessage(Info, "create share memory success: %d", shmid);
    return shmid;
}

我们先使用一个进程processA.cc来测试一下

#include "resource.hpp"

int main()
{
    int shmid = GetShareMem();

    l.logmessage(Debug, "process quit...");
    return 0;
}

结果如下:

请添加图片描述

2.2 补充:key值和shmid的区别

不同的进程在选择共享内存通信时,是通过key值来保证共享内存的唯一性,那这里就存在一个问题:shmget函数也会返回一个值,我们把这个值称为共享内存段标识符shmid,而往后要对共享内存进行操作(如附加、分离、删除等)的时候,通常使用的是shmid来进行操作。为什么要创建两种共享内存的标识呢?直接使用一个来进行标识不就够了嘛?

那么这里我就可以举一个生活中的例子来进行解释:我们中国有14+亿的人口,每个人出生之后就会拥有一个独一无二(唯一)的身份证号,通过这个身份证号我们就可以标识一个人,那为什么还要给每个人取一个名字呢?直接使用身份证号来代替名字不就可以了吗?之所以这么做是因为使用名字可以更加方便的社交,在短范围内能够更快的确定一个人,如果使用身份证号的话就会导致标识和管理的时候比较臃肿。

那么这里也是同样的道理,key就相当于身份证号,shmid相当于姓名。站在用户角度,使用key值来标定共享内存太难了太复杂了,所以我们使用shmid来标识共享内存这样可以更加的方便和容易;而操作系统它不嫌麻烦他为了更加严谨的标识共享内存就必须得使用更加复杂的key值来进行标定,就好比政府机构在处理具体某个人的事情时,都会拿身份证号来标定某个人,因为身份证号更加的复杂、更加的准确、权威性更高。因此key是给内核层使用的。

key值在哪里存储的呢?我们说操作系统中存在多个共享内存所以要进行先描述在组织,那么描述共享内存的shm结构体中就存在一个字段专门用来记录共享内存的key值,所以key值会通过系统调用shmget设置进共享内存的属性中的用来表示共享内存在内核中的唯一性。

总之,shmid相当于文件系统的文件描述符fd,而key就相当于文件系统的inode编号,inode编号是用来给操作系统看的,fd是用来给我们操作者使用的,操作系统之所以这么做是为了方便标识符的解藕,用户层和操作系统层使用不同的东西来标识内存,这样用户层和操作系统层就不会发生相互的干扰,也就是一个层面出现了问题不会影响另外的一个层面,那么这就是key(内核层使用)和shmid(用户层使用)的区别。

2.3 shmctl函数 — 释放

请添加图片描述

如上,当我再次启动进程processA的时候,我们发现使用shmget函数创建共享内存失败了,原因是文件存在(共享内存段存在)。通过分析我们知道:我使用IPC_CREATIPC_EXCL选项保证进程每次申请的共享内存一定是新的,而在第一次运行完进程processA,我们的代码内并没有手动释放共享内存段,因此导致报错!

Linux中,如果想查看操作系统管理的共享内存段,我们可以使用以下命令:

ipcs -m

请添加图片描述

共享内存的生命周期是随内核的(操作系统重启,共享内存通常才会被释放)!因为共享内存是由用户向操作系统申请的,如果不主动关闭,共享内存会一直存在。另外,如果程序频繁地分配共享内存而不释放,系统的可用内存资源会逐渐减少,可能导致系统性能下降或者其它进程受到影响(内存泄漏)。

有两种方法可以释放共享内存段:

  • 使用以下命令来指定释放共享内存段
ipcrm -m <shmid>

请添加图片描述

  • 使用系统调用接口shmctl函数

shmctl 函数用于对共享内存进行控制操作,例如获取共享内存信息、设置共享内存权限、销毁共享内存等。它的原型如下:

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

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数说明:

  • shmid:共享内存标识符shmid

  • cmd:控制命令,可以是下列之一:

    • IPC_STAT:获取共享内存的状态信息,并将其存储在 buf 结构体中。
    • IPC_SET:设置共享内存的状态信息,使用 buf 结构体中提供的新值。
    • IPC_RMID:删除共享内存段。
  • buf:指向描述共享内存的结构体shmid_ds的指针,用于存储或传递共享内存的状态信息。如果是删除共享内存段,此参数可以设置为nullptr

  • 返回值:成功返回0,失败返回-1

【代码样例】

#include "resource.hpp"

int main()
{
    // 创建共享内存段
    int shmid = GetShareMem();
    l.logmessage(Debug, "create shm done");

    // 销毁共享内存段
    int res = shmctl(shmid, IPC_RMID, nullptr);
    if (res == -1)
    {
        l.logmessage(Error, "share Memory destroy failed, shmid is %d", shmid);
        exit(Error);
    }
    l.logmessage(Debug, "share Memory destroy success, shmid is %d", shmid);

    l.logmessage(Debug, "process quit...");
    return 0;
}

【程序结果】

请添加图片描述

2.4 shmat函数 — 进程挂接共享内存

shmat函数的基本作用是将一个共享内存段映射到调用进程的地址空间的共享区,从而使得进程可以直接访问共享内存中的数据。

函数基本原型如下:

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • shmid:共享内存的标识符shmid

  • shmaddr:指定共享内存段映射到进程地址空间的起始地址,通常设为 nullptr,让系统自动选择合适的地址。

  • shmflg:附加标志,通常设为 0

  • 返回值:成功返回指向共享内存的起始地址的指针;失败返回nullptr

  • 代码写法有点类似于malloc函数

  • 注意:进程退出后,会自动解除挂接。

为了更好观察进程挂接共享内存的个数,在销毁共享内存段之前休眠10

#include "resource.hpp"

int main()
{
    // 1. 创建共享内存段
    int shmid = GetShareMem();
    l.logmessage(Debug, "create shm done");

    // 2. 挂接共享内存段
    char *shmaddress = (char *)shmat(shmid, nullptr, 0);
    if (shmaddress == nullptr)
    {
        l.logmessage(Error, "processA attach failed, shmid is %d", shmid);
    }
    l.logmessage(Debug, "processA attach success, shmid is %d", shmid);

    // 休眠10s
    sleep(10);

    // 3. 销毁共享内存段
    int res = shmctl(shmid, IPC_RMID, nullptr);
    if (res == -1)
    {
        l.logmessage(Error, "share Memory destroy failed, shmid is %d", shmid);
        exit(Error);
    }
    l.logmessage(Debug, "share Memory destroy success, shmid is %d", shmid);

    l.logmessage(Debug, "process quit...");
    return 0;
}

【程序结果】

请添加图片描述

2.5 shmdt函数 — 手动解除进程和共享内存挂接

shmdt 函数用于将共享内存从当前进程的地址空间中分离,即取消共享内存的映射。这个函数的原型如下:

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
  • shmaddr 参数是一个指向共享内存段起始地址的指针,通常是shmat函数的返回值。
  • 返回值:成功返回0,失败返回-1

代码演示略 ~

三、使用以上接口让两个进程通信

一般而言,只要有一方进程创建了共享内存段,另一方进程直接获取其共享内存段标识符shmid后,即可进行通信。

  • resource.hpp主要是封装获取共享内存段接口,确保两个进程使用的是同一块共享内存。
#pragma once

#include <iostream>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string>
#include <cstdlib>
#include <cstring>
#include "log.hpp"

using namespace std;

const string pathname = "/home/wj"; // ftok函数的第一个参数
int proj_id = 'A';                  // ftok函数的第二个参数
log l;                              // 日志对象

key_t Getkey()
{
    key_t key = ftok(pathname.c_str(), proj_id);
    if (key == -1)
    {
        l.logmessage(Fatal, "ftok error: %s", strerror(errno));
        exit(1);
    }
    l.logmessage(Info, "ftok success, key is: 0x%x", key);
    return key;
}

int GetShareMem()
{
    key_t key = Getkey();
    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        l.logmessage(Fatal, "create share memory error: %s", strerror(errno));
    }
    l.logmessage(Info, "create share memory success, shmid is %d", shmid);
    return shmid;
}

int GetShm()
{
    // 大小:获取一个已存在的共享内存段的标识符,这个参数可以设置为0来表示忽略
    // 第三个参数:获取已存在的共享内存时,可以设置为 0
    return shmget(Getkey(), 0, 0);
}
  • processA.cc主要是负责读取共享内存段的数据。注意:此进程要先运行起来。
#include "resource.hpp"

int main()
{
    // 1. 创建共享内存段
    int shmid = GetShareMem();

    // 2. 挂接共享内存段
    char *shmaddress = (char *)shmat(shmid, nullptr, 0);

    // 3. 通信
    while (true)
    {
        // 假设processA进程作为客户端,负责读取
        cout << "client say@ " << shmaddress << endl;
        if (strcmp(shmaddress, "quit\n") == 0)
        {
            break;
        }
        sleep(1);
    }

    // 4. 取消挂接
    shmdt(shmaddress);

    // 5. 销毁共享内存段
    int res = shmctl(shmid, IPC_RMID, nullptr);

    return 0;
}
  • processB主要是向共享内存段写入数据。当写入quit时整个通信过程结束。
#include "resource.hpp"

int main()
{
    // 1. 获取shmid
    int shmid = GetShm();

    // 2. 挂接共享内存
    char *shmaddress = (char *)shmat(shmid, nullptr, 0);

    // 3. 通信
    while (true)
    {
        // processB进程作为服务端,负责写
        cout << "请输入:";
        char *context = fgets(shmaddress, 4096, stdin);
        if (context != nullptr && strcmp(shmaddress, "quit\n") == 0)
        {
            break;
        }
    }
    // 4. 取消挂接
    shmdt(shmaddress);

    return 0;
}
  • 程序结果

在这里插入图片描述

四、共享内存的特性

  • 共享内存没有同步和互斥之类的保护机制。即读写双方可以同时访问共享内存,这会导致数据不一致问题,这个问题的解决方案将在下方会介绍到。

  • 共享内存是所有的进程间通信中,速度最快的!原因在于它减少数据拷贝。在使用共享内存时,多个进程可以直接访问同一块物理内存,而不需要将数据从一个进程的地址空间复制到另一个进程的地址空间。这避免了数据在内存之间的复制,从而减少了通信的开销和延迟。

  • 共享内存内部的数据由用户自己维护(读完要自己清空)。

  • 共享内存的生命周期是随内核的,用户不主动删除,共享内存会一直存在(除非内核重启或用户释放)

  • 共享内存的大小一般建议是4096的整数倍,内存管理的一页大小为4096字节(4KB)。若申请4097,则系统会分配4096 * 2,但用户还是只能使用4097的空间,会存在4095字节空间的浪费。

五、共享内存的属性

而我们知道,因为系统中不止一对进程在进行通信,可能会存在多个,那么操作系统就要在物理内存上开辟多个共享内存,那么操作系统就必须对这些区域进行管理,这又得搬出管理的六字真言:先描述,再组织。在Unix/Linux中,描述共享内存段的信息通常通过 struct shmid_ds 结构体来表示:

struct shmid_ds 
{
    /*
    这是一个 struct ipc_perm 结构体,用于描述共享内存的操作权限
    struct ipc_perm 包含了共享内存段的拥有者、组和访问权限等信息。
    */
    struct ipc_perm shm_perm; 
    // shm_segsz表示共享内存段的大小,单位是字节
    int shm_segsz; 
    // shm_atime表示最后一次附加该共享内存段的时间。
    __kernel_time_t shm_atime; 
    // shm_dtime表示最后一次分离该共享内存段的时间。
    __kernel_time_t shm_dtime; 
    // 表示最后一次更改该共享内存段的时间。
    __kernel_time_t shm_ctime;
    // shm_cpid表示关联共享内存的进程标识符PID
    __kernel_ipc_pid_t shm_cpid;
    // shm_lpid表示最后一个操作该共享内存段的进程的 PID
    __kernel_ipc_pid_t shm_lpid; 
    // shm_nattch表示当前使用到该共享内存段的进程数
    unsigned short shm_nattch; 
    // ...
};
//  struct ipc_perm 结构体
struct ipc_perm 
{
       // key 用于标识共享内存段。不同的进程可以通过这个key来访问同一个共享内存段。
       key_t  __key;    
       // uid 拥有者(owner)的有效用户ID(UID),即对共享内存段有读写权限的用户的UID。
       uid_t  uid;      
       // gid 这是拥有者的有效组ID(GID),即对共享内存段有读写权限的用户所在的组的GID。
       gid_t  gid;     
       // cuid 这是创建者(creator)的有效用户ID(UID),即创建共享内存段的进程的UID。 
       uid_t  cuid;    
       // cgid 这是创建者的有效组ID(GID),即创建共享内存段的进程所在的组的GID。 
       gid_t  cgid;  
       // mode 这个字段包含了权限和一些特定标志,如使用IPC_CREAT来创建IPC对象。
       unsigned short mode;     
       // __seq 这个字段是序列号,用于维护IPC对象的序列。                                 
       unsigned short __seq;    
};

最后再通过诸如链表、顺序表等数据结构将这些结构体对象管理起来。因此,往后我们对共享内存的管理,只需转化为对某种数据结构的增删查改。

我们可以使用shmctl函数来获取属性信息(具体用法查看往上翻)

int main()
{
    //  创建共享内存段
    int shmid = GetShareMem();

    // 挂接共享内存段
    char *shmaddress = (char *)shmat(shmid, nullptr, 0);

    // 获取共享内存的属性
    struct shmid_ds shmds;
    shmctl(shmid, IPC_STAT, &shmds);
    cout << "共享内存的大小:" << shmds.shm_segsz << endl;
    cout << "共享内存的连接数:" << shmds.shm_nattch << endl;
    printf("共享内存的key值:0x%x\n", shmds.shm_perm.__key);

    // 取消挂接
    shmdt(shmaddress);

    return 0;
}

【程序结果】

请添加图片描述

六、解决共享内存没有同步和互斥保护机制问题

共享内存的特点是 无读写规则限制,进程即可读也可写,容易造成冲突,因此我们可以对其加以限制,所使用的工具正是命名管道。

逻辑思路是这样的:当共享内存的写方向共享内存段写完数据后,使用命名管道向读方发送一条通知,说明可以向共享内存读取数据了。

  • resource.hpp增加了创建命名管道的类
#pragma once

#include <iostream>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string>
#include <sys/stat.h>
#include <cstdlib>
#include <cstring>
#include "log.hpp"

using namespace std;

const string pathname = "/home/wj"; // ftok函数的第一个参数
int proj_id = 'A';                  // ftok函数的第二个参数
log l;                              // 日志对象

key_t Getkey()
{
    key_t key = ftok(pathname.c_str(), proj_id);
    if (key == -1)
    {
        l.logmessage(Fatal, "ftok error: %s", strerror(errno));
        exit(1);
    }
    l.logmessage(Info, "ftok success, key is: 0x%x", key);
    return key;
}

int GetShareMem()
{
    key_t key = Getkey();
    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        l.logmessage(Fatal, "create share memory error: %s", strerror(errno));
    }
    l.logmessage(Info, "create share memory success, shmid is %d", shmid);
    return shmid;
}

int GetShm()
{
    // 大小:获取一个已存在的共享内存段的标识符,这个参数可以设置为0来表示忽略
    // 第三个参数:获取已存在的共享内存时,可以设置为 0
    return shmget(Getkey(), 0, 0);
}

// ================== 命名管道 ==============================
enum
{
    // 规定错误码从1开始递增
    MKFIFO_FAIL = 1, // 创建匿名管道失败
    UNLINK_FAIL,     // 删除匿名管道失败
    OPEN_FAIL        // 打开文件失败
};

class Init
{
public:
    Init()
    {
        // 创建管道
        int n = mkfifo("./myfifo", 0664);
        if (n == -1)
        {
            perror("mkfifo");
            exit(MKFIFO_FAIL);
        }
    }
    ~Init()
    {
        int m = unlink("./myfifo");
        if (m == -1)
        {
            perror("unlink");
            exit(UNLINK_FAIL);
        }
    }
};
  • processB.cc是写方,当写完后就向管道写入一个字符作为通知。
int main()
{
    // 1. 获取shmid
    int shmid = GetShm();

    // 2. 挂接共享内存
    char *shmaddress = (char *)shmat(shmid, nullptr, 0);

    // 打开命名管道
    int fd = open("./myfifo", O_WRONLY);
    if (fd == -1)
    {
        exit(OPEN_FAIL);
    }

    // 3. 通信
    while (true)
    {
        // processB进程作为服务端,负责写
        cout << "请输入:";
        char *context = fgets(shmaddress, 4096, stdin);
        // 写完之后,通知对方来读取。
        write(fd, "c", 1);
        if (context != nullptr && strcmp(shmaddress, "quit\n") == 0)
        {
            break;
        }
    }
    // 4. 取消挂接
    shmdt(shmaddress);
    close(fd);

    return 0;
}
  • processA.cc是读方,在向共享内存段读取之前,先判断管道是否有“通知”,有则可以读取。
int main()
{
    // 创建管道
    Init init;

    // 1. 创建共享内存段
    int shmid = GetShareMem();

    // 2. 挂接共享内存段
    char *shmaddress = (char *)shmat(shmid, nullptr, 0);

    // 打开命名管道
    int fd = open("./myfifo", O_RDONLY);
    if (fd == -1)
    {
        exit(OPEN_FAIL);
    }

    // 3. 通信
    while (true)
    {
        // 假设processA进程作为客户端,负责读取
        // 在读取之前先去管道看看是否有通知
        char c;
        ssize_t s = read(fd, &c, 1);
        // s == 0 说明没读到,那就继续读取
        if (s == 0)
        {
            continue;
        }
        // s == -1说明读取发生错误,那就退出
        if (s == -1)
        {
            break;
        }

        cout << "client say@ " << shmaddress << endl;
        if (strcmp(shmaddress, "quit\n") == 0)
        {
            break;
        }
        sleep(1);
    }

    // 4. 取消挂接
    shmdt(shmaddress);

    // 5. 销毁共享内存段
    int res = shmctl(shmid, IPC_RMID, nullptr);
    close(fd);

    return 0;
}
  • 程序结果

在这里插入图片描述

七、本篇博客源代码

Gitee链接:点击跳转

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1918535.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

凯中精密:下一个正丹吗?

业绩预增超十倍&#xff01; 又一匹A股业绩黑马诞生——凯中精密 近期&#xff0c;凯中精密发布2024年上半年业绩预告&#xff0c;预计净利润增速高达1068%至1402%。 从23年的209.54%到24年Q1惊人的6885.78%&#xff0c;再到24年上半年的十倍增速&#xff0c;这条业绩黑马利润…

HNU-2024操作系统实验-Lab9-Shell

一、 实验目的 理解Shell程序的原理、底层逻辑和Shell依赖的数据结构等 在操作系统内核MiniEuler上实现一个可用的Shell程序 能够根据相关原理编写一条可用的Shell指令 二、 实验过程 首先从底层出发&#xff0c;实现Shell程序 1.在src/include目录下新建prt_shell.h头文…

Splashtop 在医疗与制药领域的业务增长近五倍

2024年7月10日 加利福尼亚州库比蒂诺 Splashtop 是安全远程访问和 IT 支持解决方案领域的领先企业&#xff0c;该公司今天宣布&#xff0c;在医疗与制药领域业务同比增长492%&#xff0c;取得了里程碑式的成就。快速发展的数字实验室环境和持续的网络安全威胁需要实施无缝、安…

AWS无服务器 应用程序开发—第十七章 Application Composer

Application Composer 是 AWS 提供的一种可视化工具&#xff0c;用于设计和构建无服务器应用程序。它通过拖放界面简化了无服务器架构的创建过程&#xff0c;使开发者能够更直观地设计和配置应用程序的各个组件。 主要功能 可视化设计 通过拖放界面&#xff0c;开发者可以轻…

科技与水利的完美融合:从数据采集到智能决策,全面解析智慧水利解决方案如何助力水利行业实现智能化管理

本文关键词&#xff1a;智慧水利、智慧水利工程、智慧水利发展前景、智慧水利技术、智慧水利信息化系统、智慧水利解决方案、数字水利和智慧水利、数字水利工程、数字水利建设、数字水利概念、人水和协、智慧水库、智慧水库管理平台、智慧水库建设方案、智慧水库解决方案、智慧…

如何搭建互联网医院系统源码?医疗陪诊APP开发实战详解

今天&#xff0c;小编将为大家讲解如何搭建一个完整的互联网医院系统源码&#xff0c;并介绍医疗陪诊APP的开发实战。 一、互联网医院系统的架构设计 搭建一个完整的互联网医院系统&#xff0c;需要从架构设计开始。一个典型的互联网医院系统通常包含以下几个核心模块&#xf…

钡铼ARMxy控制器在智能网关中的应用

随着IoT物联网技术的飞速发展&#xff0c;智能网关作为连接感知层与网络层的枢纽&#xff0c;可以实现感知网络和通信网络以及不同类型感知网络之间的协议转换。钡铼技术的ARMxy系列控制器凭借其高性能、低功耗和高度灵活性的特点&#xff0c;在智能网关中发挥了关键作用&#…

KEIL5 MDK的(官网)下载安装(Win11)

一、KEIL5 MDK下载 1、Keil官网下载&#xff1a;Keil Product Downloadshttps://www.keil.com/download/product/ 支持包和破解软件和V5编译器下载链接 链接&#xff1a;https://pan.baidu.com/s/1ery0Q3FAR8_bLLlPQHSFNg?pwd9pxf 提取码&#xff1a;9pxf 选择最新版本下载&…

【leetcode】滑动窗口专题

文章目录 1.长度最小的子数组2.无重复字符的最长子串3.最大连续1的个数III4.将x减小到0的最小操作数5.水果成篮6.找到字符串中所有字母异位词7.串联所有单词的子串8.最小覆盖子串 1.长度最小的子数组 leetcode 209.长度最小的子数组 看到这个题目&#xff0c;第一眼肯定想到的…

从人工巡检到智能预警:视频AI智能监控技术在水库/河湖/水利防汛抗洪中的应用

一、背景需求分析 近日&#xff0c;我国多省市遭遇连日暴雨&#xff0c;导致水库、湖泊、河道等水域水位暴涨&#xff0c;城市内涝频发。随着夏季汛期的到来&#xff0c;降雨天气频繁&#xff0c;水利安全管理面临严峻挑战。为保障水库安全、预防和减少洪涝灾害&#xff0c;采…

神经网络识别数字图像案例

学习资料&#xff1a;从零设计并训练一个神经网络&#xff0c;你就能真正理解它了_哔哩哔哩_bilibili 这个视频讲得相当清楚。本文是学习笔记&#xff0c;不是原创&#xff0c;图都是从视频上截图的。 1. 神经网络 2. 案例说明 具体来说&#xff0c;设计一个三层的神经网络。…

Qt常用基础控件总结—带边框的部件(QFrame和QLabel)

带边框的部件 框架控件QFrame类 QFrame类介绍 QFrame 类是带有边框的部件的基类,带边框部件的特点是有一个明显的边框,QFrame类就是用来实现边框的不同效果的(把这种效果称为边框样式),所有继承自 QFrame 的子类都可以使用 QFrame 类实现的效果。 部件通常是矩形的(其他…

图纸文档管理新篇章:陕西航沣与三品软件合作 优化研发流程

近日&#xff0c;陕西航沣新材料有限公司与三品软件正式达成合作协议&#xff0c;共同打造高效、智能的图纸文档管理平台。此次合作旨在赋能陕西航沣在高性能碳纤维增强纸基摩擦材料领域的创新与发展&#xff0c;提升企业的核心竞争力。 客户简介 陕西航沣新材料有限公司&…

脚本批量修改文件名 格式xx.bat

批量修改文件名适用于windows系统 分为4步 1.新建一个 批量修改文件名.txt文件 2.复制下面代码&#xff0c;保存 echo off chcp 65001 >nul set a0 setlocal EnableDelayedExpansion for %%n in (*.png) do (set /A a1ren "%%n" "影魅!a!.jpg" )3.修…

C语言-顺序表

&#x1f3af;引言 欢迎来到HanLop博客的C语言数据结构初阶系列。在这个系列中&#xff0c;我们将深入探讨各种基本的数据结构和算法&#xff0c;帮助您打下坚实的编程基础。本次我将为你讲解。顺序表&#xff08;也称为数组&#xff09;是一种线性表&#xff0c;因其简单易用…

Base64文件流查看下载PDF方法-CSDN

问题描述 数票通等接口返回的PDF类型发票是以Base64文件流的方式返回的&#xff0c;无法直接查看预览PDF发票&#xff0c; 处理方法 使用第三方在线工具&#xff1a;https://www.jyshare.com/front-end/61/ 在Html代码框中粘贴如下代码 <embed type"application/pd…

LeetCode LCR024.反转链表 经典题目 C写法

LeetCode LCR024.反转链表 经典题目C写法 第一种思路&#x1f9d0;&#xff1a; ​ 使用三个指针&#xff0c;n1,n2,n3&#xff0c;n1为空&#xff0c;n2为头结点&#xff0c;n3为头结点的next。开始反转后&#xff0c;n1赋值给n2的next&#xff0c;n2赋值给n1&#xff0c;n3赋…

VBA 批量发送邮件

1. 布局 2. 代码 前期绑定的话&#xff0c;需要勾选 Microsoft Outlook 16.0 Object Library Option ExplicitConst SEND_Y As String "Yes" Const SEND_N As String "No" Const SEND_SELECT_ALL As String "Select All" Const SEND_CANCEL…

ASP.NET Web应用中的 Razor Pages/MVC/Web API/Blazor

如果希望使用ASP.NET Core创建新的 Web 应用程序&#xff0c;应该选择哪种方法&#xff1f;Razor Pages还是 MVC&#xff08;模型-视图-控制器&#xff09;&#xff0c;又或者使用Web API Vue/React/......。 每种方法都有各自的优点和缺点。 什么是 MVC&#xff1f; 大多数服…

Windows桌面上透明的记事本怎么设置

作为一名经常需要记录灵感的作家&#xff0c;我的Windows桌面总是布满了各种文件和窗口。在这样的环境下&#xff0c;一个传统的记事本应用往往会显得突兀&#xff0c;遮挡住我急需查看的资料。于是&#xff0c;我开始寻找一种既能满足记录需求&#xff0c;又能保持桌面整洁美观…