如果你已经完成了进程间通信的学习,相信你对进程间通信已经有了一个初步的了解。由于进程之间是相互独立的,不能相互访问,进程之间想要通信必须借助内核空间,因为内核空间对于进程之间是共享的。
像这样的进程间通信的方式有很多种,例如管道、信号、共享内存、消息队列、套接字、命名管道等,在linux早期是使用文件来完成进程间通信的。
对于文件通信,本质上也是通过内核空间来完成的。首先系统内核会创建一个内核缓冲区,通过把文件映射到内核缓冲区里,然后进程将数据写入到缓冲区,另一进程从缓冲区将数据读走,完成进程间通信。
接下来要讲的内存映射和文件通信有很多相似之处。
内存映射是让一个磁盘文件与进程空间中的一块虚拟内存区域映射,完成映射后,进程就可以通过在内存映射区中读写操作来访问文件中的数据,换句话说,你可以理解为对这块内存的读写数据等同于读写文件。
mmap函数就是用于完成内存映射的
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);
返回值说明:成功返回创建的映射区void *类型的首地址;失败返回MAP_FAILED宏(这个宏其实就是个void *类型的-1,即(void *)-1 )。
参数说明:
addr : 建立内存映射区的首地址,如果指定addr为NULL,那么将由Linux内核自动指定一个合适的首地址。
length: 创建映射区的大小,以字节为单位(建议length最好是系统内存页的整数倍)。
prot: 内存映射区的保护权限,可以多个选项组合使用,例如PROT_READ | PROT_WRITE表示可读写。
值 | 描述 |
PROT_EXEC | 内存映射区域可以被执行 |
PROT_READ | 内存映射区域可以被读取 |
PROT_WRITE | 内存映射区域可以被写入 |
PROT_NONE | 内存映射区域不可访问 |
flags:用于控制内存映射区操作的选项。
如果flags = MAP_PRIVATE,创建一个私有的映射区,在映射区所做的修改操作不会反映到物理磁盘的文件上,对使用同一映射区的其他进程不可见。
如果flags = MAP_SHARED,创建一个共享的映射区,在映射区所做的修改操作会直接反映到物理磁盘的文件上,那么这一修改操作对其他进程都可见。
fd:用来创建内存映射区的文件描述符
offset:从文件的哪个位置开始映射,如果offset为0表示从文件头开始映射,注意offset必须是系统分页4k的整数倍,数据类型为off_t,在32位系统下为long int类型,64为linux系统为long long int类型。
图1-内存映射文件过程(图片来自Linux/UNIX系统编程手册)
length是映射到进程地址空间的字节数,offset表示文件的映射位置从文件头到第offset个字节开始,通常offset设置为0表示从文件头开始映射,当映射成功,mmap将会返回映射区域的首地址。
munmap函数是用于解除内存映射,即从进程的虚拟地址空间删除映射。
#include
int munmap(void *addr, size_t length);
返回值说明:成功返回0,失败返回-1
参数addr:由mmap返回的内存映射区首地址
参数length:映射区大小
#include
#include
#include
#include
#include
#include
#include
#include
int main(void){
char *addr;
int len = 0;
int ret;
int fd = open("test.txt", O_RDWR|O_CREAT, 0644);
if (fd < 0){
perror("open error");
}
//拓展文件大小4096
lseek(fd , 4096, SEEK_SET);
write(fd , "0" , 1);
//指向文件开头
lseek(fd , 0 , SEEK_SET);
//创建共享映射区,可读写
addr = (char *)mmap(NULL , 4096 , PROT_WRITE|PROT_READ , MAP_SHARED , fd , 0);
//创建私有映射区,可读写
//addr = (char *)mmap(NULL, 4096, PROT_WRITE | PROT_READ, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED){
perror("mmap err: ");
}
//关闭文件与文件描述符间的关联
close(fd);
//实际操作是通过mmap返回的文件指针去操作文件读写,不需要用到文件描述符
strcpy(addr, "AAAAABBBBB");
printf("%s\n", addr);
//解除映射
ret = munmap(addr , 4096);
if(ret < 0){
perror("munmap error: ");
}
return 0;
}
在test文件写入hello world:
[root@localhost memory]# cat test.txt
hello world
[root@localhost memory]#
指定映射区为MAP_PRIVATE,程序执行结果:
指定MAP_PRIVATE创建私有映射区,对映射区所做的修改操作不会反映到物理磁盘上的文件。
指定映射区为MAP_SHARED:
指定MAP_SHARED创建共享映射区,对映射区所做的修改操作会反映到物理磁盘上的文件。
1. 如果参数addr和length指定的区域不存在映射关系,那么调用munmap函数将不会发生任何事情并返回0(表示成功)。
2. 当一个进程终止或调用了exec系列函数后,进程中所有的映射关系将自动解除。
3. 为确保映射区的数据写入物理磁盘上的文件中,在调用munmap解除映射前需要调用msync函数。
4. munmap函数可以解除映射区中的部分区域的映射,一旦这样做的话可能会使原来的映射区变小或分成两个映射区,强烈建议不要这么做。