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

news2024/11/25 22:47:04

目录

前言:

1. System V共享内存

1. 共享内存的理解

2. 共享内存的使用步骤

3. 共享内存的使用

        1. 共享内存的创建

        查看共享内存

        2. 共享内存的释放

        3. 共享内存的挂接

        4. 共享内存的去挂接

4. 共享内存的使用示例

1. 两进程挂接与去挂接演示:

2. 两进程通信演示;


Linux🌷

前言:

在之前写的进程间通信的两篇博客中,不管是匿名管道通信还是命名管道通信都是基于文件的一种通信方式;

而今天我们要学习另一种通信方式:System V标准下的 “共享内存”;

在这里简单介绍下System V标准,System V标准是计算机学术界的一些领头人物站在OS层面,专门为进程间通信设计的方案;

System V提供了三个主流方案:

1. 共享内存——也就是今天博客的主要内容;

2. 消息队列;

3. 信号量;

其中共享内存和消息队列是为了传输数据设计的,信号量是为了保证进程间的同步和互斥设计的;

下面开始我们的正文!🔮


1. System V共享内存

1. 共享内存的理解

共享内存是一种允许两个或多个进程访问同一块物理内存的通信机制;

用一张图更好的理解下:

其本质就是先在物理内存中开辟一块空间,然后让相互通信的进程通过页表映射将这块共享空间映射到进程的虚拟地址空间中,因此两个进程便看到了同一份资源,便可以进行进程间通信了,物理内存中开辟的空间就是我们的共享内存;

OS中可能存在多对使用共享内存进行通信的进程,共享内存也自然不止一个,OS也当然要将共享内存进行管理,管理方法——“先描述,在组织”

下面看一下共享内存的数据结构:

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

我们申请一块共享内存之后 为了保证这块共享内存的唯一性 我们必须要对它做个标识

我们在 linux 系统中我们使用 key 值来唯一标识一块共享内存

在我们的 shmid_ds 结构体的第一行有一个叫做 shm_perm 的结构体 它的类型是 ipc_perm 它的结构体定义如下

struct ipc_perm{
	__kernel_key_t  key;
	__kernel_uid_t  uid;
	__kernel_gid_t  gid;
	__kernel_uid_t  cuid;
	__kernel_gid_t  cgid;
	__kernel_mode_t mode;
	unsigned short  seq;
};

我们看它的第一行有这么一段描述 __kernel_key_t key;

而这个key就是唯一标识共享内存的key值

2. 共享内存的使用步骤

1. 共享内存的创建(开辟共享内存);

2. 共享内存的挂接(在页表中建立共享内存的映射关系);

3. 共享内存的去挂接(在页表中去掉共享内存的映射关系);

4. 共享内存的释放;

3. 共享内存的使用

1. 共享内存的创建

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

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

key:共享内存段的唯一标识,用户可使用如下函数进行key的自动生成

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

key_t ftok(const char *pathname, int proj_id);

pathname:自定义的路径名;

proj_id:自定义的项目ID;

返回值:成功返回一个非负整数,失败返回-1;

只要pathname和proj_id是一样的,那么生成的key便是一样的,因此两个进程便可以识别同一共享内存 ;

@size:共享内存的大小,建议是4KB的整数倍。

因为OS在分配内存资源时,是以页面为单位进行分配的,页面的大小是4KB,虽然是这样分配的,但是申请多少还是给你多少,为了避免给用户造成一些错误;

@shmflg:由九个权限标志组成,它们的用法和创建文件时使用的mode模式标志是一样的。

我们一般有如下两种用法:

1. IPC_CREAT共享内存不存在时,创建一个共享内存并返回,如果创建的共享内存已经存在的话,则直接返回当前已经存在的共享内存;

2. IPC_CREAT | IPC_EXCL:如果共享内存不存在则创建并返回,如果共享内存已经存在则返回出错,也就是说如果调用成功,得到的一定是一个最新的,没有被别人使用的共享内存!IPC_EXCL单独使用是没有意义的,

3. 共享内存也是有权限的,我们还可以在上述基础上(|或权限),达到共享内存权限的设定;

@返回值:创建成功返回一个非负整数,即该共享内存段的标识码,失败返回-1;
示例:
#include <stdio.h>    
#include <sys/ipc.h>    
#include <sys/shm.h>    
    
#define PATH_NAME "./test.c"    
#define PROJ_ID 0x666    
#define SIZE 4097    
    
int main()    
{    
  //生成key    
  key_t key = ftok(PATH_NAME,PROJ_ID);    
  if(key<0)    
  {    
    perror("ftok fail\n");    
    return 1;    
  }    
    
  //创建共享内存                                                          
  int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL);    
  if(shmid<0)              
  {                             
    perror("shmget fail\n");    
    return 2;              
  }                        
                                             
  printf("key:%u ,shmid:%d\n",key,shmid);    
  return 0;                
}      
结果演示:

查看共享内存

我们可以使用 ipcs 命令查看系统中的进程间通信资源的信息;

如果我们只想知道三种通信方式其中一种的通信信息的话,我们可以在 ipcs 后面携带选项:

-q:列出消息队列相关信息;

-m:列出共享内存相关信息;

-s:列出信号量相关信息;

 其中它的每列信息含义如下:

命名含义
key系统区别各个共享内存的唯一标识
shmid共享内存的用户层id
owner共享内存的拥有者
perms共享内存的权限
bytes共享内存的大小
nattch挂接共享内存的进程数
status共享内存的状态

key和shmid的区别:

我们的key是系统中是被共享内存的唯一标识 而shmid是我们用户层识别共享内存的标识

这就好像是inode和文件名的区别

系统中使用inode来标识一个文件 而我们使用文件名来标识一个文件

2. 共享内存的释放

共享内存的生命周期是随内核的 就算进程结束了共享内存也不会被释放

如果再次运行test.c程序 我们就会发现这样子的错误

想要释放共享内存只有两种方式

1. 关机重启

2. 主动进行释放

使用命令方式释放共享内存:

我们可以使用 ipcrm -m shmid命令释放指定id的共享内存资源

使用函数方式释放共享内存:

我们一般使用shmctl函数来控制共享内存 它的函数原型如下

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmid:用户层面标识唯一一个共享内存;

cmd:具体的控制命令;

buf:获取或设置所控制共享内存的数据结构(一般设为NULL);

返回值:成功返回0,失败返回-1;

其中关于 cmd 参数 常用的命令有以下几个

选项作用
IPC_STAT获取共享内存的当前关联值 此时参数buf作为输出型参数
IPC_SET在进程有足够权限的前提下 将共享内存的当前关联值设置为buf所指的数据结构中的值
IPC_RMID删除共享内存段

将上述代码修改下,继续运行:

#include <stdio.h>    
#include <sys/ipc.h>    
#include <sys/shm.h>    
    
#define PATH_NAME "./test.c"    
#define PROJ_ID 0x666    
#define SIZE 4097    
    
int main()    
{    
  //生成key    
  key_t key = ftok(PATH_NAME,PROJ_ID);    
  if(key<0)    
  {    
    perror("ftok fail\n");    
    return 1;    
  }    
    
  //创建共享内存    
  int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL);    
  if(shmid<0)    
  {    
    perror("shmget fail\n");    
    return 2;    
  }                                                                                           
    
  printf("key:%x ,shmid:%d\n",key,shmid);    
    
  //释放共享内存    
  shmctl(shmid,IPC_RMID,NULL);    
  printf("delete success!\n");    
  return 0;    
}    

 我们可以看到 共享内存 被释放了

3. 共享内存的挂接

我们一般使用 shmat函数来进行共享内存的挂接

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

void *shmat(int shmid, const void *shmaddr, int shmflg);

shmid: 待关联共享内存的用户级标识符;

shmaddr:指定共享内存映射到进程地址空间的某一地址(通常设置为NULL,让OS来指定 );
shmflg:挂接共享内存时设置的某些属性(一般设置为0);

返回值:成功则返回共享内存映射到进程地址空间中的起始地址,失败则返回-1;

4. 共享内存的去挂接

我们一般使用shmat函数来进行共享内存和虚拟地址空间的去挂接

int shmdt(const void *shmaddr);

shmaddr:shmat函数时得到的起始地址

返回值:成功返回0,失败返回-1;

4. 共享内存的使用示例

1. 两进程挂接与去挂接演示:

 我们可以看到nattch是由0->1->2->1->0的;

注意:我们在shmget的时候必须设置权限,否则会挂不上去;

  • comm.h:
#include <stdio.h>    
#include <sys/ipc.h>    
#include <sys/shm.h>    
#include <sys/types.h>    
#include <unistd.h>    
    
#define PATH_NAME "./"        
#define PROJ_ID 0x333        
#define SIZE 4097 
  • makefile:
.PHONY:all    
all:server client    
    
server:server.c    
  gcc -o $@ $^    
    
client:client.c    
  gcc -o $@ $^    
    
.PHONY:clean    
clean:    
  rm -f server client     
  • server.c:
#include "comm.h"

int main()
{
  //生成共享内存key
  key_t key = ftok(PATH_NAME,PROJ_ID);
  if(key<0)
  {
    perror("ftok fail");
    return 1;
  }

  //创建共享内存
  int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
  if(shmid<0)
  {
    perror("shmget fail");
    return 2;
  }
  printf("shmget success! key:0x%x shmid:%d\n",key,shmid);
  sleep(3);

  //挂接共享内存
  char* mem = (char*)shmat(shmid,NULL,0);                                                                  
  printf("shmat success!\n");
  sleep(3);

  //去挂接共享内存
  shmdt(mem);
  printf("shmdt success!\n");
  sleep(3);

  //释放共享内存
  shmctl(shmid,IPC_RMID,NULL);
  printf("shmrm success!\n");
  sleep(3);
  return 0;
}
  •  client.c:
#include "comm.h"    
    
int main()    
{    
  //生成key    
  key_t key = ftok(PATH_NAME,PROJ_ID);    
  if(key<0)    
  {    
    perror("ftok fail");    
    return 1;    
  }    
    
  //获取shmid    
  int shmid = shmget(key,SIZE,IPC_CREAT|0666);                                                             
  if(shmid<0)    
  {    
    perror("shmget fail");    
    return 2;    
  }    
    
  //挂接共享内存    
  char* mem = (char*)shmat(shmid,NULL,0);    
  sleep(1);    
  printf("client shmat success!\n");    
    
  //去挂接    
  shmdt(mem);    
  sleep(1);    
  printf("client shmdt success!\n");    
    
  return 0;    
}    

2. 两进程通信演示;

 我们在上述代码中添加了通信的内容,结果如上所示;

  • server.c: 
  //通信    
  while(1)    
  {    
    printf("%s\n",mem);    
    sleep(1);                                                           
  }    
  • client.c:
  //通信    
  char c='A';    
  while(c<'Z')    
  {    
    mem[c-'A']=c;    
    c++;    
    mem[c-'A']='\0';    
    sleep(2);                                                           
  }    

客户端每隔2秒往共享内存中写入数据,服务端每隔1秒读一次;

我们发现在客户端还没有写入数据时,服务端就开始读了;

并且,服务端再读数据之后,并没有把数据拿走;

这两点是区别与管道通信的,管道是具有同步机制(读写两端会相互等待的),读了之后数据就带走了;

坚持打卡!😀

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

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

相关文章

基于eNSP的IPv4加IPv6的企业/校园网络规划设计(综合实验/大作业)

作者&#xff1a;BSXY_19计科_陈永跃 BSXY_信息学院_名片v位于结尾处 注&#xff1a;未经允许禁止转发任何内容 基于eNSP的IPv4加IPv6的企业/校园网络规划设计_综合实验/大作业 前言及技术/资源下载说明&#xff08; **未经允许禁止转发任何内容** &#xff09;一、设计topo图与…

Postgresql逻辑优化学习

张树杰优化器原理学习 0 用例 drop table student; create table student(sno int primary key, sname varchar(10), ssex int); insert into student values(1, stu1, 0); insert into student values(2, stu2, 1); insert into student values(3, stu3, 1); insert into stu…

SPSS岭回归报错问题 第 8 列中的 错误号 34+乱码问题

1首先第一个问题&#xff0c;先找到Ridge Regression.sps文件 注意各国语言都有这个文件&#xff0c;选择English下的 得到位置&#xff0c;一般是安装路径\Samples\English\Ridge Regression.sps 仍然报错&#xff0c;将第二行变成大写&#xff1a;RIDGEREG ENTER&#xff0…

笔记本电脑没有声音了怎么恢复

笔记本电脑 在使用的过程中&#xff0c;突然没有声音的话&#xff0c;对于人们来说会很麻烦。那么笔记本电脑没有声音了怎么恢复呢?下面小编为大家整理了笔记本电脑没有声音的恢复方法&#xff0c;一起来看看吧。 方法/步骤&#xff1a; 方法一&#xff1a;网络适配器检查音频…

物联网工程有哪些SCI期刊推荐? - 易智编译EaseEditing

以下是一些物联网工程领域的SCI期刊推荐&#xff1a; IEEE Internet of Things Journal&#xff1a; 该期刊由IEEE出版&#xff0c;致力于物联网技术领域的研究&#xff0c;包括物联网的基础理论、通信、算法、应用、系统等方面。 Sensors&#xff1a; 该期刊由MDPI出版&…

基于el-input的数字范围输入框

数字范围组件 在做筛选时可能会出现数字范围的筛选&#xff0c;例如&#xff1a;价格、面积&#xff0c;但是elementUI本身没有自带的数字范围组件&#xff0c;于是进行了简单的封装&#xff0c;不足可自行进行优化 满足功能&#xff1a; 最小值与最大值的相关约束&#xff0…

C++默认成员函数 日期类运算符重载

赋值重载 赋值重载&#xff0c;首先我们先说一个运算符重载&#xff0c;什么是运算符重载呢&#xff1f; 当我们有一个日期类的话&#xff0c;我们想要对&#xff0c; 一个日期类进行比较&#xff0c;那么我们怎么比较呢&#xff1f; 我们是不是先得比较年的大小&#xff0c;…

day6 socket套接字及TCP的实现框架

socket套接字 Berkeley UNIX 操作系统定义了一种API它又称为套接字接口&#xff08;socket interface); socket作用&#xff1a; socket常见API介绍 /*创建套接字*/ int socket(int domain, int type, int protocol); /*绑定通信结构体*/ int bind(int sockfd, const, struc…

【数据库】MVCC原理详解

文章目录 前言1. 相关数据库知识点回顾1.1 什么是数据库事务&#xff0c;为什么要有事务1.2 事务包括哪几个特性&#xff1f;1.3 事务并发存在的问题1.3.1 脏读1.3.2 不可重复读1.3.3 幻读 1.4 四大隔离级别1.4.1 读未提交1.4.2 读已提交1.4 3 可重复读1.4.4 串行化1.4.5 四大隔…

chatgpt模拟机器人软件开发

ChatGPT的参数取决于具体的模型和实现方式&#xff0c;但以下是一些常见的ChatGPT参数&#xff1a; 模型深度&#xff1a;指模型中神经网络的层数。通常情况下&#xff0c;层数越多&#xff0c;模型的表达能力也就越强。 隐藏单元大小&#xff1a;指在模型中每个隐藏层…

局域网 - CSMA/CD(载波侦听多路访问 / 冲突检测)

文章目录 1 概述1.1 局域网的拓扑结构 2 CSMA/CD2.1 三种监听算法2.2 冲突检测原理2.3 二进制指数后退算法 3 扩展3.1 网工软考真题 1 概述 1.1 局域网的拓扑结构 2 CSMA/CD CSMA/CD&#xff1a;Carrier Sense Multiple Access/ Collision Detection&#xff0c;载波侦听多路…

从功能测试转型测试开发,薪资涨了20K,1000字讲述转型必经之路...

身处职场之中&#xff0c;犹如逆水行舟不进则退&#xff0c;想要不被后浪拍死在沙滩上&#xff0c;就要不断学习新知识&#xff0c;接受新事物。 要得到更好的发展&#xff0c;就要紧跟发展趋势&#xff0c;不断转型才能保持竞争力&#xff0c;在职场中占有一席之地。 转型不…

如何理解自动化测试数据驱动与关键字驱动的区别?

一、关键字驱动KDT(Keyword-driven testing) 1、自动化测试框架发展的第三个阶段是关键字驱动测试框架阶段&#xff0c;它是当前比较流行的一种框架之一&#xff0c;并且现在的自动化测试工具已经将关键字驱动框架融入到工具中。在录制过程中自动化测试工具会将对象及操作属性保…

如何提高倾斜摄影超大场景的三维模型轻量化处理速度和效率?

如何提高倾斜摄影超大场景的三维模型轻量化处理速度和效率&#xff1f; 倾斜摄影超大场景的三维模型轻量化处理是将高精度的三维模型进行降采样、简化等处理&#xff0c;以达到减少数据大小和提高渲染性能的目的。为了提高轻量化处理速度&#xff0c;可以从以下方面入手&#x…

【Java 】Java 类加载和类加载器

文章目录 前言一、加载二、链接验证准备解析 三、初始化发生的时机不会触发类的初始化 四、类加载器双亲委派模式 前言 Java 的类加载阶段分为&#xff1a;加载、链接、初始化&#xff0c;而链接的过程中包括&#xff1a;验证、准备、解析。 一、加载 将类的字节码载入方法区…

vue3新拟态组件库开发流程——table组件源码

基础表格 首先开发table组件之前&#xff0c;先想好要用什么样式的api&#xff0c;因为笔者在生产工作中用的都是element&#xff0c;所以前面几个组件风格和element类似&#xff0c;但是这次不打算用element的风格了&#xff0c;打算换一种&#xff0c;直接展示&#xff1a; …

LinkedHashMap顺序迭代原理与LRU算法实现

一、LinkedHashMap与HashMap的结构区别 HashMap LinkedHashMap 结构区别&#xff1a;LinkedHashMap的元素Entry中多两个用于维护双向链表的指针before、after&#xff0c;并且在LinkedHashMap中有两个head、tail指针用于记录双向链表的头结点和尾结点。 二、LinkedHashMa…

2.3 定点乘法运算

学习目标&#xff1a; 如果我要学习定点乘法运算&#xff0c;我会按照以下步骤进行学习&#xff1a; 确定学习目标&#xff1a;明确学习定点乘法运算的目的和重点&#xff0c;以便有针对性地进行学习。 掌握基础知识&#xff1a;首先需要了解定点数和定点乘法的基础知识&…

【halcon】半透明和棋盘格(未完成)

背景 想实现一个这样的效果&#xff1a; 但是发现设置HSmartWindowControlWPF 的Background 根本就没有反应。 探索过程 于是就换个思路&#xff0c;把棋盘格画到Border里面。 <Border Grid.Row"1" Grid.ColumnSpan"2" Panel.ZIndex"0" >…

MySQL:数据库的基本操作

MySQL是一个客户端服务器结构的程序, 一.关系型数据库 关系型数据库是一个结构化的数据库&#xff0c;创建在关系模型&#xff08;二维表格模型&#xff09;基础上&#xff0c;一般面向于记录。 主流的关系型数据库包括 Oracle、MySQL、SQL Server、Microsoft Access、DB2 等. …