一. 共享内存介绍
系统V共享内存指的是把所有共享数据放在共享内存区域(IPC shared memory region),任何想要访问该数据的
进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面。系统调用mmap()通
过映射一个普通文件实现共享内存。系统V则是通过映射shm文件系统中的文件实现进程间的共享内存通信。
也就是说,每个共享内存区域对应shm文件系统的一个文件.
二、系统V共享内存API
对于系统V共享内存,主要有以下几个API:shmget()、shmat()、shmdt()及shmctl()。
#include <sys/ipc.h>
#include <sys/shm.h>
shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。
shmat()把共享内存区域映射到调用进程的地址空间中去,这样,进程就可以方便地对共享区域进行访问操作。
shmdt()调用用来解除进程对共享内存区域的映射。
shmctl实现对共享内存区域的控制操作。
注:shmget的内部实现包含了许多重要的系统V共享内存机制;shmat在把共享内存区域映射到进程空间时,并不真正改变进程的页表。当进程第一次访问内存映射区域访问时,会因为没有物理页表的分配而导致一个缺页异常,然后内核再根据相应的存储管理机制为共享内存映射区域分配相应的页表。
三. 系统V共享内存范例
范例1
两个进程, 进程A创建一块共享内存, 写下Hello, World然后退出. 进程B根据key得到进程A创建的共享内存, 然后读取
共享内存中的数据. 并打印出来. 示意图如下:
进程A的代码:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <error.h> #define SHM_SIZE 4096 #define SHM_MODE (SHM_R | SHM_W) /* user read/write */ int main(void) { int shmid; char *shmptr; if ( (shmid = shmget(0x44, SHM_SIZE, SHM_MODE | IPC_CREAT)) < 0) perror("shmget"); if ( (shmptr = shmat(shmid, 0, 0)) == (void *) -1) perror("shmat"); /* 往共享内存写数据 */ sprintf(shmptr, "%s", "hello, world"); exit(0); }
进程B的代码:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <error.h> #define SHM_SIZE 4096 #define SHM_MODE (SHM_R | SHM_W | IPC_CREAT) /* user read/write */ int main(void) { int shmid; char *shmptr; if ( (shmid = shmget(0x44, SHM_SIZE, SHM_MODE | IPC_CREAT)) < 0) perror("shmget"); if ( (shmptr = shmat(shmid, 0, 0)) == (void *) -1) perror("shmat"); /* 从共享内存读数据 */ printf("%s\n", shmptr); exit(0); }
总结:
1、系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以
指定何时将数据写入磁盘文件中。 注:系统V共享内存机制实际是通过shm文件系统中的文件
实现的,shm文件系统的安装点在交换分区上,系统重新引导后,所有的内容都丢失。
2、系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删
除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。
3、通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享
内存实现通信的进程则不然。
四. shm文件系统内核实现 (linux-1.2.13)
每一个新创建的共享内存对象都用一个shmid_kernel数据结构来表达。系统中所有的shmid_kernel数据结构都保存在shm_segs向量表中,该向量表的每一个元素都是一个指向shmid_kernel数据结构的指针。
shm_segs向量表的定义如下:
struct shmid_kernel *shm_segs[SHMMNI];
SHMMNI为128,表示系统中最多可以有128个共享内存对象。如下图所示:
数据结构shmid_kernel的定义如下:
struct shmid_kernel
{
struct shmid_ds u; /* the following are private */
unsigned long shm_npages; /* size of segment (pages) */
unsigned long *shm_pages; /* array of ptrs to frames -> SHMMAX */
struct vm_area_struct *attaches; /* descriptors for attaches */
};
其中:
shm_pages代表该共享内存对象的所占据的内存页面数组,数组里面的每个元素当然是每个内存页面的起始地址.
shm_npages则是该共享内存对象占用内存页面的个数,以页为单位。这个数量当然涵盖了申请空间的最小整数倍.
(A new shared memory segment, with size equal to the value of size rounded up to a multiple of PAGE_SIZE)
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 */
};
attaches描述被共享的物理内存对象所映射的各进程的虚拟内存区域。每一个希望共享这块内存的进程都必须通过系统调用将其关联(attach)到它 的虚拟内存中。这一过程将为该进程创建了一个新的描述这块共享内存的vm_area_struct数据结构。创建时可以指定共享内存在它的虚拟地址空间的 位置,也可以让Linux自己为它选择一块足够的空闲区域。
这个新的vm_area_struct结构是维系共享内存 和使用它的进程之间的关系的,所以除了要关联进程信息外,还要指明这个共享内存数据结构shmid_kernel所在位置; 另外,便于管理这些经常变化的vm_area_struct,所以采取了链表形式组织这些数据结构,链表由attaches指向,同时 vm_area_struct数据结构中专门提供了两个指针:vm_next_shared和 vm_prev_shared,用于连接该共享区域在使用它的各进程中所对应的vm_area_struct数据结构。