内存映射
映射时,具体映射到了进程空间的什么位置呢?
映射到了“进程应用空间”堆和栈中间那片虚拟地址的位置。
- 进程内核空间:用于映射“OS”所在的物理内存空间。
- 进程应用空间:用于映射“应用程序”所在的物理内存空间。z
mmap函数
- 函数原型
- #include <sys/mman.h>
- void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
- 功能
- 将文件所在磁盘空间映射到进程空间。
- 返回值
- 调用成功,返回映射的起始虚拟地址,失败则返回(void *)-1,errno被设置。
- 参数
- addr:人为指定映射的起始虚拟地址
- 如果设置为NULL,表示由内核决定映射的起始虚拟地址,这也是最常见的设置方式,这与我们调用shmat映射虚拟内存指定NULL是一样的。
- 如果不设置为NULL,而是自己指定的,指定的起始虚拟地址必须是虚拟页(4K)的整数倍,这与自己指定的shmat的映射起始虚拟地址也是一样的。
- length:映射长度,也就是你想对文件映射多长。
- prot:指定对映射区的操作权限,可指定如下命令宏:
- PROT_EXEC:映射区的内容可执行。
如果你映射的是普通文件是一个可执行文件的话,将映射权限指定为PROT_EXEC后,是能够通过映射后的虚拟地址去执行文件中的“命令”。 - PROT_READ:映射区的内容可读。
- PROT_WRITE:映射区的内容可写。
- 以上三种选项可相互 | 操作。
- 比如:PROT_EXEC | PROT_READ
- PROT_NONE:映射区不允许访问(不允许执行、读、写),一般不会指定这个,如果指定不可访问的话,映射没有意义了。
- PROT_EXEC:映射区的内容可执行。
- flags:向映射区写入了数据,是否将数据立即更新到文件中。
- MAP_SHARED:立即更新
- fd:需要被映射文件的描述符
- offset:
- 表示从文件头的offset处开始映射。
- 一般都指定为0,表示从头文件头开始映射。
- addr:人为指定映射的起始虚拟地址
munmap:取消映射
- 函数原型
- int munmap(void *addr, size_t length);
- 功能
- 取消映射
- 返回值
- 调用成功返回0,失败则-1,errno被设置。
- 参数
- addr:映射的起始虚拟地址
- length:需要取消映射的长度
代码演示
写一个程序,将A文件的大量数据复制到B文件中。
如果采用传统方式,使用read函数从A文件读出数据,然后向B文件write,如果数据量很大的话,复制的效率会非常低,此时我们就可以使用存储映射来实现。
失败了(BUS error)
mmap映射文件size为0的文件时,会映射失败,映射失败时内核会向进程发送一个SIGBUS信号,提示mmap失败了,这个信号的默认处理方式是终止,所以当进程收到这个信号是就被异常终止了。
如果你不想被这个信号终止,你可以自己忽略或屏蔽这个信号,一般来说我们不需要忽略和屏蔽该信号。
那么解决方式就是不让目标文件size为0.
int main(int argc, char *argv[])
{
int srcfd = -1;
int dstfd = -1;
void *srcaddr = NULL;
void *dstaddr = NULL;
struct stat src_statbuf = {0};
/* Open the source file for reading and writing */
srcfd = open("./file_lock.h", O_RDWR);
if (srcfd == -1) print_err("open ./file_lock.h fail", __LINE__, errno);
/* open the target file */
dstfd = open("./file", O_RDWR|O_CREAT|O_TRUNC, 0664);
if (dstfd == -1) print_err("open ./file fail", __LINE__, errno);
/* Map source file */
/* Get file size */
fstat(srcfd, &src_statbuf);
srcaddr = mmap(NULL, src_statbuf.st_size, PROT_READ, MAP_SHARED, srcfd, 0);
if (srcaddr == (void *)-1) print_err("mmap fail", __LINE__, errno);
/* Map destination file*/
ftruncate(dstfd, src_statbuf.st_size);
dstaddr = mmap(NULL, src_statbuf.st_size, PROT_WRITE, MAP_SHARED, dstfd, 0);
if (dstaddr == (void *)-1) print_err("mmap fail", __LINE__, errno);
/* Copy the data from the source file to the destination file */
memcpy(dstaddr, srcaddr, src_statbuf.st_size);
return 0;
}