一.概述
内存映射是在调用进程的虚拟地址空间创建一个新的内存映射。
内存映射分为2种:
1.文件映射:将一个普通文件的全部或者一部分映射到进程的虚拟内存中。映射后,进程就可以直接在对应的内存区域操作文件内容!
2.匿名映射:匿名映射没有对应的文件或者对应的文件时虚拟文件(如:/dev/zero),映射后会把内存分页全部初始化为0。
当多个进程映射了同一个内存区域时,它们会共享物理内存的相同分页。通过fork()创建的子进程也会继承父进程的映射副本!!!
如果多个进程都会同一个内存区域操作时,会根据映射的特性,会有不同的行为。映射特征可分为私有映射和共享映射:
1.私有映射:映射的内容对其他进程不可见。对于文件映射来说,某一个进程在映射内存中改变文件的内容不会反映到被映射的底层文件中。内核会使用copy-on-write(写时复制)技术来解决这个问题:只要有一个进程修改了分页中的内容,内核会为该进程重新创建一个新的分页,并将需要修改的内容复制到新分页中。
2.共享映射:某一个进程对共享的内存区域操作都对其他进程可见!!!对于文件映射,操作的内容会反映到底层文件中。
注意:进程执行exec()调用后,先前的内存映射会丢失,而fork()创建的子进程会继承父进程的,映射的特征(私有和共享)也会被继承。
异常信号:
1.当映射内存的属性设置只读时,如果进行写操作会产生SIGSEGV信号。
2.当映射内存的字节数大于被映射文件的大小,且大于该文件当前的内存分页大小时。如果访问的区域超过了该文件分页大小,会产生SIGBUS信号。
有点绕口,举个简单的例子:假设内核维护的内存分页是4k(一般都是4k,4096字节),一个普通文件a.txt的大小是10字节。如果创建一个映射内存为4097字节,并映射该文件。此时,因为a.txt的大小用一个分页就可以完全映射,10字节远小于一个分页的4096字节,所以内核只会给它一个分页。内存地址是从0开始,0-9区间的内容对应a.txt文件的数据,我们也是可以访问10-4095的区间。但如果访问4096区间时,已经超过一个分页的大小了,此时会产生SIGBUS信号!!!
等会我们用个简单的例子演示下这2个异常。
二.函数接口
1.创建映射
1 #include <sys/mman.h> 2 3 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr:映射后要存放的虚拟内存地址。如果是NULL,内核会自动帮你选择。
length:映射内存的字节数。
prot:权限保护:PROT_NONE(无法访问),PROT_READ(可读),PROT_WRITE(可写),PROT_EXEC(可执行)。
flags:映射特征:MAP_PRIVATE(私有),MAP_SHARED(共享),MAP_ANONYMOUS。还有一些其他的可查询man手册。
fd:要映射的文件描述符。
offset:文件的偏移量,如果为0,且length为文件长度,代表映射整个文件。
2.解除映射
1 #include <sys/mman.h> 2 3 int munmap(void *addr, size_t length);
addr:要解除内存的起始地址。如果addr不在刚刚映射区域的开始位置,解除一部分后内存区域可能会分成两半!!!
length:要解除的字节数。
3.同步映射区
1 #include <sys/mman.h> 2 3 int msync(void *addr, size_t length, int flags);
addr:要同步的内存起始地址。
length:要同步的字节长度。
flags:MS_SYNC(执行同步文件写入),此操作内核会把内容直接写到磁盘。MS_ASYNC(执行异步文件写入),此操作内核会先把内容写到内核的缓冲区,某个合适的时候再写到磁盘。
三.文件映射实例
/**
* @file mmap_file.c
*/