共享内存是最高效的IPC机制,因为它不涉及进程之间的任何数据传输。这种高效率带来的问题是,我们必须用其他辅助手段来同步进程对共享内存的访问,否则会产生竞态条件。因此,共享内存通常和其他进程间通信方式一起使用。
Linux下有三种共享内存的IPC技术:System V共享内存、共享文件映射(mmap)、POSIX共享内存。
我们在这里只介绍POSIX共享内存。
要使用POSIX共享内存对象需要完成下列任务。
- 使用shm_open()函数打开一个与指定的名字对应的对象。shm_open()函数与open()系统调用类似,它会创建一个新的共享对象或打开一个既有对象。作为函数结果,shm_open()会返回一个引用该对象的文件描述符。
- 将上一步中获得的文件描述符传入mmap()调用并在其flags参数中指定MAP_SHARED。这会将共享内存对象映射进进程的虚拟地址空间。与mmap()的其他用法一样,一旦映射了对象之后就能够关闭该文件描述符而不会影响到这个映射。
创建共享内存对象
shm_open()函数创建和打开一个新的共享内存对象或打开一个既有对象。传入shm_open()的参数与传入open()的参数类似。
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/mman.h>
int shm_open(const char *name,int oflag,mode_t mode);
name参数标识出了待创建或待打开的共享内存对象。oflag参数是一个改变调用行为的位掩码。
oflag参数的位值
标记 | 描述 |
---|---|
O_CREAT | 对象不存在时创建对象 |
O_EXCL | 与O_CREAT互斥地创建对象 |
O_RDONLY | 打开只读访问 |
O_RDWR | 打开读写访问 |
O_TRUNC | 将对象长度截断为零 |
oflag参数的用途之一是确定是打开一个既有的共享内存对象还是创建并打开一个新对象。同时指定O_EXCL和O_CREAT能够确保调用者是对象的创建者,如果对象已经存在,那么就返回一个错误(EEXIST)。
oflag参数还表明了调用进程在共享内存对象上的访问模式,其取值为O_RDONLY或O_RDWR。
在一个新共享内存对象被创建时,其所有权和组所有权将根据调用shm_open()的进程的有效用户和组ID来设定,对象权限将会根据mode参数中设置的掩码值来设定。
与open()系统调用一样,mode中的权限掩码将会根据进程的umask来取值。与open()不同的是,在调用shm_open()时总是需要mode参数,在不创建新对象时需要将这个参数值指定为0。
shm_open()返回的文件描述符会设置close-on-exec标记,因此当程序执行了一个exec()时文件描述符会被自动关闭。
一个新共享内存对象被创建时其初始长度会被设置为0。这意味着创建完一个新共享内存对象之后通常在调用mmap()之前需要调用ftruncate()来设置对象的大小。在调用完mmap()之后可能还需要使用ftruncate()来根据需求扩大或收缩共享内存对象。
在扩展一个共享内存对象时,新增加的字节会自动被初始化为0。
使用共享内存
使用步骤:
1. 调用shm_open()函数创建或打开一个共享内存对象。
2. 调用ftruncate()调整对象大小。
3. 调用mmap()函数,将内存映射到相应的进程虚拟内存中。
mmap()系统调用在调用进程的虚拟地址空间中创建一个新映射。
#include<sys/mman.h>
void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset);
addr参数指定了映射被置放的虚拟地址。如果将addr指定为NULL,那么内核会为映射选择一个合适的地址。这是创建映射的首选做法。或者在addr中指定一个非NULL值时,内核会在选择将映射放置在何处时将这个参数值作为一个提示信息来处理。在实践中,内核至少会将指定的地址舍入到最近的一个分页边界处。
length参数指定了映射的字节数。
prot参数是一个位掩码,其取值见下表:
值 | 描述 |
---|---|
PROT_NONE | 区域无法访问 |
PROT_READ | 区域内容可读取 |
PROT_WRITE | 区域内容可修改 |
PROT_EXEC | 区域内容可执行 |
flags参数是一个控制映射操作各个方面的选项的位掩码。这里我们要设置为MAP_SHARED。
fd参数我们传入shm_open返回的描述符。
offset参数代表偏移量,设置为0代表从映射文件的开头开始映射。
代码示例
下面两个程序演示了如何使用一个共享内存对象将数据从一个进程传输到另一个进程中。
第一个程序将数据写入到共享内存中,第二个程序将数据从共享内存中读出。
使用gcc编译时,末尾加上 -lrt。
1.
/*************************************************************************
> File Name: 1.c
> Author:fengxin
> Mail:903087053@qq.com
> Created Time: 2017年08月02日 星期三 17时49分03秒
************************************************************************/
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include <unistd.h>
int main()
{
int fd;
char *addr;
fd=shm_open("test",O_RDWR|O_CREAT,0777); //创建共享内存对象
if(fd<0)
{
printf("shm_open error\n");
}
char string[20]="Hello world";
if(ftruncate(fd,strlen(string))==-1) //改变对象大小
{
printf("ftruncate error\n");
}
addr=mmap(NULL,strlen(string),PROT_READ | PROT_WRITE,MAP_SHARED,fd,0); //内存映射
if(addr==MAP_FAILED)
{
printf("mmap error\n");
}
memcpy(addr,string,strlen(string)); //拷贝字符串到共享内存中
close(fd);
return 0;
}
2.
/*************************************************************************
> File Name: 1.c
> Author:fengxin
> Mail:903087053@qq.com
> Created Time: 2017年08月02日 星期三 17时49分03秒
************************************************************************/
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<unistd.h>
int main()
{
int fd;
char *addr;
struct stat sb;
fd=shm_open("test",O_RDWR,0); //创建共享内存对象
if(fd<0)
{
printf("shm_open error\n");
}
if(fstat(fd,&sb)==-1)
{
printf("fstat error\n");
}
addr=mmap(NULL,sb.st_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0); //内存映射
if(addr==MAP_FAILED)
{
printf("mmap error\n");
}
write(STDOUT_FILENO,addr,sb.st_size); //拷贝字符串到共享内存中
close(fd);
return 0;
}