共享内存可以使多个进程共享某段内存,由于不需要进程间数据复制,所以是速度最快的IPC。
多个进程访问共享内存时需要同步机制,如进程A往共享内存中写数据时,进程B不能使用共享内存;通常采用信号量同步多进程访问共享内存。
共享内存实现主要有以下几点:
1.分配物理内存
2.将物理内存映射到进程的地址空间;通过修改进程的页表,可以虚拟地址直接访问物理内存
3.进程不再使用共享内存时,取消物理内存在进程地址空间的映射
tmpfs文件系统将所有文件存储在内存(而非硬盘等介质)中;tmpfs将所有的东西存放在内核缓存中,可以根据文件系统中所容纳的文件自动增长和收缩,也可以将不使用的页swap出去。
linux共享内存的实现基于tmpfs文件系统及mmap文件映射;通过在tmpfs中创建文件来获取物理内存,将文件映射到进程地址空间后可以使用虚拟地址访问共享内存。
I.数据结构
include/linux/shm.h
86 struct shmid_kernel /* private to the kernel */
87 {
88 struct kern_ipc_perm shm_perm; /* operation perms */
89 struct file * shm_file; /* tmpfs file */
90 unsigned long shm_nattch; /* no. of current attaches */
91 unsigned long shm_segsz; /* size of segment (bytes) */
92 time_t shm_atim; /* last attach time */
93 time_t shm_dtim; /* last detach time */
94 time_t shm_ctim; /* last change time */
95 pid_t shm_cprid; /* pid of creator */
96 pid_t shm_lprid; /* pid of last operator */
97 struct user_struct *mlock_user;
98 };
shmid_kernel用于存放共享内存信息
注:
shm_file存放tmpfs中创建的内存文件,用于分配物理内存;用tmpfs的文件映射功能直接将共享内存映射到进程地址空间
ipc/shm.c
48 struct shm_file_data {
49 int id;
50 struct ipc_namespace *ns;
51 struct file *file;
52 const struct vm_operations_struct *vm_ops;
53 };
shm_file_data主要用于保存文件(tmpfs文件)内存映射的虚拟内存操作集vm_ops,进而扩展vm_ops,使某进程已经调用IPC_RMID,其它所有进程detach后能正常释放共享内存IPC资源
注:
共享内存主要涉及两种文件,tmpfs文件与shm文件;一个共享内存对应一个tmpfs文件,有多少个进程attach到共享内存就有多少个shm文件。
为什么要在tmpfs文件上层再加shm文件呢?直接将tmpfs文件映射到多个进程地址空间不就能实现内存共享了吗?
的确,可以将tmpfs文件映射到多个进程地址空间,并能实现内存共享。但是有一种特殊情况,当多个进程attach到共享内存,此时某个进程删除共享内存,为了保证其他进程能继续正常使用共享内存,则暂不能删除共享内存的IPC资源;而所有的进程detach后,tmpfs文件munmap又不能删除IPC资源。
所以在tmpfs文件上层添加shm文件,用于扩展tmpfs文件映射的vm_ops,来实现所有进程detach后删除共享内存的IPC资源。
详细代码参见:do_shm_rmid、shm_close
II.共享内存创建
326 /**
327 * newseg - Create a new shared memory segment
328 * @ns: namespace
329 * @params: ptr to the structure that contains key, size and shmflg
330 *
331 * Called with shm_ids.rw_mutex held as a writer.
332 */
333
334 static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
335 {
336 key_t key = params->key;
337 int shmflg = params->flg;
338 size_t size = params->u.size;
339 int error;
340 struct shmid_kernel *shp;
341 int numpages = (size + PAGE_SIZE -1) >> PAGE_SHIFT;
342 struct file * file;
343 char name[13];
344 int id;
345 int acctflag = 0;
346
347 if (size < SHMMIN || size > ns->shm_ctlmax)
348 return -EINVAL;
349
350 if (ns->shm_tot + numpages > ns->shm_ctlall)
351 return -ENOSPC;
352
353 shp = ipc_rcu_alloc(sizeof(*shp));
354 if (!shp)
355 return -ENOMEM;
356
357 shp->shm_perm.key = key;
358 shp->shm_perm.mode = (shmflg & S_IRWXUGO);
359 shp->mlock_user = NULL;
360
361 shp->shm_perm.security = NULL;
362 error = security_shm_alloc(shp);
363 if (error) {
364 ipc_rcu_putref(shp);
365 return error;
366 }
367
368 sprintf (name, "SYSV%08x", key);
369 if (shmflg & SHM_HUGETLB) {
370 /* hugetlb_file_setup applies strict accounting */
371 if (shmflg & SHM_NORESERVE)
372 acctflag = VM_NORESERVE;
373 file = hugetlb_file_setup(name, size, acctflag,
374 &shp->mlock_user, HUGETLB_SHMFS_INODE);
375 } else {
376