共享内存


共享内存是进程间通信中最简单的方式之中的一个。

共享内存是系统出于多个进程之间通讯的考虑,而预留的的一块内存区。

共享内存同意两个或很多其他进程訪问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。

当一个进程改变了这块地址中的内容的时候,其他进程都会察觉到这个更改。

关于共享内存


当一个程序载入进内存后,它就被分成叫作页的块。

通信将存在内存的两个页之间或者两个独立的进程之间。

总之,当一个程序想和另外一个程序通信的时候。那内存将会为这两个程序生成一块公共的内存区域。这块被两个进程分享的内存区域叫做共享内存

由于全部进程共享同一块内存,共享内存在各种进程间通信方式中具有最高的效率。

訪问共享内存区域和訪问进程独有的内存区域一样快,并不须要通过系统调用或者其他须要切入内核的过程来完毕。同一时候它也避免了对数据的各种不必要的复制。

假设没有共享内存的概念。那一个进程不能存取另外一个进程的内存部分。因而导致共享数据或者通信失效。由于系统内核没有对訪问共享内存进行同步,您必须提供自己的同步措施。

解决这些问题的经常用法是通过使用信号量进行同步。

只是,我们的程序中仅仅有一个进程訪问了共享内存。因此在集中展示了共享内存机制的同一时候。我们避免了让代码被同步逻辑搞得混乱不堪。

为了简化共享数据的完整性和避免同一时候存取数据,内核提供了一种专门存取共享内存资源的机制。这称为相互排斥体或者mutex对象

比如。在数据被写入之前不同意进程从共享内存中读取信息、不同意两个进程同一时候向同一个共享内存地址写入数据等。

当一个进程想和另外一个进程通信的时候,它将按下面顺序运行:

  • 获取mutex对象,锁定共享区域。

  • 将要通信的数据写入共享区域。

  • 释放mutex对象。

当一个进程从从这个区域读数据时候,它将反复相同的步骤,仅仅是将第二步变成读取。

内存模型


要使用一块共享内存

  • 进程必须首先分配

  • 随后须要訪问这个共享内存块的每一个进程都必须将这个共享内存绑定到自己的地址空间中

  • 当完毕通信之后,全部进程都将脱离共享内存,而且由一个进程释放该共享内存块

/proc/sys/kernel/文件夹下,记录着共享内存的一些限制,如一个共享内存区的最大字节数shmmax。系统范围内最大共享内存区标识符数shmmni等,能够手工对其调整,但不推荐这样做。

理解 Linux 系统内存模型能够有助于解释这个绑定的过程。

linux系统内存模型


在 Linux 系统中。每一个进程的虚拟内存是被分为很多页面的。这些内存页面中包括了实际的数据。

每一个进程都会维护一个从内存地址到虚拟内存页面之间的映射关系。虽然每一个进程都有自己的内存地址,不同的进程能够同一时候将同一个内存页面映射到自己的地址空间中。从而达到共享内存的目的。

分配一个新的共享内存块会创建新的内存页面。由于全部进程都希望共享对同一块内存的訪问。仅仅应由一个进程创建一块新的共享内存。再次分配一块已经存在的内存块不会创建新的页面,而仅仅是会返回一个标识该内存块的标识符。

一个进程如需使用这个共享内存块,则首先须要将它绑定到自己的地址空间中。

这样会创建一个从进程本身虚拟地址到共享页面的映射关系。当对共享内存的使用结束之后,这个映射关系将被删除。

当再也没有进程须要使用这个共享内存块的时候,必须有一个(且仅仅能是一个)进程负责释放这个被共享的内存页面。

全部共享内存块的大小都必须是系统页面大小的整数倍。系统页面大小指的是系统中单个内存页面包括的字节数。在 Linux 系统中,内存页面大小是4KB。只是您仍然应该通过调用 getpagesize 获取这个值。

共享内存的实现分为两个步骤:

  • 创建共享内存,使用shmget函数。

  • 映射共享内存。将这段创建的共享内存映射到详细的进程空间去,使用shmat函数。

用于共享内存的函数


共享内存的使用。主要有下面几个API:ftok()shmget()shmat()shmdt()及shmctl()。

#include <sys/shm.h>
void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
int shmdt(const void *shm_addr);
int shmget(key_t key, size_t size, int shmflg);

与信号量相相似,通常须要在包括shm.h文件之前包括sys/types.h与sys/ipc.h这两个头文件。

用ftok()函数获得一个ID号


应用说明,在IPC中,我们经经常使用用key_t的值来创建或者打开信号量,共享内存和消息队列。

key_t ftok(const char *pathname, int proj_id);
參数 描写叙述
pathname 一定要在系统中存在而且进程能够訪问的
proj_id 一个1-255之间的一个整数值,典型的值是一个ASCII值。

当成功运行的时候,一个key_t值将会被返回。否则-1被返回。我们能够使用strerror(errno)来确定详细的错误信息。

考虑到应用系统可能在不同的主机上应用,能够直接定义一个key,而不用ftok获得:

#define IPCKEY 0x344378

创建共享内存


进程通过调用shmget(Shared Memory GET,获取共享内存)来分配一个共享内存块。

int shmget(key_t key ,int size,int shmflg)
參数 描写叙述
key 一个用来标识共享内存块的键值
size 指定了所申请的内存块的大小
shmflg 操作共享内存的标识

返回值:假设成功,返回共享内存表示符,假设失败,返回-1。

  • 该函数的第二个參数key是一个用来标识共享内存块的键值。

彼此无关的进程能够通过指定同一个键以获取对同一个共享内存块的訪问。

不幸的是,其他程序也可能挑选了相同的特定值作为自己分配共享内存的键值。从而产生冲突。

用特殊常量IPC_PRIVATE作为键值能够保证系统建立一个全新的共享内存块。|

key标识共享内存的键值:0/IPC_PRIVATE。当key的取值为IPC_PRIVATE,则函数shmget将创建一块新的共享内存;假设key的取值为0。而參数中又设置了IPC_PRIVATE这个标志,则相同会创建一块新的共享内存。

  • 该函数的第二个參数size指定了所申请的内存块的大小。

由于这些内存块是以页面为单位进行分配的。实际分配的内存块大小将被扩大到页面大小的整数倍。

  • 第三个參数shmflg是一组标志。通过特定常量的按位或操作来shmget。这些特定常量包括:

IPC_CREAT:这个标志表示应创建一个新的共享内存块。通过指定这个标志,我们能够创建一个具有指定键值的新共享内存块。

IPC_EXCL:这个标志仅仅能与 IPC_CREAT 同一时候使用。当指定这个标志的时候。假设已有一个具有这个键值的共享内存块存在。则shmget会调用失败。

也就是说,这个标志将使线程获得一个“独有”的共享内存块。假设没有指定这个标志而系统中存在一个具有相同键值的共享内存块。shmget会返回这个已经建立的共享内存块。而不是又一次创建一个。

模式标志:这个值由9个位组成,分别表示属主、属组和其他用户对该内存块的訪问权限。

当中表示运行权限的位将被忽略。

指明訪问权限的一个简单办法是利用

映射共享内存


shmat()是用来同意本进程訪问一块共享内存的函数。将这个内存区映射到本进程的虚拟地址空间。

int shmat(int shmid,char *shmaddr,int flag)
參数 描写叙述
shmid 那块共享内存的ID。是shmget函数返回的共享存储标识符
shmaddr 是共享内存的起始地址,假设shmaddr为0,内核会把共享内存映像到调用进程的地址空间中选定位置。假设shmaddr不为0,内核会把共享内存映像到shmaddr指定的位置。所以一般把shmaddr设为0。
shmflag 是本进程对该内存的操作模式。假设是SHM_RDONLY的话,就是仅仅读模式。

其他的是读写模式

成功时,这个函数返回共享内存的起始地址。失败时返回-1。

要让一个进程获取对一块共享内存的訪问。这个进程必须先调用 shmat(SHared Memory Attach,绑定到共享内存)。

将 shmget 返回的共享内存标识符 SHMID 传递给这个函数作为第一个參数。

该函数的第二个參数是一个指针。指向您希望用于映射该共享内存块的进程内存地址;假设您指定NULL则Linux会自己主动选择一个合适的地址用于映射。

第三个參数是一个标志位,包括了下面选项:

SHM_RND表示第二个參数指定的地址应被向下靠拢到内存页面大小的整数倍。假设您不指定这个标志,您将不得不在调用shmat的时候手工将共享内存块的大小按页面大小对齐。
SHM_RDONLY表示这个内存块将仅同意读取操作而禁止写入。 假设这个函数调用成功则会返回绑定的共享内存块相应的地址。通过 fork 函数创建的子进程同一时候继承这些共享内存块;

假设须要,它们能够主动脱离这些共享内存块。 当一个进程不再使用一个共享内存块的时候

共享内存解除映射


当一个进程不再须要共享内存时,须要把它从进程地址空间中多里。

int shmdt(char *shmaddr)
參数 描写叙述
shmaddr 那块共享内存的起始地址

成功时返回0。失败时返回-1。

应通过调用 shmdt(Shared Memory Detach。脱离共享内存块)函数与该共享内存块脱离。

将由 shmat 函数返回的地址传递给这个函数。假设当释放这个内存块的进程是最后一个使用该内存块的进程,则这个内存块将被删除。

对 exit 或不论什么exec族函数的调用都会自己主动使进程脱离共享内存块。

控制释放


shmctl控制对这块共享内存的使用

函数原型

int  shmctl( int shmid , int cmd , struct shmid_ds *buf );
參数 描写叙述
shmid 是共享内存的ID。
cmd 控制命令
buf 一个结构体指针。

IPC_STAT的时候,取得的状态放在这个结构体中。假设要改变共享内存的状态,用这个结构体指定。

当中cmd的取值例如以下

cmd 描写叙述
IPC_STAT 得到共享内存的状态
IPC_SET 改变共享内存的状态
IPC_RMID 删除共享内存

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

调用 shmctl(”Shared Memory Control”,控制共享内存)函数会返回一个共享内存块的相关信息。同一时候 shmctl 同意程序改动这些信息。

该函数的第一个參数是一个共享内存块标识。
要获取一个共享内存块的相关信息,则为该函数传递 IPC_STAT 作为第二个參数,同一时候传递一个指向一个 struct shmid_ds 对象的指针作为第三个參数。

要删除一个共享内存块,则应将 IPC_RMID 作为第二个參数。而将 NULL 作为第三个參数。

当最后一个绑定该共享内存块的进程与其脱离时,该共享内存块将被删除。

您应当在结束使用每一个共享内存块的时候都使用 shmctl 进行释放,以防止超过系统所同意的共享内存块的总数限制。

调用 exit 和 exec 会使进程脱离共享内存块,但不会删除这个内存块。

要查看其他有关共享内存块的操作的描写叙述,请參考shmctl函数的手冊页。

演示样例


简单映射一块共享内存

#include <stdio.h>
#include <stdlib.h>#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>#define IPCKEY 0x366378typedef struct st_setting
{char agen[10];unsigned char file_no;
}st_setting;int main(int argc, char** argv)
{int         shm_id;//key_t       key;st_setting  *p_setting;//  首先检查共享内存是否存在,存在则先删除shm_id = shmget(IPCKEY , 1028, 0640);if(shm_id != -1){p_setting = (st_setting *)shmat(shm_id, NULL, 0);if (p_setting != (void *)-1){shmdt(p_setting);shmctl(shm_id,IPC_RMID,0) ;}}//  创建共享内存shm_id = shmget(IPCKEY, 1028, 0640 | IPC_CREAT | IPC_EXCL);if(shm_id == -1){printf("shmget error\n");return -1;}//  将这块共享内存区附加到自己的内存段p_setting = (st_setting *)shmat(shm_id, NULL, 0);strncpy(p_setting->agen, "gatieme", 10);printf("agen : %s\n", p_setting->agen);p_setting->file_no = 1;printf("file_no : %d\n",p_setting->file_no);system("ipcs -m");//  此时可看到有进程关联到共享内存的信息,nattch为1//  将这块共享内存区从自己的内存段删除出去if(shmdt(p_setting) == -1)perror(" detach error ");system("ipcs -m");//  此时可看到有进程关联到共享内存的信息。nattch为0//  删除共享内存if (shmctl( shm_id , IPC_RMID , NULL ) == -1){perror(" delete error ");}system("ipcs -m");//  此时可看到有进程关联到共享内存的信息,nattch为0return EXIT_SUCCESS;
}

ipcrm命令删除共享内存

在使用共享内存。结束程序退出后。假设你没在程序中用shmctl()删除共享内存的话。一定要在命令行下用ipcrm命令删除这块共享内存。

你要是无论的话,它就一直在那儿放着了。

简单解释一下ipcs命令和ipcrm命令。

取得ipc信息:

usage : ipcs -asmq -tclup ipcs [-s -m -q] -i idipcs -h for help.
m      输出有关共享内存(shared memory)的信息
-q      输出有关信息队列(message queue)的信息
-s      输出有关“遮断器”(semaphore)的信息

删除ipc

usage: ipcrm [ [-q msqid] [-m shmid] [-s semid][-Q msgkey] [-M shmkey] [-S semkey] ... ]

两端通信的程序


读者程序


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define N 64typedef struct
{pid_t pid;char buf[N];
} SHM;void handler(int signo)
{//printf("get signal\n");return;
}int main()
{key_t key;int shmid;SHM *p;pid_t pid;if ((key = ftok(".", 'm')) < 0){perror("fail to ftok");exit(-1);}signal(SIGUSR1, handler);//注冊一个信号处理函数if ((shmid = shmget(key, sizeof(SHM), 0666|IPC_CREAT|IPC_EXCL)) < 0){if (EEXIST == errno)//存在则直接打开{shmid = shmget(key, sizeof(SHM), 0666);p = (SHM *)shmat(shmid, NULL, 0);pid = p->pid;p->pid = getpid();//把自己的pid写到共享内存kill(pid, SIGUSR1);}else//出错{perror("fail to shmget");exit(-1);}}else//成功{p = (SHM *)shmat(shmid, NULL, 0);p->pid = getpid();pause();pid = p->pid;//得到写端进程的pid}while ( 1 ){pause();//堵塞,等待信号if (strcmp(p->buf, "quit\n") == 0) exit(0);//输入"quit结束"printf("read from shm : %s", p->buf);kill(pid, SIGUSR1);//向写进程发SIGUSR1信号}return 0;}

写者程序


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define N 64typedef struct
{pid_t pid;char buf[N];
} SHM;void handler(int signo)
{//printf("get signal\n");return;
}int main()
{key_t key;int shmid;SHM *p;pid_t pid;if ((key = ftok(".", 'm')) < 0){perror("fail to ftok");exit(-1);}signal(SIGUSR1, handler);               //  注冊一个信号处理函数if ((shmid = shmget(key, sizeof(SHM), 0666 | IPC_CREAT | IPC_EXCL)) < 0){if (EEXIST == errno)                //  存在则直接打开{shmid = shmget(key, sizeof(SHM), 0666);p = (SHM *)shmat(shmid, NULL, 0);pid = p->pid;p->pid = getpid();kill(pid, SIGUSR1);}else//出错{perror("fail to shmget");exit(-1);}}else//成功{p = (SHM *)shmat(shmid, NULL, 0);p->pid = getpid();                  //  把自己的pid写到共享内存pause();pid = p->pid;                       //  得到读端进程的pid}while ( 1 ){printf("write to shm : ");fgets(p->buf, N, stdin);            //  接收输入kill(pid, SIGUSR1);                 //  向读进程发SIGUSR1信号if (strcmp(p->buf, "quit\n") == 0) break;pause();                            //  堵塞,等待信号}shmdt(p);shmctl(shmid, IPC_RMID, NULL);          //  删除共享内存return 0;
}

进程间通信之-共享内存Shared Memory--linux内核剖析(十一)相关推荐

  1. linux shared,从 0 开始学习 Linux 系列之「22.共享内存 Shared Memory」

    共享内存 版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处! 共享内存 Shared Memory 这次我们来学习在 Linux 中最快的一种 IPC 方式:共 ...

  2. Linux进程间通信的几种方式总结-——linux内核剖析

    进程间通信概述 Linux内核通信相关视频讲解:Linux内核,进程间通信组件的实现 linux内核,进程调度器的实现,内核源码分析 进程通信的目的 传输数据 一个进程须要将它的数据发送给还有一个进程 ...

  3. Linux——详解共享内存shared memory

    目录 一.共享内存介绍 (一).什么是共享内存 (二).共享内存优点 (三).共享内存缺点 二.共享内存使用 (一).创建-shmget ①key ②size ③shmflg ④返回值 (二).连接- ...

  4. c++ fork 进程时 共享内存_c/c++ Linux 进程间通信------共享内存

    1. 什么是共享内存 共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区.进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是 ...

  5. linux查看共享内存max,浅析Linux的共享内存与tmpfs文件系统

    浅析Linux的共享内存与tmpfs文件系统 前言 共享内存主要用于进程间通信,Linux有两种共享内存(Shared Memory)机制: (1)** System V shared memory( ...

  6. [转]Linux 进程间通信:共享内存

    (上) 级别: 初级 郑彦兴 (mlinux@163.com), 国防科大攻读博士学位 2003 年 5 月 01 日 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B ...

  7. Linux进程间通信——使用共享内存

    下面将讲解进程间通信的另一种方式,使用共享内存. 一.什么是共享内存 顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式 ...

  8. linux共享内存 dest,关于linux 共享内存查看已经完整释放

    完整删除共享内存脚本 #!/bin/sh function rmshm() { zero_status=`ipcs -m|awk '{print $6}'|grep -w 0|wc -l` if [ ...

  9. 《Linux内核剖析》(Yanlz+VR云游戏+Unity+SteamVR+云技术+5G+AI+Makefile+块设备驱动+字符设备驱动+数学协处理器+文件系统+内存管理+GDB+立钻哥哥+==)

    <Linux内核剖析> <Linux内核剖析> 版本 作者 参与者 完成日期 备注 YanlzLinux_Kernel0.12_V01_1.0 严立钻 2020.02.06 # ...

最新文章

  1. JAVA中报错AbandonedObjectPool is used (org.apache.tomcat.dbcp.dbcp.AbandonedObjectPool@f70ee1)
  2. 《挖财编程题》水花仙数
  3. js面向对象-组合使用构造函数模式和原型模式(使用最广泛、认同度最高)
  4. 1.多线程和单线程简单比较
  5. 程序员入门:三本必看的书
  6. 单片机c语言中flag用法,单片机中定义flag有啥作用flag=1和flag=0都是什么意思
  7. CAN 报文编码学习笔记二:汽车CAN协议测试——发送与接收
  8. 【目标检测】“复制-粘贴”数据增强实现
  9. 我在名牌大学毕业后的经历 (看完感动,涌动,后泪流)转
  10. 配置localhost
  11. 微信公众号群发功能的页面元素加载不全的解决办法
  12. 总结一下用面向对象写法调用借口
  13. 三文鱼肉质和虹鳟鱼肉质有什么区别差异
  14. phpstudy宝塔_宝塔和phpstudy的区别
  15. 2022CTF培训(十一)IOT 相关 CVE 漏洞分析
  16. 量子纠缠的机制是什么?一定要理解整体性概念
  17. 绘图工具——绘制地图
  18. The very initial purpose of OO
  19. 人工智能与机器学习——人脸表情识别
  20. 【windows自动备份】

热门文章

  1. php 匹配正则,php正则匹配类
  2. 苹果原壁纸高清_周易壁纸 | 八卦图阵高清壁纸
  3. linux系统sql语句报错_在linux下写的mysql无法插入,sql语句在复制中变'脏'了.
  4. 未找到要求的 from 关键字_性能优化|这恐怕是解释Explain关键字最全的一篇文章
  5. android属性动画作用范围,Android开发之动画效果浅析(一)
  6. 深度学习训练的时候gpu占用0_26秒单GPU训练CIFAR10,Jeff Dean也点赞的深度学习优化技巧...
  7. 怎么重置blockinput的锁_AppleID被锁如何解决 AppleID被锁激活方法介绍【图文】
  8. vuecli启动的服务器位置,在vue cli 3生成的项目中启动dev服务器
  9. composer php中如何执行,php中composer如何实现类的自动加载(示例讲解)
  10. cass高程点内插插件_聊聊CASS土方计算那些事-DTM法