1.mmap 使用的注意事项
(1)当 open 一个文件时,如果指定了 O_CREAT 标志并且文件不存在,就会新创建一个文件作为映射文件,此时必须调用 ftruncate 或者 lseek+write 设置文件长度,否则任然可以调用 mmap,但是对存储映射区的引用会产生 SIGBUS。另外,如果映射的长度超过了文件长度,访问超过文件长度的映射区也会出错。
(2)munmap 释放映射区时传入的指针必须指向最初分配的位置,否则将会出错(中途可移动映射区指针,但是必须从最开始分配的位置开始释放)。
(3)mmap 指定对映射区的访问权限时不能超过 open 对文件指定的访问权限。
(4)mmap 建立映射区时隐含一次对文件的读,因此打开文件时读权限是必须有的,即使你只进行写操作。
2.使用存储映射区实现父子进程间通信
注意:父子进程的内存空间遵循 读时共享、写时复制,但打开的文件和 mmap 建立的存储映射区在父子进程之间是一直共享的,因此可通过 mmap 建立存储映射区实现父子进程之间的通信。通过以下代码可说明:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
int var = 10;
int main() {
int* p;
int fd;
pid_t pid;
if ((fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) {
perror("open tmpfile");
exit(1);
}
unlink("tmpfile"); //解除硬链接,即删除了临时文件唯一的目录项,使之具备被删除的条件,但此时并未删除
ftruncate(fd, 4); //创建文件大小
p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
perror("mmap failed");
exit(1);
}
close(fd);
if ((pid = fork()) == 0) { //child
sleep(1);
printf("In child: *p = %d, var = %d\n", *p, var);
}
else if (pid > 0) { //parent
*p = 2000; //修改映射区
var = 1000; //修改全局变量
printf("In parent: *p = %d, var = %d\n", *p, var);
wait(NULL); //等待子进程执行完成
if (munmap(p, 4) < 0) { //释放映射区
perror("munmap failed");
exit (1);
}
}
return 0;
}
执行结果:
In parent: *p = 2000, var = 1000
In child: *p = 2000, var = 10
3.创建匿名存储映射区
以上方法虽然实现了父子进程之间的通信,但是每次都要依赖一个文件,如果是一个临时文件,打开后马上进行了 unlink 使文件具备了被释放的条件,在进程结束后文件就被释放,因此这个文件根本就没有存在的必要,可通过匿名映射区避免这种情况。但是匿名映射区只能实现有血缘关系的进程间的通信。
注意:宏 MAP_ANON 是 linux 所独有的,所有类 Unix 系统可以借助文件 /dev/zero 实现匿名映射区
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
int var = 10;
int main() {
int* p;
pid_t pid;
//注意这里加上 MAP_ANON 参数并将文件描述符指定为 -1
p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
if (p == MAP_FAILED) {
perror("mmap failed");
exit(1);
}
if ((pid = fork()) == 0) { //child
sleep(1);
printf("In child: *p = %d, var = %d\n", *p, var);
}
else if (pid > 0) { //parent
*p = 2000; //修改映射区
var = 1000; //修改全局变量
printf("In parent: *p = %d, var = %d\n", *p, var);
wait(NULL); //等待子进程执行完成
if (munmap(p, 4) < 0) { //释放映射区
perror("munmap failed");
exit (1);
}
}
return 0;
}
4.通过存储映射区实现非血缘关系进程间的通信
注意非血缘关系的进程间不能通过匿名映射区实现
写进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
//这里换一种数据结构进行通信
struct STU {
int id;
char name[20];
char sex;
};
void sys_err(char* s) {
perror(s);
exit(1);
}
int main() {
int fd;
struct STU stu = {10, "xiaoming", 'm'};
struct STU* mm;
if ((fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0)
sys_err("open tmpfile");
//unlink("tmpfile"); //写进程不能立即进行 unlink,因为他要保证写进程能找到这个目录项
ftruncate(fd, sizeof(stu)); //创建文件大小
mm = mmap(NULL, sizeof(stu), PROT_WRITE, MAP_SHARED, fd, 0);
if (mm == MAP_FAILED)
sys_err("mmap failed");
close(fd);
while (stu.id++ < 100) {
memcpy(mm, &stu, sizeof(stu));
sleep(1);
}
unlink("tmpfile");
munmap(mm, sizeof(stu));
return 0;
}
读进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
//这里换一种数据结构进行通信
struct STU {
int id;
char name[20];
char sex;
};
void sys_err(char* s) {
perror(s);
exit(1);
}
int main() {
int fd;
struct STU stu;
struct STU* mm;
if ((fd = open("tmpfile", O_RDONLY)) < 0)
sys_err("open tmpfile");
unlink("tmpfile"); //读进程可以立即进行 unlink
ftruncate(fd, sizeof(stu));
mm = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);
if (mm == MAP_FAILED)
sys_err("mmap failed");
close(fd);
while (mm->id < 100) {
printf("id = %d, name = %s, sex = %c \n", mm->id, mm->name, mm->sex);
sleep(1);
}
munmap(mm, sizeof(stu));
return 0;
}
运行结果:
存储映射通信实质:
程序将文件映射到一块内存,这块内存类似于管道连接了a、b两个进程,a b进程都可以对文件进行读写操作。
5.解析打开文件的实质
对于以下代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main() {
int fd;
char* s = "hello world\n";
if ((fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) {
perror("open tmpfile");
exit(1);
}
write(fd, s, strlen(s));
close(fd);
return 0;
}
通过命令 strace ./a.out 查看进程执行过程中系统调用过程如下:
从中可以看出,系统在文件描述符3上打开了文件,并且调用 mmap 为描述符3创建了存储映射区,之后也多次调用 mmap 创建了存储映射区。