【Linux】进程间通信之-- 共享内存与信号量的介绍(下)

news2024/11/15 11:38:59

前言
上一篇,我们由进程间通信,引入并讲述了管道、匿名管道和命名管道,本节,将继续学习进程间通信的另一种方式之,共享内存。还要学习几个系统调用接口,并演示两个进程通过共享内存来进行通信。。。

目录

    • 1.共享内存
    • 1.1 创建共享内存
      • 1.1 ftok()创建key值
      • 1.2 shmget函数
      • 1.3 ipcs指令查看共享内存资源
      • 1.4 shmctl()接口
      • 1.5 shmat()接口
      • 1.6shmdt函数接口
      • 1.7 共享内存总结
      • 1.8 通过管道对共享内存进行控制:
    • 2. 相关概念
    • 3. 信号量
      • 3. 1 信号量的分类
      • 3. 2 信号量的原子性说明
      • 3. 3 信号量的规则

1.共享内存

本节讲的是system V的共享内存。

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

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

  • 我们知道堆栈相对而生,堆从低地址向高地址生长,栈从高地址向低地址生长,而在这两块空间之间的则是共享区。
    堆栈之间的区域特别大,堆栈之间的区域称为共享区,其中共享库就在这里。

共享内存的实现:

  1. 在物理空间上创建共享内存(申请一段空间)。
  2. 通过两个进程调用接口,然后物理内存中的空间映射到自己的地址空间上。

进程间通信的前提是:先让不同的进程,看到同一份资源

  • 共享内存是一种进程间通信机制,它允许多个进程共享同一块物理内存区域(就能同时看到一份资源)。

  • 当一个进程向共享内存写入数据时,实际上是将数据直接写入到共享内存所对应的物理内存中。

  • 其他进程可以通过读取相同的共享内存区域来获取已写入的数据。

优点:

  • 与其他进程间通信方式(如管道、消息队列等)不同。
  • 共享内存避免了数据的复制和传输过程,因此具有较高的效率。
  • 可以提供快速的数据交换,特别适用于需要频繁共享大量数据的进程间通信场景

共享内存数据结构

在这里插入图片描述

1.1 创建共享内存

共享内存=共享内存块+对应的共享内存的内核数据结构!

1.1 ftok()创建key值

  • 共享内存存在哪里?

  • 共享内存提供者,是操作系统

  • 存在于内核中,内核会给我们维护共享内存的结构

  • 操作系统也需要管理共享内存,一定是先描述,再组织

  • 我怎么知道,这个共享内存是存在还是不存在?

  • 需要标识共享内存的唯一性!

  • 在内核中,让不同的进程看到同一份共享内存。。

  • 做法是:让他们拥有同一个key即可。。

为了实现这种共享,操作系统会给每个共享内存区域分配一个唯一的key值来标识它,其他进程可以通过这个key值来访问并操作该共享内存区域。
这样就可以实现多个进程之间的数据交换和共享。

创建key值的函数:
在这里插入图片描述

返回值:key_t(其实就是int类型)成功了返回key值,失败了就返回-1
  • Linux系统给定了ftok接口,将用户提供的pathname工作路径,以及proj_id项目编号转换为一个共享内存的key(其实就是int类型)
  • 底层会将第一个参数对应路径文件的inode和指定的项目id这两个数字做组合形成一个唯一值,返回给一个key。
    底层是一些列的算法设计,相当于是帮我们构建具有唯一性的数字就可以了。

1.2 shmget函数

功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小(建议设置成页(4KB)的正式倍
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
共享内存的用户层标识符shmid,类似于曾经的fd,

操作系统和磁盘lO的基本单位大小是4KB。
从磁盘拷贝到内存是以4KB为单位拷贝的。
所以共享内存在申请大小时,也一定是4KB的整数倍。
操作系统只会按照整4KB来申请共享内存,是向上取整。

shmflg参数设置解释:

  • IPC_CREAT:创建共享内存,如果已经存在,就获取之,不存在就创建之。
    注: 可以获取成功,但是不知道这个共享内存,是本进程创建还是别的进程创建的拿过来的。
  • IPC_EXCL:必须配合IPC_CREAT使用(用按位或|配合使用),如果不存在指定的共享内存,创建之,如果存在了,出错返回。
    注:核心作用: 可以保证,如果shmget函数调用成功,一定是一个全新的share memory(共享内存)。
    如果这两个选项合着一起配合使用,一起传, 只要函数调用成功了,得到的一定是个全新的共享内存
  • 0666或权限值: 指定共享内存的访问权限。这些权限值使用八进制表示,例如0666表示可读可写权限。

key参数说明:

  • key值得由用户来提供。
  • 如果key值是由操作系统统一提供的话,另一个进程是无法获得共享内存的key值的。

1.3 ipcs指令查看共享内存资源

通过上述讲解,我们只要获取到唯一的key值,调用shmget函数就能获得共享内存了,创建好了我们怎样才能知道有哪些IPC资源呢。
ipcs指令的几个选项:

ipcs -c #查看消息队列/共享内存/信号量
ipcs -s #单独查看信号量
ipcs -q #单独查看消息队列
ipcs -m #单独查看共享内存

ipcrm -m 删除共享内存:

  • 只要在指令后跟上自己要删除的共享内存的shmid值就可以了
  • 为什么删除共享内存要用shmid而不用key呢?

key是操作系统中用来标识共享内存唯一性的, 在用户层用的是shmid
在用户层访问共享内存只能用数字shmid。

1.4 shmctl()接口

shmctl这个函数可以用于操作共享内存:

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

第二个参数解释:
IPC_STAT:用于获取共享内存段的状态信息,包括共享内存段大小、访问权限等。
IPC_SET:用于设置共享内存段的状态信息,如更改访问权限、起始地址等。
IPC_RMID:用于删除共享内存段,释放相关的资源

第三个参数:如果IPC_RMID如果被设置了,shmid_ds设置成nullptr就可以了,还有一些其他的属性设置

 shmctl(shmid, IPC_RMID, NULL)

注意:
当进程运行结束,我们的共享内存,还存在的。
需要使用shmctl( )函数手动删除。

1.5 shmat()接口

at是attach的简写,把一个共享内存附在进程上

功能:将共享内存段连接到进程地址空间
原型
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的整数倍。公式:shmaddr -
(shmaddr % SHMLBA) shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

char *addr = shmat(shmid, NULL, 0)

补充注意事项

  • 因为shmat函数的返回值是一个void*类型的指针,所以我们就可以像使用malloc一样的方式使来挂接共享内存了。
  • 随后对这个共享内存的操作就像平时使用数组一样的方式来使用了。
  • 共享内存是一种特殊的内存区域,可以由多个进程同时访问。
  • 尽管共享内存由一个进程创建,但它并不属于任何一个特定的进程。

同样的道理另一个进程也需要挂接这个共享内存才能实现两个进程通过共享内存来进行通信.

1.6shmdt函数接口

dt是detach的简写,把一个共享内存从进程上脱离下来(去关联)。

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

1.7 共享内存总结

  • 可以将共享内存当成一个大字符串
  • 只要是通信双方使用shm,一方直接向共享内存中写入数据,另一方,就可以立马看到。 共享内存是所有进程间通信(IPC)里速度最快的!,不需要过多的拷贝
  • 共享内存缺乏访问控制!会带来并发问题。因为有临界区资源问题,要做到同步

管道通信过程演示:

  • 管道的通信,使用了read/write函数,会有访问控制,一共经历4次拷贝。。

在这里插入图片描述

1.8 通过管道对共享内存进行控制:

Log.hpp:

#pragma once

#include <iostream>
#include <ctime>

std::ostream &Log()
{
    std::cout << "For Debug | " << "timestamp: " << (uint64_t)time(nullptr) << " | ";
    return std::cout;
}

Comm.hpp:

#pragma once

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include "Log.hpp"

using namespace std;

#define PATH_NAME "/home/Zh_Ser/linux"
#define PROJ_ID 0x14
#define MEM_SIZE 4096

#define FIFO_FILE "./.fifo"

// hpp是将头文件和源文件写在一起的方式
// 函数的定义可以放在里面,一般在开源的项目里用

key_t CreateKey()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
        cerr << "ftok: " << strerror(errno) << endl;
        exit(1);
    }
    return key;
}

// 创建命名管道
void CreatFifo()
{
    umask(0);
    if (mkfifo(FIFO_FILE, 0666) < 0)
    {
        Log() << strerror(errno) << endl;
        exit(2);
    }
}

#define READER O_RDONLY
#define WRITER O_WRONLY

int Open(const string& filename, int flags)
{
    return open(filename.c_str(), flags);
}

int Wait(int fd)
{
    uint32_t values = 0;
    ssize_t s = read(fd, &values, sizeof(values));
    
    return s;
}

void Signal(int fd)
{
    uint32_t cmd = 1;
    int s = write(fd, &cmd, sizeof(cmd));
}

void Close(int fd, const string filename)
{
    close(fd);
    unlink(filename.c_str());
}

IpcShmClient.cpp:

#include "Comm.hpp"
#include "Log.hpp"

// 充当使用共享内存的角色
int main()
{
    int fd = Open(FIFO_FILE, WRITER);
    cout << "Client: " << fd << endl;
    // 创建相同的key值
    key_t key = CreateKey();
    Log() << "key: " << key << endl;

    // 等待Server端先创建共享内存
    sleep(1);
    
    // 获取共享内存
    int shmid = shmget(key, MEM_SIZE, IPC_CREAT);
    if (shmid < 0)
    {
        Log() << "IpcShmCli shmget: " << strerror(errno) << endl;
        return 2;
    }

    // 挂接
    char* addr= (char*)shmat(shmid, nullptr, 0);

    // sleep(5);
    // 用它

    // 用共享内存,竟然没有使用任何系统调用接口
    // 直接向str空间写入
    while (true)
    {
        printf("Please Enter# ");
        fflush(stdout);
        // 往共享内存写数据

        ssize_t s = read(0, addr, MEM_SIZE);
        if (s > 0)
        {
            str[s] = '\0';
        }
        Signal(fd);
    }

    // 去关联
    shmdt(addr);
    
    // 不需要删除
    return 0;
}

IpcShmServer.cpp

#include "Comm.hpp"
#include "Log.hpp"

// 创建全新的共享内存
const int flags = IPC_CREAT | IPC_EXCL;

// 充当创建共享内存的角色
int main()
{
    // 创建管道
    CreatFifo();
    int fd = Open(FIFO_FILE, READER);
    cout << "Server: " << fd << endl;
    assert(fd >= 0);

    // 创建Key
    key_t key = CreateKey();
    Log() << "key: " << key << endl;

    Log() << "create share memory begin" << endl;

    int shmid = shmget(key, MEM_SIZE, flags | 0666);
    if (shmid < 0)
    {
        Log() << "IpcShmSer shmget: " << strerror(errno) << endl;
        return 2;
    }
    Log() << "create shm success, shmid: " << shmid << endl;

    // 1. 将共享内存和自己的进程产生关联attch
    char* addr= (char*)shmat(shmid, nullptr, 0);
    Log() << "attach shm: " << shmid << "success" << endl;

    // 用它
    // 服务器端直接用
    while (true)
    {
        sleep(1);
        // 在管道当中等,让读端进行等待
        if (Wait(fd) <= 0) break;
        
        // 从共享内存里读数据
        printf("%s\n", addr);
    }
    
    // **2. 去关联shmdt的返回值就是shmat的返回值**
    shmdt(addr);
    Log() << "detach shm: " << shmid << "success" << endl;

    // 删除
    shmctl(shmid, IPC_RMID, nullptr);

    Log() << "delete shm: " << shmid << "success" << endl;

    Close(fd, FIFO_FILE);
    
    return 0;
}

基于共享内存 + 管道的一个访问控制的效果:

  • 管道本身提供保护机制,我们自己也做了一次保护机制共享内存 + 管道的机制。
  • 如果这两个方案都不提供,裸的共享内存,被双方同时看到。
  • 我们两个进程在操作,就可能出现一些来回读写交叉的问题。
  • 导致数据不一致的问题,或者是访问控制方面的问题。

2. 相关概念

  • 临界资源: 被多个进程能够看到的资源叫做临界资源。

  • 如果没有对临界资源进行任何保护,对于临界资源的访问。

  • 多方进程在进行访问的时候,就都是乱序的。

  • 可能会因为读写交叉而导致的各种乱码、废弃数据、访问控制方面的问题!!

  • 临界资源有安全的也有不安全的,取决于内部是否做了保护。

  • 临界区: 我们把自己的进程,访问临界资源的代码

  • 我的进程代码中,有大量的代码,只有一部分代码,会访问临界资源。

  • 两个进程分别对共享资源做读写的代码叫做它们俩的临界区。

  • 原子性: 我们把一件事情,要么不做,要么做完,没有中间状态,叫原子性。

  • 如果一个n–操作只有一行汇编,该操作就是原子性的。

  • 互斥: 在任何时刻,都只能有一个进程进入临界区–互斥!!

3. 信号量

信号量(Semaphore)是一种在并发编程中常用的同步机制,用于控制对共享资源的访问。本质上就是一个整数计数器,用于表示可用资源的数量。

  • 信号量也属于进程间通信的一种,只不过不是以传送数据为目的,而是控制双方协同步调为目的。

多进程可以访问临界资源的不同区域,就要保证:

  1. 信号量保证不会有多余的进程连接到这份临界资源。
  2. 还需要保证每一个进程的能够访问到临界资源的不同位置(根据上层业务决定)

3. 1 信号量的分类

  • 二元信号量: 要么为0要么为1,(表现为互斥特性)。
  • 多元信号量:这个信号量可能为十八九等(一般很少见)。

如果一个进程想访问由信号量控制的临界资源,必须先申请信号量。如果申请成功,就一定能访问到这个临界资源中的一部分。

3. 2 信号量的原子性说明

每一个进程,要先申请信号量,每一个进程,都必须,先看到这个信号量:
信号量本身就是临界资源那么问题来了谁来保护信号量呢?

我们的信号量为了保证能够正确的控制进程的访问,其就必须维护自身的原子性!不能有中间状态
信号量本身是原子性的

共享内存不做访问控制,可以通过信号量进行对资源保护!

3. 3 信号量的规则

信号量对应的操作是PV操作:

  • 申请资源:P
  • 释放资源:V
    在这里插入图片描述

尾声
看到这里,相信大家对这个Linux 有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦

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

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

相关文章

数据结构——队列(链式结构)

一、队列链式结构定义 队列的链式存储结构是一种用链表实现的队列,它不像顺序存储结构那样需要预先分配固定大小的空间。链式存储结构的队列由节点组成,每个节点包括数据和指向下一个节点的指针。队列的链式存储结构可以动态地分配内存,更灵活地处理数据。在链式存储结构中…

【07】LLaMA-Factory微调大模型——微调模型导出与微调参数分析

上文介绍了如何对微调后的模型进行使用与简单评估。本文将介绍对微调后的模型进行导出的过程。 一、llama-3微调后的模型导出 首先进入虚拟环境&#xff0c;打开LLaMA-Factory的webui页面 conda activate GLM cd LLaMA-Factory llamafactory-cli webui 之后&#xff0c;选择…

SQL39道常见题型

SQL1 查询所有列 现在运营想要查看用户信息表中所有的数据&#xff0c;请你取出相应结果。 select * from user_profile 结果&#xff1a; SQL2 查询多列 还是上面那个输入&#xff0c;题目换成&#xff1a;现在运营同学想要用户的设备id对应的性别、年龄和学校的数据&#…

TIM基本定时器

TIM基本定时器 文章目录 TIM基本定时器1.定时器的分类2.定时器运行流程3.基本定时器的配置流程4.中断配置 1.定时器的分类 以STM32F1系列为例&#xff0c;它的定时器可以根据其特性和功能被分为三大类&#xff1a; 基本定时器&#xff1a; 包括&#xff1a;TIM6和TIM7。特点&a…

数据结构全部知识-----第一 关于数据结构的介绍

数据结构是计算机存储、组织数据的方式。它是计算机科学中的一个重要概念&#xff0c;主要目的是使数据的存储和访问更高效、更方便。常见的数据结构包括&#xff1a; 线性结构&#xff1a; 1. **数组&#xff08;Array&#xff09;** &#xff1a;一种基础的数据结构&#xf…

【BUG】已解决:AttributeError: ‘WindowsPath‘ object has no attribute ‘rstrip‘

AttributeError: ‘WindowsPath‘ object has no attribute ‘rstrip‘ 目录 AttributeError: ‘WindowsPath‘ object has no attribute ‘rstrip‘ 【常见模块错误】 【错误原因】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&…

C++中的多路转接技术之epoll

epoll 是干什么的&#xff1f;举个简单的例子 epoll的相关系统调用**epoll_create**和epoll_create1区别 epoll_ctl参数解释 **epoll_wait**参数说明返回值 epoll的使用 **epoll**工作原理epoll的优点(和 **select** 的缺点对应)epoll工作方式**水平触发**Level Triggered 工作…

针对汽车应用而设计的SCT4026D、SCT4062K、SCT3105K、SCT3080A、SCT3060A全新系列碳化硅 (SiC) MOSFET

全新系列碳化硅 (SiC) MOSFET SCT4026DWAHRTL SCT4062KWAHRTL SCT3105KRC15 SCT3080ALHRC11 SCT3080ARC15 SCT3060ARC15 ——明佳达 AEC-Q101 SiC功率MOSFETs是汽车和开关电源的理想选择。SiC功率MOSFETs可以提高开关频率&#xff0c;减少所需的电容、电抗器和其他元件的体积…

react开发-配置开发时候@指向SRC目录

这里写目录标题 配置开发时候指向SRC目录VScode编辑器给出提示总体1.配置react的 2.配置Vscode的1.配置react的2,配置VSCode的提示支持 配置开发时候指向SRC目录VScode编辑器给出提示 总体1.配置react的 2.配置Vscode的 1.配置react的 1. 我么需要下载一个webpack的插件 这样…

【闲谈】我的创作纪念日(CrowdStrike、无人驾驶)

感谢地心引力 &#xff0c;有幸再次遇见你&#xff1a; 还记得 2020 年 07 月 22 日吗&#xff1f;你撰写了第 1 篇技术博客&#xff1a;《遗传算法实例解析》在这平凡的一天&#xff0c;你赋予了它不平凡的意义。也许是立志成为一名专业 IT 作者、也许是记录一段刚实践的经验。…

【iOS】——探究isKindOfClass和isMemberOfClass底层实现

isKindOfClass 判断该对象是否为传入的类或其子类的实例 // 类方法实现&#xff0c;用于检查一个类是否属于另一个类或其父类链上的任何类。(BOOL)isKindOfClass:(Class)cls {// 从当前类开始&#xff0c;tcls将沿着元类的继承链向上遍历。for (Class tcls self->ISA(); …

MySQL:库表操作

MySQL&#xff1a;库表操作 库操作查看创建字符编码集 删除修改备份 表操作创建查看删除修改 库操作 查看 查看存在哪些数据库&#xff1a; show databases;示例&#xff1a; 查看自己当前处于哪一个数据库&#xff1a; select database();示例&#xff1a; 此处由于我不处于任…

Unity UGUI 之 Input Field

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 1.Input Field是什么&#xff1f; 给玩家提供输入的输入框 2.重要参数 中英文对照着看…

JSONNode树形解析或流式解析

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 什么是JSONNode&#xff1f; JSONNode是一个用于处理JSON数据的数据结构&#xff0c;它提供了一种简单、灵活、高效的方式来操作JSON数据。JSONNode可以看作是一个树形结构&#xff0c;其中每个节点都可以包含一个值…

MongoDB自学笔记(四)

一、前文回顾 上一篇文章中我们学习了MongoDB中的更新方法&#xff0c;也学了一部分操作符。今天我们将学习最后一个操作“删除”。 二、删除 原始数据如下&#xff1a; 1、deleteOne 语法&#xff1a;db.collection.deleteOne(< query >,< options >) 具体参…

OpenCV 像素操作—证件照换底色详细原理 C++纯手写实现

文章目录 总体步骤1.RGB转HSV2.找出要换的底色3.取反&#xff0c;黑白颠倒4.将原图像的非背景部分复制到新背景上 完整代码1.C纯手写版2.官方API版本 总体步骤 1.RGB转HSV 为什么一定要转为HSV 颜色空间&#xff1f; 将图像从BGR颜色空间转换为HSV颜色空间是因为HSV颜色空间更…

vscode 文件颜色变绿色

解决&#xff1a;关闭git功能 在设置中搜索Git:Enabled&#xff0c;取消Decorations: Enabled的勾选

内网渗透隧道构建,使用github项目联动msf绕uac,使用简单的spp来进行操作icmp隧道

在我们需要木马上线的时候&#xff0c;发现上线不了&#xff0c;我们一般就想到建立隧道&#xff0c;来解决问题&#xff0c;或者是说我们直接还一种连接的操作来进行上线。比如说我们正向连接上不了&#xff0c;我们可以还成反向连接的操作。或者我们使用隧道直接硬刚waf来进行…

计算机毕业设计-程序论文文档-基于SSM的驾校管理系统

本系统开发采用技术为JSP、Bootstrap、Ajax、SSM、Java、Tomcat、Maven 此文章为本人亲自指导加编写&#xff0c;禁止任何人抄袭以及各类盈利性传播&#xff0c; 相关的代码部署论文ppt代码讲解答辩指导文件都有可私要 项目源码&#xff0c;请关注❥点赞收藏并私信博主&#x…

代码随想录 day 18 二叉树

第六章 二叉树part06 详细布置 530.二叉搜索树的最小绝对差 需要领悟一下二叉树遍历上双指针操作&#xff0c;优先掌握递归 题目链接/文章讲解&#xff1a;https://programmercarl.com/0530.%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E7%B…