共享内存的概念

共享内存是指多个进程可以把一段内存共同的内存映射到自己的进程空间中,从而实现数据的共享和传输,它是存在与内核级别的一种资源,是所有进程间通信中方式最快的一种。

在shell环境下可以使用ipcs查看当前系统IPC中的状态,例如当前的电脑中:

$ ipcs------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 2260992    deeplearni 600        524288     2          dest
0x00000000 2490369    deeplearni 600        67108864   2          dest
0x00000000 163842     root       777        7680       2
0x00000000 196611     root       777        8294400    2
0x00000000 229380     root       777        4096       2
0x00000000 262149     root       777        8192       2
0x00000000 294918     root       777        12288      2
...省略
0x00000000 2064444    root       777        233472     2
0x00000000 2097213    root       777        237568     2
0x00000000 2129982    root       777        241664     2
0x00000000 2162751    root       777        245760     2
0x00000000 2654272    deeplearni 600        524288     2          dest
0x00000000 2687041    deeplearni 600        524288     2          dest
0x00000000 2719810    deeplearni 600        524288     2          dest
0x00000000 2752579    deeplearni 600        524288     2          dest
0x00000000 2981956    deeplearni 600        524288     2          dest
0x00000000 2949189    deeplearni 600        524288     2          dest
0x00000000 3014726    deeplearni 600        67108864   2          dest------ Semaphore Arrays --------
key        semid      owner      perms      nsems

该命令使用附加参数可单独查看某一IPC:-m(共享内存),-q(消息队列),-s(信号量)。

由于多个进程对同一块内存区域具有访问权限,各进程间的同步问题需要解决,可以配合信号量进行控制。

对于每一个共享内存段,内核会为其维护一个shmid_ds类型的结构体:

// 摘自所用ubuntu18.04电脑中的/usr/include/i386-linux-gnu/bits/shm.h
struct shmid_ds{struct ipc_perm shm_perm;           /* operation permission struct */size_t shm_segsz;                   /* size of segment in bytes */__time_t shm_atime;                 /* time of last shmat() */
#ifndef __x86_64__unsigned long int __glibc_reserved1;
#endif__time_t shm_dtime;                 /* time of last shmdt() */
#ifndef __x86_64__unsigned long int __glibc_reserved2;
#endif__time_t shm_ctime;                 /* time of last change by shmctl() */
#ifndef __x86_64__unsigned long int __glibc_reserved3;
#endif__pid_t shm_cpid;                   /* pid of creator */__pid_t shm_lpid;                   /* pid of last shmop */shmatt_t shm_nattch;                /* number of current attaches */__syscall_ulong_t __glibc_reserved4;__syscall_ulong_t __glibc_reserved5;};

共享内存的相关操作

创建/打开共享内存

创建共享内存需要用到**shmget()**函数,原型如下:

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

创建成功返回共享内存的ID,出错返回-1。

参数key为共享内存的键值,参数size为创建共享内存的大小,参数flag为调用函数的操作类型。参数key和参数flag共同决定的shmget()的作用

  • key为IPC_PRIVATE时,创建一个新的共享内存,flag取值无效。
  • key不为IPC_PRIVATE,且flag设置了IPC_CREAT位,而没有设置IPC_EXCL位时,如果key为内核中的已存在的共享内存键值,则打开,否则创建一个新的共享内存。
  • key不为IPC_PRIVATE,且flag设置了IPC_CREATIPC_EXCL位时,则只执行创建共享内存操作。如果key为内核中的已存在的共享内存键值,返回EEXIST错误。

共享内存的附加(映射)

创建一个共享内存后,某个进程若想使用,需要将此内存区域附加(attach)到自己的进程空间(或称地址映射),需要用到shmat()函数

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

运行成功返回指向共享内存段的地址指针,出错返回-1。

参数shmid为共享内存的ID,参数addr和参数flag共同说明要引入的地址值,通常只有2种用法

  • addr为0,表明让内核来决定第1个可引用的位置
  • addr非0,且flag中指定SHM_RND,则此段引入到addr所指向的位置。

shmat()函数执行成功后,会将shmid的共享内存段的shmid_ds结构的shm_nattch计数器值加1。

共享内存的分离

当进程使用完共享内存后,需要将共享内存从其进程空间中去除(detach),使用shmdt()函数

#include <sys/types,h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void *addr);

运行成功返回0,出错返回-1。

参数addr是调用shmat()函数的返回值,即共享内存段的地址指针。shmdt()函数执行成功后,shm_nattch计数器值减1。

共享内存的控制

使用**shmctl()**可以对共享内存段进行多种控制操作,函数原型:

#include <sys/types,h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_s *buf);

运行成功返回0,出错返回-1。

参数shmid为共享内存的ID,参数cmd指明了所要进行的操作,与参数*buf配合使用:

cmd取值 含义
IPC_STAT 取shmid指向的共享内存的shmid_ds结构,对参数buf指向的结构赋值
IPC_SET 使用buf指向的结构对sh_mid段的相关结构赋值
IPC_RMID shm_nattch为0时,删除shmid所指向的共享内存段
IPC_LOCK 锁定共享内存段在内存(只能由超级用户执行)
IPC_UNLOCK 对共享内存段解锁(只能由超级用户执行)

编程示例

基本步骤:

  • 生成key,ftok()
  • 使用key创建/获得一个共享内存,shmget()
  • 映射共享内存,得到虚拟地址,shmat()
  • 使用共享内存,通过地址指针
  • 移除映射,shmdt()
  • 销毁共享内存,shmctl()

示例1

进程1创建共享内存并写入数据,shm1.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>int main()
{// generate keykey_t key = ftok("./", 200);printf("key=%#x\n", key);// create a share memoryint shmid = shmget(key, 8, IPC_CREAT|0666|IPC_EXCL);if(shmid == -1){perror("shmget failed\n");exit(1);}printf("shmid=%#x\n", shmid);// map share memory to get the virtual addressvoid *p = shmat(shmid, 0, 0);if((void *)-1 == p){perror("shmat failed");exit(2);}// write data to share memoryint *pi = p;*pi = 0xaaaaaaaa;*(pi+1) = 0x55555555;// remove the mapif(shmdt(p) == -1){perror("shmdt failed");exit(3);}// delete the share memoryprintf("use Enter to destroy the share memory\n");getchar();if(shmctl(shmid, IPC_RMID, NULL) == -1){perror("shmctl");exit(4);}return 0;
}

进程2读取共享内存中的内容,shm2.c;

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>int main()
{// generate keykey_t key = ftok("./", 200);printf("key=%#x\n", key);// get the share memoryint shmid = shmget(key, 0, 0);if(shmid == -1){perror("shmget failed\n");exit(1);}printf("shmid=%#x\n", shmid);// map share memory to get the virtual addressvoid *p = shmat(shmid, 0, 0);if((void *)-1 == p){perror("shmat failed");exit(2);}// read: get data from the share memoryint x = *((int *)p);int y = *((int *)p+1);printf("x=%#x y=%#x\n", x, y);// remove the mapif(shmdt(p) == -1){perror("shmdt failed");exit(3);}return 0;
}

编译运行测试,在一个shell中先运行shm1程序:

$ ./shm1
key=0xc81102ed
shmid=0x2e8047
use Enter to destroy the share memory

在另一个shell中先运行shm2程序:

$ ./shm2
key=0xc81102ed
shmid=0x2e8047
x=0xaaaaaaaa y=0x55555555

可以看到通信成功,在第一个shell中按下“Enter”可以销毁共享内存。

示例2

示例1使用ftok()函数生成的key创建共享内存,本示例使用IPC_PRIVATE参数创建共享内存。

创建一个共享内存,并输出其ID号,create_shm.c:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#define BUFSZ 4096int main(void)
{int shm_id;shm_id = shmget(IPC_PRIVATE, BUFSZ, 0666);if(shm_id < 0){printf("shmget failed!\n");exit(1);}printf("create a shared memory segment successfully: %d\n", shm_id);system("ipcs -m");exit(0);
}

打开指定ID的共享内存,写入内容,write_shm.c:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>typedef struct
{char name[4];int age;
}people;int main(int argc, char **argv)
{int shm_id, i;char temp;people *p_map;if(argc != 2){printf("USAGE:atshm <identifier>\n");exit(1);}// get share memory ID from inputshm_id = atoi(argv[1]);// str to int// map the share memory to get the virtul addressp_map = (people *)shmat(shm_id, NULL, 0);// writetemp = 'a';for(i=0; i<10; i++){temp+=1;memcpy((*(p_map+i)).name, &temp, 1);(*(p_map+i)).age=20+i;}// remove mapif(shmdt(p_map)==-1)perror("detach error!\n");return 0;
}

打开指定ID的共享内存,读取内容,read_shm.c:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>typedef struct
{char name[4];int age;
}people;int main(int argc, char** argv)
{int shm_id, i;people *p_map;if(argc != 2){printf("USAGC: atshm <identifier>\n");exit(1);}// get the share memory ID from inputshm_id = atoi(argv[1]);// str to int// map the share memory to get the virtual addressp_map = (people*)shmat(shm_id, NULL, 0);// read datafor(i=0; i<10; i++){printf("name:%s   ", (*(p_map+i)).name);printf("age %d\n", (*(p_map+i)).age);}// remove the mapif(shmdt(p_map)==-1)perror("detach error!\n");return 0;
}

编译上述3个程序,首先运行create_shm程序创建一个共享内存:

$ ./create_shm
create a shared memory segment successfully: 3080264------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 2260992    deeplearni 600        524288     2          dest
0x00000000 2490369    deeplearni 600        67108864   2          dest
0x00000000 163842     root       777        7680       2
0x00000000 196611     root       777        8294400    2
0x00000000 229380     root       777        4096       2
...省略
0x00000000 2031675    root       777        229376     2
0x00000000 2064444    root       777        233472     2
0x00000000 2097213    root       777        237568     2
0x00000000 2129982    root       777        241664     2
0x00000000 2162751    root       777        245760     2
0x00000000 2654272    deeplearni 600        524288     2          dest
0x00000000 2687041    deeplearni 600        524288     2          dest
0x00000000 2719810    deeplearni 600        524288     2          dest
0x00000000 2752579    deeplearni 600        524288     2          dest
0x00000000 2981956    deeplearni 600        524288     2          dest
0x00000000 2949189    deeplearni 600        524288     2          dest
0x00000000 3014726    deeplearni 600        67108864   2          dest
0xc81102ed 3047495    deeplearni 666        8          0
0x00000000 3080264    deeplearni 666        4096       0

可以看到程序创建了一个shmid为3080264的共享内存,然后可以运行write_shm程序,需要将ID号作为参数:

$ ./write_shm 3080264

虽然没有输出,但程序已将内容写入共享内存,并且共享内存没有被删除,可以运行read_shm程序读取共享内存中的内容:

$ ./read_shm 3080264
name:b   age 20
name:c   age 21
name:d   age 22
name:e   age 23
name:f   age 24
name:g   age 25
name:h   age 26
name:i   age 27
name:j   age 28
name:k   age 29

可以看到,程序读取到了共享内存中存储的10个字符。

另外,无用的共享内存也可以通过命令的方式收到删除,使用ipcrm -m 共享内存id的形式,如:

$ ipcrm -m 3080264

此时再用ipcs -m命令查看,已经没有shmid为3080264的共享内存。

参考:

  1. 《精通Linux C编程》- 程国钢
  2. 《Linux C编程完全解密》- 闫敬 吴淑坤

Linux进程间通信详解(四) —— 共享内存及函数相关推荐

  1. Linux进程间通信详解(三) —— 消息队列及函数

    消息队列的概念 消息队列就是一个消息的链表,每个消息队列都有一个队列头,用结构struct msg_queue来描述.队列头中包含了该队列的大量信息,包括消息队列的键值.用户ID.组ID.消息数目.读 ...

  2. Linux信号详解:signal与sigaction函数【2】

    我们已经讨论了Linux操作系统中"信号"."中断"."僵尸进程"'等重要概念.结合C语言中的setjmp.longjump.sigsetj ...

  3. Linux进程间通信一 System V 共享内存简介与示例

    目录 1. System V共享内存简介 2. API介绍 2.0 key_t和标识符 2.1  创建system v共享内存 2.2 映射共享内存并使用 2.3 取消共享内存映射 2.4 控制共享内 ...

  4. Linux 进程间通信:管道、共享内存、消息队列、信号量

    进程间通信 管道 共享内存 消息队列 信号量 进程间通信 https://blog.csdn.net/qq_35423154/article/details/105294963 在之前的一篇博客中讲过 ...

  5. Linux进程间通信详解

    之前我总结了有关进程及进程控制的相关知识,不是很了解的朋友可以看一看: 进程:https://blog.csdn.net/Sun_Life_/article/details/88580785 进程控制 ...

  6. linux进程间通信:system V 共享内存

    文章目录 思维导图如下 通信原理 优势 运行流程 编程接口 编程实例 思维导图如下 通信原理 多个进程共享物理内存的同一块区域(通常称之为"段":segment) 抛弃了内核态消息 ...

  7. exit函数_Linux进程间通信详解(三) 消息队列及函数

    消息队列的概念 消息队列就是一个消息的链表,每个消息队列都有一个队列头,用结构struct msg_queue来描述.队列头中包含了该队列的大量信息,包括消息队列的键值.用户ID.组ID.消息数目.读 ...

  8. linux 进程间通信 dbus-glib【实例】详解四(上) C库 dbus-glib 使用(附代码)(编写接口描述文件.xml,dbus-binding-tool工具生成绑定文件)(列集散集函数)

    linux 进程间通信 dbus-glib[实例]详解一(附代码)(d-feet工具使用) linux 进程间通信 dbus-glib[实例]详解二(上) 消息和消息总线(附代码) linux 进程间 ...

  9. Linux环境进程间通信(五): 共享内存(上)

    Linux环境进程间通信(五): 共享内存(上) 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B共享内存的意思是,同一块物理内存被映射到进程A.B各自的进程地址空间. ...

最新文章

  1. 【机器视觉案例】(9) AI视觉,手势控制电脑键盘,附python完整代码
  2. 4月份阿里云ECS和VPC升级公告
  3. 快速创建 shell脚本
  4. Android使用ConstraintLayout 加载RecyclerView数据显示不全
  5. android平台使用java动态生成公私钥,并导出证书文件
  6. 【Azure Services Platform Step by Step-第5篇】.NET Services 概述
  7. java 多线程基础(一)
  8. Mac怎么看剩余空间,Mac怎么看硬盘空间
  9. 续:FPGA设计基本原则及设计思想
  10. 程序猿的每日单词(一)
  11. RMAN备份恢复Oracle_wuli大世界_新浪博客
  12. Google地图 Google Places API中附近搜索,文本搜索,地址搜索,地点详情的简单介绍
  13. 区块链单笔交易字段解释
  14. 爱了,阿里P9开源分享内部Java核心开发手册覆盖P5到P8
  15. 请善待,那些舍得借钱给你们的人!
  16. PostgreSQL获取年月日,获取年份
  17. Maven deploy项目到私服报错
  18. echarts盒须图颜色填充
  19. css实现圆形进度条加载动画
  20. 基于51单片机8位竞赛抢答器_倒计时可调+LED跑马灯

热门文章

  1. Vue3 + Element-Plus upload组件封装限制上传数量,再次上传则覆盖
  2. Cisco Packet Tracer 7.2登录问题
  3. 如何删除pdf文档中的某一页?
  4. Python:初步完成IE浏览器的查询、点击和文件下载
  5. 迈出建站第一步,23元注册一个属于自己的com域名!
  6. SARscape 处理ALOS数据
  7. MultipartFile API
  8. 电磁波实验之理想介质中的均匀平面波
  9. 可视化看板,新一代汽车研发项目管理必备!
  10. c语言系统api,C语言接口与实现方法实例详解