当前位置: 首页 > news >正文

深入了解linux系统—— 共享内存

前言

本篇文章所涉及的代码已同步到博主本人gitee

在之前的学习中,了解到了进程间通信——管道,知道了匿名管道和命名管道实现进程间通信的原理以及通过匿名管道实现进程池、通过命名管道实现两个进程之间的通信。

现在来看System V 版本的进程间通信

System V 进程间通信

什么是System V IPC

System V IPC(Inter-Process Communication)是Unix System V操作系统引入的一组进程间通信机制,后来被大多数Unix-like系统所采纳。它包含三种主要的通信方式:

  1. 消息队列(Message Queues) - 进程可以通过消息队列发送和接收格式化数据块
  2. 信号量(Semaphores) - 用于进程间的同步控制
  3. 共享内存(Shared Memory) - 允许多个进程访问同一块内存区域,是最快的IPC方式

System V IPC的特点

  1. 持久性:IPC资源一旦创建,会持续存在直到被显式删除或系统重启
  2. 键值标识:通过唯一的key_t类型键值标识IPC对象
  3. 权限控制:使用类似文件权限的模式位(读/写/执行对应用户/组/其他)
  4. 系统范围:IPC对象在整个系统范围内可见,不局限于创建它的进程

共享内存

在之前探究动态库的链接和加载时,我们知道动态库在运行时会被加载到内存中,并映射到进程的地址空间中;映射到了进程地址空间的共享代码区(堆栈之间)

这样通过将库加载到内存的物理地址与映射到进程地址空间的虚拟地址进行关联(通过页表),两个进程就看到了同一个库;(所以,动态库也被称为共享库

那两个进程可以通过虚拟地址和物理地址的映射关系,找到同一个库文件的物理地址,看到同一个库,那可不可以看到同一块内存资源,从而实现进程间通信呢?

共享内存的原理

在这里插入图片描述

如上图所示,只要将物理内存地址,映射到进程地址空间中,并填写页表对应关系;

这样多个进程就可以使用虚拟地址,通过页表映射,找到同一块内存资源;这样多个进程就可以看到同一份资源,就具备了通信的条件。

所以,想要让两个/多个进程看到同一份资源,就只需申请物理内存资源、然后再将物理内存映射到进程地址空间,修改进程对应的页表;这样就可以让多个进程看到同一份内存资源。

对于上述中申请物理内存资源、将物理内存映射到进程地址空间中,修改进程页表这些操作,貌似只有操作系统才可以做这些事情啊。

所以,操作系统就会提供相对应的系统调用,我们就可以通过调用系统调用,让操作系统去完成这些事情从而实现进程间通信。

共享内存相关接口

1. 创建共享内存shmget

在这里插入图片描述

shmget系统调用,用来创建一个共享内存

可以看到,shmget一共存在三个参数:keysizeshmflg

参数size

size参数表示要创建共享内存的大小;

通常情况下size4KB的整数倍(因为在内存中是以4KB块为单位的)

参数shmflg

在这里插入图片描述

对于这个参数,常见选项有两个:IPC_CREATIPC_EXCL

  • IPC_CREAT:表示在创建共享内存时,如果共享内存不存在就新建并返回新建共享内存的标识符;如果共享内存存在就返回该共享内存的标识符。
  • IPC_CREAT | IPC_EXCL:在创建共享内存时,如果共享内存不存在就新建;如果共享内存不存在就报错。

简单来说就是IPC_CREAT单独使用时,创建共享内存如果不存在就新建,如果存在就返回这个已经存在共享内存。

IPC_EXCL单独使用没有任何意义,IPC_CREAT | IPC_EXCL一起使用,返回的共享内存一定是最新的。

这里在创建共享内存时,也要带上共享内存的默认权限(在使用shmat时再详细介绍权限问题)

参数key

在这里插入图片描述

在上述中,IPC_CREATIPC_CREAT | IPC_EXCL选项的唯一区别就是当共享内存已经存在时的区别,那如何区分共享内存(如何判断一个共享内存是否存在呢?

现在要实现进程A和进程B的通信,那如何保证进程A和进程B打开的是同一个共享内存呢?

现在来看shmget的第一个参数key_t key

参数key用来区分共享内存,这个参数由用户使用时传递;

这样进程A和进程B打开共享内存时,只要保证key是相等的,那拿到的就是同一个共享内存。

key从哪来呢?

参数key由用户使用时传递,那key从哪来呢,任意值都可以吗?

本质上来说,key可以是任意值,但是通常情况下,都会使用ftok函数来生成key

在这里插入图片描述

frok函数的作用就是根据给定的pathnameproj_id生成对应的key

  • pathname:任意字符串都可以,通常情况下传递当前路径
  • proj_id:任意数都可以

shmget返回值

对于shmget函数,如果创建共享内存成功,就返回共享内存的标识符;如果创建失败就返回-1,且错误码被设置。

了解了shmget的参数keysizeshmflg、函数的返回值以及key值的创建;现在来简单使用shmget创建一个共享内存

#include <iostream>
#include <cstdio>
#include <sys/shm.h>
#include <sys/ipc.h>
#define PATHNAME "."
const int proj_id = 066;
const int size = 4096;
int main()
{// 生成keykey_t key = ftok(PATHNAME, proj_id);printf("key : %d\n", key);// 创建共享内存int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL);printf("shmif : %d\n",shmid);if(shmid < 0){perror("shmget : ");exit(1);}return 0;
}

在这里插入图片描述

这里我们是以%d十进制的方式输出key,在使用ipcs -m查看共享内存时查看到的key16进制数。

可以看到共享内存创建成功了,但是我们如何看到这个创建的共享内存呢?

ipcs -m查看共享内存

我们可以使用ipcs -m来查看共享内存

在这里插入图片描述

可以看到共享内存key值,标识符shmid,创建者owner等等

2. 删除共享内存

在上述代码中,我们成功创建了共享内存,但是我们再次运行程序会发现程序报错了

在这里插入图片描述

这里我们使用IPC_CREAT | IPC_EXCL选项,当共享内存存在时就会报错;

所以,我们会发现,共享内存的生命周期不是随进程的,当进程退出时,共享内存不会关闭。

(而我们的文件的声明周期是随进程的,当进程退出时,进程打开的文件就会被关闭)

那也就是说,我们不主动关闭共享内存,共享内存是一直存在的(当系统关机/重启时共享内存就被关闭了)

那如何释放共享内存资源呢?

指令ipcrm

在这里插入图片描述

我们可以通过指令ipcrm -m来释放共享内存。

通过系统调用释放共享内存

当然,我们也可以通过系统调用来释放共享内存;系统调用shmctl

在这里插入图片描述

shmctl系统调用是用来控制/管理共享内存资源的系统调用,不止是用来删除共享内存。

参数shmid

这个参数指要控制共享内存的标识符,在调用shmget创建共享内存成功的返回值。

参数op

这个是一个标志位,通过传递特定的选项,来表示要进行的相关操作。

在这里插入图片描述

选项有很多,这里只截取一部分;

要想要删除一个共享内存,要传递的标志位就是IPC_RMID

参数struct shmid_ds* buf

我们可以使用shmctl函数来获取共享内存的内核数据结构,这里我们要删除共享内存,这个参数传递NULL/nullptr即可。

所以,我们现在就可以通过调用系统调用来删除共享内存:

#include <iostream>
#include <cstdio>
#include <sys/shm.h>
#include <sys/ipc.h>
#define PATHNAME "."
const int proj_id = 066;
const int size = 4096;
int main()
{// 生成keykey_t key = ftok(PATHNAME, proj_id);printf("key : %d\n", key);// 创建共享内存int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL);printf("shmid : %d\n", shmid);if (shmid < 0){perror("shmget ");exit(1);}// 删除共享内存int n = shmctl(shmid, IPC_RMID, nullptr);if (n < 0){perror("shmctl");exit(1);}std::cout << "rmshm success" << std::endl;return 0;
}

在这里插入图片描述

3. 连接共享内存

在上述操作中,我们创建了共享内存,也释放了共享内存;

但是我们将共享内存创建出来之后,进程如何使用共享内存啊,进程也并不知道共享内存的存在啊?

所以,我们就要将共享内存映射到进程的进程地址空间中,要用的系统调用就是shmat

在这里插入图片描述

可以看到shmat函数一共存在三个参数:shmidshmaddrshmflg

参数shmid

shmid:要映射的共享内存的标识符shmid

参数shmaddr

这个参数表示我们要映射到进程地址空间的起始虚拟地址(由我们指定映射到进程地址空间的哪个位置)

但是,我们并不清楚进程地址空间的使用情况啊,所以可以传NULL来啊让操作系统决定映射到进程地址空间的哪个位置。

shmflg

这个参数可以说是一个标志位,它可以指定附加共享内存段到进程地址空间的方式;它有以下几种取值

  • 0:通常表示以读/写方式附加共享内存段
  • SHM_RDONLY:指定以只读方式附加共享内存段
  • SHM_REMAP:如果次标志被设置,且共享内存已经映射到进程地址空间中,则会重新进行映射到新的地址
  • SHM_EXEC:运行共享内存段作为可执行代码进行映射。

这里我们在映射时无需考虑这个标志位,传0即可。

在这里插入图片描述

返回值

如果shmat映射共享内存成功,就会返回一个起始虚拟地址,然后我们就可以使用这个起始虚拟地址,根据页表映射,访问共享内存中数据了。

如果映射失败,就返回(void*) -1,并且错误码被设置。

现在来将创建好的共享内存进行映射:

#include <iostream>
#include <cstdio>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
#define PATHNAME "."
const int proj_id = 066;
const int size = 4096;
int main()
{// 生成keykey_t key = ftok(PATHNAME, proj_id);printf("key : %d\n", key);// 创建共享内存int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL);printf("shmid : %d\n", shmid);if (shmid < 0){perror("shmget ");exit(1);}//连接共享内存void* start = shmat(shmid, nullptr, 0);if((long long) start < 0){perror("shmat");exit(1);}//连接成功printf("Start Virtual Address : %p\n", start);//删除共享内存int n = shmctl(shmid, IPC_RMID, nullptr);if (n < 0){perror("shmctl");exit(1);}std::cout << "rmshm success" << std::endl;return 0;
}

在这里插入图片描述

权限问题

在上述操作中,共享内存被创建出来了,但是在shmat时报错,权限被拒绝,权限问题?

这是因为,在创建共享内存时,我们没有指明它的权限,这就导致创建出来的共享内存权限就是0

我们的进程既没有r权限也没有w权限,在连接共享内存时,就被拒绝连接。(没有读写权限为什么要链接该共享内存啊)

所以,在创建共享内存时就要指明创建出来的共享内存的权限

int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0666);

这里的权限和文件的权限一样。

在这里插入图片描述

4. 去连接共享内存

我们可以将共享内存映射到进程地址空间中,当然我们可以对已经映射到进程地址空间的共享内存进行去连接、

要进行共享内存的去连接操作,要使用系统调用shmdt

在这里插入图片描述

如果去连接成功,shmdt函数就返回0,去连接失败就返回-1

这里就不演示了,这个接口相对比较简单,只需传shmat连接成功返回的起始虚拟地址即可。

5. 写入和读取

在上述操作中,创建了共享内存,也将共享内存映射到进程地址空间中,得到了起始虚拟地址;还可以对共享内存进程去连接、释放共享内存等操作。

但是,进程之间没有通信啊,我们只是创建共享内存映射到进程地址空间,让不同的进程看到了同一份资源(共享内存);但是没有进行通信啊

那如何利用共享内存进行进程间通信呢?也就是说如何进行读写操作呢?

创建共享内存shmget,创建成功返回共享内存标识符shmid

将共享内存映射到进程地址空间shmat,连接共享内存成功,返回起始虚拟地址。

现在也就能够获得共享内存标识符和映射到进程地址空间的起始虚拟地址;

我们想要进行读写操作,那就要根据这个起始虚拟地址来进行读写操作,就像malloc函数那样。

所以,现在就可以根据起始虚拟地址来进行读写操作。

在这里插入图片描述

这里我们可以发现共享内存实现的进程间通信的一些问题:

  • server端启动之后,直接就可以读取共享内存中的内容;不会等待client端启动。
  • client端写入内容之后,server端就可以直接读取到client写入的内容。

6. 获取内核数据结构

进程A和进程B要使用共享内存进行通信,就要有对应的共享内存;与此同时,存在非常多的进程要进行通信,那在内存当中就势必会存在非常多的共享内存;这些共享内存有的是刚创建的、有的是即将释放的、还有的是正在使用的。

那内存中存在如此多的共享内存,操作系统是不是也要将这些共享内存管理起来呢,如何管理呢?

先描述、再组织

在内核当中就势必存在描述一个共享内存的结构体,在这个结构体当中存在着该共享内存的shmidsizekey等等。

操作系统就只要将这些内核数据结构对象管理起来,就可以将所有的共享内存管理起来。

那我们可不可以获取共享内存的内核数据结构,查看一下呢?当然是可以的。

在这里插入图片描述

shmctl系统调用是对共享内存进行管理,我们可以通过第二个参数op来控制进行某种操作。

  • IPC_STAT:第二个参数传IPC_STAT,就可以将内核数据结构struct shmid_ds的内容拷贝到buf中,这个buf就是我们要传递的第三个参数。

struct shmid_ds中有什么呢?

在这里插入图片描述

可以看到struct shmid_ds在存在一个shm_perm,而在shm_perm中就存储着__key,就是我们在创建共享内存时传递的key值。

在其中还存在其他属性,例如uidpid等等,在shmid_ds中还存在着shm_atimeshm_dtimeshm_ctime等一系列时。

所以,我们就可以获取一下内核数据结构:

#include <iostream>
#include <cstdio>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
#define PATHNAME "."
const int proj_id = 066;
const int size = 4096;
int main()
{// 生成keykey_t key = ftok(PATHNAME, proj_id);printf("key : %d\n", key);// 创建共享内存// int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL);int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0666);printf("shmid : %d\n", shmid);if (shmid < 0){perror("shmget ");exit(1);}//连接共享内存void* start = shmat(shmid, nullptr, 0);if((long long) start < 0){perror("shmat");exit(1);}//连接成功printf("Start Virtual Address : %p\n", start);//获取内核数据结构struct shmid_ds ds;shmctl(shmid, IPC_STAT, &ds);printf("ds->_key : %d\n",ds.shm_perm.__key);printf("ds->natch : %ld\n",ds.shm_nattch);//删除共享内存int n = shmctl(shmid, IPC_RMID, nullptr);if (n < 0){perror("shmctl");exit(1);}std::cout << "rmshm success" << std::endl;return 0;
}

在这里插入图片描述

可以看到内核中是确实存在共享内存对应的数据结构对象的,我们通过shmctlIPC_STAT标志位也成功获取了内核的数据结构对象shmid_ds

基于共享内存实现进程间通信

通过以上的描述和对共享内存接口的使用,了解了共享内存的大致操作,现在来基于共享内存来实现进程间的通信;

现在我们要实现客户端client与服务端server的通信,由客户端client发送信息(向共享内存中写入)、由服务端server接受信息(从共享内存中读取)

这里就面向对象编程,设计出类shm,客户端client和服务端server进程创建类shm对象,通过调用接口来完成共享内存的创建和连接;从而具备能够进行通信的条件,来完成进程的通信。

shm类设计

对于shm类,这里就要提供服务端进程和客户端进程的接口,这里先逻辑出shm类的成员函数和成员变量:

#pragma once
#include <iostream>#define PATHNAME "."
#define PROJID 0x66
class shm
{
public:shm() : _shmid(-1), _key(-1), _size(SIZE), _proj_id(PROJID), _pathname(PATHNAME), _start_addr(nullptr){}~shm() {}void _creat() {}     // 创建共享内存void _get() {}       // 获取共享内存void _connect() {}   // 连接共享内存void _delete_connect() {} // 去连接void _destroy() {}   // 释放共享内存void *_get_addr() {} // 获取起始虚拟地址
private:int _shmid;int _key;int _size;int _proj_id;std::string _pathname;void *_start_addr;
};

shm类中,我们要给服务端提供创建共享内存_creat、连接共享内存_connect、释放共享内存_destroy接口和获取起始虚拟地址_start_addr接口。

给客户端进程提供获取共享内存_get、获取起始虚拟地址_get_addr接口。

1. 创建共享内存

首先,我们这里实现的shm要提供一个接口_creat,用来创建共享内存;

这里在shm类中存在成员变量_pathname_proj_id,就可以利用这两个成员变量来生成key值,然后使用生成的key值来创建共享内存。

这里判断系统调用是否出错比较常用,我们就可以设计一个宏EXIT,在系统调用出错时使用该宏输出错误信息和终止进程。

#define EXIT(x)    \do             \{              \perror(x); \exit(1);   \} while (0)
    void _creat() // 创建共享内存{// 生成key值_key = ftok(_pathname.c_str(), _proj_id);if (_key < 0){EXIT("ftok");}printf("key : %d\n", _key);_shmid = shmget(_key, _size, IPC_CREAT | IPC_EXCL | 0666);if (_shmid < 0){EXIT("shmget");}std::cout << "shmget success" << std::endl;}

2. 获取共享内存

服务端进程server能够创建共享内存,要想实现serverclient进程之间的通信,那先要让client获取这个共享内存啊,这样这两个进程才具备通信的条件。

    void _get() // 获取共享内存{// 生成key值_key = ftok(_pathname.c_str(), _proj_id);if (_key < 0){EXIT("ftok");}printf("key : %d\n", _key);// 获取共享内存--共享内存已经存在_shmid = shmget(_key, _size, IPC_CREAT);if (_shmid < 0){EXIT("shmget");}std::cout << "shmget success" << std::endl;}

3. 连接共享内存

server进程创建共享内存,client进程获取共享内存;进程要使用共享内存,就要将共享内存映射到自己的进程地址空间中。

这里要注意,shmat连接成功返回的是起始虚拟地址;连接失败则返回(void*) -1

    bool _connect() // 连接共享内存{if (_shmid < 0)return false;_start_addr = shmat(_shmid, nullptr, 0);if ((long long)_start_addr < 0){EXIT("shmat");}std::cout << "connect success" << std::endl;return true;}

4. 获取起始虚拟地址

server端和client端连接共享内存之后,两个进程就看到了同一份资源,就具备了通信的条件;

但是server端和client端要使用起始虚拟地址,才能对共享内存进行写入和读取操作啊,而起始虚拟地址被封装在shm类中;所以,就要提供一个获取共享内存虚拟地址的方法_get_addr

    void *_get_addr()  // 获取起始虚拟地址{return _start_addr;}

5. 释放共享内存

共享内存能够被创建,连接;那也可以去连接和释放

    void _delete_connect() // 去连接{int n = shmdt(_start_addr);if (n < 0){EXIT("shmdt");}std::cout << "shmdt success" << std::endl;}void _destroy() // 释放共享内存{int n = shmctl(_shmid, IPC_RMID, 0);if (n < 0){EXIT("shmctl");}std::cout << "destroy success" << std::endl;}

测试

设计并实现shm类,现在来进行简单测试:

server端

  • 首先就是创建shm类对象
  • 调用_creat创建共享内存
  • 调用_connect连接共享内存
  • 进行读取共享内存中数据
  • 调用delete_connect去连接、_destroy释放共享内存
#include "shm.hpp"int main()
{shm sh;sh._creat();sh._connect();char* addr = (char*)sh._get_addr();while(true){ //一直读取共享内存中的数据printf("%s\n",addr);sleep(1);}sh._delete_connect();sh._destroy();return 0;
}

client端

  • 首先创建shm类对象
  • 调用_get获取共享内存
  • 调用_connect连接共享内存
  • 进行读取共享内存中数据
  • 调用delete_connect去连接
#include "shm.hpp"int main()
{shm sh;sh._get();sh._connect();char* addr = (char*) sh._get_addr();//写入int index = 0;for(char ch = 'A'; ch<='Z';ch++,index++){addr[index] = ch;sleep(1);}sh._delete_connect();return 0;
}

在这里插入图片描述

可以看到,client向共享内存中写入的数据,server确实读取到了。

但是,我们会发现一些问题:

  • 服务端进程不会等待client启动;(在命名管道中,server创建管道文件,在打开管道文件时会阻塞在open中,等待client端)
  • 客户端写入共享内存的数据,server立马就可以读取到。
  • 客户端client退出之后,server不知道,就会一直读取。

优化代码

在上述实现的shm类中,无论是在server端还是client端,都需要自己显示去调用_creat_get_connect等接口;

  • 但是我们创建shm类就是为了通信,那在创建shm类对象时,创建、获取、连接共享内存都做完,这样创建shm类对象之后、获取起始虚拟地址就可以开始通信的。
  • 此外,我们也不想要显示的调用去连接、释放共享内存;这些在shm析构函数中做就可以在程序退出时自动调用。
  • 还有,创建共享内存和获取共享内存只有shmget中参数标识符的差别,这是不是也可以在shm类存在一个身份标识符,来统一实现。
  • 最后,生成key值使用的pathnameproj_id能不能由用户提供,这样多个用户传递不同的pathnameproj_id就可以使用不同的共享内存了。
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATHNAME "."
#define PROJID 0x66
#define SIZE 4096
#define EXIT(x)    \do             \{              \perror(x); \exit(1);   \} while (0)
#define CREATER 0
#define USER 1class shm
{
private:void _get_key(){_key = ftok(_pathname.c_str(), _proj_id);if (_key < 0){EXIT("ftok");}}void _creat(int flg){_shmid = shmget(_key, _size, flg);if (_shmid < 0){EXIT("shmget");}}void _connect(){_start_addr = shmat(_shmid, nullptr, 0);if ((long long)_start_addr < 0){EXIT("shmat");}}void _delete_connect(){if (_start_addr == 0)return;shmdt(_start_addr);_start_addr = nullptr;}void _destroy() // 释放共享内存{if (_shmid < 0)return;shmctl(_shmid, IPC_RMID, 0);}public:shm(const std::string &pathname, int proj, int id): _shmid(-1), _key(-1), _size(SIZE), _proj_id(proj), _pathname(pathname), _start_addr(nullptr), _id(id){// 生成key_get_key();// 创建/获取共享内存int flg;if (_id == USER)flg = IPC_CREAT;else if (id == CREATER)flg = IPC_CREAT | IPC_EXCL | 0666;_creat(flg);// 连接共享内存_connect();}~shm(){// 去连接_delete_connect();// 释放共享内存if (_id == CREATER)_destroy();}void *_get_addr(){return _start_addr;}private:int _shmid;int _key;int _size;int _proj_id;std::string _pathname;void *_start_addr;int _id;
};

这样,在server端创建shm类对象只要传入pathnameproj_id用来生成key值,传入CREATER表明自己要创建共享内存

client端创建类对象时只要传入pathnameproj_id用来生成key值,传入USER表明自己是使用者,使用已经创建好的共享内存即可。

同步问题

通过上述的操作,我们也会发现,共享内存在写端写入数据之后,读端立马就可以读到;这也是共享内存的一个特点;

  1. 高性能
    进程直接读写内存,无需内核介入或数据拷贝,速度远快于管道、消息队列等IPC方式。
  2. 低延迟
    省去了系统调用和数据序列化的开销,适合频繁通信或大数据量交换的场景。
  3. 灵活性
    共享内存区域可存储任意数据结构(如数组、结构体),方便复杂数据共享。

在共享内存中,系统并没有通过读取和写入的系统调用;我们在链接共享内存之后,获取起始虚拟地址地址;

根据获取到的起始虚拟地址就可以对共享内存进行访问;(像malloc开辟空间那样)

我们可以按照任意方方式进行写入和读取。

但是共享内存也存在一些缺点:

首先就是同步问题,在server启动创建共享内时,并不会等待另一端进程,这就会导致只有一个进程在使用共享内存;在写端写入数据之后,读端立马就可以读取到;这样就可能导致数据的不一致,也可能写端还没有写完,读端就读取了数据。

此外,共享内存要我们自己创建、连接等等;我们自己要管理共享内存,稍有遗漏就看导致内存泄露问题。(共享内存的生命周期是随内核的)

最后,当写端退出时,读端不知道;就会导致写端退出,读端一直在读取。

基于命名管道对共享内存进行同步管理

共享内存存在的一个问题就是同步问题,在写端写入时,读端是可以读取的;

在这里插入图片描述

可以看到,如果这里client每次写两个字符;但是server读取到的字符可能是链各个、也可能读取到了一个。

这就可能导致数据不一致问题,那以现在的知识储备,有没有办法解决呢?

  • 当然是有的,还记得在命名管道部分,在创建管道文件后,打开文件时就会阻塞open函数等待另一端。
  • 命名管道读端在进行时read,如果管道文件没有数据就会阻塞在read函数中,如果写端退出read函数返回值就为0;这样就可以很好的解决共享内存的同步以及写端退出,读端不知道的问题。

这里直接使用深入了解linux系统—— 进程间通信之管道-CSDN博客的命名管道文件代码

//client.cc
#include "shm.hpp"
#include "fifo.hpp"
int main()
{file wf(_FIFO_PATH_, _FIFO_NAME_);wf._writeopen();shm sh(PATHNAME, PROJID, USER);char *msg = (char *)sh._get_addr();int index = 0;for (char ch = 'A'; ch <= 'Z'; ch++, index += 2){msg[index] = ch;msg[index + 1] = ch;printf("write : %s\n",msg);wf._write(); // 写入一条数据,唤醒server进程sleep(1);}wf._close();return 0;
}
//server.cc
#include "shm.hpp"
#include "fifo.hpp"int main()
{// 创建共享内存shm sh(PATHNAME, PROJID, CREATER);// 创建并打开管道文件fifofile ff(_FIFO_PATH_, _FIFO_NAME_);file rf(_FIFO_PATH_, _FIFO_NAME_);rf._readopen();char *msg = (char *)sh._get_addr();msg[0] = 0;printf("start address : %p\n", msg);// 读取数据while (true){int n = rf._read(); // 等待写端写入if (n == 0)break;printf("read : %s\n", msg);sleep(1);}rf._close();return 0;
}

在这里插入图片描述

这样,使用命名管道简单对共享内存进行同步约束;

可以看到server读取和client写入数据一致了。

到这里本篇文章内容就结束了,感谢各位的支持。

相关文章:

  • 【每天学点 Go 知识】Go 基础知识 + 基本数据类型快速入门
  • 【Hot 100】139. 单词拆分
  • arduino Nano+asrpro2.0制作桌面宠物
  • (javaEE)网络原理-初识 局域网和广域网 ip地址和端口号 协议 五元组 协议分层 OSI七层模型 网络数据通信的基本流程
  • 全球人工智能技术大会(GAITC 2025):技术前沿与产业融合的深度交响
  • Go内存池设计与实现:减少GC压力
  • **解锁 C++ std::map 的力量**
  • 26考研——数据的表示和运算_整数和实数的表示(2)
  • 2025-06-01-Hive 技术及应用介绍
  • 【hive】函数集锦:窗口函数、列转行、日期函数
  • QT的工程文件.pro文件
  • 使用 IntelliJ IDEA 安装通义灵码(TONGYI Lingma)插件,进行后端 Java Spring Boot 项目的用户用例生成及常见问题处理
  • 25.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--用户服务接口
  • SQL手工测试(MySQL数据库)
  • 树莓派超全系列教程文档--(58)通过网络启动树莓派
  • 【LeetCode 热题100】网格路径类 DP 系列题:不同路径 最小路径和(力扣62 / 64 )(Go语言版)
  • 第6章:Neo4j数据导入与导出
  • 自定义连接线程池
  • 408第一季 - 数据结构 - 图
  • mybatis执行insert如何返回id
  • 安阳网站建设优化/热狗seo顾问
  • 湘潭网站制作公司/南宁今日头条最新消息
  • 哪些网站做的好处和坏处/黄页88网站推广方案
  • 做威尼斯网站代理算是违法吗/培训网站搭建
  • 重庆建设教育协会网站/网络营销推广的方式
  • 北京西站附近的景点有哪些/天津百度网络推广