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

共享内存

2015-11-22 21:54 633 查看
    共享内存是IPC形式中最快的方式。一旦将这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传递就不再涉及内核。然而往该共享内存区存放信息或从中取走信息的进程间通常需要某种方式的同步。

    实现共享内存的方式:利用mmap函数、使用Posix共享内存区、使用SystemV共享内存区,下面分别介绍。

1.1 利用mmap进行内存映射

    作用:open文件之后调用mmap把它映射到调用进程地址空间的某个文件。之后所有的I/O都在内核的掩盖下完成,只需编写存取映射区中各个值的代码即可,而不必调用read、write或lseek进行操作。

    需要说明的一点:并不是所有文件都能进行内存映射,如终端和套接字的描述符不支持内存映射,试图将其映射到内存将导致mmap返回一个错误。这些类型的描述符必须使用read和write(或者相关变体)来访问。

    相关函数:mmap,mumap(删除共享内存)及msync(用于同步共享内存和文件的内容),下面仅介绍mmap。

void *mmap(void *addr, size_t len, intprot, inf flag, off_t offset);

返回值:成功返回被映射区的起始地址,失败返回MAP_FAILED

参数说明:

         addr指定描述符fd应被映射到的进程内空间的起始地址,为了保证可移植性,通常被指定为一个空指针,这样就有内核自行选择起始地址。

         len参数为映射到进程地址空间中的 字节数,它从被映射文件开头的offset个字节处开始算。

         prot参数指定了映射内存的读、写、执行等相关权限,取值可为PROT_READ、PROT_WRITE、PROT_EXEC、PROT_NONE(不可访问)

         flag指示该共享内存的作用效果,通常取为MAP_SHARED(变量是共享的,调用进程对被映射数据所作的修改对于共享该对象的所有进程可见,并改变了底层支持对象——通常是文件);如果指定为MAP_PRIVATE,那么调用进程对被映射数据所做的修改只对该进程可见,同时也不改变底层支持对象;以上两个标志必须指定一个,当需要准确解释addr指向的地址参数时或上MAP_FIXED。

    作用:分别用于把一个文件或一个Posix共享内存区对象映射到调用进程的地址空间,有三种目的:

1)      使用普通文件以提供内存映射I/O(应用最广泛)

2)      使用特殊文件以提供匿名内存映射  

有两种方法:

4.4BSD提供匿名内存映射,避免了文件的创建和打开。其通过把mmap的flag参数指定成MAP_SHARED|MAP_ANON,把fd参数指定为-1.忽略offset参数。这样的内存区被初始化为0。(由fork或具有亲缘关系的线程使用)

SVR4提供了/dev/zero设备文件,open它之后可以使用得到的文件描述符,然后里面mmap进行内存映射,之后执行关闭即可。这样的映射保证内存映射区被初始化为0(可由无亲缘关系的进程使用)

3)      使用shm_open以提供无亲缘关系进程间的Posix共享内存区

 

    访问共享内存时,文件大小和内存映射大小可以不同,下面进行访问情况的两点说明:

a)        文件大小等于内存映射区的大小:只能访问相应大小的共享内存,超出的话会引发SIGSEGV信号

b)        文件大小小于共享内存区的大小:只能访问文件大小+最后一页上该对象以远的那些字节,访问超过的内存(超过文件大小但小于共享内存大小)时,会引发SIGBUS信号。若想访问这部分内存,需要增加文件大小,可用lseek或者ftruncate函数调整文件大小。

1.2 POSIX共享内存

         Posix共享内存是由shm_open打开一个Posix IPC名字(也许是在文件系统中的一个文件),所返回的描述符由mmap函数映射到当前进程的地址空间。即涉及以下步骤:

1、  指定一个名字参数调用shm_open,以创建一个新的共享内存区对象或打开一个已存在的共享内存区对象

2、  调用mmap把这个共享内存区映射到调用进程的地址空间

相关函数:

头文件:#include <sys/mman.h>

创建或打开共享内存区对象:

int shm_open(const char *name, into flag,mode_t mode)  //成功返回非负描述符,若出错则为-1,name为绝对路径的Posix IPC名字形式

删除共享内存区对象:

int shm_unlink(const char *name)   //成功返回0,失败-1

p.s:当调用mmap完成内存映射之后就可以关闭描述符了,因为已经得到共享内存区的指针了;之后的操作同mmap。与mmap映射文件实现共享内存基本类似,不同的是获取文件描述符的方式。

1.3 System V共享内存

         SystemV共享内存区在概念上类似与Posix共享内存区。其获取共享内存区的方式为先调用shmget,再调用shmat,对于每个共享内存区,内存维护一个shmid_ds对象

相关函数:

头文件 #include <sys/shm.h>

1)      创建或打开:

int shmget(key_t key, size_t size, intoflag)   //若成功则为共享内存区对象,若出错则为-1;其中key可以是ftok函数的返回值,也可以是IPC_PRIVATE;size指示了共享内存区的大小(以字节为单位),当实际操作为访问一个已存在的共享内存区时,应该为0,而创建时则应大于0

p.s:shmget并没有给调用进程提供访问该内存区的手段,故需要调用shmat

2)      附加共享内存区到调用进程的地址空间:

void *shmat(int shmid, const void *shmaddr,int flag); //若成功则返回映射区的起始地址,若出错则为-1;shmaddr一般取为0,这样系统会替调用者选择地址,这是推荐的的方法(可移植性好),若不为0的话则附加由shmaddr参数指定的地址,这时还和flag参数有关

断开共享内存(当使用完后调用):

int shmdt(const void * shmaddr);    //成功返回0,失败返回-1

p.s:当进程终止时,它当前附接着的所有共享内存区都自动断接掉。函数并不会删除共享内存,执行删除的话需要以参数为IPC_RMID调用shmdt

3)      控制操作:

int shmctl(int shmid, int cmd, structshmid_ds * buff); //若成功返回0,若出错为-1

当cmd取不同值时,函数可以实现三个功能如下:

IPC_RMID:从系统中删除指定的共享内存,这时buff参数置空

IPC_SET:给所指定的共享内存区设置其shmid_ds结构的以下三个成员:shm_perm.uid,shm_perm.did及shm_perm.mode,它们的值来自buff参数的相应成员。shm_ctime的值也用当前时间替换。

IPC_STAT:获取所指定共享内存当前的shmid_ds结构

 

与System V消息对了和System V信号量一样,System V共享内存区也存在特定的系统限制。可以在共享内存相关manual 文件中查看,如man shmget
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息