Posix与System V共享内存区
-
共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,它是在多个进程之间对内存段进行映射的方式实现内存共享的。
-
这是IPC最快捷的方式,因为共享内存方式的通信没有中间过程,而管道、消息队列等方式则是需要将数据通过中间机制进行转换;
-
与此相反,共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是在同一块物理空间,仅仅是地址不同而已,因此不需要进行复制,可以直接使用此段空间。
一、Posix共享内存区
Posix提供了两种在无亲缘关系进程之间共享内存的方法:
-
内存映射文件: 由open函数打开,由mmap函数把得到的描述符映射到当前进程地址空间中的 一 个文件。并给出了它在父子进程间共享内存区时的用法。内存映射文件也可以在无亲缘关系的进程间共享。
-
共享内存区对象: 由
sbm_open
打开一个Posix.1 IPC名字(也许是在文件系统中的一个路径名),所返回的描述符由mmap函数映射到当前进程的地址空间。
两种技术都需要调用mmap
, 差别在于作为mmap
的参数之 一 的描述符的获取手段:通过open或通过shm,_open。下图展示了这个差别。Posix把两者合称为内存区对象。
1.概述
要使用 POSIX 共享内存对象需要完成下列任务:
-
使用
shm_open()
函数打开一个与指定的名字对应的对象。shm_open()函数与 open()系统调用类似,它会创建一个新共享对象或打开一个既有对象。作为函数结果,shm_open()
会返回一个引用该对象的文件描述符。 -
将上一步中获得的文件描述符传入
mmap()
调用并在其flags
参数中指定MAP_SHARED
。这将共享内存对象映射进进程的虚拟地址空间。与mmap()
一样,一旦映射了对象之后就能够关闭该文件描述符而不会影响到这个映射。然而,有可能需要将这个文件描述符保持在打开状态以便后续的fstat()和ftruncate()
调用使用这个文件描述符。
由于共享内存对象的引用是通过文件描述符来完成的,因此可以直接使用Unix系统中已经定义好的各种文件描述符系统调用(如 ftruncate())而无需增加新的用途特殊的系统调用(System V 共享内存就需要这样做)。
2.shm_open和shm_unlink函数
Posix共享内存区涉及以下两个步骤要求:
- 指定一个名字参数调用
shm_open,
以创建一 个新的共享内存区对象或打开一 个已存在的共享内存区对象。 - 调用mmap把这个共享内存区映射到调用进程的地址空间。
传递给shm_open
的名字参数随后由希望共享该内存区的任何其他进程使用。
#include<sys/mman.h>
//创建和打开一个新共享内存对象或者打开一个既有对象
int shm_open(const char *name,int oflag,mode_t mode);
//成功返回非负描述符,错误返回-1。
//删除共享内存对象
int shm_unlink(const char *name);
//成功返回0错误-1。
name 参数标识出了待创建或待打开的共享内存对象,不能有任何斜杠符。
oflag参数属性值如下:
结合使用标志:
-
O_EXCL或O_TRUNC
。 -
O_CREAT和O_EXCL
。
如果随O_RDWR
指定O_TRUNC
标志,而且所需的共享内存区对象已经存在,那么它将被截短成0长度。
mode参数指定权限位,在指定了O_CREAT
标志的前提下使用:
-
与
mq_open和sem_open
函数不同,shm_open
的mode
参数总是必须指定。如果没有指定O_CREAT
标志,那么该参数可以指定为0。 -
shm_open
的返回值是一个整数描述符,它随后用作mmap
的第五个参数。 -
shm_unlink删除一个名字不会影响对于其底层支撑对象的现有引用,直到对于该对象的引用全部关闭为止。删除一个名字仅仅防止后续的
open、mq_open或sem_open
调用取得成功。
3.ftruncate和fstat函数
处理mmap时候,普通文件或共享内存区对象的大小都可以通过ftruncate
修改。
#include<unistd.h>
int ftruncate(int fd,off_t length);
Posix就该函数对普通文件和共享内存大小的定义稍有不同:
-
普通文件:若该文件的大小大于length参数,额外的数据就被丢弃掉;若该文件的大小小于length,那么该文件是否修改以及其大小是否增长是未加说明的。实际上对于一个普通文件,把它的大小扩展到length字节的可移植方法是:先
lseek
到偏移为length-1
处,然后write
1个字节的数据。 -
共享内存区对象:
ftruncate
把该对象的大小设置成length
字节。
调用ftruncate来指定新创建的共享内存区对象的大小,或者修改已存在的对象的大小。当打开 一 个已存在的共享内存区对象时,我们可调用fstat来获取有关该对象的信息。
#include<sys/types.h>
#include<sys/stat.h>
int fstat(int fd,struct stat *buf);
//stat结构有12个或以上的成员,然后当fd指代一个共享内存区对象时,只有四个成员含义信息:
struct stat{
...
mode_t st_mode;
uid_t st_uid;
gid_t st_gid;
off_t st_size;
...
}
4.给一个共享的计数器持续加1 例子
下面例子由多个进程给存放在共享内存区中的某个计数器持续加1。
- 把该计数器存放在一个共享内存区中,并用一个有名信号量来同步,不过不再需要父子进程关系了。
- Posix共享内存区对象和Posix有名信号量都是以名字来访间的,因此将给共享的计数器待续加1的各个进程间可以没有亲缘关系,不过它们都得知道该共享内存区和该信号量的IPC名字,并有访问这两个IPC对象的足够权限。
下列程序给出的服务器程序创建所指定的共享内存区对象,创建并初始化所指定的信号量,然后终止。
struct shmstruct {
/*存储在共享内存中的结构 */
int count;
};
sem_t *mutex; /* 指向命名信号量的指针*/
int main(int argc, char **argv)
{
int fd;
struct shmstruct *ptr;
if (argc != 3)
err_quit("usage: server1 <shmname> <semname>");
//使用shm_unlink防止共享内存区对象已经存在
shm_unlink(px_ipc_name(argv[1])); /*如果失败,则确定*/
/* 创建对象,设置其大小,映射它,关闭描述符 */
fd = shm_open(px_ipc_name(argv[1]), O_RDWR | O_CREAT | O_EXCL, FILE_MODE);
//将该对象的大小设置成shmstruct结构大小
ftruncate(fd, sizeof(struct shmstruct));
//该对象映射到调用进程的地址空间
ptr = mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
//关闭该对象的描述符
close(fd);
//sem_unlink防止所需信号量存在
sem_unlink(px_ipc_name(argv[2])); /*如果失败,则确定*/
//创建有名信号量,初始化为1.
mutex =sem_open(px_ipc_name(argv[2]), O_CREAT | O_EXCL, FILE_MODE, 1);
sem_close(mutex);
exit