7.3.3 共享内存

共享内存可以被描述成内存一个区域()的映射,这个区域可以被更多的进程所共享。这是IPC机制中最快的一种形式,因为它不需要中间环节,而是把信息直接从一个内存段映射到调用进程的地址空间。 一个段可以直接由一个进程创建,随后,可以有任意多的进程对其读和写。但是,一旦内存被共享之后,对共享内存的访问同步需要由其他 IPC 机制,例如信号量来实现。所有的System V IPC 对象一样,Linux 对共享内存的存取是通过对访问键和访问权限的检查来控制的。

 

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; /* 对共享段的描述 */

        };

我们用图 74 来表示共享内存的数据结构shmid_ds与其它相关数据结构的关系。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


74 共享内存的数据结构

2. 共享内存的处理过程            

某个进程第一次访问共享虚拟内存时将产生缺页异常。这时,Linux 找出描述该内存的 vm_area_struct 结构,该结构中包含用来处理这种共享虚拟内存段的处理函数地址。共享内存缺页异常处理代码对shmid_ds 的页表项表进行搜索,以便查看是否存在该共享虚拟内存的页表项。如果没有,系统将分配一个物理页并建立页表项,该页表项加入 shmid_ds 结构的同时也添加到进程的页表中。这就意味着当下一个进程试图访问这页内存时出现缺页异常,共享内存的缺页异常处理代码则把新创建的物理页给这个进程。因此说,第一个进程对共享内存的存取引起创建新的物理页面,而其它进程对共享内存的存取引起把那个页加添加到它们的地址空间。

当某个进程不再共享其虚拟内存时,利用系统调用将共享段从自己的虚拟地址区域中移去,并更新进程页表。当最后一个进程释放了共享段之后,系统将释放给共享段所分配的物理页。

当共享的虚拟内存没有被锁定到物理内存时,共享内存也可能会被交换到交换区中。

3. 系统调用:shmget()

 

  原型:int shmget ( key_t key, int size, int shmflg );                                            

  返回:成功,则返回共享内存段的识别号, 失败返回-1                                                            

shmget()系统调用类似于信号量和消息队列的系统调用,在此不进一步赘述。

 

4. 系统调用:shmat()

 

 

   原型: int shmat ( int shmid, char *shmaddr, int shmflg);

   返回:成功,则返回附加到进程的那个段的地址,失败返回-1

 

其中shmid是由shmget()调用返回的共享内存段识别号,shmaddr是你希望共享段附加的地址,shmflag允许你规定希望所附加的段为只读(利用SHM_RDONLY)以代替读写。通常,并不需要规定你自己的shmaddr,可以用传递参数值零使得系统为你取得一个地址。

这个调用可能是最简单的,下面看一个例子,把一个有效的识别号传递给一个段,然后返回这个段被附加到内存的内存地址。

 

char *attach_segment( int shmid )

{

        return(shmat(shmid, 0, 0));

}

 

一旦一个段适当地被附加,并且一个进程有指向那个段起始地址的一个指针,那么,对那个段的读写就变得相当容易。

5. 系统调用: 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时,内核才从物理上删除这个共享段。