- 论坛徽章:
- 13
|
本帖最后由 _nosay 于 2016-10-12 13:18 编辑
可以说如果对“虚拟地址-物理地址”以及文件系统相关内容没有一定程度的理解,光是通过man手册的使用说明,是不可能完全理解这个函数的。
至少,很多人对mmap()肯定存在以下误解:
① 进程间通信时才会想到它
② 跟文件有关,涉及磁盘操作,一定很慢
想想日常的通信,不管是古代还是现在,都会“约到同一个地方”,比如一起到茶馆喝茶、写信、电话,都会有一个通道,将通信的双方联系起来。
对于程序来讲,不同进程的用户空间独立,用相同的虚拟地址,根本走不到同一个地方(比如看新闻时,发现习大大走到沙发旁了,你也走到你家的沙发旁,你们就能通信了么 )。那么不同的用户进程怎么可以通信呢?
磁盘等硬件,都是由内核直接操作,比如读完文件,先是在内核空间有文件的内容,然后用户空间才有机会访问到:
方式①:直接将这块实际的内存,映射到用户空间的虚拟地址(这样就像在个人家里,放了一个控制公共场所的遥控器)
方式②:在实际内存中复制一份,并将复制的那份映射到用户空间(这样就纯粹属于个人了)
mmap()相对于上图只不过多了一道映射,即将用户空间的虚拟地址与文件信息建立映射,而内核中所操作该文件相关的内存只有一份,从而间接达到不同进程中的虚拟地址,访问到相同实际内存地址的目的。
通过以上分析可以看出,进程1通过往建立映射的虚拟区间写,进程2就可以通过相应的虚拟区间访问进程1写的内容,因为在实际内存对应的都是同一块。
那么进程1写的时候,会触发磁盘的写操作吗?
内核只会将写的页标志为脏,到需要的时候才写回磁盘(《Linux内核源代码情景分析》第5章:文件系统),所以对mmap()映射的内存操作,几乎不涉及磁盘操作,更甚至父子进程之间利用mmap()进行通信,连打开文件都不用,即“匿名映射”,这跟Linux内核创建进程使用的机制有关(详见《Linux内核源代码情况分析》4.2节:进程的创建)。
另外,相比于read(),它有两点不同:
① 在实际内存中少了一次复制
② 在读一个大文件时,mmap()调用完成,并不表示内容就真的从磁盘全部读到内存了,其实道理和交换文件一样,虚拟页面没有对应的物理页面映射,当访问到时,才分配物理页面,并根据映射指向的磁盘位置,从磁盘读入内容
这两点往往也算是优点,不过也要根据具体的使用场合来看。
父子进程通过匿名映射通信示例代码:
- #include <stdio.h>
- #include <unistd.h>
- #include <string.h>
- #include <sys/mman.h>
- int main()
- {
- char *p = NULL;
- int size = 1024;
- pid_t pid;
- // 父进程建立映射,指针p保存返回的虚拟地址
- p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
- if (p == (void *)-1) {
- printf("mmap() failed\n");
- goto err;
- }
- memcpy(p, "shared\0", 6);
- // 创建子进程很多资源都是从父进程复制的,比如mm_struct结构以及映射关系,所以用指针p访问到和父进程同样的实际内存
- if ((pid = fork()) < 0) {
- printf("fork() failed\n");
- goto err;
- }
- if (pid == 0) {
- while (1) {
- printf("child: %s(%p)\n", p, p);
- sleep(1);
- }
- } else {
- while (1) {
- printf("parent: %s(%p)\n", p, p);
- sleep(1);
- }
- }
- munmap(p, size);
- return 0;
- err:
- if (p != NULL)
- munmap(p, size);
- return -1;
- }
复制代码
- 内核中的mmap()实现
结合《Linux内核源代码情景分析》的说明,看代码。
mmap()是《Linux内核源代码情景分析》这本书对内存管理的最后一节说明,学完之后对Linux内存管理的奥秘就应该有比较深的理解了。
但整个内存管理一章,还涉及到中断、进程调度、文件系统以及系统引导启动过程中的一些道理,所以如果初学,到目前为止,肯定还不能完全理解,需要后面积累到新知识后再回来理解。
|
|