实现进程间通信最简单也是最直接的方法就是共享内存——为参与通信的多个进程在内存中开辟一个共享区。由于进程可以直接对共享内存进行读写操作,因此这种通信方式效率特别高,但其弱点是,它没有互斥机制,需要信号量之类的手段来配合。

共享内存原理与shm系统

共享内存,顾名思义,就是两个或多个进程都可以访问的同一块内存空间,一个进程对这块空间内容的修改可为其他参与通信的进程所看到的。

显然,为了达到这个目的,就需要做两件事:一件是在内存划出一块区域来作为共享区;另一件是把这个区域映射到参与通信的各个进程空间。

通常在内存划出一个区域的方法是,在内存中打开一个文件,若通过系统调用mmap()把这个文件所占用的内存空间映射到参与通信的各个进程地址空间,则这些进程就都可以看到这个共享区域,进而实现进程间的通信。

为了方便,再把mmap()的原理简述如下:

mmap()原型如下:

void * mmap(void *start, size_t len, int prot, int flags, int fd, off_t offset);

其中,参数fd用来指定被映射的文件;offset指定映射的起始位置偏移量(通常为0);len指定文件被映射部分的长度;start用来指定映射到虚地址空间的起始位置(通常为NULL,即由系统确定)。

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。

mmap()映射过程示意图如下所示:

那么mmap是怎么形成这个文件映射过程呢?

mmap本身其实是一个很简单的操作,在进程页表中添加一个页表项,该页表项是物理内存的地址。调用mmap的时候,内核会在该进程的地址空间的映射区域查找一块满足需求的空间用于映射该文件,然后生成该虚拟地址的页表项,改页表项此时的有效位(标志是否已经在物理内存中)为0,页表项的内容是文件的磁盘地址,此时mmap的任务已经完成。

简而言之,就是在进程对应的虚存段添加一个段,也就是创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。在创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,引发缺页异常,内核进行请页。

IPC的共享内存通信方式与上面的mmap()方式极为相似,但因为建立一个文件的目的仅是为了通信,于是这种文件没有永久保存的意义,因此IPC并没有使用正规的文件系统,而是在系统初始化时在磁盘交换区建立了一个专门用来实现共享内存的特殊临时文件系统shm,当系统断电后,其中的文件会全部自行销毁。

Linux共享内存结构

Linux的一个共享内存区由多个共享段组成。用来描述共享内存段的内核数据结构shmid_kernel如下:

struct shmid_kernel /* private to the kernel */
{   struct kern_ipc_perm    shm_perm;        //描述进程间通信许可的结构struct file *        shm_file;            //指向共享内存文件的指针unsigned long     shm_nattch;            //挂接到本段共享内存的进程数unsigned long     shm_segsz;            //段大小time_t           shm_atim;            //最后挂接时间time_t         shm_dtim;            //最后解除挂接时间time_t           shm_ctim;            //最后变化时间pid_t          shm_cprid;            //创建进程的PIDpid_t           shm_lprid;            //最后使用进程的PIDstruct user_struct    *mlock_user;
};

shmid_kernel中最重要的域是指针shm_file,它指向临时文件file对象。当进程需要使用这个文件进行通信时,由内核负责将其映射到用户地址空间。

为了便于管理,内核把共享内存区的所有描述结构shmid_kernel都存放在结构ipc_id_ary中的一个数组中。结构ipc_id_ary的定义如下:

struct ipc_id_ary
{int size;struct kern_ipc_perm *p[0];            //存放段描述结构的数组
};

同样,为了描述一个共享内存区的概貌,内核使用了数据结构ipc_ids。该结构的定义如下:

struct ipc_ids {int in_use;unsigned short seq;unsigned short seq_max;struct rw_semaphore rw_mutex;struct idr ipcs_idr;struct ipc_id_ary *entries;        //指向struct ipc_id_ary的指针
};

由多个共享段组成的共享区的结构如下所示:

共享内存的使用

头文件:

#include <sys/shm.h>

共享内存的打开或创建

进程可以通过调用函数shmget()来打开或创建一个共享内存区。函数shmget()内部由系统调用sys_shmget来实现。函数shmget()的原型如下:

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

其中,参数key为用户给定的键值。

所谓的键值,是在IPC的通信模式下每个IPC对象的名字。进程通过键值识别所有的对象。如果不使用键,进程将无法获取IPC对象,因此IPC对象并不存在于进程本身所使用的的内存中。

因此任何进程都无法为一块共享内存定义一个键值。因此,在调用函数shmget()时,需要key设为IPC_PRIVATE,这样,操作系统将忽略键,建立一个新的共享内存,指定一个键值并返回这块共享内存的IPC标识符ID,然后再设法将这个新的共享内存的标识符ID告诉其他需要使用这个共享内存区的进程。

函数中的参数size为所申请的共享存储段的长度(以页为单位)。

函数中的参数flag为标志,常用的有效标志有IPC_CREAT和IPC_EXCL,它们的功能与文件打开函数open()的O_CREAT和O_EXCL相当。如果用户希望所创建的共享内存区可读,则需要使用标志S_IRUSR;若可读,则需要使用标志S_IWUSR。

函数shmget()调用成功后,返回共享内存区的ID,否则返回-1。

Linux用shmid_ds数据结构表示每个新建的共享内存。当shmget()创建一块新的共享内存后,返回一个可以引用该共享内存的shmid_ds数据结构的标识符。定义在include/linux/shm.h文件中的shmid_ds如下:

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

例如:调用函数shmget()为当前进程创建一个共享内存区。

代码如下:

int main(void)
{int shmid;if((shmid = shmget(IPC_PRIVATE, 10, IPC_CREAT)) < 0){perror("shmget error!");exit(1);}elseprintf("shmget success!");return 0;
}

共享内存与进程的连接

如果一个进程已创建或打开一个共享内存,则在需要使用它时,要调用函数shmat()把该共享内存连接到进程上,即要把待使用的共享内存映射到进程空间。函数shmat()通过系统调用sys_shmat()实现。函数shmat()的原型如下:

void * shmat(int shmid, char __user * shmaddr, int shmflg);

其中,参数shmid为共享内存的标识;参数shmaddr为映射地址,如果该值为0,则由内核决定;参数shmflg为共享内存的标志,如果shmflg的值为SHM_RDONLY,则进程以只读的方式访问共享内存,否则以读写方式访问共享内存。

若函数调用成功,则返回共享存储段地址;若出错,则返回-1。

断开共享内存与进程的连接

调用函数shmdt()可以断开共享内存与进程的连接,其原型如下:

int shmdt(coid * addr);

其中,参数addr为共享存储段的地址,即调用shmat时的返回值。shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1。

共享内存的控制

调用函数shmctl()可以对共享内存进行一些控制,其原型如下:

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

其中,参数shmid为共享存储段的ID;参数cmd为控制命令,常用的值有IPC_STAT(赋值)、IPC_SET(赋值)、IPC_RMID(删除)、SHM_LOCK(上锁)、SHM_UNLOCK(解锁)等等;参数buf为struct shmid_ds类型指针,由buf返回的数值与命令参数cmd表示的操作相关。

共享内存不会随着程序的结束而自动消除,要么调用shmctl()删除,要么手动使用命令ipcrm -m shmid去删除,否则一直保留在系统中,直至系统掉电。

例子:调用函数shmget()为当前进程创建一个共享内存区并使用它。

代码如下:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/stat.h>int main(void)
{int shm_id;                            //定义共享内存键char* shared_memory;               //定义共享内存指针struct shmid_ds shmbuffer;            //定义共享内存缓冲int shm_size;                     //定义共享内存大小shm_id = shmget(IPC_PRIVATE, 0x6400, IPC_CREAT | IPC_EXCL | S_IRUSE | S_IWUSE);      //创建一个共享内存区shared_memory = (char*)shmat(shm_id, 0, 0);                 //绑定到共享内存printf("shared memory attached at address %p\n", shared_memory);shmctl(shm_id, IPC_STAT, &shmbuffer);                //读共享内存结构struct shmid_dsshm_size = shmbuffer.shm_segsz;                        //自结构struct shmid_ds获取内存大小printf("segment size:%d\n", shm_size);sprintf(shared_memory, "Hello,world.");             //向共享内存中写入一个字符串shmdt(shared_memory);                            //脱离该共享内存shared_memory = (char*)shmat(shm_id, (void *)0x500000, 0);            //重新绑定共享内存printf("shared memory reattched at address %p\n", shared_memory);printf("%s\n", shared_memory);shmdt(shared_memory);                      //脱离该共享内存shmctl(shm_id, IPC_RMID, 0);               //释放共享内存return 0;
}

共享内存的互斥

从上面的叙述中可以看到,共享内存是一种低级的通信机制,它没有提供进程间同步和互斥的功能。所以,共享内存通常是要与信号量结合使用。

原文链接:https://blog.csdn.net/qq_38410730/article/details/81488145?utm_medium=distribute.pc_relevant_right.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant_right.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase

linux存储--共享内存机制shm(十三)相关推荐

  1. linux存储--共享内存机制shm(十四)

    共享存储允许两个或多个进程共享一个给定的存储区,是进程间通信最快的一种方式. 不要同时对共享存储空间进行写操作,通常,信号量用于同步共享存储访问. 最简单的共享内存的使用流程 ①ftok函数生成键值 ...

  2. linux存储--共享内存机制mmap(十二)

    mmap基础概念 mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系.实现这样的映射关系后,进程就可以采用指 ...

  3. 【Linux】共享内存(shm)代码实现

    文章目录 共享内存介绍 最快的IPC形式 共享内存示意图 共享内存数据结构 共享内存函数 shmget函数 shmfig shmat函数 说明: shmdt函数 shmctl函数 共享内存的原理 小结 ...

  4. 嵌入式Linux系统编程学习之二十三 System V 共享内存机制

    文章目录 前言 一.ftok 函数 二.shmget 函数 三.shmat 函数 四.shmdt 函数 五.shmctl 函数 补充 前言   共享内存也是进程间(进程间不需要有继承关系)通信的一种常 ...

  5. linux如何创建共享内存,linux实现共享内存同步的四种方法

    https://blog.csdn.net/sunxiaopengsun/article/details/79869115 本文主要对实现共享内存同步的四种方法进行了介绍. 共享内存是一种最为高效的进 ...

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

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

  7. linux获取共享内存失败2,unix/linux共享内存应用与陷阱

    共享内存是系统出于多个进程之间通讯的考虑,而预留的的一块内存区.在/proc/sys/kernel/目录下,记录着共享内存的一些限制,如一 个共享内存区的最大字节数shmmax,系统范围内最大共享内存 ...

  8. Linux内核在中国大发展的黄金十年-写于中国Linux存储、内存管理和文件系统峰会十周年之际...

    Linux阅码场: 国内首屈一指的专注Linux核心技术开发的公众号,扫描下方二维码关注 CLSF: CLSF是中国Linux存储.内存管理和文件系统峰会的简称, 至今已举办十年, 参会成员由组委会根 ...

  9. 共享内存机制——mmap和shm

    共享内存是进程间通信的一种方法,常用到的有mmap和shm,下面做一个比较. mmap机制: 在磁盘上建立一个文件,然后把文件内容映射到虚拟内存上,在每个进程的虚拟存储器里面,单独开辟一个空间来进行映 ...

最新文章

  1. 什么是外函数,什么是内函数?闭包(Closure)是什么?说说你对闭包(Closure)的理解?
  2. 国内比较好的python中文教材-最好的Python入门教材是哪本?
  3. 3星|《数据思维:从数据分析到商业价值》:有趣的数据分析案例
  4. 最小的权限+最少的服务=最大的安全
  5. 八周二次课 rsync工具介绍,常用参数选项以及和ssh同步
  6. U-Boot 之二 详解使用 eclipse + J-Link 进行编译及在线调试
  7. 小师妹学IO系列文章集合-附PDF下载
  8. 前端学习(1504):组件通信的几种情况
  9. php在菜单栏里加子菜单,WordPress后台添加子菜单add_submenu_page()
  10. 标记语言Markdown介绍以及日常使用
  11. CGCKD2021大会报告整理(1)--宽度学习
  12. python 钉钉机器人发送图片_利用Python自动发送钉钉数据消息
  13. android程序连接网络出现android.os.NetworkOnMainThreadEx
  14. mysql配置my.cnf文件,以及参数优化提升性能
  15. Lync部署之Lync Mobile服务器端的外网设置
  16. 线性方程组的几种解法以及解的性质和结构
  17. MEMS传感市场,美/日/德企占主导地位
  18. 智能电导率系统电路设计详解
  19. 创建ArrayList对象,添加5个元素,使用Iterator遍历输出
  20. RAC下Fatal NI connect error 12170.报错处理

热门文章

  1. 黑客用python还是ruby-Python、Ruby、Go语言哪个更值得学习?
  2. python和java一样吗-python和java的区别,看了这个就会区分了!
  3. 以下属于python标准库的选项是-Python 标准库一览(Python进阶学习)
  4. 学python用什么系统好-初次接触python,怎么样系统的自学呢?
  5. 学python的基础-老司机学python篇:第一季(基础速过、机器学习入门)
  6. python编程培训-课程:尹会生的Python编程培训课程
  7. 使用conda报错:from conda.cli import main ModuleNotFoundError: No module named conda
  8. lua学习笔记之语句
  9. LeetCode Palindrome Partitioning(dfs +回文串 )
  10. LeetCode Water and Jug Problem(巧妙转换为gcd问题)