1.引言
存储映射I/O(Memory-mapped I/O)使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当在缓冲区中存取数据,就相当于读写文件中的相应字节。这样就可以在不使用read和write的情况下执行I/O。
2.mmap函数
首先,需要告诉内核将一个给定的文件映射到一个存储区域中。这由mmap函数实现。
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off );
//返回值:若成功则返回映射区的地址,若出错则返回MAP_FAILED
addr参数指定映射存储区的起始地址。通常将其设置为0,表示由系统选择映射区的起始地址(有利于可移植性)。mmap的返回地址是该映射区的起始地址。
filedes指定要被映射文件的描述符。在映射该文件到一个地址空间之前,先要打开该文件。
len是映射的字节数。
off是要映射字节在文件中的起始(SEEK_SET)偏移量。
prot参数说明对映射区域的保护要求,可以是PROT_READ(映射区可读)、PROT_WRITE(映射区可写)、PROT_EXEC(映射区可执行)或PROT_NONE(映射区不可访问)。注意:对指定映射存储区的保护要求不能超过文件open模式访问权限。
flag参数影响映射存储区的多种属性,取值可以是:1)MAP_FIXED,表示返回值必须等于addr,这不利于可移植性。如果未设置MAP_FIXED,且addr不为0,那么内核只是将addr作为映射地址的一种建议,并不保证实际映射地址就是addr;2)MAP_SHARED,指示存储操作修改映射文件。也就是说直接对原文件进行操作,必须指定该标志或下一个标志中的一个;3)MAP_PRIVATE,说明对映射区的存储操作导致创建该映射文件的一个私有副本。
此外,需要记住以下几点:
off和addr的值(如果指定了MAP_FIXED)通常应当是系统虚存页长度的倍数。虚存页长度可用带参数_SC_PAGESIZE或_SC_PAGE_SIZE的sysconf函数得到。因为off和addr常常指定为0,所以这种要求通常并不重要。
因为映射文件的起始偏移量受系统虚存页长度的限制,那么如果映射区的长度不是页长的整数倍时,将如何呢?假定文件长队为12,系统页长度为512字节,则系统通常提供512字节的映射区,其中后500字节被设置为0。可以修改这500字节,但是任何变动不会在文件中反映出来。所以,不能用mapp将数据添加到文件中。为了做到这一点,必须首先加长该文件。
由于子进程复制父进程的地址空间,而存储映射区是该地址空间的一部分,因此,在调用fork之后,子进程继承存储映射区。由于同样的理由,调用exec后的新程序则不继承此存储映射区。
3.其他接口
调用mprotect可以更改一个现存映射存储区的权限。prot的许可值与mmap中prot参数一样,地址参数addr的值必须是系统页长的整数倍。
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
//返回值:成功返回0,错误返回-1
如果在共享存储映射区中的页已经修改,那么可以调用msync将该页冲洗到被映射的文件中,其作用类似于fsync。如果映射是私有的,那么不修改被映射的文件。addr同样必须与页边界对齐。flags参数一定要指定MS_ASYNC和MS_SYNC中的一个。如果希望在函数返回之前等待写操作完成,则可指定MS_SYNC标志。MS_INVALIDATE是一个可选标志,使用它可以通知操作系统丢弃与底层存储器没有同步的任何页。
#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);
//返回值:成功返回0,错误返回-1
进程终止时,或调用了munmap后,存储映射区就被自动解除映射。关闭文件描述符filedes并不解除映射区。调用munmap不会使映射区的内容写到磁盘文件上。对于MAP_SHARED区磁盘文件的更新,在写到存储映射区时按内核虚存算法自动进行。在解除了映射后,对于MAP_PRIVATE存储区的修改被丢弃。
#include <sys/mman.h>
int munmap(caddr_t addr, size_t len);
//返回值:成功返回0,错误返回-1
4.范例
//存储映射复制一个文件(类似于cp(1)命令)
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>//为了取得文件信息,主要需要文件长度,才能知道映射多长
#include <stdio.h>
#include <stdlib.h>
int main(int argc, int **argv)
{
int fdin,fdout;
void *src,*dst;
struct stat statbuf;
fdin=open(argv[1],O_RDONLY);
fdout=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0x777);
fstat(fdin,&statbuf);
lseek(fdout,statbuf.st_size-1,SEEK_SET);//mmap并不加长文件,需要自行加长文件
write(fdout,"",1);//在这里写一下文件就加长了。好比创建了一个有空洞的文件
src=mmap(0,statbuf.st_size,PROT_READ,MAP_SHARED,fdin,0);
dst=mmap(0,statbuf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fdout,0);
printf("source address: %lxndestination address: %lxn",(long unsigned int)src,(long unsigned int)dst);
memcpy(dst,src,statbuf.st_size);
munmap(src,statbuf.st_size); //解除映射
munmap(dst,statbuf.st_size);
return 0;
}