进程间通信之共享内存_进程间的通信共享内存-程序员宅基地

技术标签: 进程间通信  Linux学习笔记  linux  


1.进程间通信的分类:
(1)管道:1、匿名管道pipe;2、命名管道mkfifo
(2)System V IPC:1、System V 消息队列;2、System V 共享内存;3、System V 信号量。
(3)POSIX IPC:1、消息队列;2、共享内存;3、信号量;4、互斥量;5、条件变量;6、读写锁。
前面已经了解了进程间管道通信,那么共享内存又是什么原理?

1.共享内存的概念

什么是共享内存?
共享内存通信是一种进程间通信的方式,它允许两个或更多进程访问同一块内存,就如同 malloc () 函数向不同进程返回了指向同一个物理内存区域的指针。共享内存是 Unix/Linux下的多进程之间的通信方法,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。而且共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存和管道的区别:

管道通信和共享内存都是进程间通信的方式,但是它们的实现方式不同。管道通信需要在内核和用户空间进行四次的数据拷贝:由用户空间的buffer中将数据拷贝到内核中 -> 内核将数据拷贝到内存中 -> 内存到内核 -> 内核到用户空间的buffer。而共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。多个进程可以同时操作,所以需要进行同步。

共享内存示意图:
在这里插入图片描述

共享内存数据结构:
用man shmctl指令可以查看共享内存的数据结构。

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 */
    ...
};

而其中ipc_perm是一个内核为每个IPC对象所维护的一个数据结构,如下:

struct ipc_perm {
    
    key_t          __key;    /* Key supplied to shmget(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 + SHM_DEST and
                                SHM_LOCKED flags */
    unsigned short __seq;    /* Sequence number */
};

2.共享内存函数

2.1 shmget函数

shmget函数功能:用来创建共享内存。

NAME
       shmget - allocates a System V shared memory segment
	   //分配System V共享内存段
SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

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

DESCRIPTION
       shmget() returns the identifier of the System V shared memory segment associated with the value of the argument key.  A new shared memory seg?
       ment, with size equal to the value of size rounded up to a multiple of PAGE_SIZE, is created if key has the value  IPC_PRIVATE  or  key  isn't
       IPC_PRIVATE, no shared memory segment corresponding to key exists, and IPC_CREAT is specified in shmflg.

       If shmflg specifies both IPC_CREAT and IPC_EXCL and a shared memory segment already exists for key, then shmget() fails with errno set to EEX?
       IST.  (This is analogous to the effect of the combination O_CREAT | O_EXCL for open(2).)

       The value shmflg is composed of:

       IPC_CREAT   to create a new segment.  If this flag is not used, then shmget() will find the segment associated with key and check  to  see  if
                   the user has permission to access the segment.

       IPC_EXCL    used with IPC_CREAT to ensure failure if the segment already exists.

RETURN VALUE
       On success, a valid shared memory identifier is returned.  On errir, -1 is returned, and errno is set to indicate the error.
       //成功返回有效的共享内存标识符,失败返回-1,并且错误码被设置。

int shmget(key_t key, size_t size, int shmflg);
key_t key:这个值用ftok生成,ftok会经过算法生成出一个冲突概率低的值,这个值保证唯一;目的是申请的共享内存块是尽可能不同的;
size_t size:申请共享内存块的大小;
int shmflg:这个参数常用两个选项,分别是IPC_CREAT and IPC_EXCL;
单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回;
IPC_CREAT | IPC_EXCL: 创建一个共享内存,如果共享内存不存在,就创建之, 如果已经存在,则立马出错返回
IPC_EXCL不能单独使用,一般都要配合IPC_CREAT;
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1。

ftok函数:

NAME
       ftok - convert a pathname and a project identifier to a System V IPC key

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

       key_t ftok(const char *pathname, int proj_id);
       
RETURN VALUE
       On success, the generated key_t value is returned.  On failure -1 is returned, with errno indicating the error as for the stat(2) system call.

ftok会将这个路径pathname和proj_id(可以随便写)经过算法生成出一个冲突概率低的值。

2.2 shmat函数

shmat函数功能:将共享内存段连接到进程地址空间。

void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid: 共享内存标识;
shmaddr:指定连接的地址;
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY;
返回值:成功返回一个指针,指向共享内存;失败返回-1;
shmaddr为NULL,核心自动选择一个地址;
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址;
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍;(所以一般直接设为nullptr就可)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。

返回值为一个指针,并且指针指向共享内存,所以使用这个指针进行数据的写入或读出。

2.3 shmdt函数

shmdt函数功能:将共享内存段与当前进程脱离。

int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

2.4 shmctl函数

shmctl函数功能:用于控制共享内存。

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值),如下图所示;
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构;
返回值:成功返回0;失败返回-1。

命令 说明
IPC_STAT 将shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 在进程有足够权限的前提下,将共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID 删除共享内存段

3. 共享内存的使用

使用之前,先认识下面的IPC指令,共享内存,消息队列,信号量等指令基本相似,所以在使用共享内存,消息队列,信号量进行通信时,其都有一批函数,总的说是大同小异,但是原理是不同的。

查看命令 删除命令
ipcs -m : 查看共享内存 ipcrm -m shmid : 删除共享内存
ipcs -q : 查看共享内存 ipcrm -q msqid : 删除消息队列
ipcs -s : 查看共享内存 ipcrm -s semid : 删除信号量

comm.hpp代码如下:

#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <cassert>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
using namespace std;

//  IPC_CREAT and IPC_EXCL
// 单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回
// IPC_EXCL不能单独使用,一般都要配合IPC_CREAT
// IPC_CREAT | IPC_EXCL: 创建一个共享内存,如果共享内存不存在,就创建之, 如果已经存在,则立马出错返回 -- 如果创建成功,对应的shm,一定是最新的!

#define PATHNAME "."
#define PROJID 0x6666

const int gsize = 4096; // 共享内存的大小

key_t getKey()
{
    
    key_t k = ftok(PATHNAME, PROJID); //key_t ftok(const char *pathname, int proj_id);
    if(k == -1)
    {
    
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(1);
    }
    return k;
}

server.cc代码如下:

#include "comm.hpp"

int main()
{
    
    //1.创建共享内存先要创建一个key_t k(ftok)
    key_t k = getKey();

    //2.创建一个共享内存(shmget)
    umask(0); //默认权限
    int shmid = shmget(k, gsize, IPC_CREAT | IPC_EXCL | 0666); //int shmget(key_t key, size_t size, int shmflg)
    // 因为server创建共享内存,所以第三个参数为IPC_CREAT | IPC_EXCL,这个共享内存一定是最新的
    if(shmid == -1)
    {
    
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(2);
    }

    //3.将共享内存段连接到进程地址空间(shmat)
    char* start = (char*)shmat(shmid, nullptr, 0);
    if(*start == -1)
    {
    
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(3);
    }

    //3.写入信息"i am process server"
    char buffer[64] = "i am process server";
    int i = 0;
    while (buffer[i])
    {
    
        start[i] = buffer[i];
        ++i;
    }
    //start = buffer; 错误写法,因为这样写直接就将start指针修改,start就不是指向共享内存的地址
    sleep(10);

    //将共享内存段与当前进程脱离(shmdt)
    int n = shmdt(start);
    assert(n != -1);
    (void)n;

    //4.删除共享内存(shmctl)
    int m = shmctl(shmid, IPC_RMID, nullptr); // IPC_RMID | 删除共享内存段
    assert(m != -1);
    (void)m;

    return 0;
}

client.cc代码如下:

#include "comm.hpp"

int main()
{
    
    //1.获取已经存在的共享内存
    key_t k = getKey();

    int shmid = shmget(k, gsize, IPC_CREAT); //int shmget(key_t key, size_t size, int shmflg)
    // 单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回;
    // 这里获取的是已经存在的


    //2.将共享内存段连接到进程地址空间(shmat)
    char* start = (char*)shmat(shmid, nullptr, 0);
    if(*start == -1)
    {
    
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(3);
    }

    //3.从共享内存中读取数据
    int m = 3;
    while (m--)
    {
    
        cout << "i am client,i read: " << start << endl;
        sleep(3);
    }

    //将共享内存段与当前进程脱离(shmdt)
    int n = shmdt(start);
    assert(n != -1);
    (void)n;

    return 0;
}

makefile代码如下:

.PHONY:all
all:server client

server:server.cc
	g++ -o $@ $^ -std=c++11
client:client.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f server client

运行结果如下:
在这里插入图片描述

如果在自写代码中有如下错误,File exists,这是因为执行server.cc程序,程序并不是完整退出,而是程序进行一半时退出,例如:程序进行一半时按ctrl+c强制退出,程序没有执行到最后,也就是共享内存没有被删除,这时,就可以用ipcs -m查看共享内存;
在这里插入图片描述
然后ipcrm -m shmid(4)如下shmid为4,进行删除,重新运行程序即可。
在这里插入图片描述
如果是别的问题,那一定是代码错着,仔细检查代码吧。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_68278653/article/details/131791521

智能推荐

前端开发之vue-grid-layout的使用和实例-程序员宅基地

文章浏览阅读1.1w次,点赞7次,收藏34次。vue-grid-layout的使用、实例、遇到的问题和解决方案_vue-grid-layout

Power Apps-上传附件控件_powerapps点击按钮上传附件-程序员宅基地

文章浏览阅读218次。然后连接一个数据源,就会在下面自动产生一个添加附件的组件。把这个控件复制粘贴到页面里,就可以单独使用来上传了。插入一个“编辑”窗体。_powerapps点击按钮上传附件

C++ 面向对象(Object-Oriented)的特征 & 构造函数& 析构函数_"object(cnofd[\"ofdrender\"])十条"-程序员宅基地

文章浏览阅读264次。(1) Abstraction (抽象)(2) Polymorphism (多态)(3) Inheritance (继承)(4) Encapsulation (封装)_"object(cnofd[\"ofdrender\"])十条"

修改node_modules源码,并保存,使用patch-package打补丁,git提交代码后,所有人可以用到修改后的_修改 node_modules-程序员宅基地

文章浏览阅读133次。删除node_modules,重新npm install看是否成功。在 package.json 文件中的 scripts 中加入。修改你的第三方库的bug等。然后目录会多出一个目录文件。_修改 node_modules

【】kali--password:su的 Authentication failure问题,&sudo passwd root输入密码时Sorry, try again._password: su: authentication failure-程序员宅基地

文章浏览阅读883次。【代码】【】kali--password:su的 Authentication failure问题,&sudo passwd root输入密码时Sorry, try again._password: su: authentication failure

整理5个优秀的微信小程序开源项目_微信小程序开源模板-程序员宅基地

文章浏览阅读1w次,点赞13次,收藏97次。整理5个优秀的微信小程序开源项目。收集了微信小程序开发过程中会使用到的资料、问题以及第三方组件库。_微信小程序开源模板

随便推点

Centos7最简搭建NFS服务器_centos7 搭建nfs server-程序员宅基地

文章浏览阅读128次。Centos7最简搭建NFS服务器_centos7 搭建nfs server

Springboot整合Mybatis-Plus使用总结(mybatis 坑补充)_mybaitis-plus ruledataobjectattributemapper' and '-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏3次。前言mybatis在持久层框架中还是比较火的,一般项目都是基于ssm。虽然mybatis可以直接在xml中通过SQL语句操作数据库,很是灵活。但正其操作都要通过SQL语句进行,就必须写大量的xml文件,很是麻烦。mybatis-plus就很好的解决了这个问题。..._mybaitis-plus ruledataobjectattributemapper' and 'com.picc.rule.management.d

EECE 1080C / Programming for ECESummer 2022 Laboratory 4: Global Functions Practice_eece1080c-程序员宅基地

文章浏览阅读325次。EECE 1080C / Programming for ECESummer 2022Laboratory 4: Global Functions PracticePlagiarism will not be tolerated:Topics covered:function creation and call statements (emphasis on global functions)Objective:To practice program development b_eece1080c

洛谷p4777 【模板】扩展中国剩余定理-程序员宅基地

文章浏览阅读53次。被同机房早就1年前就学过的东西我现在才学,wtcl。设要求的数为\(x\)。设当前处理到第\(k\)个同余式,设\(M = LCM ^ {k - 1} _ {i - 1}\) ,前\(k - 1\)个的通解就是\(x + i * M\)。那么其实第\(k\)个来说,其实就是求一个\(y\)使得\(x + y * M ≡ a_k(mod b_k)\)转化一下就是\(y * M ...

android 退出应用没有走ondestory方法,[Android基础论]为何Activity退出之后,系统没有调用onDestroy方法?...-程序员宅基地

文章浏览阅读1.3k次。首先,问题是如何出现的?晚上复查代码,发现一个activity没有调用自己的ondestroy方法我表示非常的费解,于是我检查了下代码。发现再finish代码之后接了如下代码finish();System.exit(0);//这就是罪魁祸首为什么这样写会出现问题System.exit(0);////看一下函数的原型public static void exit (int code)//Added ..._android 手动杀死app,activity不执行ondestroy

SylixOS快问快答_select函数 导致堆栈溢出 sylixos-程序员宅基地

文章浏览阅读894次。Q: SylixOS 版权是什么形式, 是否分为<开发版税>和<运行时版税>.A: SylixOS 是开源并免费的操作系统, 支持 BSD/GPL 协议(GPL 版本暂未确定). 没有任何的运行时版税. 您可以用她来做任何 您喜欢做的项目. 也可以修改 SylixOS 的源代码, 不需要支付任何费用. 当然笔者希望您可以将使用 SylixOS 开发的项目 (不需要开源)或对 SylixOS 源码的修改及时告知笔者.需要指出: SylixOS 本身仅是笔者用来提升自己水平而开发的_select函数 导致堆栈溢出 sylixos

推荐文章

热门文章

相关标签