您的位置:首页 > 运维架构 > Linux

Linux共享内存常见问题分析

2014-02-22 22:19 781 查看

前言

这个是接上篇,本来是记录在一篇草稿上的,但是,内容根本不相关,排版怎么都觉得不好看,也不方便以后查阅。干脆再起一篇。

System V 共享内存问题

shmget创建共享内存问题

shmget函数用来创建一个新的,或者访问一个已存在的共享内存区。
#include <sys/types.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int oflag);
返回值为整型的共享内存区标识符,用其指代刚创建或已存在的共享内存区。

key是ftok()返回的键值,size指定内存区大小,oflag是一些列读写权限组合。具体不做介绍,请参考《网络编程卷二》。

现在的问题是,如果两个进程以一个相同的key共享一块内存区,但不同的进程调用shmget的size不一样,是否能成功?

已经创建的共享内存的大小是可以调整的,但是已经创建的共享内存的大小只能调小,不能调大。也就是说,如果进程A先调用shmget创建一块size大小为10M的内存,进程B再次调用shmget时的size参数只能比10M小,如果大于10M,程序会报shmget error: Invalid argument错误。

这种关系可能导致的问题:

当多个进程都能创建共享内存的时候,如果key出现相同的情况,并且一个进程需要创建的共享内存的大小要比另外一个进程要创建的共享内存小,共享内存大的进程先创建共享内存,共享内存小的进程后创建共享内存,小共享内存的进程就会获取到大的共享内存进程的共享内存,并修改其共享内存的大小和内容,从而可能导致大的共享内存进程崩溃。

解决方法:

在oflag参数中使用排他性创建,即使用IPC_EXCL标记;
key值使用IPC_PRIVATE,然后将最终返回的共享内存标识符用其他IPC方法通知给相关进程;

两种方法应视具体情况使用。

ftok生成key问题

ftok函数是把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键。原型如下:
#include <sys/ipc.h>

key_t ftok(const char * pathname, int id)
该函数将会把pathname导出的信息与id组合成一个整数IPC键。在一般的UNIX实现中,是将pathname所指定的文件索引节点号取出,前面加上id号得到key_t的返回值。如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的id值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
(具体组合情况可以参考《网络编程卷二》3.2节)

下面这段话很重要:
根据pathname指定的文件(或目录)名称,以及id参数指定的数字,ftok函数为IPC对象生成一个唯一性的键值。在实际应用中,很容易产生的一个理解是,在id相同的情况下,只要文件(或目录)名称不变,就可以确保ftok返回始终一致的键值。然而,这个理解并非完全正确,有可能给应用开发埋下很隐晦的陷阱。因为ftok的实现存在这样的风险,即在访问同一共享内存的多个进程先后调用ftok函数的时间段中,如果pathname指定的文件(或目录)被删除且重新创建,则文件系统会赋予这个同名文件(或目录)新的i节点信息,于是这些进程所调用的ftok虽然都能正常返回,但得到的键值却并不能保证相同。由此可能造成的后果是,原本这些进程意图访问一个相同的共享内存对象,然而由于它们各自得到的键值不同,实际上进程指向的共享内存不再一致;如果这些共享内存都得到创建,则在整个应用运行的过程中表面上不会报出任何错误,然而通过一个共享内存对象进行数据传输的目的将无法实现。

解决方法:

如果要确保key_t值不变,要么确保ftok的文件不被删除,要么不用ftok,指定一个固定的key_t值。

另外,建议:创建进程在通知其他进程挂接的时候,建议不使用ftok方式来获取Key,而使用文件或者进程间通信的方式告知。

同一进程多次调用shmat问题

当由shmget首次创建共享内存时,此时是不能被任何进程所访问的。为了使共享内存区可以被访问,则必须通过 shmat 函数将其附加( attach )到自己的进程空间中,这样进程就与共享内存建立了连接。该函数原型如下:

#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int flag);
shmid即shmget()函数返回的共享内存标识符,flag是读写权限组合,比如常见的几种:
#define SHM_RDONLY     XXX          /* attach read-only else read-write */
#define SHM_RND        XXX          /* round attach address to SHMLBA */
#define SHM_REMAP      XXX          /* take-over region on attach */

shmaddr指向共享内存附加的地址,它的取值可以有以下情况:

shmaddr为NULL,由内核自己选择一个地址,通常都如此;
shmaddr非NULL,则返回地址取决于调用者是否设置了flag标志位。如果flag设为SHM_RND,则共享内存区附加到由shmaddr指定的地址;否则,附加地址为 shmaddr 向下舍入一个共享内存低端边界地址后的地址 (SHMLBA ,一个常址)。LBA代表"低端边界地址(Lower Boundary Address)"

具体情况请参考《网络编程卷二》

重要部分:

在程序使用中,因为我们一般不清楚进程中哪些地址没有被占用,所以不好指定物理空间的内存要映射到本进程的虚拟内存地址,一般会让内核自己指定,也就是让shmaddr设置为NULL,这样挂载一个共享内存如果是一次调用是没有问题的,但是一个进程是可以对同一个共享内存多次 shmat进行挂载的,物理内存是指向同一块,如果shmaddr为NULL,则每次返回的线性地址空间都不同。而且指向这块共享内存的引用计数会增加。也就是进程多块线性空间会指向同一块物理地址。这样,如果之前挂载过这块共享内存的进程的线性地址没有被shmdt掉,即申请的线性地址都没有释放,就会一直消耗进程的虚拟内存空间,很有可能会最后导致进程线性空间被使用完而导致下次shmat或者其他操作失败。

解决方法:

通过判断需要申请的共享内存指针是否为空来标识是否是第一次挂载共享内存,若是则使用进行挂载,若不是则退出。

XXX_XXX()
{
	void *ptr = NULL;

	...

	if (NULL !=  ptr)
    	  return;

	ptr = shmat(shmid, ptr, 0666);
	
	...
}


共享内存删除问题

当一个进程完成某个共享内存区的使用时,它可调用shmdt断接这个内存区。原型如下:
#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr);
shmaddr是shmat函数的返回值,同样可参考《网络编程卷二》。
进程脱离共享内存区后,数据结构 shmid_ds 中的 shm_nattch 就会减 1 。但是共享段内存依然存在,只有 shm_attch 为 0 后,即没有任何进程再使用该共享内存区,共享内存区才在内核中被删除。一般来说,当一个进程终止时,它所附加的共享内存区都会自动脱离。

我们同样也可以通过shmctl函数对共享内存进行诸如删除的操作。原型如下:
int shmctl(int shmid , int cmd , struct shmid_ds *buff);
cmd参数提供了三个命令:

IPC_RMID 删除shmid标识的共享内存并拆除;
IPC_STAT (通过buff参数)向调用者返回所指定共享内存去当前的shmid_ds结构;
IPC_SET 如果进程有相应的权限,将与共享内存相关联的值设置为shmid_ds数据结构中所提供的值。

重要部分:

如果共享内存已经与所有访问它的进程断开了连接,则调用IPC_RMID子命令后,系统将立即删除共享内存的标识符,并删除该共享内存区,以及所有相关的数据结构;如果仍有别的进程与该共享内存保持连接,则调用IPC_RMID子命令后,该共享内存并不会被立即从系统中删除,而是被设置为IPC_PRIVATE状态,并被标记为"已被删除"(使用ipcs命令可以看到dest字段);直到已有连接全部断开,该共享内存才会最终从系统中消失。

一旦通过shmctl对共享内存进行了删除操作,则该共享内存将不能再接受任何新的连接,即使它依然存在于系统中!所以,可以确知,在对共享内存删除之后不可能再有新的连接,则执行删除操作是安全的;否则,在删除操作之后如仍有新的连接发生,则这些连接都将可能失败!

shmdt和shmctl的区别:

shmdt 是将共享内存从进程空间detach出来,使进程中的shmid无效化,不可以使用,但是保留空间;
shmctl(sid,IPC_RMID,0)则是删除共享内存,彻底不可用,释放空间;

总结

这篇文章介绍的东西,在我之前看过的所有介绍IPC相关的书基本都没有比较详细的介绍,也许是我看的书不够多。但是,上面的这些问题基本都是在编程使用中特别容易遇到的问题,这是我自己的亲身体验。所以,当搜到这篇文章时,忍不住多看了两眼,然后就发现真的是很珍贵的东西。每一个问题都是经验知识的积累。再次发次原文连接《Linux共享内存使用常见陷阱与分析》以示感谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: