Linux 支持两种方式的共享内存:System V 和 POSIX 共享内存。
1. POSIX 共享内存
1.1 POSIX 共享内存的由来
System V 共享内存和共享文件映射的不足:
- System V 共享内存模型使用的是键和标识符,这与标准的 UNIX I/O 模型使用文件名和描述符的做法是不一致的。这种差异意味着使用 System V 共享内存需要一整套全新的系统调用和命令。
- 使用一个共享文件映射来进行 IPC 要求创建一个磁盘文件,即使无需对共享区域进行持久存储也需要这样做。除了因需要创建文件所带来的不便之处,这种技术还会带来一些文件 I/O 开销。
基于以上不足,POSIX.1b 定义了一组新的共享内存 API: POSIX 共享内存。
1.2 POSIX 共享内存概述
POSIX 共享内存能够让无关进程共享一个映射区域而无需创建一个相应的映射文件。Linux 从内核 2.4 起开始支持 POSIX 共享内存。
很多类 UNIX 实现采用了文件系统来标识共享内存对象。一些 UNIX 实现将共享内存对象名创建为标准文件系统上一个特殊位置处的文件。Linux 使用挂载于 /dev/shm 目录下的专用 tmpfs 文件系统. 这个文件系统具有内核持久性--它所包含的共享内存对象会一直持久,即使当前不存在任何进程打开它,但这些对象会在系统关闭之后丢失.
系统上 POSIX 共享内存区域占据的内存总量受限于底层的 tmpfs 文件系统的大小。这个文件系统通常会在启动时使用默认大小(如 256 MB)进行挂载。如果有必要的话,超级用户能够通过使用命令 mount -o remount,size=
重新挂载这个文件系统来修改它的大小。
使用 POSIX 共享内存对象的流程:
- 使用 shm_open() 函数打开一个与指定的名字对应的对象。shm_open() 函数与 open() 系统调用类似,它会创建一个新共享对象或打开一个既有对象。作为函数结果,shm_open() 会返回一个引用该对象的文件描述符。
- 将上一步中获得的文件描述符传入 mmap() 调用并在其 flags 参数中指定 MAP_SHARED。这会将共享内存对象映射进进程的虚拟地址空间。与 mmap() 的其他用法一样,一旦映射了对象之后就能够关闭该文件描述符而不会影响到这个映射。然后,有可能需要将这个文件描述符保持在打开状态以便后续的 fstat() 和 ftruncate() 调用使用这个文件描述符。
1.3 创建共享内存对象
shm_open() 函数创建和打开一个新的共享内存对象或打开一个既有对象。传入 shm_open() 的参数与传入 open()的参数类似。
#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 参数的位值:
- 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
", (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("
");
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