第54章 POSIX共享内存

        在前面的章节中介绍了两种允许无关进程共享内存以便执行IPC技术:System V共享内存(48章)和共享文件映射(49.4.2节)。这两种技术都存在一些不足。

  • System V 共享内存使用的是键和biaoshifu-,这与标准的UNIX I/O模型使用文件名和描述符的做法是不一致的。这种差异意味着使用System V 共享内存段需要一整套全新的系统调用和命令。
  • 使用一个共享文件映射来进行IPC要求创建一个磁盘文件,即使无需对共享区域进行持久存储也需要这样做。除了因需要创建文件带来的不变之外,这种技术还会带来一些文件I/O开销。

        由于存在这些不足,所以POSIX.1b定义了一组新的共享内存API:POSIX共享内存,这也是本章的主题。

54.1 概述

        POSIX共享内存能够让无关进程共享一个映射区域而无需创建一个相应的映射文件。Linux从内核2.4起开始支持POSIX共享内存。

        SUSv3并没有对POSIX共享内存的实现细节进行规定,特别是没有要求使用一个(真实或虚拟)文件系统来标识共享内存对象,但很多UNIX 实现都采用了文件系统来标识共享内存对象。一些UNIX实现将共享内粗对象名创建为标准文件系统上一个特殊位置处为文件。Linux 使用挂在于 /dev/shm 目录下的专用tmpfs文件系统(14.10节)。这个文件系统具有内核持久性--它所包含的内存对象会一直持久,即使当前不存在任何进程打开它,但这些对象会在系统关闭后丢失。

系统上POSIX共享内存区域占据的内存总量受限于底层的tmpfs文件系统的大小。这个文件系统通常会在启动时使用默认大小(如256M)进行挂载。如果有必要的话,超级用户能够通过使用命令mount -o remount,size=<num -bytes>重新挂载这个文件系统来修改他的大小。

要使用POSIX共享内存对象需要完成下列任务。

  1. 使用shm_open()函数打开一个与指定的名字对应的对象。(在51.1节中介绍了控制POSIX共享内存对象的命名规则。)shm_open()函数与open()系统调用类似,他会创建一个新共享对象或打开一个既有对象。最为函数结果,sham_open()会返回一个引用该对象的文件描述符。
  2. 将上一步中获得的文件描述符传入mmap()调用并在其flags参数中指定MAP_SHARED。着会将共享内存对象映射进进程的虚拟地址空间。与mmap()的其他用法一样,一旦映射了对象之后就能够关闭该文件描述符而不会影响到这个映射。然而,有可能需要将这个文件描述符保持在打开状态以便后续的fstat()和ftruncate()调用使用这个文件描述符(54.2节)

POSIX 共享内存上shm_open()和mmap()的关系类似于System V共享内存上shmget()和shmat()的关系。使用POSIX共享内存对象需要两步式过程(shm_open()加上mmap())而没有使用单个函数来执行两项任务是因为历史原因。在POSIX委员会增加这个特性时,mmap()调用已经存在了。实际上这里所需要做的事情是使用shm_open()调用替换open()调用,其中的差别是使用shm_open()无需在一个基于磁盘的文件系统上双肩一个文件。

        由于共享内存对象的引用是通过文件描述符来完成的,因此可以直接时候UNIX系统中已经定义好的各种文件描述符系统调用(如ftruncate())而无需增加新的用途特殊的系统调用(System V 共享内存就需要这样做)。

54.2 创建共享内存对象

        shem_open()函数创建和打开一个新的共享内存对象或打开一个既有对象。传入shm_open()的参数与传入的open()参数类似。

#incllude <fcntl.h>    /* Defines 0_* constants*/
#include <sys/stat.h>  /*Defines mode constants*/
#include <sys/mman.h>

int shm_open(const char *name,int oflag,mode_t mode);

        Returns file descriptor on success 0r -1 on error

name 参数标识出了带创建或打开的共享内存对象。oflag参数是一个改变调用行为的位掩码,表54-1对这个参数进行了总结。

         oflag 参数的用途之一是确定是打开一个既有的共享内存对象还是创建并打开一个新对象。如果oflag中不包含O_CREAT,那么就打开一个既有对象。如果制定了O_CREAT,那么在对象不存在时就创建对象。同时指定O_EXCL和O_CREAT能够确保时对象的创建者,如果对象已存在,那么就返回一个错误(EEXIST).

        oflag参数还表明了调用进程在共享内存对象上的访问模式。,其取值为O_RDONLY或O_RDWR.

        剩下的标记值O_TRUNC会导致在成功打开一个既有共享内存之后将对象的长度截断为0.

在Linux 上,阶段在制度打开时也会发生。但SUSv3声称使用O_TRUNC进行一个只读打开操作的结果是未定义的,因此在这种情况下无法可移植地依赖于某个特定地行为。

        在一个新共享内存对象被创建时。其所有权地组所有权根据调用shm_open()的进程的有效用户和组ID来设定,对象权限会根据mode参数中设置的掩码值来设定。mode参数能取的位值与文件上的权限位值是一样的(表15-4).与open()系统调用一样,mode中的权限掩码会根据进程的umask(15.4.6节)来取值。与open()不同的是,在调用shm_open()时总是需要mode参数,在不创建新对象时需要这个参数值指定为0.

        shm_open()返回的文件描述符会这是close-on-exec标记(FD_CLOEXEC,27.4节),因此当程序执行了一个exec()时文件描述符会自动关闭。(这与在执行exec()时会被解除的事实是一致的。)

        一个新共享内存对象被创建时其初始长度会被设置为0.这意味着在创建完一个新共享内存对象之后通常在调用mmap()之前需要调用ftruncate()(5.8节)来这是对象的大小。在调用mmap()之后可能还需要使用ftruncate()来根据需求扩大或收缩共享内存对象,但需要记住49.4.3节讨论过的各个要点。

        在扩展一个共享内存对象时,新增加的自己会自动被初始化为0。

        在任何时候都可以在shm_open()返回的文件描述符上使用fstat()(15.1节)以获取一个stat结构,该结构的字段会包含与这个共享内存相关的心爱,包括其大小(st_size)、权限(st_mode)、所有者(st_uid)以及组(st_gid)。(这些字段是SUSv3 唯一要求fstat()在stat结构中设置的字段,但Linux还会在时间字段中返回有意义的信息,并且会在剩下的字段中返回各种用处稍小一点的信息。)

        使用fchmod()和fchown()能够分别修改共享内存对象的权限和所有权。

示例程序

        程序清单54-1提供了一个简单的使用shm_open()、ftruncate()以及mmap()的例子。这个程序创建了一个大小通过命令行参数指定的共享内存对象并将该对象映射进进程的虚拟地址空间。(映射这一步是多余的,因为实际上不会对共享内存做任何操作,这里仅仅是为了延时如何使用mmap().)这个程序允许使用命令行选项来选择shm_open()调用使用的标记(O_CREAT和O_EXCL)。

        下面的例子使用这个程序创建了一个10000字节的共享内存对象,然在/dev/shm中使用ls命令显示出了这个对象

程序清单54-1 :创建一个POSIX共享内存对象----pshm_create.c

#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
static void usageError(const char *progName)
{
    fprintf(stderr,"Usage: %s[-cx] name size [octal-perms]\n",progName);
    fprintf(stderr,"    -c Create shared memory(O_CREAT)\n");
    fprintf(stderr,"    -x Create exclusively (O_EXCL))\n");
    exit(EXIT_FAILURE);
}

int main(int argc,char *argv[])
{
    int flags,opt,fd;
    mode_t perms;
    size_t size;
    void *addr;

    flags = O_RDWR;
    while((opt = getopt(argc,argv,"cx")) !=-1){
        switch(opt){
            case 'c':flags |= O_CREAT;  break;
            case 'x':flags |= O_EXCL;  break;
            default: usageError(argv[0]);
        }
    }

    if(optind +1>=argc)
        usageError(argv[0]);
    size = atoi(argv[optind+1]);
    perms = (argc < optind+2)?(S_IRUSR|S_IWUSR):atoi(argv[optind+2]);

    /*Create shared memory object and set its size*/
    fd = shm_open(argv[optind],flags,perms);
    if(fd == -1)
    {
        perror("shm_open:");
        return -1;
    }

    if(ftruncate(fd,size) == -1)
    {
        perror("ftruncate:");
        return -1;
    }

    /*Map shared memory object*/
    addr = mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(addr == MAP_FAILED)
    {
        perror("ftruncate:");
        return -1;
    }

    exit(EXIT_SUCCESS);
}

54.3 使用共享内存对象

        程序清单54-2和程序清单54-3演示了如何使用一个共享内存对象将数据从一个几次呢很难过传输到另一个进程中。程序清单54-2将其第二个命令行参数中包含的字符串复制到了一个名字通过其第一个命令行参数指定的既有共享内存对象中。在映射这个对象和执行赋值之前,这个程序使用了ftruncate()来将共享内存对象的长度设置为与待渎职的字符串的长度一样。

程序清单54-2 :将数据复制进一个POSIX共享内存对象

#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
    int fd;
    size_t len;   /*size of shared mem7ory object*/
    char *addr;

    if(argc !=3 || strcmp(argv[1],"--help") == 0)
    {
        printf("%s shm-name string\n",argv[0]);
        return 0;
    }

     fd = shm_open(argv[1],O_RDWR,0);
    if(fd == -1)
    {
        perror("shm_open:");
        return -1;
    }
    len = strlen(argv[2]);
    if(ftruncate(fd,len) == -1)  /*Resize object to hold string*/
    {
        perror("ftruncate:");
        return -1;
    }
    printf("Resized to %ld bytes\n",(long)len);

    addr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(addr == MAP_FAILED)
    {
        perror("mmap:");
        return -1;
    }

    if(close(fd) == -1)
    {
        perror("close:");        /**fd  is no longer needed*/
        return -1;
    }

    printf("copying %ld bytes\n",(long)(len));
    memcpy(addr,argv[2],len);   // Copy string to shared memory

    exit(EXIT_SUCCESS);
}

程序清单54-3中的程序在标准输出上显示了名字通过其命令行参数指定的既有共享内存对象的字符串。在调用shm_open()之后,这个程序使用了fstat()来确定共享内存的大小并在映射该独享的mmap()调用中和打印这个字符串的write()调用中使用这个值。

程序清单54-3:从一个POSIX共享内存对象中赋值数据

#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc,char *argv[])
{
    int fd;
    char *addr;
    struct stat sb;

    if(argc !=2 || strcmp(argv[1],"--help") == 0)
    {
        printf("%s shm-name string\n",argv[0]);
        return 0;
    }

    fd = shm_open(argv[1],O_RDONLY,0);
    if(fd == -1)
    {
        perror("shm_open:");
        return -1;
    }
    /*Use shared memory object size as length argument for mmap()
    and as number of bytes to write()*/

    if(fstat(fd,&sb) == -1)
     {
        perror("fstat:");
        return -1;
    }

    addr = mmap(NULL,sb.st_size,PROT_READ,MAP_SHARED,fd,0);
    if(addr == MAP_FAILED)
    {
        perror("mmap:");
        return -1;
    }

    if(close(fd) == -1)
    {
        perror("close:");        /**fd  is no longer needed*/
        return -1;
    }

    write(STDOUT_FILENO,addr,sb.st_size);
   printf("\n");

    exit(EXIT_SUCCESS);
}

下面的shell会话演示了如何使用程序清单54-2和程序清单54-3中的程序,首先使用程序清单54-1中的程序创建一个长度为2(0的话会报错)的共享内存对象。

 然后使用程序清单54-2中的程序将一个自负床复制进共享内存对象

从上面的输出可以看出这个程序重新设定了共享内存对象的大小使之具备足够的空间来存储指定的字符串。

最后使用程序清单54-3中的程序来显示共享内存对象中的字符串。

 应用程序通常需要使用一些同步技术来让进程协调他们对共享内存的访问。在这里给出的示例shell会话中,这种协调是通过用户一个一个运行这些程序来完成的。通常,应用程序会使用一种同步原语(如信号量)来协调对共享内存对象的访问。

54.4 删除共享内存对象

        SUSv3要求POSIX共享内存对象至少具备内核持久性,即他们会持续存在直到显式删除或系统重启。当不在需要一个共享内存对象时就应该使用shm_unlink()删除它。

#include <sys/mman.h>
int shm_unlink(const char *name);
        Returns 0 on success,or -1 onerror

        shm_unlink()函数会删除通过name指定的共享内存对象。删除一个共享内存对象不会影响对象的既有映射(他会保持有效知道相应的进程调用munmap()或终止),但会组织后续的shm_open()调用打开这个对象。一旦所有进程都接触这个对象,对象就会被删除;其中的内容会丢失。

        程序清单54-4中的程序使用shm_unlink()来删除通过程序命令行参数指定的共享内存对象。

程序清单54-4:使用shm_unlink()来断开连接一个POSIX共享内存对象---pshm_unlink.c

#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

int main(int argc,char *argv[])
{
    if(argc !=2 || strcmp(argv[1],"--help") == 0)
    {
        printf("%s shm-name string\n",argv[0]);
        return -1;
    }

    if(shm_unlink(argv[1]) == -1)
    {
        perror("shm_unlink:");
        return -1;
    }

    exit(EXIT_SUCCESS);
}

54.5 共享内存 API比较

        到现在为止已经考虑了集中不同的在无关进程间共享内存区域的技术。

  • System V共享内存(48章)
  • 共享内存映射(49.4.2节)
  • POSIX共享内存对象(本章主题)

本节中列出的很多要点也适用于共享匿名映射(49.7)它用于通过fikr关联的进程间共享内存。

        下列要点适用于所有这些技术。

  • 他们提供了快速IPC,应用程序必须要使用一个信号量(或其他同步原语)来同步对共享区的访问。
  • 一旦共享内存对象区域被映射进进程的虚拟地址空间之后,他就与进程的内存空间中的其他部分无异了。
  • 系统会以类似的方式将共享内存区域放置进进程的虚拟地址空间中。在48.5节中介绍System V 共享内存的时候对这种防止进行了概括。Linux特有的/proc/PID/maps文件会列出与所有种类的共享内存区域相关的信息。
  • 假设不会将一个共享内存区域映射到一个固定的地址处,那么就需要确保所有对区域中的位置的引用会使用偏移量来表示,而不是使用指针来表示,这是因为这个区域在不同进程中所处的虚拟地址可能是不同的(48.6节)。
  • 在第50章中介绍的操作虚拟内存区域的函数可被应用于使用这些技术中任意一项技术创建的共享内存区域。

        在这些共享内存技术之间还存在一些显著的差异。

  • 一个共享文件映射的内容会与底层映射文件同步意味着存储在共享内存区域中的数据能够在系统重启之间得到持久保存。
  • System V和POSIX共享内存使用了不同的机制来标识和引用共享内存对象。System V使用了其自己的键和标识符模型,他们与标准的 UNIX I/O模型是不匹配的并且需要单独调用(如shmctl())和命令(ipcs和ipcrm)。与之形成对比的是,POSIX共享内存使用了名字和描述符,其结果是使用各种既有的UNIX系统调用(如fstat()和 fchmod())就能够查看和操作共享内存对象了。
  • System V共享内存段的大小在创建时(shmget())就确定了。与之形成对比的是,在基于文件的映射和POSIX共享内存对象上可以使用ftruncate()来调整底层对象的大小,然后使用munmap()和mmap()(或Linux特有的mremap())重建映射。
  • 因为历史原因,System V 共享内存受支持成都比mmap()和POSIX共享内存对象广的多,尽管在大多数UNIX实现都已经提供这些所有技术。

除了最后有关可移植性的一点之外,上面列出的差异都是共享文件映射和POSIX共享内存对象的优势。因此在新应用程序中应该优先从这些接口中挑选一个使用,而不是System V共享内存。至于选择哪个几口取决于是否需要一个持久性存储。共享文件映射提供了持久性存储,而POSIX共享内存对象则避免了在无需持久化存储时使用磁盘文件所产生的开销。

54.6 总结

        POSIX 共享内存对象用来在无关进程间共享yik-内存区域而无需创建一个底层的磁盘文件。为创建POSIX共享内存对象需要使用shm_open()调用来替换通常在mmap()调用之前调用的open()。shm_open()调用会在基于内存的文件系统中创建一个文件,并且可以使用传统的文件描述符系统调用在这个虚拟文件上执行各种操作。特别的,必须要使用ftruncate()来设置共享内存的大小,因为其初始长度为零。

        现在已经介绍了无关进程的三种共享内存区域技术:System V共享内存、共享文件映射以及POSIX共享内存对象。这三种技术之间存在很多相似之处,但也存在一些重要的差别,除了可移植性外,这些差异都对共享文件映射和POSIX共享内存对象有利。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值