版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、UNIX共享内存应用中的问题及解决方法简介共享内存是一种非常重要且常用的进程间通信方式,相对于其它 IPC机制,因其速度最快、效率最高,被广泛应用于各类软件产品及 应用开发中。System V IPC为Unix平台上的共享内存应用制定了统 一的API标准,从而为在UNIX/Linux平台上进行跨平台开发提供了 极大的便利;开发人员基于一套基本相同的源代码,便可开发出同时 支持AIX、Solaris、HP-UX、Linux等平台的产品。然而,各个平台对System V标准的API在实现上各有差异,由 此对相关应用开发带来影响,甚至引入难以调试的问题。本文将结合 作者在Tivoli产品开发中的实际
2、经验,对这些平台相关的问题,以 及具有共性的问题,逐一进行分析,并提出解决方法。System V共享内存概述SystemV进程间通信(IPC)包括3种机制:消息队列、信号量、 共享内存。消息队列和信号量均是内核空间的系统对象,经由它们的 数据需要在内核和用户空间进行额外的数据拷贝;而共享内存和访问 它的所有应用程序均同处于用户空间,应用进程可以通过地址映射的 方式直接读写内存,从而获得非常高的通信效率。System V为共享内存定义了下列API接口函数:include include include key_t ftok(const char *pathname, int proj_id);i
3、nt shmget(key_t key, int size, int shmflg);void* shmat(int shmid, const void *shmaddr, int shmflg);intshmdt(void *shmaddr);intshmctl(int shmid, int cmd, struct shmid_ds *buf);函数用于生成一个键值:key_t key,该键值将作为共享内存对象的唯一性标识符,并提供给为shmget函数作为其输入参数;ftok函数的输入参数包括一个文件(或目录)路径名: ftokpathname,以及一个额外的数字:proj_id,其中pat
4、hname所指定的文件(或目录)要求必须已经存在,且proj_id不可为0 ;函数用于创建(或者获取)一个由key键值指定的共享内存 shmget对象,返回该对象的系统标识符:shmid;函数用于建立调用进程与由标识符shmid指定的共享内存对 shmat象之间的连接;shmdt函数用于断开调用进程与共享内存对象之间的连接;函数用于对已创建的共享内存对象进行查询、设值、删除等 shmctl操作;ftok的陷阱根据pathname指定的文件(或目录)名称,以及proj_id参数 指定的数字,ftok函数为IPC对象生成一个唯一性的键值。在实际 应用中,很容易产生的一个理解是,在proj_id相同
5、的情况下,只要 文件(或目录)名称不变,就可以确保ftok返回始终一致的键值。 然而,这个理解并非完全正确,有可能给应用开发埋下很隐晦的陷阱。 因为ftok的实现存在这样的风险,即在访问同一共享内存的多个进 程先后调用ftok函数的时间段中,如果pathname指定的文件(或目 录)被删除且重新创建,则文件系统会赋予这个同名文件(或目录) 新的i节点信息,于是这些进程所调用的ftok虽然都能正常返回, 但得到的键值却并不能保证相同。由此可能造成的后果是,原本这些 进程意图访问一个相同的共享内存对象,然而由于它们各自得到的键 值不同,实际上进程指向的共享内存不再一致;如果这些共享内存都 得到创建
6、,则在整个应用运行的过程中表面上不会报出任何错误,然 而通过一个共享内存对象进行数据传输的目的将无法实现。AIX、Solaris. HP-UX均明确指出,key文件被删除并重建后, 不保证通过ftok得到的键值不变,比如AIX上ftok的man帮助信息 即声明:Attention: If the Path parameter of the ftok subroutine names a file that has been removed while keys still refer to it, the ftok subroutine returns an error. Ifthat fil
7、e is then re-created, the ftok subroutine will probably return a key different from the original one.Linux没有提供类似的明确声明,但我们可以通过下面的简单例 程test01.c,得到相同的印证: #include #include void main(int argc, char* argv)if (argc !=2 ) printf(Usage: %s KeyFilen e.g. %s /tmp/mykeyfilen”, argv0, argv0);return;printf(Key g
8、enerated by ftok: 0 x%xn,ftok(argv1, 1);将上述例程在 Red Hat Enterprise Linux AS release 平台上 编程成可执行程序test01,并且通过touch命令在/tmp目录下创建 一个新文件mykeyfile,然后为该文件生成键值:touch /tmp/mykeyfile./test01 /tmp/mykeyfileKey generated by ftok: 0 x101000b然后,将/tmp/mykeyfile删除,并且通过vi命令重新创建该文 件,再次生成键值:# ./test01 /tmp/mykeyfileKey
9、generated by ftok: 0 x1010017我们可以看到,虽然文件名称都是/tmp/mykeyfile,并未改变, 但由于中间发生了文件删除并重新创建的操作,前后两次所得到的键 值已经不再相同。避免此类问题最根本的方法,就是采取措施保证pathname所指 定的文件(或目录)在共享内存的使用期间不被删除,不要使用有可 能被删除的文件;或者十脆直接指定键值,而不借助ftok来获取键 值。AIX中shmat的问题AIX系统中,SystemV各类进程间通信机制在使用中均存在限制。 区别于其它UNIX操作系统对IPC机制的资源配置方式,AIX使用了 不同的方法;在AIX中定义了 IPC机
10、制的上限,且是不可配置的。 就共享内存机制而言,在4.2.1及以上版本的AIX系统上,存在下列 限制:对于64位进程,同一进程可连接最多268435456个共享内存段;对于32位进程,同一进程可连接最多11个共享内存段,除非使 用扩展的shmat;上述限制对于64位应用不会带来麻烦,因为可供连接的数量已 经足够大了;但对于32位应用,却很容易带来意外的问题,因为最 大的连接数量只有11个。在某些事件触发的多线程应用中,新的线 程不断地为进行事件处理而被创建,这些线程如果都需要去连接特定的共享内存,则极有可能造成该进程连接的共享内存数量超过11个, 事实上同时拥有几十个甚至上百个处理线程的应用并
11、不少见一旦超 个这个限制值,则所有后续的处理线程都将无法正常工作,从而导致 应用运行失败。下面的例程test02.c演示了这个问题,为了精简代码,它反复 连接的是同一个共享内存对象;实际上,无论所连接的共享内存对象 是否相同,该限制制约的是连接次数: #include#include#include#include#include#defineMAX_ATTACH_NUM 15 void main(int argc, char* argv) key_tmem_key;longmem_id;void*mem_addrMAX_ATTACH_NUM;inti;if ( (mem_key = ftok
12、(/tmp/mykeyfile,1) ) = (key_t)(-1)printf(Failed to generate shared memory accesskey, ERRNO=%dn,errno);goto MOD_EXIT;if ( ( mem_id = shmget(mem_key, 256, IPC_CREAT) ) = (-1)printf(Failed to obtain shared memory ID, ERRNO=%dn, errno);goto MOD_EXIT;for ( i=1; i=MAX_ATTACH_NUM; i+ ) if ( ( mem_addri = (
13、void *)shmat(mem_id, 0, 0)=(void *)(-1)printf(Failed to attach shared memory, times%02d, errno:%dn, i,errno);elseprintf(Successfully attached shared memory, times %02dn,i);MOD_EXIT:shmctl(mem_id, IPC_RMID, NULL);在AIX系统上,我们将其编译为test02,并运行,可以看到如下输出:Successfully attached shared memory, times 01Successf
14、ully attached shared memory, times 02Successfully attached shared memory, times 03Successfully attached shared memory, times 04Successfully attached shared memory, times 05Successfully attached shared memory, times 06Successfully attached shared memory, times 07Successfully attached shared memory, t
15、imes 08Successfully attached shared memory, times 09Successfully attached shared memory, times 10Successfully attached shared memory, times 11Failed toattachsharedmemory,times12,errno:24Failed toattachsharedmemory,times13,errno:24Failed toattachsharedmemory,times14,errno:24Failed toattachsharedmemor
16、y,times15,errno:24说明超出11个连接之后,所有后续的共享内存连接都将无法建立。错误码24的定义是EMFILE,AIX给予的解释是:The number of shared memory segments attached to the calling process exceeds the system-imposed limit解决这个问题的方法是,使用扩展的shmat ;具体而言就是,在 运行相关应用之前(确切地说,是在共享内存被创建之前),首先在 shell中设置EXTSHM环境变量,通过它扩展shmat,对于源代码本身 无需作任何修改:export EXTSHM=ON
17、值得注意的是,虽然设置环境变量,在程序中也可通过setenv 函数来做到,比如在程序的开始,加入下列代码:setenv(EXTSHM,ON, 1);但实践证明这样的方法在解决这个问题上是无效的;也就是说唯 一可行的办法,就是在shell中设置EXTSHM环境变量,而非在程序 中。在AIX上配置32位DB2实例时,也要求确保将环境变量EXTSHM 设为 ON,这是运行 Warehouse Manager 和 Query Patroller 之前 必需的操作: export EXTSHM=ONdb2set DB2ENVLIST=EXTSHMdb2start其原因即来自我们刚刚介绍的AIX中32位应
18、用连接共享内存时, 存在最大连接数限制。这个问题同样普遍存在于AIX平台上Oracle 等软件产品中。HP-UX 中 shmget 和 shmat 的问题4.1 32位和64位应用兼容问题在HP-UX平台上,如果同时运行32位应用和64位应用,而且它 们访问的是一个相同的共享内存区,则会遇到兼容性问题。在HP-UX中,应用程序设置IPC_CREAT标志调用shmget,所创 建的共享内存区,只可被同类型的应用所访问;即32位应用程序所 创建的共享内存区只可被其它的32位应用程序访问,同样地,64位 应用程序所创建的共享内存区只可被其它的64位应用程序访问。如果,32位应用企图访问一个由64位应
19、用创建的共享内存区, 则会在调用shmget时失败,得到EINVAL错误码,其解释是:A shared memory identifIErexists for key but is in 64-bit address space and the process performing therequest has been compiled as a 32-bit executable.解决这一问题的方法是,当64位应用创建共享内存时,合并 IPC_CREAT标志,同时给定IPC_SHARE32标志: shmget(mem_key, size, 0666 | IPC_CREAT | IPC_SH
20、ARE32)对于32位应用,没有设定IPC_SHARE32标志的要求,但设置该 标志并不会带来任何问题,也就是说无论应用程序将被编译为32位 还是64位模式,都可采用如上相同的代码;并且由此解决32位应用 和64位应用在共享内存访问上的兼容性问题。4.2对同一共享内存的连接数限制在HP-UX上,应用进程对同一个共享内存区的连接次数被限制为 最多1次;区别于上面第3节所介绍的AIX上的连接数限制,HP-UX 并未对指向不同共享内存区的连接数设置上限,也就是说,运行在 HP-UX上的应用进程可以同时连接很多个不同的共享内存区,但对于 同一个共享内存区,最多只允许连接1次;否则,shmat调用将失败
21、, 返回错误码EINVAL,在shmat的man帮助中,对该错误码有下列解 释:shmid is not a valid shared memory identifier, (possibly because the shared memory segment was already removed using shmctl(2) with IPC_RMID), or the calling process is already attached to shmid.这个限制会对多线程应用带来无法避免的问题,只要一个应用进 程中有超过1个以上的线程企图连接同一个共享内存区,则都将以失 败而告终。
22、解决这个问题,需要修改应用程序设计,使应用进程具备对同一 共享内存的多线程访问能力。相对于前述问题的解决方法,解决这个 问题的方法要复杂一些。作为可供参考的方法之一,以下介绍的逻辑可以很好地解决这个 问题:基本思路是,对于每一个共享内存区,应用进程首次连接上之后, 将其键值(ftok的返回值)、系统标识符shmid,shmget调用的返 回值)和访问地址(即shmat调用的返回值)保存下来,以这个进程的全局数组或者链表的形式留下记录。在任何对共享内存的连接操作 之前,程序都将先行检索这个记录列表,根据键值和标志符去匹配希 望访问的共享内存,如果找到匹配记录,则从记录中直接读取访问地 址,而无需
23、再次调用shmat函数,从而解决这一问题;如果没有找到 匹配目标,则调用shmat建立连接,并且为新连接上来的共享内存添 加一个新记录。记录条目的数据结构,可定义为如下形式:typedef struct_Shared_Memory_Recordkey_tmem_key;/ key generated by ftok()intmem_id;/ id returned byshmget()void*mem_addr;/ access address returnedby shmat()intnattach;/ times ofattachment Shared_Memory_Record;其中,n
24、attach成员的作用是,记录当前对该共享内存区的连接数目;每一次打开共享内存的操作都将对其进行递增,而每一次关闭 共享内存的操作将其递减,直到nattach的数值降到0,则对该共享 内存区调用shmdt进行真正的断开连接。打开共享内存的逻辑流程可参考如下图一:图一关闭共享内存的逻辑流程可参考如下图二:图二Solaris中的shmdt函数原型问题Solaris系统中的shmdt调用,在原型上与System V标准有所 不同,Defaultint shmdt(char *shmaddr);即形参shmaddr的数据类型在Solaris上是char *,而System V 定义的是void *类型
25、;实际上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时, 采用ch
26、ar *类型的形参,而对其它平台,均仍采用System V标准 的void *类型形参,比如:#ifdef _SOLARIS_SHARED_MEMORYshmdt(char *)mem_addr);#elseshmdt(void *)mem_addr);#endif通过shmctl删除共享内存的风险当进程断开与共享内存区的连接后,一般通过如下代码删除该共 享内存:shmctl(mem_id, IPC_RMID, NULL);从HP-UX上shmctl函数的man帮助,我们可以看到对IPC_RMID 操作的说明:IPC_RMID Remove the sharednemory identifie
27、r 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 proce
28、ss detaches it.其它UNIX平台也有类似的说明。关于shmctl的IPC_RMID操作, 其使用特点可简述如下:如果共享内存已经与所有访问它的进程断开了连接,则调用 IPC_RMID子命令后,系统将立即删除共享内存的标识符,并删除该 共享内存区,以及所有相关的数据结构;如果仍有别的进程与该共享内存保持连接,则调用IPC_RMID子 命令后,该共享内存并不会被立即从系统中删除,而是被设置为 IPC_PRIVATE状态,并被标记为已被删除;直到已有连接全部断开, 该共享内存才会最终从系统中消失。于是,存在这样的一种状态:N个进程(进程1至进程N)已经与某共享内存区连接;进程1已完成对
29、此共享内存的操作,断开连接后,调用shmctl 的IPC_RMID子命令,企图删除该共享内存;由于进程2至进程N仍保持与该共享内存的连接,因此在它们全 部断开连接之前,这个共享内存区毫无疑问地会依然存在。此时,如果有其它的进程(比如第N+1号进程)想建立对这个共 享内存的连接,是否能够成功呢?类似的状态,在Windows上同样存在,只是程序借助的API有所 不同,比如通过CreateFileMapping函数创建共享内存,通过 MapViewOfFile函数建立连接,通过UnmapViewOfFile函数断开连接, 通过CloseHandle函数删除共享内存等。在Windows上,对此问题的
30、回答是肯定的;也就是说,只要共享内存依然存在,则进程总是可以 建立对它的连接,而无论之前是否有进程对其执行过删除操作。然而,对于包括AIX、Solaris、HP-UX等在内的UNIX平台,答 案却是否定的!这也正是本节所讨论的使用shmctl中的风险所在; 通过以下test03.P1.c和test03.P2.c两个例程,我们可以很直观地 得到答案:test03.P1.c:创建共享内存,并建立连接,保持10秒后(在 此期间,test03.P2将反复连接、并删除该共享内存),断开连接, 并最后再次尝试连接以验证该共享内存是否已被真正删除;test03.P2.c:反复连接由test03.P 1创建的
31、共享内存,并在期 间通过shmctl的IPC_RMID子命令删除该共享内存,以观察共享内 存被执行删除操作之后,在被彻底销毁之前是否还能接受连接; /* test03.P1.c */ #include #include #include #include #include #include int main(int argc, char* argv) key_tmem_key;longmem_id;void*mem_addr;intisAttached = 0;mem_key = ftok(/tmp/mykeyfile, 1);mem_id = shmget(mem_key, 256, IPC
32、_CREAT);if ( ( mem_addr = (void *)shmat(mem_id, 0, 0) ) = (voidprintf(%s, Failed to attach shared memory, errno:%dn, argv0, errno);else isAttached = 1;printf(%s, +.Successfullyattached shared memoryn, argv0);/* sleep 10 seconds, to wait test03.P2 to run */ sleep(10);if (isAttached) / Attention: the
33、following line should beshmdt(char *)mem_addr); ifon Solarisshmdt(void *)mem_addr);printf(s, -.Successfullydetached shared memoryn, argv0);/* try to attach the shared memory which hasbeen removed!*/if ( ( mem_addr = (void *)shmat(mem_id, 0, 0) ) = (void*)(-1)printf(%s, Failed to attach the removed s
34、hared memory, errno:%dn,argv0, errno);return 0;/* test03.P2.c */#include #include #include #include #include int main(int argc, char* argv)key_tmem_key;longmem_id;void*mem_addr;inti, isAttached;mem_key = ftok(/tmp/mykeyfile, 1);mem_id = shmget(mem_key, 0, 0);/ repeated attaching & detachingfor (i=1;
35、 i10; i+) isAttached = 0;if ( ( mem_addr = (void *)shmat(mem_id, 0, 0) = (void *)(-1)printf(s, Failed to attach shared memory, times%02d,errno:%dn,argv0, i, errno);else isAttached = 1;printf(%s, +.Successfully attached shared memory, times%02dn,argv0, i);if (isAttached) / Attention: the following li
36、ne should be shmdt(char*)mem_addr);, if on Solarisshmdt(void *)mem_addr);printf(%s, -.Successfully detached, times %02dn, argv0, i);/ purposely remove the shared memory at times 5 if (i=5) shmctl(mem_id, IPC_RMID, NULL);printf(s, *.Remove executed, times %02d,errno=%dn ,argv0, i, errno);return 0;上述程
37、序均可在AIX、HP-UX、Linux平台上编译通过;在Solaris 平台上只需按注释提示的要求,将shmdt的参数强制为char *类型 也可编译通过(第5节中已介绍过)。将test03.P1.c、test03.P2.c各自编译为可执行程序 test03.P1、test03.P2,并通过下面的shell 脚本:runtest,运行 它们:#!/bin/sh./test03.P1&sleep 2./test03.P2在Linux平台(Red Hat 8.0)上的运行结果如下: rootlocalhost tmp# ./runtest ./test03.P1, +.Successfully a
38、ttached shared memory ./test03.P2, +.Successfullyattached shared memory, times 01./test03.P2, -.Successfully detached, times 01./test03 . P2, +. Successfullyattached shared memory, times 02./test03.P2, -.Successfully detached, times 02./test03. P2, +. Successfullyattached shared memory, times 03./te
39、st03.P2, -.Successfully detached, times 03./test03.P2, +.Successfullyattached shared memory, times 04./test03.P2, -.Successfully detached, times 04./test03.P2, +.Successfullyattached shared memory, times 05./test03.P2, -.Successfully detached, times 05./test03.P2, *.Remove executed, times 05, errno=
40、0./test03.P2, +.Successfullyattached shared memory, times 06./test03.P2, -.Successfully detached, times 06./test03.P2, +.Successfullyattached shared memory, times 07./test03.P2, -.Successfully detached, times 07./test03.P2, +.Successfullyattached shared memory, times 08./test03.P2, -.Successfully de
41、tached, times 08./test03.P2, +.Successfullyattached shared memory, times 09./test03.P2, -.Successfully detached, times 09rootlocalhost tmp# ./test03.P1, -.Successfully detached shared memory./test03.P1, Failed to attach the removed shared memory,根据运行结果,我们可以看到,在Linux平台上,即便对共享内 存执行了删除操作(在第5次连接之后,test03.P2进程调用了 shmctl 的IPC_RMID删除操作),只要该共享内存依然存在(test03.P1进 程保持着连接,因此共享内存不会被立即删除),则它仍然是可连接 的(test03.P2进程的第6到第9次连接均是成功的)。然而,在AIX,HP-UX,Solaris平台上的运行结果却不同于Linux: # ./runtest ./test03.P1, +.Successfully attached shared memory ./test03.P2, +.Successfullyattached shared memory, times 01./tes
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论