欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 金融 > 【Linux笔记】进程间通信——system v 共享内存

【Linux笔记】进程间通信——system v 共享内存

2025/4/2 3:33:00 来源:https://blog.csdn.net/GGDxianv/article/details/146697976  浏览:    关键词:【Linux笔记】进程间通信——system v 共享内存

🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:Linux
🌹往期回顾🌹:【Linux笔记】进程间通信——命名管道
🔖流水不争,争的是滔滔不


  • 一、共享内存简介
  • 二、使用共享内存的接口
  • 三、简单用共享内存实现进程间的通信

一、共享内存简介

共享内存是Linux系统中进程间通信(IPC)的一种方式,允许多个进程直接访问同一块物理内存区域。它的核心特点是零拷贝——数据直接在内存中共享,无需通过内核缓冲区中转,因此成为速度最快的IPC机制。共享内存是system v的一种标准,Linux内核支持这种标准,专门设计了这个模块。
在这里插入图片描述
两个进程访问同一块物理内存,如上图共享内存通过页表映射到两个进程的地址空间,两个进程就可以看到同一块资源,进而实现进程间的通信。

二、使用共享内存的接口

在物理内存中创建块共享内存的区域

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

在这里插入图片描述
第一个参数的key是标识共享内存的唯一性,不同的进程通过共享内存来进行通信,key来区分是不是对应的共享内存,key不是内核直接形成的而是在用户层构建并传入给操作系统的。
第二个参数是共享内存的大小。
第三个参数传 IPC_CREAT和IPC_EXCL
IPC_CREAT表示创建共享内存不存在就创建,否则打开已经存在的共享内存并返回。
IPC_CREAT | IPC_EXCL表示如果要创建的共享内存不存在,就创建它,如果已经存在,shmflg就会出错返回。只有shmflg成功返回一定是一个全新的共享内存。


创建key值

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

在这里插入图片描述
第一个参数是路径,第二个参数随便写


删除管道

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

在这里插入图片描述
第一个参数是创建共享内存时的返回值。
第二个参数传入IPC_RMID,表示标记要销毁的段。
第三个参数传入null就可以


让共享内存与进程建立联系

   void *shmat(int shmid, const void *shmaddr, int shmflg);

在这里插入图片描述
第一个参数也是创建共享内存时的返回值。
第二个参数指定了共享内存段要附加到的地址,若 shmaddr 为 nullptr(也就是 NULL),系统会自动选择一个合适的地址来附加共享内存段,这是最常用的做法。
第三个参数是一组标志位,用于控制共享内存段的附加方式


将共享内存段与当前进程脱离

int shmdt(const void *shmaddr);

在这里插入图片描述
参数传入共享内存与进程建立连接时返回的指针。

三、简单用共享内存实现进程间的通信

私用成员变量

private:int _gsize;int _shmid;void *_start_mem;key_t _k;string _usertype;

全局变量

const string pathname = ".";
const int projid = 0x66;
const int gdefaultid = -1;
const int gsize = 4096;
#define CREATE "create"
#define GET "get"

 Shm(string pathname, int projid, string usertype) // 构造: _shmid(gdefaultid), _gsize(gsize), _start_mem(nullptr), _usertype(usertype){_k = ftok(pathname.c_str(), projid);if (_k < 0){ERR_EXIT("ftok");}if (_usertype == CREATE){Create();}else if (_usertype == GET){Get();}Attach();}

首先要构造这个共享内存,用户调用的时候传入路径和projid以及选择时创建共享内存还是从共享内存中读取数据。

    void CreateHelper(int flg) // 创建共享内存{printf("key: 0x%x\n", _k);_shmid = shmget(_k, _gsize, flg);if (_shmid < 0){ERR_EXIT("shmat");}printf("shmid: %d\n", _shmid);}void Create(){CreateHelper(IPC_CREAT | IPC_EXCL | 0666); // 创建共享内存}void Get(){CreateHelper(IPC_CREAT); // 读取共享内存}void Destroy() // 删除共享内存{Detach();if (_usertype == CREATE){int n = shmctl(_shmid, IPC_RMID, nullptr);if (n < 0){ERR_EXIT("shmctl");}else if (n > 0){printf("deleat success:%d", n);}}}

创建共享内存通过调用相应的函数,创建共享内存,注意创建共享内存和读取共享内存的内容只用shmgt函数的第三个参数是不同的。删除共享内存调用相应的函数。

   void Attach() // 让进程与共享内存建立联系{_start_mem = shmat(_shmid, nullptr, 0);if ((long long)_start_mem < 0){ERR_EXIT("shmat");}cout << "attch success" << endl;}void *VirtualAddr()//打印{printf("VirtualAddr: %p\n", _start_mem);return _start_mem;}void Detach()//将共享内存段与当前进程脱离{int n = shmdt(_start_mem);if (n == 0){printf("detach success\n");}}

创建出共享内存了要想让进程基于共享内存进行通信,就需要共享内存与进程建立联系。通过shmat函数让进程与共享内存建立联系,结束之后通过shmdt函数让共享内存与进程脱离联系。


共享内存没有同步机制,也就是说,客户端写客户端的服务端写服务端的。也就是说共享内存没有保护机制。
正经的应该是通过互斥锁信号量来实现同步机制。但是下面通过之前写过的命名管道这种方法(效率太低了)来简单实现。
通过命名管道的管道,这里进行通信的管道不用做数据的通信使用,而是用作通知使用。当共享内存的客户端进程写操作满足用户需求的时候(比方说想打印AA,当满足这一条件的时候),向命名管道里放一个字符唤醒进程B服务端读操作。

    void wakeup()//写激活共享内存{char c;int n=write(_fd,&c,1);printf("尝试唤醒: %d\n",n);}bool wait()//服务端读{char c;int number=read(_fd,&c,1);if(number>0){printf("醒来: %d\n",number);return true;}return false;}

上面代码就是之气的命名管道的写操作和读操作,稍微改一下,让写端往里面写一个字符,激活服务端读。

//client.cc#include "shm.hpp"
#include "Fifo.hpp"
int main()
{FillOper wf(PATH,FILENAME);wf.OpenForWrite();Shm shm(pathname,projid,GET);char* mem=(char*)shm.VirtualAddr();int index=0;for(char c='A';c <='Z';c++,index+=2){// 才是向共享内存写入sleep(1);mem[index] = c;mem[index + 1] = c;sleep(1);mem[index+2] = 0;wf.wakeup();}wf.Close();return 0;
}
//server.cc
#include "shm.hpp"
#include "Fifo.hpp"
int main()
{Shm shm(pathname,projid,CREATE);shm.Attr();NamedFifo fifo(PATH,FILENAME);FillOper rd(PATH,FILENAME);rd.OpenForRead();char* mem=(char*)shm.VirtualAddr();while(true){if(rd.wait()){printf("%s\n",mem);}elsebreak;     }rd.Close();std::cout << "server end normal!" << std::endl; // server段的析构函数没有被成功调用!return 0;
}

客户端写操作,打印26个字母,通过命名管道当满足条件打印两个字母,让命名管道的写端激活服务端进行读操作


comm.hpp

#pragma once#include <cstdio>
#include <cstdlib>#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)

shm.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <string>
#include "comm.hpp"
using namespace std;const string pathname = ".";
const int projid = 0x66;
const int gdefaultid = -1;
const int gsize = 4096;
#define CREATE "create"
#define GET "get"class Shm
{
private:void CreateHelper(int flg) // 创建共享内存{printf("key: 0x%x\n", _k);_shmid = shmget(_k, _gsize, flg);if (_shmid < 0){ERR_EXIT("shmat");}printf("shmid: %d\n", _shmid);}void Create(){CreateHelper(IPC_CREAT | IPC_EXCL | 0666); // 创建共享内存}void Detach(){int n = shmdt(_start_mem);if (n == 0){printf("detach success\n");}}void Get(){CreateHelper(IPC_CREAT); // 读取共享内存}void Destroy() // 删除共享内存{Detach();if (_usertype == CREATE){int n = shmctl(_shmid, IPC_RMID, nullptr);if (n < 0){ERR_EXIT("shmctl");}else if (n > 0){printf("deleat success:%d", n);}}}void Attach() // 让进程与共享内存建立联系{_start_mem = shmat(_shmid, nullptr, 0);if ((long long)_start_mem < 0){ERR_EXIT("shmat");}cout << "attch success" << endl;}public:Shm(string pathname, int projid, string usertype) // 构造: _shmid(gdefaultid), _gsize(gsize), _start_mem(nullptr), _usertype(usertype){_k = ftok(pathname.c_str(), projid);if (_k < 0){ERR_EXIT("ftok");}if (_usertype == CREATE){Create();}else if (_usertype == GET){Get();}Attach();}void *VirtualAddr(){printf("VirtualAddr: %p\n", _start_mem);return _start_mem;}int size(){return _gsize;}void Attr(){struct shmid_ds ds;int n = shmctl(_shmid, IPC_STAT, &ds);printf("shm_segsz: %ld\n", ds.shm_segsz);printf("key: 0x%x\n", ds.shm_perm.__key);}~Shm(){std::cout << _usertype << std::endl;if (_usertype == CREATE)Destroy();}private:int _gsize;int _shmid;void *_start_mem;key_t _k;string _usertype;
};

Fifo.hpp

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "comm.hpp"
using namespace std;#define PATH "."
#define FILENAME "fifo"class NamedFifo
{
public:NamedFifo(const string& path,const string& name):_path(path),_name(name){_fifoname=_path+"/"+_name;//创建管道umask(0);int n=mkfifo(_fifoname.c_str(),0666);if(n<0){cout<<"管道创建失败"<<endl;}else{cout<<"管道创建成功"<<endl;}}~NamedFifo(){int n=unlink(_fifoname.c_str());if(n<0){cout<<"管道删除失败"<<endl;}else{cout<<"管道删除成功"<<endl;}}
private:string _path;string _name;string _fifoname;
};class FillOper
{
public:FillOper(const string& path,const string& name):_path(path),_name(name),_fd(-1){_fifoname=_path+"/"+_name;}void OpenForRead()//打开管道文件进行读操作{_fd=open(_fifoname.c_str(),O_RDONLY);if(_fd<0){cout<<"管道文件打开失败"<<endl;}else{cout<<"管道文件打开成功"<<endl;}}void OpenForWrite(){_fd=open(_fifoname.c_str(),O_WRONLY);if(_fd<0){cout<<"管道文件打开失败"<<endl;}else{cout<<"管道文件打开成功"<<endl;}}void wakeup()//写激活共享内存{char c;int n=write(_fd,&c,1);printf("尝试唤醒: %d\n",n);}bool wait()//服务端读{char c;int number=read(_fd,&c,1);if(number>0){printf("醒来: %d\n",number);return true;}return false;}void Close(){if(_fd>0){close(_fd);}}~FillOper(){}
private:string _path;string _name;string _fifoname;int _fd;};

server.cc

#include "shm.hpp"
#include "Fifo.hpp"
int main()
{Shm shm(pathname,projid,CREATE);shm.Attr();NamedFifo fifo(PATH,FILENAME);FillOper rd(PATH,FILENAME);rd.OpenForRead();char* mem=(char*)shm.VirtualAddr();while(true){if(rd.wait()){printf("%s\n",mem);}elsebreak;     }rd.Close();std::cout << "server end normal!" << std::endl; // server段的析构函数没有被成功调用!return 0;
}

client.cc

#include "shm.hpp"
#include "Fifo.hpp"
int main()
{FillOper wf(PATH,FILENAME);wf.OpenForWrite();Shm shm(pathname,projid,GET);char* mem=(char*)shm.VirtualAddr();int index=0;for(char c='A';c <='Z';c++,index+=2){// 才是向共享内存写入sleep(1);mem[index] = c;mem[index + 1] = c;sleep(1);mem[index+2] = 0;wf.wakeup();}wf.Close();return 0;
}

Makefile

.PHONY:all
all:client server
client:client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f client server

在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词