参考
概述
存储映射I/O(Memory-mapped I/O)使一个磁盘文件与存储空间中的一个缓冲区相映射。
于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址 (指针)完成I/O操作。
共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式,因为进程可以直接读写内存,而不需要任何数据的拷贝。
存储映射函数
mmap函数
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
功能:
一个文件或者其它对象映射进内存
参数:
addr :指定映射的起始地址,通常设为NULL,由系统指定
length:映射到内存的文件长度
prot:映射区的保护方式,最常用的 :
a) 读: PROT_READ
b) 写: PROT_WRITE
c) 读写: PROT_READ | PROT_WRITE
flag:映射区的特性,可以是
a) MAP_SHARED : 写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享
b) MAP PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy - on - write),对此区域所做的修改不会写回原文件。
fd:由open返回的文件描述符,代表要映射的文件
offset:以文件开始处的偏移量,必须是4k的整数倍(内存页大小),通常为0,表示从文件头开始映射
返回值:
成功:返回创建的映射区首地址
失败:MAP_FAILED宏
关于mmap函数的使用总结:
- 1)、第一个参数写成NULL
- 2)、第二个参数要映射的文件大小 > 0
- 3)、第三个参数:PROT_READ 、PROT_WRITE
- 4)、第四个参数:MAP_SHARED 或者 MAP_PRIVATE
- 5)、第五个参数:打开的文件对应的文件描述符
- 6)、第六个参数:4的整数倍,通常为0
munmap
#include <sys/mman.h>
int munmap(void addr, size_t length);
功能
释放内存映射区
参数:
addr: 使用mmap函数创建的映射区的首地址
length: 映射区的大小
返回值
成功: 0
失败: -1
代码测试mmap
注意test.txt要提前创建,并加入内容,否则内存拷贝会段错误。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#define SIZE 1024
//以只写的方式打开管道2
int main(void)
{
int fd = -1;
void* addr = NULL;
// 1.打开文件
fd = open("test.txt",O_RDWR | O_CREAT,0644);
if (-1 == fd)
{
perror("open");
return 1;
}
printf("fd = %d\n",fd);
// 2.将文件映射到内存
addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if( addr == MAP_FAILED )
{
perror("mmap");
return 1;
}
printf("文件存储映射 ok \n");
// 3.关闭文件
close(fd);
// 4.写文件
memcpy(addr,"1234567890", 10);
// write(addr,"1234567890", 10);
// 5.断开存储映射
munmap(addr, 1024);
return 0;
}
共享存储映射代码测试
父子进程间通信
test.txt要有数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#define SIZE 1024
//以只写的方式打开管道2
int main(void)
{
int fd = -1;
pid_t pid;
void* addr = NULL;
// 1.打开文件
fd = open("test.txt",O_RDWR | O_CREAT,0644);
if (-1 == fd)
{
perror("open");
return 1;
}
printf("fd = %d\n",fd);
// 2.将文件映射到内存
addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if( addr == MAP_FAILED )
{
perror("mmap");
return 1;
}
printf("文件存储映射 ok \n");
// 3.关闭文件
close(fd);
// 4.创建一个子进程
pid = fork();
if(pid <0)
{
// 没有创建成功
perror("fork");
return 0;
}
if (0 == pid)
{
//子进程
// 4.写文件
memcpy(addr,"1234567890", 10);
}
else
{
//父进程
// 等待子进程结束
wait(NULL);
printf("addr:%s\n", (char*)addr);
}
// 5.断开存储映射
munmap(addr, 1024);
return 0;
}
进程间通信
write.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#define SIZE 1024
//以只写的方式打开管道2
int main(void)
{
int fd = -1;
void* addr = NULL;
// 1.打开文件
fd = open("test.txt",O_RDWR | O_CREAT,0644);
if (-1 == fd)
{
perror("open");
return 1;
}
printf("fd = %d\n",fd);
// 2.将文件映射到内存
addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if( addr == MAP_FAILED )
{
perror("mmap");
return 1;
}
printf("文件存储映射 ok \n");
// 3.关闭文件
close(fd);
// 4.写文件
memcpy(addr,"1234567890", 10);
// write(addr,"1234567890", 10);
// 5.断开存储映射
munmap(addr, 1024);
return 0;
}
read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#define SIZE 1024
//以只写的方式打开管道2
int main(void)
{
int fd = -1;
void* addr = NULL;
// 1.打开文件
fd = open("test.txt",O_RDWR | O_CREAT,0644);
if (-1 == fd)
{
perror("open");
return 1;
}
printf("fd = %d\n",fd);
// 2.将文件映射到内存
addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if( addr == MAP_FAILED )
{
perror("mmap");
return 1;
}
printf("文件存储映射 ok \n");
// 3.关闭文件
close(fd);
// 4.读文件
printf("addr:%s\n", (char*)addr);
// 5.断开存储映射
munmap(addr, 1024);
return 0;
}
Makefile
all: read write
read:read.c
gcc $< -o $@
write:write.c
gcc $< -o $@
.PHONY:clean
clean:
rm -rf read write
运行
# 创建文件
touch test.txt
echo "aaaaaaaaaaaaaaaa " > test.txt
# 编译
make
# 执行
匿名映射实现父子进程间通信
通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。
通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。可以直接使用匿名映射来代替。
其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。
使用MAP_ANONYMOUS或(MAP_ANON)。
int *p = mmap(NULL,4,PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,-1,0);
- 4”随意举例,该位置表示映射区大小,可依实际需要填写
- MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏。在类Unix系统中如无该宏定义,可使用如下两步来完成匿名映射区的建立。
示例代码:
创建匿名内存映射区
int len = 4096;
void *ptr = mmap(NULL,len,PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,-1,0)
if (ptr == MAP_FAILED)
{
perror("mmap error");
exit(1);
}
// 创建子进程
pid_t pid = fork();
if (pid > 0)
{
//父进程
// 也可以用 memcpy(ptr,"1234567890", 10);
wait(NULL);
// 读数据
printf("父进程:%s n",(char*)ptr);
}
else if (pid == 0)
{
//子进程
// 写数据
strcpy((char*)ptr,"he11o mike!!");
}
// 释放内存映射区
int ret = munmap(ptr, len);
if (ret == -1)
{
perror("munmaperror");
exit(1);
}