进程间数据共享--文件与内存的映射
前言
在运行strace -c nginx的时候,我们可以看出mmap、mprotect和munmap使用的比例是很高的。因此可以看出nginx运行的时候有很大一部分时间在处理文件与内存之间的映射。
接口说明
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,那么内核将它作为提示映射位置的提示;在Linux上,映射将在附近的页面边界
创建。作为调用的结果,新映射的地址被返回。
length:映射的长度。文件映射的内容使用文件描述符fd引用的文件中的偏移量offset开始的长度字节进
行初始化。
prot:参数描述映射所需的内存保护(并且不得与文件的打开模式冲突)。 它是PROT_NONE或一个或多个
的按位或,具体标志如下所示:
PROT_EXEC页可能会被执行。
PROT_READ页可能被读取。
PROT_WRITE页可以写入。
PROT_NONE页可能无法访问。
offset:必须是由sysconf(_SC_PAGE_SIZE)返回的页面大小的倍数。
flags:参数确定对映射的更新对映射同一区域的其他进程是否可见,以及是否将更新传递到底层文件。常用
的flags如下:
MAP_SHARED
和其他进程共享这个映射。 映射的更新对映射此文件的其他进程可见,并传递到底层文件。 在调用
msync(2)或munmap()之前,文件可能不会实际更新。
MAP_PRIVATE
创建一个私人写时拷贝映射。 对映射的更新对映射相同文件的其他进程不可见,并且不会传递到底层
文件。 没有指定在映射区域中是否可见mmap()调用后对文件所做的更改。
munmap
int munmap(void *addr, size_t length);
munmap()系统调用会删除指定地址范围的映射。 该进程终止时,该区域也会自动取消映射。 另一方面,关闭文件描述
符不会取消映射该区域。
地址addr必须是页面大小的倍数。 包含部分指定范围的所有页面都未映射,随后对这些页面的引用将生成SIGSEGV。 如
果指定的范围不包含任何映射页面,则不是错误。
mprotect
int mprotect(void *addr, size_t len, int prot);
mprotect为包含区间[addr,addr + len-1]中地址范围的任何部分的调用进程的内存页面更改保护。
addr必须与页面边界对齐。
如果调用进程试图以违反保护的方式访问内存,那么内核会为该进程生成一个SIGSEGV信号。
prot是PROT_NONE或按位或以下列表中的其他值:
PROT_NONE内存根本无法访问。
PROT_READ可以读取内存。
PROT_WRITE可以修改内存。
PROT_EXEC可以执行内存。
举例说明
测试代码
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define DIR_MODE (FILE_MODE | S_IXUSR | S_IXGRP | S_IXOTH)
int main(int argc, char *argv[])
{
int fdin, fdout;
void *src, *dst;
size_t copysz = 4096;
struct stat sbuf;
off_t fsz = 0;
if (argc != 3){
printf("usage: %s <fromfile> <tofile> \n", argv[0]);
exit(-1);
}
if ((fdin = open(argv[1], O_RDONLY)) < 0){
printf("can't open %s for reading", argv[1]);
exit(-1);
}
if ((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0){
printf("can't creat %s for writing", argv[2]);
exit(-1);
}
if (fstat(fdin, &sbuf) < 0){
/* need size of input file */
printf("fstat error");
exit(-1);
}
//设置目标文件的长度
if (ftruncate(fdout, sbuf.st_size) < 0){
/* set output file size */
printf("ftruncate error");
exit(-1);
}
//循环从原文件读取文件内容,并复制到目标内容
while (fsz < sbuf.st_size) {
//设置原文件文件映射,
if ((src = mmap(0, copysz, PROT_READ, MAP_SHARED,fdin, fsz)) == MAP_FAILED){
printf("mmap error for input");
exit(-1);
}
//设置目标文件映射
if ((dst = mmap(0, copysz, PROT_READ,MAP_SHARED, fdout, fsz)) == MAP_FAILED){
printf("mmap error for output");
exit(-1);
}
//修改目标文件映射的权限,否则程序报SIGSEGV中断
if(mprotect(dst, copysz, PROT_WRITE)!=0){
printf("mprotect error for output");
exit(-1);
}
//拷贝文件
memcpy(dst, src, copysz);
munmap(src, copysz);
munmap(dst, copysz);
fsz += copysz;
}
exit(0);
}
参考资料
[1]. http://www.man7.org/linux/man-pages/man2/mmap.2.html
[2]. http://www.man7.org/linux/man-pages/man2/mprotect.2.html