UNIX 共享内存应用中的问题及解决方法( 四 )


这个限制会对多线程应用带来无法避免的问题,只要一个应用进程中有超过1个以上的线程企图连接同一个共享内存区,则都将以失败而告终 。
解决这个问题,需要修改应用程序设计,使应用进程具备对同一共享内存的多线程访问能力 。相对于前述问题的解决方法,解决这个问题的方法要复杂一些 。
作为可供参考的方法之一,以下介绍的逻辑可以很好地解决这个问题:
基本思路是,对于每一个共享内存区,应用进程首次连接上之后,将其键值(ftok的返回值)、系统标识符(shmid,shmget调用的返回值)和访问地址(即shmat调用的返回值)保存下来,以这个进程的全局数组或者链表的形式留下记录 。在任何对共享内存的连接操作之前,程序都将先行检索这个记录列表,根据键值和标志符去匹配希望访问的共享内存,如果找到匹配记录,则从记录中直接读取访问地址,而无需再次调用shmat函数,从而解决这一问题;如果没有找到匹配目标,则调用shmat建立连接,并且为新连接上来的共享内存添加一个新记录 。
记录条目的数据结构,可定义为如下形式:
typedef struct _Shared_Memory_Record
{
key_tmem_key;// key generated by ftok()
intmem_id;// id returned by shmget()
void*mem_addr;// access address returned by shmat()
intnattach;// times of attachment
} Shared_Memory_Record;
其中,nattach成员的作用是,记录当前对该共享内存区的连接数目;每一次打开共享内存的操作都将对其进行递增,而每一次关闭共享内存的操作将其递减,直到nattach的数值降到0,则对该共享内存区调用shmdt进行真正的断开连接 。
打开共享内存的逻辑流程可参考如下图一:
图一
关闭共享内存的逻辑流程可参考如下图二:
图二
5. Solaris中的shmdt函数原型问题
Solaris系统中的shmdt调用,在原型上与System V标准有所不同,
Default
int shmdt(char *shmaddr);
即形参shmaddr的数据类型在Solaris上是char *,而System V定义的是void * 类型;实际上Solaris上shmdt调用遵循的函数原型规范是SVID-v4之前的标准;以Linux系统为例,libc4和libc5 采用的是char * 类型的形参,而遵循SVID-v4及后续标准的glibc2及其更新版本,均改为采用void * 类型的形参 。
如果仍在代码中采用System V的标准原型,就会在Solaris上编译代码时造成编译错误;比如:
Error: Formal argument 1 of type char* in call to shmdt(char*)
is being passed void*.
解决方法是,引入一个条件编译宏,在编译平台是Solaris时,采用char * 类型的形参,而对其它平台,均仍采用System V标准的void * 类型形参,比如:
#ifdef _SOLARIS_SHARED_MEMORY
shmdt((char *)mem_addr);
#else 
shmdt((void *)mem_addr);
#endif
6. 通过shmctl删除共享内存的风险
当进程断开与共享内存区的连接后,一般通过如下代码删除该共享内存:
shmctl(mem_id, IPC_RMID, NULL);
从HP-UX上shmctl函数的man帮助,我们可以看到对IPC_RMID操作的说明:
IPC_RMID Remove the shared memory identifier specified by shmid from the system and destroy the shared memory segment and data structure associated with it. If the segment is attached to one or more processes, then the segment key is changed to IPC_PRIVATE and the segment is marked removed. The segment disappears when the last attached process detaches it.
其它UNIX平台也有类似的说明 。关于shmctl的IPC_RMID操作,其使用特点可简述如下:
如果共享内存已经与所有访问它的进程断开了连接,则调用IPC_RMID子命令后,系统将立即删除共享内存的标识符,并删除该共享内存区,以及所有相关的数据结构;

推荐阅读