实现进程间通信最简单也是最直接的方法就是共享内存——为参与通信的多个进程在内存中开辟一个共享区。由于进程可以直接对共享内存进行读写操作,因此这种通信方式效率特别高,但弱点是没有互斥机制,需要信号量之类的手段来配合
Linux的共享内存
- 共享内存原理
- Linux共享内存结构
- 共享内存的使用
共享内存原理
共享内存,顾名思义,就是两个或多个进程都可以访问的同一块内存空间,一个进程对这块空间内容的修改可为其他参与通信的进程所看到,为了达到这个目的,需要做两件事:
- 在内存划出一块区域来作为共享区
- 把这个区域映射到参与通信的各个进程空间
通常在内存划出一个区域的方法是,在内存中打开一个文件,若通过系统调用mmap()把这个文件所占用的内存空间映射到参与通信的各个进程地址空间,则这些进程就都可以看到这个共享区域,进而实现进程间的通信
mmap()的原理简述
void * mmap(void *start, size_t len, int prot, int flags, int fd, off_t offset);
start用来指定映射到虚地址空间的起始位置(通常为NULL,即由系统确定)
len指定文件被映射部分的长度;fd用来指定被映射的文件
offset指定映射的起始位置偏移量(通常为0)
mmap是一种内存映射文件的方法: 即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享
mmap映射过程:
mmap是一个简单的操作,在进程页表中添加一个页表项(物理内存的地址)
在调用mmap时,内核会在该进程的地址空间的映射区域查找一块满足要求的空间用于映射该文件,然后生成该虚拟地址的页表项,该页表此时的有效位(标志是否已经在物理内存中)为0,页表项的内容是文件的磁盘地址。
bref,就是在进程对应的虚存段添加一个段,即创建一个新的vm_area_struct,并将其与文件的物理磁盘地址相连。
创建虚拟区间并完成地址映射,但是并没有任何文件数据拷贝到主存。真正的文件读取是当进程发起读写操作时。
进程的读写操作访问虚拟地址空间这一段映射地址,通过查询页表,引发却页异常,内核进行请页。
IPC的共享内存通行方式与mmap()方式极为相似,但是因为建立一个文件的目的仅是为了通信,这种文件没有永久保存的意义,因此IPC并没有使用正规的文件系统,而是在系统初始化的时候在磁盘交换区建立了一个专门用来实现共享内存的临时文件系统shm,当系统断电后,其中的文件会全部自动销毁。
Linux共享内存结构
- Linux的一个共享内存区由多个共享段组成,用来描述 共享内存段的内核数据结构 shmid_kernel :
struct shmid_kernel /* private to the kernel */
{
struct kern_ipc_perm shm_perm; //描述进程间通信许可的结构permission
struct file * shm_file; //指向共享内存文件的指针
unsigned long shm_nattch; //挂接到本段共享内存的进程数
unsigned long shm_segsz; //段大小segmataion size
time_t shm_atim; //最后挂接时间attach time
time_t shm_dtim; //最后解除挂接时间delete time
time_t shm_ctim; //最后变化时间change time
pid_t shm_cprid; //创建进程的PID
pid_t shm_lprid; //最后使用进程的PID
struct user_struct *mlock_user;
};
shmid_kernel中最重要的域是指针shm_file,它指向临时文件file对象。当进程需要使用这个文件进行通信时,由内核负责将其映射到用户地址空间。
- 为了便于管理,内核把共享内存区的所有共描述结构shmid_kernel都存放在结构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>
- 共享内存的打开或创建
int shmget(key_t key, size_t size, int flag);
- 共享内存与进程的连接
void * shmat(int shmid, char __user * shmaddr, int shmflg);
- 共享内存与进程的连接断开
int shmdt(coid * addr);
- 共享内存的控制
int shmctl(int shmid, int cmd, struct shmid_ds * buf);
共享内存不会随着程序的结束而自动消除,要么调用shmctl()删除,要么手动使用命令ipcrm -m shmid去删除,否则一直保留在系统中,直至系统掉电