共享内存
1. 共享内存概述
共享内存是允许两个不相关的进程访问同一个逻辑内存的进程间通信方法,是在两个正
在运行的进程之间共享和传递数据的一种非常有效的方式。
不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接
到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用 C
语言 malloc()分配的内存一样。两个进程使用共享内存通信机制如图 所示。
共享内存示意图
POSIX 共享内存区涉及四个主要步骤:
指定一个名字参数调用 shm_open,以创建一个新的共享内存区对象(或打开一个
以存在的共享内存区对象);
调用 mmap 把这个共享内存区映射到调用进程的地址空间;
调用 munmap() 取消共享内存映射;
调用 shm_unlink()函数删除共享内存段。
在编译 POSIX 共享内存应用程序时需要加上-lrt 参数。
2. 打开或创建一个共享内存区
shm_open()函数用来打开或者创建一个共享内存区,两个进程可以通过给 shm_open()
函数传递相同的名字以达到操作同一共享内存的目的。它的原型如下:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int shm_open(const char *name, int oflag, mode_t mode);
函数成功返回创建或打开的共享内存描述符,与文件描述符相同作用,供后续操作使用,
失败返回-1。
参数 name 为指定创建的共享内存的名称,其它进程可以根据这个名称来打开共享
内存;
参数 oflag 为以下值的或值:
O_RDONLY:共享内存以只读方式打开;
O_RDWR:共享内存以可读写方式打开;
O_CREAT:共享内存不存在才创建;
O_EXCL:如果指定了 O_CREAT,但共享内存已经存在时返回错误;
O_TRUNC:如果共享内存已存在则将大小设置为 0;
参数 mode 只有指定 O_CREAT 才有效指出,指出共享内存的权限,与 open()函数
类似。
注意:新创建或打开的共享内存大小默认为 0,需要设置大小才能使用。
3. 删除共享内存
当使用完共享内存后,需要将其删除,以便释放系统资源,可通过 shm_unlink()函数完
成。shm_unlink()函数原型如下:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int shm_unlink(const char *name);
函数成功返回 0,否则返回-1。参数 name 为共享内存的名字。
4. 设置共享内存大小
创建一个共享内存后,默认大小为 0,所以需要设置共享内存大小。ftruncate()函数可用
来调整文件或者共享内存的大小,它的原型如下:
#include <unistd.h>
#include <sys/types.h>
int ftruncate(int fd, off_t length);
函数成功返回 0,失败返回-1。
参数 fd 为需要调整的共享内存或者文件,length 为需要调整的大小。
5. 映射共享内存
创建共享内存后,需要将共享内存映射到调用进程的地址空间,可通过 mmap()函数来
完成。mmap()函数原型如下:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
函数成功返回映射后指向共享内存的虚拟地址,失败返回 MAP_FAILED 值。
参数如下:
addr:指向映射存储区的起始地址,通常将其设置为 NULL,这表示由系统选择该
映射区的起始地址;
len:映射的字节数;
port:对映射存储区的保护要求,对指定映射存储区的保护要求不能超过文件 open
模式访问权限。它可以为以下值的或值:
PROT_READ:映射区可读);
PROT_WRITE:映射区可写;
,PROT_EXEC:映射区可执行;
PROT_NONE:映射区不可访问。
flag:映射标志位,可为以下值的或值:
MAP_FIXED:返回值必须等于 addr。因为这不利于可移植性,所以不鼓励使
用此标志;
MAP_SHARED:多个进程对同一个文件的映射是共享的,一个进程对映射的
内存做了修改,另一个进程也会看到这种变化;
MAP_PRIVATE:多个进程对同一个文件的映射不是共享的,一个进程对映射
的内存做了修改,另一个进程并不会看到这种变化。
fd:要被映射的文件描述符或者共享内存描述符;
offset:要映射字节在文件中的起始偏移量。
6. 取消共享内存映射
已经建立的共享内存映射,可通过 munmap()函数用来取消。munmap()函数原型如下:
#include <sys/mman.h>
int munmap(void *addr, size_t length);
函数成功返回 0,否则返回-1;
参数 addr 为 mmap()函数返回的地址,length 是映射的字节数。取消映射后再对映射地
址访问会导致调用进程收到 SIGSEGV 信号。
7. 共享内存范例
两个范例实现了两个无关进程使用共享内存进行通信
的功能。一个进程往共享内存的起始地址写入一个整型数据 18,另一个进程则检测该区域
的数据,如果不是 18 则继续等待,直到该区域的数据变化为 18。
所示代码完成共享内存写操作:
先创建共享内存,
设置大小并完成映射,
随后往共享内存起始地址写入一个值为 18 的整型数据,最后取消和删除共享内存。
sample10_write 共享内存写数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#define SHMSIZE
10
#define SHMNAME "shmtest"
/* 共享内存大小,10 字节 */
/* 共享内存名称 */
int main()
{
int fd;
char *ptr;
/* 创建共享内存 */
fd = shm_open(SHMNAME, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
if (fd<0) {
perror("shm_open error");
exit(-1);
}
ftruncate(fd, SHMSIZE);
/* 设置大小为 SHMSIZE
/* 设置共享内存大小*/
ptr = mmap(NULL, SHMSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap error");
exit(-1);
}
*ptr = 18; /* 往起始地址写入 18 */
munmap(ptr, SHMSIZE); /* 取消映射 */
shm_unlink(SHMNAME); /* 删除共享内存 */
return 0;
}
程序清单则实现了共享内存的读取操作:首先创建共享内存,设置大小并映射共
享内存后,检测共享内存首字节数据是否为 18,如果不是,继续等待,否则打印显示,并
取消和删除共享内存。
程序清单 共享内存读数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#define SHMSIZE
10
/* 共享内存大小,10 字节 */
#define SHMNAME "shmtest"
/* 共享内存名称
*/
int main()
{
int fd;
char *ptr;
fd = shm_open(SHMNAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
/*创建共享内存
*/
if (fd < 0) {
perror("shm_open error");
exit(-1);
}
ptr = mmap(NULL, SHMSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);/*映射共享内存*/
if (ptr == MAP_FAILED) {
perror("mmap error");
exit(-1);
}
ftruncate(fd, SHMSIZE);
/* 设置共享内存大小
while (*ptr != 18) {
/* 读起始地址,判断值是否为 18
sleep(1);
*/
*/
/* 不是 18,继续读取 */
printf("ptr : %d\n", *ptr); /* 数据是 18,打印显示 */
munmap(ptr, SHMSIZE); /* 取消内存映射 */
shm_unlink(SHMNAME);