【Linux修行路】进程通信——共享内存

2024-08-31 13:12

本文主要是介绍【Linux修行路】进程通信——共享内存,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

⛳️推荐

一、直接原理

1.1 共享内存的的申请

1.2 共享内存的释放

二、代码演示

2.1 shmget

2.1.1 详谈key——ftok

2.2 创建共享内存样例代码

2.3 获取共享内存——进一步封装

2.4 共享内存挂接——shmat

2.5 共享内存去关联——shmdt

2.6 释放共享内存——shmctl

2.7 开始通信

2.7.1 processb 基础代码编写

2.7.2 通信代码编写

三、共享内存的特点

3.1 共享内存 VS 管道

四、拓展内容

4.1 查看共享内存的属性

4.2 借助管道实现共享内存的同步与互斥


⛳️推荐

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站【Linux修行路】动静态库详解点击跳转到网站

一、直接原理

1.1 共享内存的的申请

共享的原理和动态库的共享原理一致,共享内存的申请主要分为以下三步:

1.2 共享内存的释放

去关联,释放共享内存。申请、挂接、去关联、释放这些动作都是由操作系统来做的,进程不能自己去做,进程中可以通过 malloc 去申请空间,但是因为进程独立性的存在,一个进程自己 malloc 申请的空间,只属于当前进程,不能由多个进程共享。

  • 操作系统在物理内存上申请一块空间。

  • 将申请到的空间,通过页表挂接到进程地址空间的共享区。

  • 返回起始虚拟地址,供程序中使用。

系统中可能同时有多组进程 都需要通信,因此系统中可能存在多个共享内存,所以操作系统要把这多个共享内存管理起来。先描述,再组织,操作系统中一定有一个内核结构体是用来描述共享内存的。

二、代码演示

2.1 shmget

shmget 函数用来申请一块共享内存。

image-20240306100932350

  • key:一个数字,是几不重要,关键在于它必须在内核中具有唯一性,能够让不同的共享内存具有唯一性标识。
  • size:创建共享内存的大小,单位是字节。一般建议是4096的整数倍,如果传的是4097,操作系统实际上申请的空间大小是 4096*2,虽然操作系统多申请了,但是多的部分用户不能使用,用了会被判定为越界。
  • shmflg:标记位,常用选项有 IPC_CREAT :如果申请的共享内存不存在,就创建,存在,就获取并返回。IPC_EXCL :如果申请的共享内存存在,就出错返回。IPC_CREAT | IPC_EXCL :如果申请的共享内存不存在,就创建,存在就出错返回。这俩选项一起使用保证了,如果我们申请成功了一个共享内存,这个共享内存一定是一个新的。IPC_EXCL 不单独使用。其次,共享内存的权限也通过这个标志位进行传递。
  • 返回值:创建成功,返回共享内存标识符;创建失败,返回-1。

2.1.1 详谈key——ftok

无论是创建共享内存还是使用共享内存,都需要调用该函数。第一个进程可以通过 key 创建共享内存,第二个之后的进程,只需要拿着同一个 key 就可以和第一个进程看到同一个共享内存。key 在共享内存的描述对象中,第一次创建共享内存的时候,就必须有一个 key 了。使用 ftok 函数生成一个 key

image-20240306105116360

  • pathname:路径名。
  • proj_id:项目 ID。
  • 返回值:生成成功 key 被返回;生成失败 -1 被返回(路径名如果不存在的话是有可能生成失败的)。

只要这两个参数一样,那么两个进程就可以得到同一个 key 值。

**key值为什么是通过用户传参来生成的,而不是操作系统直接生成的?**因为操作系统不知道哪两个进程需要通信,假设 A 进程和 B 进程进行通信,在 A 进程中操作系统随机生成了一个 key 给 A 进程,此时 B 进程也需要知道 key 值,但是操作系统是不知道 A 进程要和 B 进程进行通信,所以操作系统没办法将这个 key 值交给 B 进程,只有程序员(写代码的人)才知道 A、B 进程之间需要通信。其实 ftok 函数相当于是两个通信进程之间的一种约定,只要它们约定好同一个 pathname 和 proj_id,那么这两个进程就能得到同一个 key

2.2 创建共享内存样例代码

#ifndef __COMM_HPP__
#define __COMM_HPP__#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string>
#include "log.hpp"
#include <string.h>
#include <errno.h>
using namespace std;const int size = 4096;
const string path_name = "/home/wcy";
const int proj_id = 0x6666;
Log log;key_t GetKey() // 获取 key
{key_t k = ftok(path_name.c_str(), proj_id);if(k < 0){// 获取 key 失败log(Fatal, "ftok error: %s", strerror(errno));exit(1);}log(Info, "ftok sucess, key is: %d", k);return k;
}int GetShareMem() // 创建共享内存
{key_t key = GetKey();int shmid = shmget(key, size, IPC_CREAT|IPC_EXCL);if(shmid < 0){log(Fatal, "creat share memeory error: %s", strerror(errno));exit(2);}log(Info, "creat share memory success, shmid is: %d", shmid);return shmid;
}#endif

key 和 shmid:

key 是在操作系统内部来唯一标识一块共享内存,是给操作系统来使用的;shmid 是给进程使用的,用来表示资源的唯一性。虽然共享内存属于文件系统,但是 shmid 和文件描述符的兼容性做的并不好,共享内存给自己单独设置了一个类似于文件描述符表的东西。

**共享内存的生命周期是随内核的,用户不主动关闭,共享内存会一致存在。**只有内核重启或者用户主动释放,共享内存才会被释放。

查看操作系统中所有的共享内存:ipcs -m

image-20240306113648742

  • perms:权限位
  • nattch:和当前共享内存关联的进程个数。

命令行中删除共享内存:ipcrm -m shmid。指令是由用户输入的的,在用户层统一使用 shmid

2.3 获取共享内存——进一步封装

int GetShareMemHelper(int flag)
{key_t key = GetKey();int shmid = shmget(key, size, flag);if(shmid < 0){log(Fatal, "creat share memeory error: %s", strerror(errno));exit(2);}log(Info, "creat share memory success, shmid is: %d", shmid);return shmid;
}// 创建共享内存
int CreatMem()
{return GetShareMemHelper(IPC_CREAT|IPC_EXCL|0666);
}// 获取共享内存
int GetMem()
{return GetShareMemHelper(IPC_CREAT); // 这里也可以传0
} 

2.4 共享内存挂接——shmat

shmat:将某个共享内存挂接到当前进程的地址空间中。

image-20240306130457459

  • shmid:共享内存的标识符。
  • shmaddr:指向挂接在地址空间的什么位置,因为我们也不知道挂接在什么位置,所以一般设为 nullptr 即可。
  • shmflg:设置当前进程对该共享内存的权限,一般设置成0,表示采用共享内存自身的权限。
  • **返回值:**共享内存挂接在地址空间中的地址。
// processa
#include "comm.hpp"
#include <unistd.h>int main()
{// 创建共享内存int shmid = CreatMem();log(Debug, "creat sharemem done...");sleep(5);// 挂接共享内存char *sharmem = (char*)shmat(shmid, nullptr, 0);log(Debug, "%d attch success", shmid);sleep(5);log(Debug, "processa quit...");return 0;
}

2.5 共享内存去关联——shmdt

shmdt:去掉某个共享内存与当前进程的关联。

image-20240306132053723

  • shmaddr:就是 shmat 函数返回的那个地址。
// processa
#include "comm.hpp"
#include <unistd.h>int main()
{// 创建共享内存int shmid = CreatMem();log(Debug, "creat sharemem done...");sleep(5);// 挂接共享内存char *shamem = (char*)shmat(shmid, nullptr, 0);log(Debug, "%d attch done, to 0x%x", shmid, shamem);sleep(5);// 去关联shmdt(shamem);log(Debug, "%d deattch done", shmid);log(Debug, "processa quit...");return 0;
}

2.6 释放共享内存——shmctl

shmctl:用来释放一个共享内存。

image-20240306133608142

  • cmd:操作选项。IPC_STAT:将内核中共享内存的属性拷贝到 buf 里面。IPC_RMID:删除共享内存。
  • struct shmid_ds *buf:语言层面用来描述一个共享内存的结构体,里面保存了共享内存的部分属性。
  • **返回值:**如果操作是 IPC_RMID,那么删除成功返回0,失败返回-1。
// processa
#include "comm.hpp"
#include <unistd.h>int main()
{// 创建共享内存int shmid = CreatMem();log(Debug, "creat sharemem done...");sleep(5);// 挂接共享内存char *shamem = (char*)shmat(shmid, nullptr, 0);log(Debug, "sharemem %d attch done, to 0x%x", shmid, shamem);sleep(5);// 去关联shmdt(shamem);log(Debug, "sharemem %d deattch done", shmid);sleep(5);// 释放共享内存int ret = shmctl(shmid, IPC_RMID, NULL);if(ret < 0)log(Debug, "sharemem delete error: %s", strerror(errno));elselog(Debug, "sharemem delete success...");sleep(5);log(Debug, "processa quit...");return 0;
}

2.7 开始通信

2.7.1 processb 基础代码编写
#include "comm.hpp"
#include <unistd.h>int main()
{// 获取共享内存int shmid = GetMem();log(Debug, "Get sharemem done...");sleep(5);// 挂接char *shmaddr = (char*)shmat(shmid, nullptr, 0);log(Debug, "sharemem %d attch done, to 0x%x", shmid, shmaddr);sleep(5);// 去关联shmdt(shmaddr);log(Debug, "sharemem %d deattch done", shmid);return 0;
}
2.7.2 通信代码编写

processa:

#include "comm.hpp"
#include <unistd.h>int main()
{// 创建共享内存int shmid = CreatMem();// 挂接共享内存char *shamem = (char*)shmat(shmid, nullptr, 0);// ipc-cod 通信代码while(true){cout << "client asy@ " << shamem << endl; // 直接访问共享内存sleep(1);}// 去关联shmdt(shamem);// 释放共享内存int ret = shmctl(shmid, IPC_RMID, NULL);return 0;
}

processb:

#include "comm.hpp"
#include <unistd.h>int main()
{// 获取共享内存int shmid = GetMem();// 挂接char *shmaddr = (char*)shmat(shmid, nullptr, 0);// ipc-code 通信代码while(true){cout << "Please enter:";fgets(shmaddr, size, stdin);}// 去关联shmdt(shmaddr);return 0;
}

一旦有了共享内存,并且挂接到当前进程的地址空间上了,在程序中就把它当做该进程自己的内存空间来使用即可,无需再调用系统调用。一旦有人把数据写入到共享内存,其实我们立马就能看到,不需要经过系统调用,就能直接看到数据。可以把共享内存就当做用户自己 malloc 出来的一块空间。

三、共享内存的特点

  • 共享内存没有同步与互斥之类的保护机制,即写端没有向共享内存中写入,读端可以正常读取。

  • 共享内存是所有的进程间通信中,速度最快的。原因在于数据拷贝次数少。

  • 共享内存中的数据,完全是由用户自己维护,操作系统不会帮我们做清空工作。

3.1 共享内存 VS 管道

管道通信中:数据要拷贝两次。主要原因在于,管道本质上是文件,我们不能通过键盘直接往文件里面进行写入,要想让键盘输入的内容写入的文件中,首先需要在程序中定义一个字符数组(或者 string 对象),暂时存储键盘的输入,然后在将这个数组中的内容写入到文件,这就涉及一次拷贝(将数组中的内容,拷贝到文件缓冲区),其次另一端在进行读取的时候,所有的文件读取操作,都要求定义一段空间,将读取到的内容存储起来,这个过程又会涉及一次拷贝,总体算下来,完成一次管道通信,需要进行两次拷贝。

共享内存通信:程序中可以把共享内存当做自己的内存空间来使用,因此对于写端,可以直接从键盘读取数据存储到共享内存中,无需创建字符数组(或者 string 对象)来暂时存储输入的内容;对于读端,可以直接从共享内存中进行读取,然后打印,在没有特殊要求的情况可以不用将共享内存中的数据存储起来。

四、拓展内容

4.1 查看共享内存的属性

通过 shmctl 函数区获取共享内存的属性。struct shmid_ds 结构体就是用户层面去描述一个共享内存的结构体。

image-20240306151122027

int main()
{// 创建共享内存int shmid = CreatMem();struct shmid_ds shmds; // 用来存储共享内存的属性// 挂接共享内存char *shamem = (char*)shmat(shmid, nullptr, 0);// ipc-cod 通信代码while(true){cout << "client asy@ " << shamem << endl; // 直接访问共享内存// 打印共享内存的属性shmctl(shmid, IPC_STAT, &shmds);// cout << "__key: " << shmds.shm_perm.__key << endl;printf("0x%x\n", shmds.shm_perm.__key);cout << "shm_atime: " << shmds.shm_atime << endl;cout << "shm_cpid: " << shmds.shm_cpid << endl;cout << "shm_nattch: " << shmds.shm_nattch << endl;sleep(1);}// 去关联shmdt(shamem);// 释放共享内存int ret = shmctl(shmid, IPC_RMID, NULL);return 0;
}

4.2 借助管道实现共享内存的同步与互斥

processa:

#include "comm.hpp"
#include <unistd.h>int main()
{// 创建共享内存int shmid = CreatMem();Init init; // 创建有名管道// 打开管道int fd = open(FIFO_FILE, O_RDONLY);if (fd < 0){perror("open");exit(FIFO_OPEN_ERR);}struct shmid_ds shmds;// 挂接共享内存char *shamem = (char *)shmat(shmid, nullptr, 0);// ipc-cod 通信代码while (true){char ch;int n = read(fd, &ch, sizeof(ch));if (n == 0)break;else if (n > 0){cout << "client asy@ " << shamem; //<< endl; // 直接访问共享内存}else{log(Fatal, "read error: %s\n", strerror(errno));exit(FIFO_READ_ERR);}}// 去关联shmdt(shamem);// 释放共享内存int ret = shmctl(shmid, IPC_RMID, NULL);// 关闭管道close(fd);return 0;
}

processb:

#include "comm.hpp"
#include <unistd.h>int main()
{// 获取共享内存int shmid = GetMem();// 打开管道int fd = open(FIFO_FILE, O_WRONLY);if(fd < 0){perror("open");exit(FIFO_OPEN_ERR);}// 挂接char *shmaddr = (char*)shmat(shmid, nullptr, 0);// ipc-code 通信代码while(true){cout << "Please enter:";fgets(shmaddr, size, stdin);write(fd, "c", 1);}// 去关联shmdt(shmaddr);// 关闭管道close(fd);return 0;
}

在没有管道的情况下,processa 进程一直在不间断的读取共享内存中的数据,现在创建一个管道,在 processa 进程读取共享内存之前,想让它从管道中读取,只有读到了特定的信号,才能去共享内存中进行读取。processb 进程在向共享内存中写入数据之后,向管道中写入一个字符,以此为信号,通知 processa 进程,现在共享内存中有数据了,你可以去读取了。在 processb 进程没有向共享内存中写入数据的时候,此时管道为空,读端就会阻塞,也就是 processa 进程就会被阻塞住,以此来实现同步与互斥。

🎁结语:

        今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是我前进的动力!

这篇关于【Linux修行路】进程通信——共享内存的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1124002

相关文章

VScode连接远程Linux服务器环境配置图文教程

《VScode连接远程Linux服务器环境配置图文教程》:本文主要介绍如何安装和配置VSCode,包括安装步骤、环境配置(如汉化包、远程SSH连接)、语言包安装(如C/C++插件)等,文中给出了详... 目录一、安装vscode二、环境配置1.中文汉化包2.安装remote-ssh,用于远程连接2.1安装2

Linux中shell解析脚本的通配符、元字符、转义符说明

《Linux中shell解析脚本的通配符、元字符、转义符说明》:本文主要介绍shell通配符、元字符、转义符以及shell解析脚本的过程,通配符用于路径扩展,元字符用于多命令分割,转义符用于将特殊... 目录一、linux shell通配符(wildcard)二、shell元字符(特殊字符 Meta)三、s

Linux之软件包管理器yum详解

《Linux之软件包管理器yum详解》文章介绍了现代类Unix操作系统中软件包管理和包存储库的工作原理,以及如何使用包管理器如yum来安装、更新和卸载软件,文章还介绍了如何配置yum源,更新系统软件包... 目录软件包yumyum语法yum常用命令yum源配置文件介绍更新yum源查看已经安装软件的方法总结软

linux报错INFO:task xxxxxx:634 blocked for more than 120 seconds.三种解决方式

《linux报错INFO:taskxxxxxx:634blockedformorethan120seconds.三种解决方式》文章描述了一个Linux最小系统运行时出现的“hung_ta... 目录1.问题描述2.解决办法2.1 缩小文件系统缓存大小2.2 修改系统IO调度策略2.3 取消120秒时间限制3

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Linux:alias如何设置永久生效

《Linux:alias如何设置永久生效》在Linux中设置别名永久生效的步骤包括:在/root/.bashrc文件中配置别名,保存并退出,然后使用source命令(或点命令)使配置立即生效,这样,别... 目录linux:alias设置永久生效步骤保存退出后功能总结Linux:alias设置永久生效步骤

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

Linux Mint Xia 22.1重磅发布: 重要更新一览

《LinuxMintXia22.1重磅发布:重要更新一览》Beta版LinuxMint“Xia”22.1发布,新版本基于Ubuntu24.04,内核版本为Linux6.8,这... linux Mint 22.1「Xia」正式发布啦!这次更新带来了诸多优化和改进,进一步巩固了 Mint 在 Linux 桌面