【Linux】进程间通信之共享内存与信号量初识

news2024/11/24 2:24:34

目录

  • 🌈前言
  • 🌸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、基于共享内存+管道实现访问控制
  • 🍁4、信号量初识
    • 🍡4.1、临界资源与临界区

🌈前言

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


🌸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;
}

---


🍁4、信号量初识

🍡4.1、临界资源与临界区

临界资源概念:

  • 一次仅允许一个进程使用,且各进程采取互斥的方式,实现共享的资源,叫做临界资源

  • 共享资源有:管道、通过

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

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

相关文章

使用 DataAnnotations(数据注解)实现模型的通用数据校验

DataAnnotations 实现数据模型的通用校验参数校验的意义常用参数的校验.NET 中内置 DataAnnotations 提供的特性校验关于 DataAnnotations 中的特性介绍基于 DataAnnotations 的通用模型校验封装基于 DataAnnotations 的特性校验助手实现步骤如何使用 DataAnnotations 封装的特…

某农业学校 算法设计与分析-第五次实验-回溯算法

1. 罗密欧与朱丽叶的迷宫问题 问题描述 罗密欧与朱丽叶的迷宫。罗密欧与朱丽叶身处一个mn的迷宫中&#xff0c;如图所示。每一个方格表示迷宫中的一个房间。这mn个房间中有一些房间是封闭的&#xff0c;不允许任何人进入。在迷宫中任何位置均可沿8 个方向进入未封闭的房间。罗…

第二章:关系数据库

一、关系数据库结构及形式化定义 1、【单选题】 下图中&#xff0c;关系D1、D2、D3笛卡尔积的目和基数分别为 正确答案&#xff1a; B 2、【多选题】下图中能够作为候选码的属性组为 正确答案&#xff1a; ABD 3、【多选题】关于关系数据库&#xff0c;说法正确的是 正确答…

二、栈和队列

二、栈和队列 栈——后进先出 应用&#xff1a;数制转换、括号匹配、行编辑程序、迷宫求解、表达式求值、八皇后问题、函数调用、递归调用的实现 队列——先进先出 应用&#xff1a;脱机打印输出 多用户系统用户排队分时循环使用CPU和主存 按用户优先级排队&#xff0c;每…

编译gtest报错‘is_trivially_copy_constructible’ is not a member of ‘std’

编译gtest报错‘is_trivially_copy_constructible’ is not a member of ‘std’一、问题描述二、原因分析三、升级gcc版本四、验证一、问题描述 在一个新的Redhat7.6 linux虚拟机上&#xff0c;将gtest clone下来之后编译&#xff0c;一堆报错&#xff1a; /opt/googletest/…

多线程问题(二)(安全问题)

目录 一、多线程不安全引例 二、线程不安全的原因 1、线程是抢占式执行 2、多线程共享同一变量 3、对变量的操作不是原子性 4、内存可见性 5、指令重排序 三、线程不安全问题的解决方案 1、使用synchronized关键字进行加锁 a、 synchronized修饰普通方法 b、sy…

Maleimide-PEG-Biotin,Biotin-PEG-MAL,生物素PEG马来酰亚胺用于生物分子检测

化学试剂生物素聚乙二醇马来酰亚胺&#xff0c;其英文名为Maleimide-PEG-Biotin&#xff0c;Biotin-PEG-MAL&#xff0c;它所属分类为Biotin PEG Multi-arm PEGs。 该试剂质量控制为95%&#xff0c;试剂的储存条件为&#xff1a; -20℃长期保存&#xff0c;避光&#xff0c;干…

数据结构---图

&#xff08;一&#xff09; 相关知识点 图&#xff08;graph&#xff09;&#xff1a;图是由顶点的有穷非空集合和顶点之间边的集合组成&#xff0c;通常表示为&#xff1a;G(V,E)&#xff0c;其中&#xff0c;G表示一个图&#xff0c;V是图G中的顶点的集合&#xff0c;E是图G…

SpringBoot系列之自动装配原理详解

文章目录前言一、SpringBoot自动配置-Condition-11、观察spring自动创建bean过程2、创建自定义bean对象3、根据条件创建自定义bean二、 SpringBoot自动配置-Condition-2三、SpringBoot自动配置-切换内置web服务器1、查看继承关系图2、shiftdelete 排除Tomcat四、SpringBoot自动…

Win10启动Pycharm报错

Win10启动Pycharm报错报错信息解决方法报错信息 Internal error. Please report to http://jb.gg/ide/critical-startup-errors java.net.BindException: Address already in use: bind at java.base/sun.nio.ch.Net.bind0(Native Method) at java.base/sun.nio.ch.Net.bind(U…

如何在3个月内写出博士论文

在阅读本文之前&#xff0c;请注意&#xff1a;我花了三年半的时间进行全职研究&#xff0c;为我的博士论文收集数据&#xff1b;这三个月只涉及写作&#xff0c;我在最后很快就完成了。我并不是说每个人都能写得那么快&#xff0c;如果你没有做过研究&#xff0c;那是不可能的…

全国各省368个地级市河流密度数据(工具变量)

数据来源&#xff1a;国家基础地理信息中心 时间跨度&#xff1a;-- 区域范围&#xff1a;全国各省市 指标说明&#xff1a; 根据河流矢量和中国城市行政边界矢量地理信息&#xff0c;计算每个城市河流的总长度&#xff1b;根据各城市的行政区划面积&#xff0c;计算中国各城…

第三章:关系数据库标准语言SQL

一、sql概述和数据定义 1、【单选题】 create user A identified by B default tablespace C temporary tablespace D&#xff1b; 上述oracle数据库查询语句中A、B、C、D分别代表&#xff1a; 正确答案&#xff1a; A 2、【单选题】下表为患者缴费记录&#xff0c;现需…

2023跨年烟花3D最炫烟花,html最酷炫动态烟花源码分享,点击即可直接运行

&#x1f4cb; 前言 &#x1f5b1; 博客主页&#xff1a;在下马农的碎碎念✍ 本文由在下马农原创&#xff0c;首发于CSDN&#x1f4c6; 首发时间&#xff1a;2022/12/25&#x1f4c5; 最近更新时间&#xff1a;2022/12/25&#x1f935; 此马非凡马&#xff0c;房星本是星。向前…

lambda表达式,函数式接口,链式编程,Stream流式计算

新时代的程序员&#xff1a;lambda表达式&#xff0c;函数式接口&#xff0c;链式编程&#xff0c;Stream流式计算 函数式接口 函数式接口&#xff1a;只有一个方法的接口(简化编程模型&#xff0c;在新版本框架底层中大量应用&#xff01;) 只要是 函数型接口 就可以使用lambd…

iOS 16.2 在 SwiftUI 子视图中无法关闭弹出的(sheet)导航视图(NavigationView)之解决

问题现象 iOS 16.2 中,若在 SwiftUI 4.0 里弹出(sheet)一个导航视图 A,则不能在 A 的子视图中将 A 关闭(dismiss): 如上图所示:上面的按钮直接放在导航视图 A 中,点击它可以直接关闭 A;而下面的按钮放在一个子视图中,点击它想要关闭 A 却没有任何反应。 那么,该如…

可转债网格交易策略回测

什么是网格交易策略&#xff1a;基于股票波动高抛低吸策略&#xff0c;自动化反复买卖赚取差价。投资者借助条件单&#xff0c;把资金分成多份&#xff0c;从基准价开始&#xff0c;每跌x%就自动买入一份&#xff0c;每涨y%就自动卖掉一份。股价越波动高抛低吸的机会越多 什么…

Java项目:springboot基于java+mysql+springboot的社区养老医疗综合服务平台

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目分为管理员、医生、病人三种角色&#xff0c; 管理员的功能包含如下&#xff1a; 个人信息&#xff1a;个人资料、修改密码 系统管理&…

Python入门学习之字符串与比较运算符

Python字符串 字符串或串(String)是由数字、字母、下划线组成的一串字符。 一般记为 : 1 s"a1a2an"(n>0) 它是编程语言中表示文本的数据类型。 python的字串列表有2种取值顺序: 从左到右索引默认0开始的&#xff0c;最大范围是字符串长度少1从右到左索引默认-1开…

【C++】各种排序涉及到的选择小题合集(每日小细节009)

昨天更新完各种排序之后今天来检验一下是否真的全部掌握了呢&#xff1f; 今天的合集里面包括各种排序的综合选择题和一些解题技巧 花一两分钟看一下真的很有帮助哦 &#xff08;最好能先认真复习一下各种排序&#xff09; 1. 冒泡排序就是相邻元素的两两比较所以依次写出来就…