Linux编程之共享内存

Linux 支持两种方式的共享内存:System V 和 POSIX 共享内存。函数

1. POSIX 共享内存

1.1 POSIX 共享内存的由来

System V 共享内存和共享文件映射的不足:ui

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

基于以上不足,POSIX.1b 定义了一组新的共享内存 API: POSIX 共享内存。code

1.2 POSIX 共享内存概述

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

不少类 UNIX 实现采用了文件系统来标识共享内存对象。一些 UNIX 实现将共享内存对象名建立为标准文件系统上一个特殊位置处的文件。Linux 使用挂载于 /dev/shm 目录下的专用 tmpfs 文件系统. 这个文件系统具备内核持久性--它所包含的共享内存对象会一直持久,即便当前不存在任何进程打开它,但这些对象会在系统关闭以后丢失.进程

系统上 POSIX 共享内存区域占据的内存总量受限于底层的 tmpfs 文件系统的大小。这个文件系统一般会在启动时使用默认大小(如 256 MB)进行挂载。若是有必要的话,超级用户可以经过使用命令 mount -o remount,size= 从新挂载这个文件系统来修改它的大小。 ip

使用 POSIX 共享内存对象的流程:内存

  • 使用 shm_open() 函数打开一个与指定的名字对应的对象。shm_open() 函数与 open() 系统调用相似,它会建立一个新共享对象或打开一个既有对象。做为函数结果,shm_open() 会返回一个引用该对象的文件描述符。
  • 将上一步中得到的文件描述符传入 mmap() 调用并在其 flags 参数中指定 MAP_SHARED。这会将共享内存对象映射进进程的虚拟地址空间。与 mmap() 的其余用法同样,一旦映射了对象以后就可以关闭该文件描述符而不会影响到这个映射。而后,有可能须要将这个文件描述符保持在打开状态以便后续的 fstat() 和 ftruncate() 调用使用这个文件描述符。

1.3 建立共享内存对象

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

#include <sys/mman.h>
#include <sys/stat.h>        /* For mode constants */
#include <fcntl.h>           /* For O_* constants */

int shm_open(const chart *name, int oflag, mode_t mode);
        Returns file descriptor on success, or -1 on error.
        
Link with -lrt.

name 参数标识出了待建立或待打开的共享内存对象。oflag 参数是一个改变调用行为的位掩码。字符串

oflag 参数的位值:string

  • O_CREAT: 对象不存在时建立对象,一个新的共享内存对象最初长度为 0,对象的大小可使用 ftruncate 来设置。
  • O_EXCL: 与 O_CREAT 互斥地建立对象,即若是 O_CREAT 已经指定了,而且指定名字的共享内存对象已经存在,则会返回错误。
  • O_RDONLY: 打开只读访问。
  • O_RDWR: 打开读写访问。
  • O_TRUNC: 将对象长度截断为零。

在一个新共享内存对象被建立时,其全部权和组全部权将根据调用 shm_open() 的进程的有效用户和组 ID 来设定,对象权限将会根据 mode 参数中设置的掩码值来设定。mode 参数能取的位值与文件上的权限位值是同样的。与 open() 系统调用同样,mode 中的权限掩码将会根据进程的 umask 来取值。与 open() 不一样的是,在调用 shm_open() 时老是须要 mode 参数,在不建立新对象时须要将这个参数值指定为 0.

shm_open() 返回的文件描述符会设置 close-on-exec 标记,所以当程序执行了一个 exec() 时文件描述符会被自动关闭。

一个新共享内存对象被建立时其初始长度被会设置为 0。这意味着在建立完一个新共享内存对象以后一般在调用 mmap() 以前须要调用 ftruncate() 来设置对象的大小。在调用完 mmap() 以后可能还须要使用 ftuncate() 来根据需求扩大或收缩共享内存对象。

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

在任什么时候候均可以在 shm_open() 返回的文件描述符上使用 fstat() 以获取一个 stat 结构,该结构的字段会包含与这个共享内存对象相关的信息,包括其大小(st_size)、权限(st_mode)、全部者(st_uid)以及组(st_gid)。

使用 fchmod() 和 fchown() 可以分别修改共享内存对象的权限和全部权。

示例程序: pshm_create.c

该程序建立了一个大小经过命令参数指定的共享内存对象并将该对象映射进进程的虚拟地址空间。

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

int main(int argc, char *argv[])
{
    int flags, opt, fd;
    mode_t perms;
    size_t size;
    void *addr;
    
    /* Create shared memory object and set its size */
    fd = shm_open(argv[1], O_RDWR|O_CREAT, 0777);
    if (fd == -1) 
    {
        printf("shm_open failed");
        exit(-1);
    }
    
    if (ftruncate(fd, 10000) == -1)
    {
        printf("ftruncate failed");
        exit(-1);
    }
    
    /* Map shared memory object */
    addr = mmap(NULL, 10000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) 
    {
        printf("mmap failed");
        exit(-1);
    }
    
    exit(EXIT_SUCCESS);
}

如上程序建立一各 10000 字节的共享内存对象:

# gcc pshm_create.c -o pshm_create -lrt
# ./pshm_create demo_shm
# ls -l /dev/shm/demo_shm 
-rwxr-xr-x 1 root root 10000 Jun 16 03:34 /dev/shm/demo_shm

1.4 使用共享内存对象

pshm_write.c

将数据复制进一个 POSIX 共享内存对象.

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

int main(int argc, char *argv[])
{
    int fd;
    size_t len;
    char *addr;
    
    /* Open existing object */
    fd = shm_open(argv[1], O_RDWR, 0); 
    if (fd == -1) 
    {
        printf("shm_open failed");
        exit(-1);
    }
    
    len = strlen(argv[2]);
    /* Resize object to hold string */
    if (ftruncate(fd, len) == -1) 
    {
        printf("ftruncate failed");
        exit(-1);
    }
    
    addr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) 
    {
        printf("mmap failed");
        exit(-1);
    }
    
    if (close(fd) == -1)
    {
        printf("close failed");
        exit(-1);
    }
    
    printf("copying %ld bytes\n", (long)len);
    /* Copy string to shared memory */
    memcpy(addr, argv[2], len);
    exit(EXIT_SUCCESS);
}

向 1.3 建立的共享内存对象 demo_shm 写入数据:

# gcc pshm_write.c -o pshm_write -lrt
# ls -l /dev/shm/demo_shm 
-rwxr-xr-x 1 root root 10000 Jun 16 03:34 /dev/shm/demo_shm
# ./pshm_write demo_shm 'hello'
copying 5 bytes
# ls -l /dev/shm/demo_shm 
-rwxr-xr-x 1 root root 5 Jun 16 03:46 /dev/shm/demo_shm

pshm_read.c

从一个 POSIX 共享内存对象中复制数据。

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

int main(int argc, char *argv[])
{
    int fd;
    char *addr;
    struct stat sb;
    
    /* Open existing object */
    fd = shm_open(argv[1], O_RDONLY, 0); 
    if (fd == -1) 
    {
        printf("shm_open failed");
        exit(-1);
    }
    
    /* Use shared memory object size as length argument for mmap()
     * and as number of bytes to write() */
    
    if (fstat(fd, &sb) == -1)
    {
        printf("fstat failed");
        exit(-1);
    }
    
    addr = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) 
    {
        printf("mmap failed");
        exit(-1);
    }
    
    if (close(fd) == -1)
    {
        printf("close failed");
        exit(-1);
    }
    
    write(STDOUT_FILENO, addr, sb.st_size);
    printf("\n");
    exit(EXIT_SUCCESS);
}

显示刚才写到共享内存对象 demo_shm 中的字符串:

# gcc pshm_read.c -o pshm_read -lrt
# ./pshm_read demo_shm
hello

1.5 删除共享内存对象

POSIX 共享内存对象具备内核持久性,即它们会持续存在直到被显示删除或系统重启。当再也不须要一个共享内存对象时就应该使用 shm_unlink() 删除它。

#include <sys/mman.h>

int shm_unlink(const char *name);
        Returns 0 on success, or -1 on error

Link with -lrt.

shm_unlink() 函数会删除经过 name 指定的共享内存对象。删除一个共享内存对象不会影响对象的既有映射(它会保持有效直到相应的进程调用 munmap() 或终止),但会阻止后续的 shm_open() 调用打开这个对象。一旦全部进程都接触映射这个对象,对象就会删除,其中的内容会丢失。

pshm_unlink.c

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

int main(int argc, char *argv[])
{
    if (shm_unlink(argv[1]) == -1)
    {
        printf("shm_unlink failed");
        exit(-1);
    }
    
    exit(EXIT_SUCCESS);
}

删除上面建立的共享内存对象 demo_shm:

# gcc pshm_unlink.c -o pshm_unlink -lrt
# ./pshm_unlink demo_shm
相关文章
相关标签/搜索