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

news2024/12/27 12:48:49

目录

  • 🌈前言
  • 🌸1、System V共享内存
    • 🍡1.1、概念
    • 🍢1.2、原理
  • 🌺2、共享内存相关函数和指令
    • 🍡2.1、shmget函数(创建)
    • 🍢2.2、shmctl函数(控制)
    • 🍧2.3、shmat函数(挂接)
    • 🍨2.4、shmdt(去挂接)
  • 🍀3、共享内存的使用
    • 🍡3.1、测试
    • 🍢3.2、共享内存与管道的区别
    • 🍧3.3、基于共享内存+管道实现访问控制

🌈前言

这篇文章给大家带来进程间通信中共享内存的学习!!!


🌸1、System V共享内存

🍡1.1、概念

概念:

  • 共享内存是与管道不同的一种进程间通信方式

  • 它们都是先让多个进程先看到一份相同的资源,所以必须提供存储数据的空间

  • 管道是在磁盘中创建文件,每次要加载到内存;共享内存是通过在物理内存创建一段内存,然后映射到共享区进行数据交互(只要创建一次,后面都会存在,因为共享内存是内核维护的)


🍢1.2、原理

进程间通信的前提是:让二个或多个不同的进程看到相同的资源(FIFO或pipe)!!!

  • 进程地址空间中有一个共享区:该区域除了映射动态库以外,还能映射其他数据(共享内存

  • 共享内存(Shared Memory):允许不同进程访问同一段的空间,常用于多进程间的通信

  • 共享内存被某个进程创建后,在物理内存中通过页表映射到该进程的进程地址空间的共享区中

  • 最后返回一个key值(标识这段空间的唯一性,key是一个整形),其他进程想要映射到相同的内存空间,必须通过key值!!!

左边是进程1,右边是进程2!!!

  • 共享内存区是最快的进程间通信方式,它不用在磁盘中创建管道文件,而是直接在物理内存映射到进程空间中进行数据交互即可

  • 一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

  • 共享内存是内核级的数据结构,进程退出后,共享内存不会被释放

在这里插入图片描述


🌺2、共享内存相关函数和指令

🍡2.1、shmget函数(创建)

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

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

函数解析:
  • 功能:用于创建共享内存,并返回共享内存的标识符
  • 返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
参数:size
  • size:共享内存大小。共享内存是以页(4KB = 4096Bytes)为单位的,建议设置成页的整数倍,共享内存是由内核数据结构(struct shmid_ds)维护的

在这里插入图片描述


参数:shmflg
  • 由九个权限标记位构成,它们的用法和创建文件(open)时使用的mode模式标志是一样的
  • 该参数传参的是一个宏,创建共享内存一般传二个标记位(IPC_CREAT | IPC_EXCL)
  • 其他进程想要获取共享内存,只需要传一个标记位(IPC_CREAT)即可
  • 想要正常使用共享内存时,必须设置共享内存的权限,(… | 0666)

IPC_EXCL和IPC_CREAT配合使用,是因为如果shmget调用成功后,保证创建的是一个全新的共享内存

作用
IPC_CREAT创建共享内存,如果已经存在,就获取它,不存在,就创建它
IPC_EXCL不单独使用,必须和IPC_CREAT配合(按位或)使用,如果不存在指定的共享内存,就创建,如果存在,就报错返回
共享内存存在哪里?
  • 存在内核中,内核会给我们维护共享内存的结构,该结构也要被OS管理起来
  • OS对共享内存的管理,就是对描述共享内存的数据结构的数组进行管理(增删查改)

凡是涉及管理:都是先描述事物的属性(struct shmid_ds),然后对其进行组织(数据结构)

struct shmid_ds 
{
	struct ipc_perm shm_perm; 	 	/* operation perms */
	int shm_segsz; 				 	/* size of segment (bytes) */
	__kernel_time_t shm_atime;   	/* last attach time */
	__kernel_time_t shm_dtime;   	/* last detach time */
	__kernel_time_t shm_ctime;   	/* last change time */
	__kernel_ipc_pid_t shm_cpid; 	/* pid of creator */
	__kernel_ipc_pid_t shm_lpid; 	/* pid of last operator */
	unsigned short shm_nattch; 		/* no. of current attaches */
	unsigned short shm_unused; 		/* compatibility */
	void *shm_unused2; 		   		/* ditto - used by DIPC */
	void *shm_unused3; 		   		/* unused */
};

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

这个key值就是共享内存再内核数据结构中的唯一标识符的值(整形)-- 唯一性:只有一个

结论:

  • 共享内存要被管理,需要在struct shmid_ds[]中找到某一页内部中的struct ipc_perm中的key值(共享内存在内核数据结构中唯一的标识符的值)

  • 共享内存,在内核中,让不同的进程看到同一份资源,做法是”让它们拥有相同的key值“即可

在这里插入图片描述


参数:key
  • 共享内存段的名字(唯一标识符),该参数由用户设置
  • key值需要用户通过ftok系统调用获取
#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char* pathname, int proj_id)
  • pathname:指定一个文件或目录的路径,主要是文件和目录都有一个唯一的inode值

  • proj_id:项目id,取值范围是【0,255】

  • 返回值:生成成功返回一个key值,失败返回-1

  • 作用:通过指定路径的文件/目录的inode值和项目id生成一个唯一性的key值!!!


shmget的使用:

comm.hpp – hpp文件:函数声明和定义可以混合使用

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
#include <cerrno>

// 在某个路径下的某个文件或目录
#define PATH_NAME "/home/lyh/Linux_Study"

// 项目id
#define PROJ_ID 0x12

key_t CreateKey()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
        std::cout <<  "ftok errno: " << strerror(errno) << std::endl;
        exit(1); // key值冲突,结束调用该函数的进程
    }
    return key;
}

// 用于调试打印消息 -- 该函数返回一个ostream& 可充当左值进行使用
std::ostream& Log()
{
    return std::cout << "Fot Debug | " << "timestamp: " << (long long)time(nullptr)
        << " | ";
}

IpcShmSer.cpp – 该文件创建共享内存

#include "comm.hpp"
// 设置共享内存的大小
#define MIN_SIZE 4096

// 共享内存的状态标记位(创建全新的,如果存在则设置errno,并且返回-1)
const int shmflags = IPC_CREAT | IPC_EXCL;

// 该文件创建共享内存
int main()
{
    // 1、获取key值(共享内存的唯一标识符)
    key_t key = CreateKey();
    Log() << "key: " << key << std::endl;
   	// 2、创建共享内存 -- 0666是共享内存的权限,标识谁能使用
    int shmid = shmget(key, MIN_SIZE, shmflags | 0666);
    if (shmid < 0)
    {
        Log() << "shmget errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmget sucess!!! | " << "shmid: " << shmid << std::endl;
    return 0;
}
[lyh@192 lesson4(共享内存)]$ pwd
/home/lyh/Linux_Study/lesson4(共享内存)
[lyh@192 lesson4(共享内存)]$ ./IpcShmSer 
Fot Debug | timestamp: 1671720131 | key: 302207089
Fot Debug | timestamp: 1671720131 | shmget sucess!!! | shmid: 31
Fot Debug | timestamp: 1671720131 | shmctl sucess!!! | shmid: 31

验证OS是否存在共享内存 – ipcs -m指令(查看共享内存的信息)

[lyh@localhost lesson4(共享内存)]$ ./IpcShmSer 
Fot Debug | timestamp: 1671799258 | key: 302207089
Fot Debug | timestamp: 1671799258 | shmget sucess!!! | shmid: 31
[lyh@localhost lesson4(共享内存)]$ ipsc -m
bash: ipsc: command not found...
Similar command is: 'ipcs'
[lyh@localhost lesson4(共享内存)]$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 9          lyh        777        16384      1          dest         
0x00000000 10         lyh        777        6193152    2          dest         
0x00000000 16         lyh        600        524288     2          dest         
0x00000000 17         lyh        777        6193152    2          dest         
0x00000000 20         lyh        600        524288     2          dest         
0x00000000 24         lyh        600        16777216   2          dest         
0x00000000 25         lyh        600        524288     2          dest         
0x00000000 26         lyh        777        2064384    2          dest         
0x00000000 27         lyh        600        524288     2          dest         
0x12035071 31         lyh        666        4096       0                   
  • key是共享内存唯一标识符;shmid是共享内存标识符;owner是创建者;perms是共享内存的权限;nattch是挂接的(映射到指定的进程)数量

  • System V共享内存下的生命周期是随内核的(只能关机重启或显示调用函数或使用指令来进行删除),进程退出后也没有释放!


🍢2.2、shmctl函数(控制)

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

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数解析:
  • 功能:用于控制共享内存,在System V共享内存段上执行cmd指定的控制操作,该段的标识符在shmid中给出
  • 返回值:成功返回0,失败返回-1,并且设置errno(错误码)
参数shmid:
  • shmget函数创建共享内存返回的标识符
参数cmd:
  • 将要采取的动作(有三个可取值)
  • 该函数主要用于显示删除共享内存段,只需要IPC_RMID(宏)进行删除

注意:cmd参数中的值都是标记位,可以通过按位或进行结合使用

在这里插入图片描述

参数shmid_ds *buf:
  • 指向一个保存着共享内存的模式状态访问权限的数据结构(struct shmid_ds)
  • 该参数一般设置为nullptr

shmctl函数的使用:

comm.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>

#define PATH_NAME "/home/lyh/Linux_Study"
#define PROJ_ID 0x12

key_t CreateKey()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
        std::cout <<  "ftok errno: " << strerror(errno) << std::endl;
        exit(1); // key值冲突,结束调用该函数的进程
    }
    return key;
}

// 该函数返回一个ostream& 可充当左值进行使用
std::ostream& Log()
{
    return std::cout << "Fot Debug | " << "timestamp: " << (long long)time(nullptr)
        << " | ";
}

IpcShmSer.cpp

#include "comm.hpp"
// 设置共享内存的大小
#define MIN_SIZE 4096
// 共享内存的状态(创建全新的,如果存在则设置errno,并且返回-1)
const int shmflags = IPC_CREAT | IPC_EXCL;
// 该文件创建共享内存
int main()
{
    // 1、获取key值(共享内存的唯一标识符)
    key_t key = CreateKey();
    Log() << "key: " << key << std::endl;
    // 2、创建共享内存
    int shmid = shmget(key, MIN_SIZE, shmflags);
    if (shmid < 0)
    {
        sleep(5);
        Log() << "shmget errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmget sucess!!! | " << "shmid: " << shmid << std::endl;

    // 使用共享内存
    

    // 删除共享内存
    sleep(5);
    int RM = shmctl(shmid, IPC_RMID, nullptr);
    if (RM < 0)
    {
        Log() << "shmctl errno: " << strerror(errno) << std::endl;
    }
    Log() << "shmctl sucess!!! | " << "shmid: " << shmid << std::endl;
    return 0;
}

在这里插入图片描述

  • 还可以使用指令进行删除 – ipcrm -m shmid
[lyh@localhost lesson4(共享内存)]$ make
g++ -o IpcShmSer IpcShmSer.cxx -std=c++11 -g

[lyh@localhost lesson4(共享内存)]$ ./IpcShmSer 
Fot Debug | timestamp: 1671797310 | key: 302207089
Fot Debug | timestamp: 1671797310 | shmget sucess!!! | shmid: 30

[lyh@localhost lesson4(共享内存)]$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 9          lyh        777        16384      1          dest         
0x00000000 10         lyh        777        6193152    2          dest         
0x00000000 16         lyh        600        524288     2          dest         
0x00000000 17         lyh        777        6193152    2          dest         
0x00000000 20         lyh        600        524288     2          dest         
0x00000000 24         lyh        600        16777216   2          dest         
0x00000000 25         lyh        600        524288     2          dest         
0x00000000 26         lyh        777        2064384    2          dest         
0x00000000 27         lyh        600        524288     2          dest         
0x12035071 30         lyh        0          4096       0                       

[lyh@localhost lesson4(共享内存)]$ ipcrm -m 30
[lyh@localhost lesson4(共享内存)]$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 9          lyh        777        16384      1          dest         
0x00000000 10         lyh        777        6193152    2          dest         
0x00000000 16         lyh        600        524288     2          dest         
0x00000000 17         lyh        777        6193152    2          dest         
0x00000000 20         lyh        600        524288     2          dest         
0x00000000 24         lyh        600        16777216   2          dest         
0x00000000 25         lyh        600        524288     2          dest         
0x00000000 26         lyh        777        2064384    2          dest         
0x00000000 27         lyh        600        524288     2          dest 

🍧2.3、shmat函数(挂接)

  • 共享内虽然在进程中被创建,但是不属于进程,它是由OS进行管理的

  • 我们想要使用共享内存,必须将共享内存与进程关联共享内存映射到当前进程的共享区)起来

shmat函数:

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

void *shmat(int shmid, const void *shmaddr, int shmflg);
函数解析:
  • 功能:将shmid标识的SystemV共享内存段附加到调用进程的地址空间
  • 返回值:成功时返回0;错误时返回-1,并设置errno以指示错误的原因。该返回值的使用与C语言的malloc一样!!!
参数shmid:
  • shmget函数创建共享内存返回的标识符
参数shmflg:
  • 它的两个可能取值是SHM_RND和SHM_RDONLY,一般为0,详细内容查man手册
参数shmaddr:
  • 该参数传递一个地址,表示我们需要将共享内存附加到进程地址空间的某一个位置
  • 如果shaddr为NULL,系统将选择一个合适的(未使用的)地址来附加段
  • shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址
  • shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
  • shmflg = SHM_RDONLY,表示连接操作用来只读共享内存

挂接共享内存:

comm.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>

#define PATH_NAME "/home/lyh/Linux_Study"
#define PROJ_ID 0x12

key_t CreateKey()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
        std::cout <<  "ftok errno: " << strerror(errno) << std::endl;
        exit(1); // key值冲突,结束调用该函数的进程
    }
    return key;
}

// 该函数返回一个ostream& 可充当左值进行使用
std::ostream& Log()
{
    return std::cout << "Fot Debug | " << "timestamp: " << (long long)time(nullptr)
        << " | ";
}

IpcShmSer.cpp

#include "comm.hpp"
// 设置共享内存的大小
#define MIN_SIZE 4096
// 共享内存的状态(创建全新的,如果存在则设置errno,并且返回-1)
const int shmflags = IPC_CREAT | IPC_EXCL;
// 该文件创建共享内存
int main()
{
    // 1、获取key值(共享内存的唯一标识符)
    key_t key = CreateKey();
    Log() << "key: " << key << std::endl;

    // 2、创建共享内存 -- 0666是共享内存的权限,标识谁能使用
    sleep(5);
    int shmid = shmget(key, MIN_SIZE, shmflags | 0666);
    if (shmid < 0)
    {
        Log() << "shmget errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmget sucess!!! | "
          << "shmid: " << shmid << std::endl;

    // 3、挂接共享内存
    sleep(5);
    char *pIps = (char *)shmat(shmid, nullptr, 0);
    if (pIps < 0)
    {
        Log() << "shmat errno: " << strerror(errno) << std::endl;
        return 3;
    }
    Log() << "shmat sucess!!! | "
          << "shmid: " << shmid << std::endl;

    // 删除共享内存
    sleep(5);
    int RM = shmctl(shmid, IPC_RMID, nullptr);
    if (RM < 0)
    {
        Log() << "shmctl errno: " << strerror(errno) << std::endl;
        return 4;
    }
    Log() << "shmctl sucess!!! | "
          << "shmid: " << shmid << std::endl;
    return 0;
}

在这里插入图片描述


🍨2.4、shmdt(去挂接)

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

int shmdt(const void *shmaddr);
函数解析:
  • 功能:将共享内存段与当前进程脱离(去除共享内存与进程地址空间的映射)
  • 返回值:成功时shmdt()返回0;错误时返回-1,并设置errno以指示错误的原因
参数shmaddr:
  • shmaddr:由shmat所返回的指针,与C语言中使用free释放堆空间类似

IpcShmSer.cpp

#include "comm.hpp"
// 设置共享内存的大小
#define MIN_SIZE 4096
// 共享内存的状态(创建全新的,如果存在则设置errno,并且返回-1)
const int shmflags = IPC_CREAT | IPC_EXCL;

int main()
{
    // 1、获取key值(共享内存的唯一标识符)
    key_t key = CreateKey();
    Log() << "key: " << key << std::endl;

    // 2、创建共享内存 -- 0666是用户访问共享内存的权限,标识谁能读、写、执行
    sleep(5);
    int shmid = shmget(key, MIN_SIZE, shmflags | 0666);
    if (shmid < 0)
    {
        Log() << "shmget errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmget sucess!!! | " << "shmid: " << shmid << std::endl;

    // 3、挂接共享内存
    sleep(5);
    char *pIps = (char *)shmat(shmid, nullptr, 0);
    if (pIps < 0)
    {
        Log() << "shmat errno: " << strerror(errno) << std::endl;
        return 3;
    }
    Log() << "shmat sucess!!! | " << "shmid: " << shmid << std::endl;
          
    // 4、使用共享内存

    // 5、去挂接
    sleep(5);
    int flag = shmdt(pIps);
    if (flag < 0)
    {
        Log() << "shmdt errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmdt sucess!!! | " << "shmid: " << shmid << std::endl;

    // 5、删除共享内存
    sleep(5);
    int RM = shmctl(shmid, IPC_RMID, nullptr);
    if (RM < 0)
    {
        Log() << "shmctl errno: " << strerror(errno) << std::endl;
        return 4;
    }
    Log() << "shmctl sucess!!! | " << "shmid: " << shmid << std::endl;
    return 0;
}

在这里插入图片描述

🍀3、共享内存的使用

🍡3.1、测试

  • 我们上面所讲的内容,只完成了共享内存的创建、挂接、去挂接和删除共享内存

  • 共享内存实际上是映射进程地址空间的用户空间(堆、栈之间

  • 对于每个进程而言,挂接到自己上下文当中的共享内存,是属于自己的空间,可以被用户直接使用

  • 共享内存被映射到进程地址空间后类似于堆、栈空间,可以直接被使用

comm.hpp – 头文件和定义混编

  • 测试:服务器文件(IpcShmCli)写入数据,客户端文件(IpcShmSer)读取数据

  • 每次写入一个字节的数据,查看共享内存的读写顺序(是否存在同步与互斥

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>

#define PATH_NAME "/home/lyh/Linux_Study"
#define PROJ_ID 0x12
#define MIN_SIZE 4096

// 获取key值
key_t CreateKey()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
        std::cout <<  "ftok errno: " << strerror(errno) << std::endl;
        exit(1); // key值冲突,结束调用该函数的进程
    }
    return key;
}

// 该函数返回一个ostream& 可充当左值进行使用 -- 用于debug
std::ostream& Log()
{
    return std::cout << "Fot Debug | " << "timestamp: " << (long long)time(nullptr) << " | ";
}

IpcShmSer – 客户端创建共享内存并且读取共享内存的数据

  • 使用共享内存时,shmat跟malloc一样,返回的是引用共享内存块/堆块的虚拟地址

  • 我们可以对这些地址进行解引用写入数据

#include "comm.hpp"
// 共享内存的状态(创建全新的,如果存在则设置errno,并且返回-1)
const int shmflags = IPC_CREAT | IPC_EXCL;

int main()
{
    // 1、获取key值
    key_t key = CreateKey();
    Log() << "key: " << key << std::endl;

    //--------------------------------------------------------------------------

    // 2、创建共享内存 -- 0666是用户访问共享内存的权限,标识谁能读、写、执行
    int shmid = shmget(key, MIN_SIZE, shmflags | 0666);
    if (shmid < 0)
    {
        Log() << "shmget errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmget sucess!!! | " << "shmid: " << shmid << std::endl;

    //--------------------------------------------------------------------------

    // 3、挂接
    char *pIps = (char *)shmat(shmid, nullptr, 0);
    if (pIps < 0)
    {
        Log() << "shmat errno: " << strerror(errno) << std::endl;
        return 3;
    }
    Log() << "shmat sucess!!! | " << "shmid: " << shmid << std::endl;

    //--------------------------------------------------------------------------
          
    // 4、使用共享内存 -- 读取数据
    int cnt = 0;
    while (true)
    {
        sleep(2);
        std::cout << "Read data success: " << pIps << std::endl;
        if (cnt > CNT)
            break;
        ++cnt;
    }

    //--------------------------------------------------------------------------

    // 5、去挂接
    int flag = shmdt(pIps);
    if (flag < 0)
    {
        Log() << "shmdt errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmdt sucess!!! | " << "shmid: " << shmid << std::endl;

    //--------------------------------------------------------------------------

    // 6、删除共享内存
    int RM = shmctl(shmid, IPC_RMID, nullptr);
    if (RM < 0)
    {
        Log() << "shmctl errno: " << strerror(errno) << std::endl;
        return 4;
    }
    Log() << "shmctl sucess!!! | " << "shmid: " << shmid << std::endl;
    return 0;
}

IpcShmCli – 服务器写入数据

#include "comm.hpp"

// 该文件使用共享内存
int main()
{
    // 1、获取key值
    key_t key = CreateKey();
    Log() << "key: " << key << std::endl;

    //--------------------------------------------------------------------------

    // 2、获取共享内存标id
    int shmid = shmget(key, MIN_SIZE, IPC_CREAT | 0666);
    if (shmid < 0)
    {
        Log() << "shmget errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmget sucess!!! | " << "shmid: " << shmid << std::endl;

    //--------------------------------------------------------------------------

    // 3、挂接
    char *pIps = (char *)shmat(shmid, nullptr, 0);
    if (pIps < 0)
    {
        Log() << "shmat errno: " << strerror(errno) << std::endl;
        return 3;
    }
    Log() << "shmat sucess!!! | " << "shmid: " << shmid << std::endl;

    //--------------------------------------------------------------------------
          
    // 4、使用共享内存 -- 写入数据
    int cnt = 0;
    while (cnt <= CNT)
    {
        pIps[cnt] = 'A' + cnt;
        ++cnt;
        pIps[cnt] = '\0';
        sleep(1);
    }

    //--------------------------------------------------------------------------

    // 5、去挂接
    int flag = shmdt(pIps);
    if (flag < 0)
    {
        Log() << "shmdt errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmdt sucess!!! | " << "shmid: " << shmid << std::endl;
    return 0;
}

每次读取等待两秒,每次写入等待一秒,A->ABC->ABCDE…

在这里插入图片描述


🍢3.2、共享内存与管道的区别

通过上面的测试,我们知道:

  • 共享内存,因为它自身的特性,没有任何访问控制(同步与互斥)

  • 共享内存可以直接被二个或多个进程看到,属于二个或多个进程的用户空间,可以直接通信

  • 可以直接通信意味着:多个进程可以各自随便写入和读取不安全,没有访问控制

  • 共享内存,是所有IPC中,速度最快的

管道与共享内存中进程通信所拷贝数据次数的对比

在这里插入图片描述


🍧3.3、基于共享内存+管道实现访问控制

前言:

  • 虽然共享内存不能同步和互斥,但是我们可以通过管道的特性(访问控制)来间接的让共享内存获得访问控制这一特性!

  • 首先,前面的代码不变,我们还需增加命名管道的代码

  • 注意:命名管道只是辅助共享内存,主要是共享内存进行通信!!!

comm.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string>
#include <cstring>
#include <cerrno>

#define PATH_NAME "/home/lyh/Linux_Study"
#define PROJ_ID 0x12
#define MIN_SIZE 4096
const int CNT = 5;
// 获取key值
key_t CreateKey()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
        std::cout <<  "ftok errno: " << strerror(errno) << std::endl;
        exit(1); // key值冲突,结束调用该函数的进程
    }
    return key;
}

// 该函数返回一个ostream& 可充当左值进行使用 -- 用于debug
std::ostream& Log()
{
    return std::cout << "Fot Debug | " << "timestamp: " << (long long)time(nullptr) << " | ";
}

// 创建命名管道
void CreateName_Fifo()
{
    umask(0);
    // 在当前进程cwd下创建名为fifo的管道文件
    if (mkfifo("./fifo", 0666) == -1)
    {
        Log() << "fifo errno: " << strerror(errno) << std::endl;
        exit(1);
    }
    Log() << "fifo success!!!" << std::endl;
}

// 向命名管道写入任务码
void Write_Data()
{
    int fifofd = open("./fifo", O_WRONLY);
    if (fifofd == -1)
    {
        Log() << "fifofd errno: " << strerror(errno) << std::endl;
        exit(1);
    }
    // 写入任务码为:"1"的数据 -- 随便写,命名管道是辅助共享内存
    uint32_t taskcode = 1;
    write(fifofd, &taskcode, sizeof(uint32_t));
}

// 读取命名管道的任务码
ssize_t Read_Data()
{
    int fifofd = open("./fifo", O_RDONLY);
    if (fifofd == -1)
    {
        Log() << "fifofd errno: " << strerror(errno) << std::endl;
        exit(1);
    }
    uint32_t taskcode = 0;
    ssize_t s = read(fifofd, &taskcode, sizeof(uint32_t));
    return s;
}

IpcShmSer – 增加命名管道访问控制代码(读端)

#include "comm.hpp"
// 共享内存的状态(创建全新的,如果存在则设置errno,并且返回-1)
const int shmflags = IPC_CREAT | IPC_EXCL;

int main()
{
    // 打开命名管道
    CreateName_Fifo();

    // 1、获取key值
    key_t key = CreateKey();
    Log() << "key: " << key << std::endl;

    //--------------------------------------------------------------------------

    // 2、创建共享内存 -- 0666是用户访问共享内存的权限,标识谁能读、写、执行
    int shmid = shmget(key, MIN_SIZE, shmflags | 0666);
    if (shmid < 0)
    {
        Log() << "shmget errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmget sucess!!! | "
          << "shmid: " << shmid << std::endl;

    //--------------------------------------------------------------------------

    // 3、挂接
    char *pIps = (char *)shmat(shmid, nullptr, 0);
    if (pIps < 0)
    {
        Log() << "shmat errno: " << strerror(errno) << std::endl;
        return 3;
    }
    Log() << "shmat sucess!!! | "
          << "shmid: " << shmid << std::endl;

    //--------------------------------------------------------------------------

    // 4、使用共享内存 -- 读取数据
    int cnt = 0;
    while (cnt <= CNT)
    {
        // 调用命名管道,进行访问控制
        ssize_t s = Read_Data();
        // 二个进程中的读端关闭,则退出循环
        if (s == 0)
            break;
        else if (s > 0)
        {
            std::printf("read success: %s\n", pIps);
        }
        ++cnt;
    }

    //--------------------------------------------------------------------------

    // 5、去挂接
    int flag = shmdt(pIps);
    if (flag < 0)
    {
        Log() << "shmdt errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmdt sucess!!! | "
          << "shmid: " << shmid << std::endl;

    //--------------------------------------------------------------------------

    // 6、删除共享内存
    int RM = shmctl(shmid, IPC_RMID, nullptr);
    if (RM < 0)
    {
        Log() << "shmctl errno: " << strerror(errno) << std::endl;
        return 4;
    }
    Log() << "shmctl sucess!!! | "
          << "shmid: " << shmid << std::endl;

    // 7、删除管道文件
    unlink("./fifo");
    return 0;
}

IpcShmCli – 增加命名管道访问控制代码(写端)

#include "comm.hpp"

// 该文件使用共享内存
int main()
{
    // 1、获取key值
    key_t key = CreateKey();
    Log() << "key: " << key << std::endl;

    //--------------------------------------------------------------------------

    // 2、获取共享内存标id
    int shmid = shmget(key, MIN_SIZE, IPC_CREAT | 0666);
    if (shmid < 0)
    {
        Log() << "shmget errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmget sucess!!! | "
          << "shmid: " << shmid << std::endl;

    //--------------------------------------------------------------------------

    // 3、挂接
    char *pIps = (char *)shmat(shmid, nullptr, 0);
    if (pIps < 0)
    {
        Log() << "shmat errno: " << strerror(errno) << std::endl;
        return 3;
    }
    Log() << "shmat sucess!!! | "
          << "shmid: " << shmid << std::endl;

    //--------------------------------------------------------------------------

    // 4、使用共享内存 -- 写入数据
    int cnt = 0;
    while (cnt <= CNT)
    {
        std::string str;
        std::cout << "请输入需要写入的数据: ";
        std::fflush(stdout);

        std::getline(std::cin, str);
        // 调用命名管道,进行访问控制
        Write_Data();
        strcpy(pIps, str.c_str());
        ++cnt;
    }

    //--------------------------------------------------------------------------

    // 5、去挂接
    int flag = shmdt(pIps);
    if (flag < 0)
    {
        Log() << "shmdt errno: " << strerror(errno) << std::endl;
        return 2;
    }
    Log() << "shmdt sucess!!! | "
          << "shmid: " << shmid << std::endl;
    return 0;
}

---

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

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

相关文章

TCP 的重传机制、选择确认、缓存与流量控制、连接管理、拥塞控制(计算机网络-运输层)

目录 TCP 的重传机制 TCP 的选择确认 TCP 的缓存与流量控制 TCP 的连接管理 TCP的拥塞控制 TCP 的重传机制 TCP 每发送一个报文段就设置一个超时计时器&#xff1b; TCP 使用指数加权移动平均算法计算 RTT 及其偏差的估计值&#xff0c;并据此计算超时重传时间&#xff1b…

8 NP完全性理论

8 NP完全性理论 p问题 NP问题 NP完全问题 NPC(complete ) NP难问题NP-hard p问题 是一类能够用**(确定的)算法**在多项式时间内求解的可判定问题 ●这种问题类型也称为多项式类型 NP问题 是一类能够用不确定算法在多项式时间内求解的可判定问题 在确定性计算模型下多项式时…

程序员高手解决问题,都是从正确的提问开始

回顾各大技术网站、社区、问答&#xff0c;我们发现&#xff1a;真正的程序员高手都极度擅长提问。 好的提问不但能得到建设性的解决方案&#xff0c;更加能激发人们的好奇心、创造力和学习的动力。 毫不夸张地说&#xff0c;会提问的程序员一开口就赢了&#xff01; 所以今…

HashMap源码剖析(下)——java集合

系列文章目录 文章目录系列文章目录前言一、 回顾总结二、HashMap数据插入流程JDK1.8 HashMap的put方法源码如下:2.1 扩容机制2.2 链表树化2.3 红黑树转链三、查找总结四、删除五、遍历前言 既上一节内容 本节内容是&#xff0c;HashMap还有基本的数据功能&#xff1b;存储、删…

(三)GNSS定位中的定位系统和误差源

翻看了一下记录&#xff0c;离上一次GNSS内容的更新已经过去9个月了。时间过的太快&#xff0c;硕士生涯已经过去一半&#xff0c;有遗憾也有收获&#xff0c;过去的9个月经历了各种喜怒哀乐以及迷茫。永远相信美好的事情即将发生&#xff0c;GO LONG CHINA&#xff01; 今天写…

魅族大会公布未来多项规划!全场景门店、旗舰新品明年将落地

如果说中国手机行业中哪个品牌的粉丝忠诚度最高&#xff0c;那恐怕魅族说第二&#xff0c;也没几家敢说第一。这些年来&#xff0c;虽然魅族手机经历风风雨雨&#xff0c;但依然有着非常庞大的粉丝基础。前不久刚刚结束的魅友大会2022大会现场人潮涌动&#xff0c;随着天南海北…

Linux系统下的常用查找指令及用法

find指令&#xff1a;将从指定目录向下递归地遍历其各个子目录&#xff0c;将满足条件的文件或者目录显示在终端&#xff0c; 语法&#xff1a;find[搜索范围][选项] 方式作用-name<查询方式>按照指定的文件名查找模式查找文件-user<用户名>查找属于指定用户名所有…

Java项目:springboot教务管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 1.系统默认超级管理员账号为admin&#xff0c;默认密码为123456 系统配置&#xff1a;用户管理、角色管理、权限管理 默认已配置好基本数据&…

4.3.2、分类编址的 IPv4 地址

分类编址的 IPv4 地址分为 A、B、C、D、E 五类 A 类地址的网络号部分占 888 比特&#xff0c;主机号部分占 242424 比特。网络号的最高位固定为 000B 类地址的网络号部分占 161616 比特&#xff0c;主机号部分占 161616 比特。网络号的最高两位固定为 101010C 类地址的网络号部…

Reason: CORS request did not succeed 浏览器

放上我的错误&#xff1a; 一定要坚信他给你的提提示&#xff01;&#xff01;&#xff01;一定要怀疑是浏览器的设置 因为我试过网上的两种方法&#xff0c;均无效 法一&#xff1a;将Access-Control-Allow-Origin设置为&#xff1a;* 法二&#xff1a;因为我使用的是vue3&…

深度学习——转置卷积(笔记)

1.卷积层和汇聚层通常会减少下采样输入图像的空间维度&#xff08;高和宽&#xff09;&#xff0c;语义分割对输入进行像素级别的输出&#xff0c;但是卷积会不断减小高宽&#xff0c;不利于像素级别的输出。通过转置卷积能增大输入的高和宽 2.具体操作&#xff1a;步幅为1没有…

uni-app整包更新与热更新方案(安卓和IOS)

原文链接&#xff1a;uni-app整包更新与热更新方案(安卓和IOS) 效果预览 大致效果&#xff1a; 打开App&#xff0c;进入首页&#xff08;首次&#xff09;&#xff0c;检测线上是否存在新版本&#xff0c;如果存在&#xff0c;弹窗提示用户是否进行版本更新。Android 有热更新…

VMware17虚拟机安装及Linux系统搭建(详细版)

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; &#x1f4dc;前言&#xff1a; VMware是一个虚拟PC的软件&#xff0c;可以在现有的操作系统上虚…

虚拟机最小化方式安装Centos7后,ping和ifconfig都不可用解决方法

我用的是台式机&#xff0c;主机连接的是网线&#xff0c;在以最小化方式安装Centos7后&#xff0c;ping和ifconfig不可用。这里要记住&#xff0c;vm虚拟机使用网络适配器是&#xff1a;NAT 模式(N): 用于共享主机的 IP 地址 问题&#xff1a; ifconfig command not found p…

QT基本组件与常用类

目录 一、设计师 Designer&#xff08;掌握&#xff09; 二、布局 Layout 2.1 布局的基本使用&#xff08;掌握&#xff09; 2.2 布局属性&#xff08;掌握&#xff09; 2.3 伸展器&#xff08;掌握&#xff09; 2.4 嵌套&#xff08;掌握&#xff09; 2.5 伸展与策略&#xff…

【C语言进阶】想用好C++?那就一定要掌握动态内存管理

目录 &#x1f929;前言&#x1f929;&#xff1a; 一、动态内存概述⚔️&#xff1a; 1.什么是动态内存&#xff1a; 2.动态内存分配的意义&#xff1a; 二、常用的动态内存函数&#x1f3f9;&#xff1a; 1. malloc 和 free函数&#xff1a; ①. malloc 函数&#xff1a; …

基于PSO粒子群优化的带时间窗VRPTW问题matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 粒子群优化算法(PSO)是一种进化计算技术(evolutionary computation)&#xff0c;1995 年由Eberhart 博士和kennedy 博士提出&#xff0c;源于对鸟群捕食的行为研究 。该算法最初是受到飞鸟集群活…

数据结构初阶:树与二叉树(1)——堆

许久没发博客&#xff0c;在这里跟各位看客道声久等了~ 冬至已至&#xff0c;各位有没有吃上热乎的饺子呢 下面给各位奉上承载着满满干货的饺子吧&#xff1a; 目录 一、树 1. 树的结构定义 2. 树的相关概念 3. 树的表示 孩子兄弟表示法 二、二叉树 1. 二叉树的结构定义 2. 特…

自己整理的vue实现生成分享海报(含二维码),看着网上的没实现

大家好&#xff0c;我是雄雄。 前言 相信大家在许多的场景下&#xff0c;看到过这样的案例。 当我们在某购物app上看好一件商品&#xff0c;想分享给别人时&#xff0c;app会给我们生成一张海报&#xff0c;我们将其保存在手机里面转发给其他人达到分享。当我们逛CSDN的时候&…

【Android弹窗】Dialog Bottom Translate Animation

文章目录1. 系统Dialog2. 自定义Dialog3. 其余1. 系统Dialog 首先先来使用回顾一下系统的Dialog弹窗&#xff0c;这里使用比较简单的AlertDialog为例&#xff1a; AlertDialog.Builder builder new AlertDialog.Builder(this).setTitle("弹窗标题").setMessage(&q…