【Linux】Linux的共享内存
实现进程间通信最简单也是最直接的方法就是共享内存——为参与通信的多个进程在内存中开辟一个共享区。由于进程可以直接对共享内存进行读写操作,因此这种通信方式效率特别高,但其弱点是,它没有互斥机制,需要信号量之类的手段来配合。
共享内存原理与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,当系统断电后,其中的文件会全部自行销毁。
文章参考:mmap内存映射、认真分析mmap:是什么 为什么 怎么用。
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;
}
共享内存的互斥
从上面的叙述中可以看到,共享内存是一种低级的通信机制,它没有提供进程间同步和互斥的功能。所以,共享内存通常是要与信号量结合使用。
【Linux】Linux的共享内存相关推荐
- linux 查看共享内存最大值,linux上更改共享内存的最大值
linux下更改共享内存的最大值 System V IPC 参数 名字 描述 合理取值 SHMMAX 最大共享内存段尺寸(字节) 最少若干兆(见文本) SHMMIN 最小共享内存段尺寸(字节) 1 S ...
- Linux下进程间通信--共享内存:最快的进程间通信方式
内存共享最新整理: Linux下进程间通信-共享内存 - 码到城攻共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式https://www.codecomeon.com/posts/109/ ...
- 【操作系统实验】Linux进程通信—共享内存通信、管道通信
Linux进程通信-共享内存通信.管道通信 一.实验目的: 二.实验题目: 1. 试设计程序利用共享内存完成如下进程通信 1.shmget函数 2.shmat函数 3.shmdt函数 4.shmctl ...
- [转]Linux 进程间通信:共享内存
(上) 级别: 初级 郑彦兴 (mlinux@163.com), 国防科大攻读博士学位 2003 年 5 月 01 日 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B ...
- Linux进程间通信——使用共享内存
下面将讲解进程间通信的另一种方式,使用共享内存. 一.什么是共享内存 顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式 ...
- Linux进程间通信:共享内存与管道
references: [1] IPC through shared memory [2] Inter Process Communication (IPC) [3] https://www.geek ...
- Linux进程间通信(四) - 共享内存
共享内存的优势 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝.对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只 ...
- linux共享内存示例,linux 进程间共享内存示例
写入端: #include #include #include #include #include using namespace std; struct MappingDataType { int ...
- linux如何创建共享内存,linux实现共享内存同步的四种方法
https://blog.csdn.net/sunxiaopengsun/article/details/79869115 本文主要对实现共享内存同步的四种方法进行了介绍. 共享内存是一种最为高效的进 ...
- Linux四种共享内存技术(附源码):SystemV、POSIX mmap、memfd_create、dma-buf
<Linux 下的进程间通信:管道.消息队列.共享文件.共享内存> <[共享内存]基于共享内存的无锁消息队列设计> <File Sealing & memfd_c ...
最新文章
- 目标检测+mAP+IoU
- Spring MVC 完整示例
- Intellij IDEA 的使用
- Java 关系运算符
- Ajax — 第三天
- 经典蓝牙和低功耗蓝牙(BLE)有什么区别?
- cmake 构建路径_新手必备:win10 系统下 VSCode+CMake+Clang+GCC 环境的搭建
- java学习(88):Charactor包装类
- Python中出现:RunTimeError:implement_array_function method already has a docstring.异常解决
- 线性表--链式实现方式
- 387.字符串中的第一个唯一字符
- java实现人民币金额大写
- 课堂派题库格式转换程序
- 《Presto(Trino)——The Definitive Guide》CHAPTER 6 Connectors Advanced CHAPTER 7 Connector Examples
- BAT等公司高薪招聘Android开发面试题目集锦
- 2021-03-26
- 数据移动指令-----mov,lea,xchg
- esp8266连接mqtt服务器
- 深入浅出WPF(8)——数据的绿色通道,Binding(中)
- 伴随状语的动作与主句的动作间的关系
热门文章
- 大闹天竺里的机器人_王宝强的电影《大闹天竺》都植入了哪些品牌?
- 状态空间的离散时间模型
- 力扣算法学习计划打卡:第七天
- 3D 霍尔效应位置传感器原理解析
- typeorm中文网【TS】你们要的TypeORM中文文档Ta来了
- ai人工智能在手机的应用_常识在人工智能中仍然不常见
- 怎样找到ant压缩这个软件_如何在手机上把照片压缩成512Kb以下的体积?可用snapseed这样做...
- smobiler仿京东app搜索页面
- 服务器装系统卡LOGO,使用U盘安装Linux系统时卡在logo界面的解决办法
- 访南京后,回昆山之夜