1. 存储映射是什么?
如上图,存储映射是将块设备的文件映射到进程的虚拟地址空间。之后,进程可以直接使用指针操作其地址空间中映射的文件,对这块映射区操作就相当于操作文件。
2. 存储映射函数mmap的简单使用
(1)mmap函数:
#include<sys/mman.h>
void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
/*
功能:
将文件fd从开头偏移offset个字节处开始,
映射到调用该函数的进程的虚拟地址空间的addr地址开始的往后length个字节的虚拟地址空间长度。
映射区的保护方式为prot,特性为flags。
参数:
addr:映射到进程地址空间的起始地址,通常为NULL,由内核指定;
length:映射到进程地址空间的大小;
prot:映射区的保护方式:
a)读:PROT_READ
b)写:PROT_WRITE
c)读写:PROT_READ | PROT_WRITE
flags:映射区的特性:
a)MAP_SHARED:写入映射区的数据会复制回文件,且允许共享给其他映射该文件的进程;
b)MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy-on-write),
对此区域的修改不会写回原文件;
fd:要映射的文件,即open返回的文件描述符;
offset:从文件起始位置偏移offset开始映射,必须是4K的整数倍;
通常为0,表示从文件起始位置开始映射;
返回值:
成功:映射区的首地址
失败:MAP_FAILED宏
*/
mmap使用总结:
(1)第一个参数通常为 NULL;
(2)第二个参数映射文件大小 > 0;
(3)第三个参数:PROT_READ、PROT_WRITE;
(4)第四个参数:MAP_SHARED、MAP_PRIVATE;
(5)第五个参数:要映射的文件描述符;
(6)第六个参数:4K的整数倍,通常为0;
mmap注意事项:
(1)创建映射区过程中,隐含一次对映射文件的读操作;
(2)当flags为MAP_SHARED时,要求:映射区权限 ≤ 文件打开的权限;而MAP_PRIVATE无要求;
(3)映射成功文件即可关闭;
(4)文件大小为0时不能创建映射区。使用mmap时常出错总线错误(bus error),通常是映射文件大小的问题;
(5)munmap的传入地址一定是mmap的返回值,禁止对其地址++操作;
(6)mmap调用出错几率高,一定要检查返回值。
(2)munmap函数 :
#include<sys/mman.h>
int munmap(void* addr, size_t length);
/*
功能:
释放进程地址空间addr开始的length个字节的存储映射区
参数:
addr:映射区的起始地址,即mmap函数的返回值;
length:映射区大小,即mmap函数的第二个参数;
返回值:
成功:0
失败:-1
*/
(3)mmap使用示例:
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
int main(int argc, const char* argv[]) {
int fd = -1;
int ret = -1;
void* addr = NULL;
// 1.以读写的方式打开txt文件
fd = open("txt", O_RDWR);
if (-1 == fd) {
perror("open");
return 1;
}
// 2.将文件映射到进程的虚拟地址空间
addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
return 1;
}
printf("映射成功.\n");
// 3.关闭文件
close(fd);
// 4.写文件
memcpy(addr, "123456", 6);
// 5.断开存储映射
munmap(addr, 1024);
return 0;
}
运行结果:
3. mmap实现父子进程通信
(1)原理:
a)父进程先创建存储映射区,得到映射区在其虚拟地址空间中的起始地址addr和映射长度length;
b)fork子进程后,子进程虚拟地址空间存在和父进程addr、length一样的存储映射区;
c)因为父子进程都映射同一个文件,因此可通过该文件进行通信。
(2)代码示例:
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>
int main(int argc, const char* argv[]) {
int fd = -1;
int ret = -1;
pid_t pid = -1;
void* addr = NULL;
// 1.以读写的方式打开txt文件
fd = open("txt", O_RDWR);
if (-1 == fd) {
perror("open");
return 1;
}
// 2.将文件映射到进程的虚拟地址空间
addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
return 1;
}
printf("映射成功.\n");
// 3.关闭文件
close(fd);
// 4.创建子进程
pid = fork();
if (-1 == pid) {
perror("fork");
return 1;
}
if (0 == pid) { // 子进程写文件
memcpy(addr, "ABCD", 4);
} else { // 父进程读文件
wait(NULL); // 等待子进程结束
printf("子进程写的内容:%s\n", (char*)addr);
}
// 5.断开存储映射
munmap(addr, 1024);
return 0;
}
运行结果:
4. mmap实现无关系的进程通信
代码示例:
mmap_a.c文件:
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>
int main(int argc, const char* argv[]) {
int fd = -1;
int ret = -1;
pid_t pid = -1;
void* addr = NULL;
// 1.以读写的方式打开txt文件
fd = open("txt", O_RDWR);
if (-1 == fd) {
perror("open");
return 1;
}
// 2.将文件映射到进程的虚拟地址空间
addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
return 1;
}
printf("映射成功.\n");
// 3.关闭文件
close(fd);
// 4. 读存储映射区
printf("读存储映射区:%s\n", (char*)addr);
// 5.断开存储映射
munmap(addr, 1024);
return 0;
}
mmap_b.c文件:
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>
int main(int argc, const char* argv[]) {
int fd = -1;
int ret = -1;
pid_t pid = -1;
void* addr = NULL;
// 1.以读写的方式打开txt文件
fd = open("txt", O_RDWR);
if (-1 == fd) {
perror("open");
return 1;
}
// 2.将文件映射到进程的虚拟地址空间
addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
return 1;
}
printf("映射成功.\n");
// 3.关闭文件
close(fd);
// 4.写存储映射区
memcpy(addr, "XYZ", 3);
// 5.断开存储映射
munmap(addr, 1024);
return 0;
}
运行结果:
5. 匿名映射
父子进程使用存储区映射进行通信的缺陷是需要依赖一个文件。
为了克服该缺陷,父子进程可使用匿名映射,无需依赖文件即可创建映射区,需要借助flags参数MAP_ANONYMOUS(或MAP_ANON)来指定为匿名映射,如下:
int* addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
/*
MAP_ANONYMOU需要和MAP_SHARED 一起使用
MAP_ANONYMOUS和MAP_ANON是Linux系统特有的宏,类unix系统中无该宏定义。
*/
匿名映射示例:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>
int main(int argc, const char* argv[]) {
int ret = -1;
pid_t pid = -1;
void* addr = NULL;
// 1.创建匿名映射
addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (MAP_FAILED == addr) {
perror("mmap");
return 1;
}
// 2.创建子进程
pid = fork();
if (-1 == pid) {
perror("fork");
munmap(addr, 4096);
return 1;
}
// 3.父子进程通信
if (0 == pid) {
memcpy(addr, "1234", 4); // 子进程写
} else {
wait(NULL); // 等待子进程结束
printf("父进程读到:%s\n", (char*)addr);
}
// 4.断开映射
munmap(addr, 4096);
return 0;
}
运行结果: