实际编程中,我们通常使用信号量,传递消息(管道or ipc消息队列),生成信号等方法来达到对共享内存的同步访问。
以下是转的(补充):
1. 数据结构
与消息队列和信号量集合类似,内核为每一个共享内存段(存在于它的地址空间)维护着一个特殊的数据结构shmid_ds,这个结构在include/linux/shm.h中定义如下:
/* 在系统中 每一个共享内存段都有一个shmid_ds数据结构. */
struct shmid_ds {
struct ipc_perm shm_perm; /* 操作权限 */
int shm_segsz; /* 段的大小(以字节为单位) */
time_t shm_atime; /* 最后一个进程附加到该段的时间 */
time_t shm_dtime; /* 最后一个进程离开该段的时间 */
time_t shm_ctime; /* 最后一次修改这个结构的时间 */
unsigned short shm_cpid; /*创建该段进程的 pid */
unsigned short shm_lpid; /* 在该段上操作的最后一个进程的pid */
short shm_nattch; /*当前附加到该段的进程的个数 */
/* 下面是私有的 */
unsigned short shm_npages; /*段的大小(以页为单位) */
unsigned long *shm_pages; /* 指向frames -> SHMMAX的指针数组 */
struct vm_area_struct *attaches; /* 对共享段的描述 */
};
------2. 共享内存的处理过程
某个进程第一次访问共享虚拟内存时将产生缺页异常。这时,Linux 找出描述该内存的 vm_area_struct 结构,该结构中包含用来处理这种共享虚拟内存段的处理函数地址。共享内存缺页异常处理代码对shmid_ds 的页表项表进行搜索,以便查看是否存在该共享虚拟内存的页表项。如果没有,系统将分配一个物理页并建立页表项,该页表项加入 shmid_ds 结构的同时也添加到进程的页表中。这就意味着当下一个进程试图访问这页内存时出现缺页异常,共享内存的缺页异常处理代码则把新创建的物理页给这个进程。(因此说,第一个进程对共享内存的存取引起创建新的物理页面,而其它进程对共享内存的存取引起把那个页加添加到它们的地址空间。)
当某个进程不再共享其虚拟内存时,利用系统调用将共享段从自己的虚拟地址区域中移去,并更新进程页表。当最后一个进程释放了共享段之后,系统将释放给共享段所分配的物理页。
当共享的虚拟内存没有被锁定到物理内存时,共享内存也可能会被交换到交换区中。
--------
系统调用: shmctl()
原型: int shmctl ( int shmqid, int cmd, struct shmid_ds *buf );
返回:成功为 0 , 失败 为-1
这个特殊的调用和semctl()调用几乎相同,因此,这里不进行详细的讨论。有效命令的值是:
IPC_STAT :检索一个共享段的shmid_ds结构,把它存到buf参数的地址中。
IPC_SET :对一个共享段来说,从buf 参数中取值设置shmid_ds结构的ipc_perm域的值。
IPC_RMID :把一个段标记为删除
IPC_RMID 命令实际上不从内核删除一个段,而是仅仅把这个段标记为删除,实际的删除发生在最后一个进程离开这个共享段时。
当一个进程不再需要共享内存段时,它将调用shmdt()系统调用取消这个段,但是,这并不是从内核真正地删除这个段,而是把相关shmid_ds结构的 shm_nattch域的值减1,当这个值为0时,内核才从物理上删除这个共享段。
之所以内存共享是最快速的IPC机制,是因为读它的访问是不需要涉及内核的。所以快。但因此我们也要提供对他的同步机制。