Linux小黑板(9):共享内存

news2024/11/19 15:37:47

"My poor lost soul"

上章花了不少的篇幅讲了讲基于管道((匿名、命名))技术实现的进程间通信。进程为什么需要通信?目的是为了完成进程间的"协同",提高处理数据的能力、优化业务逻辑的实现等等,在linux中我们已经谈过了一个通信的大类——管道。根据System V标准的基础上提出了,另一套进程通信的标准。

-----前言


一、System V简介

System V,曾经也被称为AT&TSystem V,是Unix操作系统众多版本中的一支。
System V的第一个版本,发布于1983年。它引进了一些特性,例如vi编辑器和curses库。其中也包括了对DEC VAX机器的支持。同时也支持使用消息进行进程间通信,信号量和共享内存。 取自这里

为什么这么"隆重"地介绍System V呢?因为这是一套通信标准。当在后面学了共享内存的多个API后,你会发现消息队列、信号量的接口函数极为相似。

二、共享内存

共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。 取自这里

文字描述显然很恼人,我们直接上图。

通信的本质,就是让不同能够看到同一份资源。

由此,我们对共享内存的理解是:其本质就是开辟在物理内存里的一块内存块,用来进行IPC通信的。
让需要通信的进程能够看到这块内存块,并在上面进行通信行为。

三、实现共享内存

(1)创建共享内存块

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

shmget() returns the identifier of the System V shared memory segment associated with the value of the argument key. A new shared memory segment, 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.
这里也就简单说说shmget的参数。

key:这个参数至关重要。它是连接共享内存,找到共享内存块的关键。也就是说,一个进程只要拿到了在这个key值,就可以访问这一块内存块。
size:申请共享内存块的大小。这个size会按照PAGE_SIZE大小(向上对齐)。
shmflg:我们常用的参数就是 IPC_CREATE \ IPC_EXCL:
IPC_CREATE:如果不存在就创建、如果存在就获取
IPC_EXCL:不能单独使用。IPC_CREATE | IPC_EXCL 如果不存在就创建,存在就返回错误(保证给用户的共享内存块一定是新创建的)。

如果创建成功,shmget会返回一个有效标记内存块的标识符

那么怎么形成key这个唯一标识的关键字呢?库里给我们提供了一个函数,可以让生成的key是一个唯一标识的数字key_t类型。

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

convert a pathname and a project identifier to a System V IPC key:用pathname + proj_id转换为key

并且这个pathname不是随便乱取的。而是一个"现有"、"可访问"的文件。

如何让两个进程看到一块共享内存?只需要知道key就行了。key是怎么生成的?ftok(pathname,proj_id)。这两个参数一样,不久可以生成相同的key了嘛?

如何理解key?

在操作系统中,一定会存在多个key_t类型的 有效标识符。那么这么多标识符一定会被操作系统管理。管理的本质:先描述、再组织。

我们举个例子:

我们可以看到,OS会为共享内存块维护一份结构体struct shimid_ds 用来管理共享内存块。

共享内存的生命周期随OS:

我们在学习管道通信时,一旦进程有一方结束,那么双方的通信管道也会随之关闭。为什么现如今,进程结束了,它们申请的共享内存块仍然存在呢??

管道的生命周期随进程,共享内存的生命周期随OS。

为此,我们不得不手动去关闭掉,我们编写的进程所打开的共享内存块。

ipcrm -m + shmid

(2)挂接与去关联

我们现如今拿到了共享内存块的标识符shmid,那么如何通过这个shmid找到共享内存的位置呢?

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);

shmat() attaches the System V shared memory segment identified by shmid to the address space of the calling process. The attaching address is specified by shmaddr with one of the following criteria.
通过这个函数可以通过shmid 与 共享内存块挂接,并返回该共享内存块的地址。

参数: shmaddr 、shmflag
If shmaddr is NULL, the system chooses a suitable (unused) address at which to attach the segment.
这里我们通常设置为 nullptr,那么OS就会为我们选择一个适合的挂接到了这个内存块的地址。
shmflag通常为设置为0.

返回参数:
On success shmat() returns the address of the attached shared memory segment; on error (void *) -1 is returned, and errno is set to indicate the cause of the error.
如果成功,shmat()返回挂接这个共享内存块的地址,返回-1是error的
void *attachShm(int shmid)
{
    void *mem = shmat(shmid, nullptr, 0);
    if ((long long)mem == -1) // Linux下 是64位系统 一个指针大小为8字节
    {

        std::cerr << "attachShm: " << mem << std::endl;
        exit(3);
    }
    return mem;
}

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);

shmdt() detaches the shared memory segment located at the address specified by shmaddr from the address space of the calling process. The to-be-detached segment must be currently attached with shmaddr equal to the value returned by the attaching shmat() call.
shmdt()会将传入的共享内存块地址去挂接(关联)。调用shmdt(),必须传入通过shmat()获取的地址。

On success shmdt() returns 0; on error -1 is returned, and errno is set to indicate the cause of the error.
0为去关联成功,-1位失败。
void detachShm(void* start)
{
    int ret = shmdt(start);
    if(ret == -1)
    {
        std::cerr << "attachShm: " << ret << std::endl;
        exit(4);
    }
}

(3)释放共享内存块

共享内存块的生命周期随OS,我们每申请共享内存空间,都得"ipcrm -m + shmid"释放内存空间,当我们不再使用时,这未免太过麻烦了。

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

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl() performs the control operation specified by cmd on the System V shared memory segment whose identifier is given in shmid.
shmctl()通过cmd传入的命令操作,对shmid表示的共享内存块进行处理。

要在进程结束时,关闭共享内存,我们需要传入的参数是:
IPC_RMID Mark the segment to be destroyed.
void delShm(int shmid)
{
    if (shmctl(shmid, IPC_RMID, nullptr) == -1)
    {
        std::cerr << "delShm: " << shmid << std::endl;
        exit(5);
    }
}

当然,shmctl不仅仅可以归还共享内存,当cmd传入的参数是IPC_STAT时,我们可以获取由shmid填充的结构体内容。

像这样:

int main()
{
    int shmid = shmget(key);
    struct shmid_ds ds;
    shmctl(shmid,IPC_STAT,&ds);
    ds.shm_perm.__key;
    ds.shm_atime;
    //..
    return 0;
}

(4)测试

有了上面对函数的理解和实现,我们可以进行一定的通信了。

我们想让Sever读取client发送的内容: "Hello Server! pid + 发送次数"。

Client:

int main()
{
    // 1.申请key
    key_t key = getKey(PATH_NAME, PROJ_ID);
    std::cout << "Client key: " << key << std::endl;
    // 2.申请内存块
    int shmid = getShm(key);
    std::cout << "Client shmid: " << shmid << std::endl;
    // 3.挂接
    void *start = attachShm(shmid);
    printf("attach success, address start: %p\n", start);
    // 真正的通信区域
    const char* msg = "Hello Server!";
    int cnt = 0;
    while (true)
    {   
        snprintf((char*)start,MAX_PAGE,"%s[pid:%d]编号信息:[%d]\n",msg,getpid(),cnt++);
        sleep(1);
    }

    // 4.去关联
    detachShm(start);
    return 0;
}

Server:

int main()
{
    // 1.申请key
    key_t key = getKey(PATH_NAME, PROJ_ID);
    std::cout << "Server key: " << key << std::endl;
    // 2.申请内存块
    int shmid = createShm(key);
    std::cout << "Server shmid: " << shmid << std::endl;
    // 3.挂接
    void *start = attachShm(shmid);
    printf("attach success, address start: %p\n", start);
    // 真正的通信区域
    while (true)
    {
        printf("client say : %s\n", start);
        sleep(1);
    }

    // 4.去关联
    detachShm(start);
    printf("detach success");

    // 5.关闭共享内存
    delShm(shmid);
    return 0;
}

我们来看看效果吧~

这是怎么回事??噢,原来是权限问题。

我们在创建共享内存的时候,需要附上权限大小。

这样就完成了双方进程间的通信。


四、共享内存 vs 管道

但是,管道自带同步与互斥!如果写端不写入,读端会成阻塞状态,直到写端写入,此时有数据可读。但是共享内存不一样,它没有同步与互斥操作,即便写端没有写入数据,读端照样会向内存块中读取。

总结:

①共享内存是一块存在物理内存的,由操作系统管理的内存块。它属于IPC通信方式的一种,在所有的通信方式中速度是最快的。

②共享内存的创建:1.申请(shmget) 2.挂接(shmat) 3.去关联(shmdt) 4.关闭空间(shmctl).

③管道自带同步与互斥,共享内存不支持。

本篇也就告一段落了,感谢你的阅读。

祝你好运~向阳而生。

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

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

相关文章

数据库的基本查询

注意&#xff1a;LIMIT的两个参数&#xff0c;第一个是起始位置&#xff0c;第二个是一次查询到多少页。注意&#xff1a;什么类型的数字都是可以排序的。日期的降序是从现在到以前&#xff0c;MySQL ENUM值如何排序&#xff1f;在MYSQL中&#xff0c;我们知道每个ENUM值都与一…

安装MySQL数据库

安装MySQL数据库 获取软件&#xff1a;https://dev.mysql.com/downloads/mysql/ 下载完成后进行解压操作 若安装目录里没有my.ini配置文件&#xff0c;则需要新建一个my.ini的配置文件。 编辑my.ini配置文件&#xff0c;将配置文件中的内容修改成下面内容 [client] # 设置…

基于企业微信应用消息的每日早安推送

基于企业微信应用消息的每日早安推送 第一步&#xff1a;注册企业微信 企业微信注册地址&#xff1a;https://work.weixin.qq.com/wework_admin/register_wx 按照正常流程填写信息即可&#xff0c;个人也可以注册企业微信&#xff0c;不需要公司 注册完成后&#xff0c;登录…

户籍管理系统测试用例

目录 一、根据页面的不同分别设计测试用例 登录页面 用户信息列表 用户编辑页面 用户更新页面 二、根据目的不同分别设计测试用例 一、根据页面的不同分别设计测试用例 上图是针对一个网站的测试&#xff0c;按照页面的不同分别来设计对应的测试用例。 登录页面 用户信息列…

[MySQL]MySQL数据类型

文章目录数据类型分类数值类型tinyint类型bit类型float类型decimal类型字符串类型char类型varchar类型char和varchar对比日期和时间类型enum和set类型数据类型分类 MySQL中&#xff0c;支持各种各样的类型&#xff0c;比如表示数值的整型浮点型&#xff0c;文本、二进制类型、…

【密码学】 一篇文章讲透数字签名

【密码学】 一篇文章讲透数字签名 数字签名介绍 数字签名&#xff08;又称公钥数字签名&#xff09;是只有信息的发送者才能产生的别人无法伪造的一段数字串&#xff0c;这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名…

问题解决:Excel中依据某一列数据进行匹配

问题描述Excel处理时&#xff0c;常常需要从一个大表里&#xff0c;按照条件提取子集。需要我们按照某一序列为标准&#xff0c;匹配筛选出有效信息&#xff0c;案例如下&#xff1a;依据名称匹配销售额。解决方法使用函数&#xff1a;VLOOKUP(lookup_value,table_array,col_in…

Elasticsearch7.8.0版本进阶——自定义分析器

目录一、自定义分析器的概述二、自定义的分析器的测试示例一、自定义分析器的概述 Elasticsearch 带有一些现成的分析器&#xff0c;然而在分析器上 Elasticsearch 真正的强大之 处在于&#xff0c;你可以通过在一个适合你的特定数据的设置之中组合字符过滤器、分词器、词汇单 …

刚来的薪资20k,是我的2倍,我是真的卷不过,真的太变态了

在这个行业爬摸滚打5年了&#xff0c;从最开始点点点的功能测试到现在到现在成为高级测试&#xff0c;工资也翻了几倍&#xff0c;简单的说几句吧 改变的开始 之所以改变的原因很简单&#xff0c;我快被新来的卷死了&#xff0c;新来的本科是某211的&#xff0c;干劲十足&…

Python为CANoe工程添加/删除DBC文件

前面文章我们对于通过COM来实现打开CANoe、导入CANoe配置工程、导入执行文件、启动CANoe软件和执行脚本;但是这只能完成最基本的功能调用,在实际得到使用过程中,特别是各家在推的CI/CD以及平台化,仅仅是实现这些功能是完全不够用的;比如dbc的添加和删除,这是我们非常必要…

【洛谷 P1563】[NOIP2016 提高组] 玩具谜题(模拟+结构体数组+指针)

[NOIP2016 提高组] 玩具谜题 题目背景 NOIP2016 提高组 D1T1 题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业。 有一天, 这些玩具小人把小南的眼镜藏了起来。 小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外。如下图: 这时 singer 告诉小南一个谜…

Python变量的定义和使用

定义&#xff1a;变量就是计算机内存中存储某些数据的位置的名称 形象理解变量就是一个存放东西的容器&#xff0c;该容器的名字就叫做变量&#xff0c;容器存放的东西就是变量的值 变量的组成&#xff1a; 标识&#xff1a;标识对象所储存的内存地址&#xff0c;使用内置函数i…

Redis的持久化方式

Redis支持两种方式的持久化&#xff0c;一种是RDB方式、另一种是AOF&#xff08;append-only-file&#xff09;方式&#xff0c;两种持久化方式可以单独使用其中一种&#xff0c;也可以将这两种方式结合使用。 •RDB&#xff1a;根据指定的规则“定时”将内存中的数据存储在硬…

Android中使用GRPC简明教程

引言 Android作为一个开发平台&#xff0c;本身是使用java进行封装的&#xff0c;因此java可以调用的库&#xff0c;在Android中同样可以进行调用&#xff0c;这样就使得Android设备具有丰富的功能&#xff0c;可以进行各种类型的开发。 这篇文章就介绍如何在Android设备中使…

Spring Cloud Nacos源码讲解(七)- Nacos客户端服务订阅机制的核心流程

Nacos客户端服务订阅机制的核心流程 ​ 说起Nacos的服务订阅机制&#xff0c;大家会觉得比较难理解&#xff0c;那我们就来详细分析一下&#xff0c;那我们先从Nacos订阅的概述说起 Nacos订阅概述 ​ Nacos的订阅机制&#xff0c;如果用一句话来描述就是&#xff1a;Nacos客…

十四、项目实战二(CORS、同源策略、cookie跨域)

项目实战二 需求 以前后端分离的方式实现学生的增删改查操作 学生列表接口 url:/students/ 请求方法&#xff1a;get 参数&#xff1a; 格式&#xff1a;查询参数 参数名类型是否必传说明pageint否页码&#xff0c;默认为1sizeinit否每页数据条数默认为10namestr否根据姓…

SpringBoot+HttpClient+JsonPath提取A接口返回值作为参数调用B接口

前言 在做java接口自动化中&#xff0c;我们常常需要依赖多个接口&#xff0c;A接口依赖B&#xff0c;C&#xff0c;D接口的响应作为请求参数&#xff1b;或者URL中的参数是从其他接口中提取返回值作获取参数这是必不可少的。那么怎么实现呢&#xff1f;下面就来介绍多业务依赖…

大数据技术之Hive(三)函数

hive有大量内置函数&#xff0c;大致可分为&#xff1a;单行函数、聚合函数、炸裂函数、窗口函数。查看内置函数show functions;查看内置函数用法desc function upper;查看内置函数详细信息desc function extended upper;一、单行函数单行函数的特点是一进一出&#xff0c;输入…

Kubernetes之服务的基本管理

svc是kubernetes最核心的概念&#xff0c;通过创建Service&#xff0c;可以为一组具有相同功能的容器应用提供一个统一的入口地址&#xff0c;并将请求进行负载分发到后端的各个容器应用上。pod生命周期短不稳定&#xff0c;pod异常后新生成的pod的IP会发生变化&#xff0c;通过…

JSD2212班第二次串讲-面向对象阶段

1. 概述 系统介绍一下接下来阶段要学习的内容&#xff1a; 1.JAVA&#xff1a; 1.1 java语言基础 1.2 java面向对象阶段&#xff08;很重要&#xff01;起的承上启下&#xff01;&#xff01;&#xff09; 1.3 API阶段&#xff08;Application Interface &#xff09;学会看字…