一、函数声明
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
/*
参数:
addr: 建立映射区的首地址,由linux内核指定。使用时,直接传递NULL
length: 欲创建映射区的大小
prot: 映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags: 标志位参数
MAP_SHARED:会将映射区所做的操作反映到物理磁盘上。
MAP_PRIVATE:映射区所做的修改不会反映到物理设备。
fd: 用来建立映射区的文件描述符
offset: 映射文件的偏移(4K的整数倍,不可传4095、4098等)
*/
/*
返回值:
成功:返回创建的映射区首地址
失败:MAP_FAILED宏
*/
int munmap(void *addr, size_t length);
/*
参数:
addr: mmap返回的地址
length: mmap创建映射区的大小
*/
/*
返回值:
成功: 0
失败: -1, and errno is set
*/
二、借助共享内存访问磁盘文件
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h> //mmap munmap
#include <fcntl.h>
#include <stdlib.h>
int main()
{
// 打开文件
int fd = open("./my_file.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1)
{
perror("open file failed.");
exit(EXIT_FAILURE);
}
// 设定文件大小
unsigned fileSize = 64;
int ret = ftruncate(fd, fileSize);
if (ret == -1)
{
perror("open file failed.");
exit(EXIT_FAILURE);
}
// 建立内存映射
char *p = (char *)mmap(NULL, fileSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
{
perror("mmap failed.");
exit(EXIT_FAILURE);
}
// 往文件中写入信息
strcpy(p, "hello, world!\n");
munmap(p, fileSize);
close(fd);
return 0;
}
三、mmap使用注意事项
1、open文件的时候,可以O_CREAT一个新文件来创建映射区吗?
可以,但是需要设定文件的大小,不然新创建的文件大小为0,mmap创建映射区的大小为能0。
2、如果参返回的地址进行addr++
操作,mumap可否成功?
不能,释放映射区的时候,需要传入创建映射区的返回地址,映射区大小,必须保持一致。
3、如果open时用O_RDONLY,而mmap时用了PROT_READ|PROT_WRITE
会怎样?
当MAP_SHARED
时,要求创建映射区的权限要小于等于打开文件的权限(出于对映射区的保护)。
而MAP_PRIVATE
则没有要求,因为mmap中的权限是对内存进行限制。
映射区的创建过程当中隐含一次对文件的读操作。
4、如果文件偏移量设置成1000会怎样?
最后一个offset的大小必须是4K
的整数倍(一内存页大小),mmu
在映射的过程当中,单位是4K
。
5、如果不检测mmap的返回值,会怎样?
mmap创建映射区出错的概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
6、mmap什么情况下会调用失败?
映射区大小不能大于文件大小。
7、对addr进程越界操作会怎样?
会core。
8、文件描述符先关闭,对mmap映射有没有影响?
不影响。所谓的文件描述符只是用来操作文件的句柄,mmap操作文件的方式是通过地址来操作文件,一旦映射区创建成功,文件描述符已经没有作用了,可以关闭。
四、mmap用于父子进程间通信
父子等有血缘关系的进程之间也可以通过mmap建立的映射区来完成数据通信。但相应的要创建映射区的时候指定对应的标志位参数flags:
MAP_PRIVATE
:(私有映射) 父子进程各自独占映射区;
MAP_SHARED
:(共享映射) 父子进程共享映射区;
#include <sys/mman.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int v = 100;
// 创建临时文件
int fd = open("./tmp", O_RDWR | O_CREAT |O_TRUNC, 0644);
if (fd == -1)
{
perror("open file failed.");
exit(EXIT_FAILURE);
}
unlink("./tmp"); // 删除临时文件目录项,使之具备被释放的条件。当进程不再占用就删除文件。
ftruncate(fd, 4);
int *p = (int *)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
//父子进程不共享映射区
//int *p = (int *)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (p == MAP_FAILED)
{
perror("mmap failed.");
exit(EXIT_FAILURE);
}
close(fd);
pid_t pid = fork();
if (pid == 0)
{
v = 1000;
*p = 1000;
printf("child v=%d, *p=%d\n", v, *p);
}
else if (pid > 0)
{
sleep(1);
printf("parent v=%d, *p=%d\n", v, *p);
wait(NULL);
int ret = munmap(p, 4);
if (ret == -1)
{
perror("munmap failed.");
exit(EXIT_FAILURE);
}
}
return 0;
}
结论:
父子进程共享:
1、打开的文件
2、mmap建立的映射区(但必须要使用MAP_SHARED)
五、匿名映射区
通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易,但缺陷是,每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了再unlink,close掉,比较麻烦。可以直接使用匿名映射来代替。其实linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数falgs来 指定。
使用 MAP_ANONYMOUS
(或MAP_ANON
),如:
int *p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
// 4是随意举例,表示大小
// fd == -1, 不需要传文件描述符
// flags == MAP_SHARED|MAP_ANONYMOUS
需要注意的是,MAP_ANONYMOUS
和MAP_ANON
这两个宏是linux操作系统特有的宏。在类Unix系统中如无该宏定义,可以使用如下两步来完成匿名映射区的建立。
fd = open("/dev/zero", O_RDWR);
P = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);
示例:
#include <sys/mman.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int v = 100;
int *p = (int *)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED |MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED)
{
perror("mmap failed.");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == 0)
{
v = 1000;
*p = 1000;
printf("child v=%d, *p=%d\n", v, *p);
}
else if (pid > 0)
{
sleep(1);
printf("parent v=%d, *p=%d\n", v, *p);
wait(NULL);
int ret = munmap(p, 4);
if (ret == -1)
{
perror("munmap failed.");
exit(EXIT_FAILURE);
}
}
return 0;
}
六、mmap用于非血缘关系进程间通信
mmap_write.c
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/mman.h>
struct STU {
int id;
char name[20];
char sex;
};
void sys_err(const char *str)
{
perror(str);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
int fd;
struct STU stu = {10, "xiaoming", 'm'};
struct STU *p;
if (argc < 2) sys_err("./a.out file_shared\n");
fd = open(argv[1], O_RDWR|O_CREAT, 0644);
if (fd == -1) sys_err("open error");
// 创建文件成功后,一定要记得设置文件大小,否则会报Bus error
ftruncate(fd, sizeof(stu));
p = (struct STU *)mmap(NULL, sizeof(stu), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) sys_err("mmap error");
close(fd);
// 第隔一秒,更新mmap中的内容。
while (1)
{
memcpy(p, &stu, sizeof(stu));
stu.id++;
sleep(1);
}
munmap(p, sizeof(stu));
return 0;
}
mmap_read.c
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
struct STU {
int id;
char name[20];
char sex;
};
void sys_err(const char *str)
{
perror(str);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
int fd;
struct STU stu;
struct STU *p;
if (argc < 2) sys_err("./a.out file_shared\n");
fd = open(argv[1], O_RDONLY);
if (fd == -1) sys_err("open error");
p = (struct STU *)mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) sys_err("mmap error");
close(fd);
// 每隔两秒,读取mmap中的内容。
while (1)
{
printf("stu.id = %d\t stu.name=%s\t stu.sex = %c\n", p->id, p->name, p->sex);
sleep(2);
}
munmap(p, sizeof(stu));
return 0;
}