5.3 内存映射
内存映射允许不同的进程通过一个共享的文件通信。内存映射可以被用在进程间通信或者一种方便的对文件的存取方式。
内存映射把一个文件和一个进程的内存空间联系在一起。Linux 把文件分成页大小的块,然后把它们复制到虚拟内存页中
,这样内容就可以在进程的地址空间可用。这样,进程就可以通过一般的内存读写的方式读写取文件的内容。这样就可以
快速的访问文件的内容。
你可以认为内存映射如同分配一个足够的缓冲区,然后把文件的内容读到该缓冲区,当缓冲区的内容改变时再写回到文
件中。Linux为你处理了读和写的操作。
5.3.1 映射一个普通文件
使用 mmap() 把一个文件映射到进程空间。
SYNOPSIS
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
描述:
mmap() 在进程的虚拟地址空间创建了一个新的映射。这个新映射的开始地址由 addr指定。 length 指定了映射的
长度(以字节为单位)。 prot 描述了需要的内存保护(不能和文件的打开方式相冲突)它可以是 PROT_NONE 或者
是如下各项的 按位相或:
PROT_EXEC //页可以执行
PROT_READ //页可读
PROT_WRITE//页可写
PROT_NONE //页不可访问[只可单独使用]
flags 决定了对内存中映射的改变是否对其他进程可见,是否对相应的文件执行更新操作。它们只可以为下面的某一项。
MAP_SHARED//共享映射。对内存映射的更新对其他进程可见,而且要反应到文件上。改变可能在调用 msync()或者
munmap()之前没有被真正的反映到文件上。
MAP_PRIVATE//对内存映射的改变不会被写回到相关的文件。但会写到一个相应的私有的复制文件中。这对其他进程不可见。
这一项也可以和上面的两项一起使用。
MAP_FIXED//如果指定了这一标志,Linux将会使用你请求的地址来映射文件,而不是仅仅把你传入的地址作为一个参考暗示。
这时这个地址必须是按页对齐的。
fd 是要执行映射的文件的描述符。
offset 必须是页大小的整数倍。
返回值:成功时返回内存映射的开始地址。失败时返回 MAP_FAILED.
munmap()可以用来释放内存映射。
5.3.3 例子
如下的程序 A:
产生一个随机数,写入到相应的映射文件中。
程序B:
读出这个随机数,乘以2写回到映射文件中。
Listing 5.5 (mmap-write.c) Write a Random Number to a Memory-Mapped File #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <time.h> #include <unistd.h> #define FILE_LENGTH 0x100 /* Return a uniformly random number in the range [low,high]. */ int random_range (unsigned const low, unsigned const high) { unsigned const range = high - low + 1; return low + (int) (((double) range) * rand () / (RAND_MAX + 1.0)); } int main (int argc, char* const argv[]) { int fd; void* file_memory; /* Seed the random number generator. */ srand (time (NULL)); /* Prepare a file large enough to hold an unsigned integer. fd = open (argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); lseek (fd, FILE_LENGTH+1, SEEK_SET); write (fd, “”, 1); lseek (fd, 0, SEEK_SET); /* Create the memory mapping. */ file_memory = mmap (0, FILE_LENGTH, PROT_WRITE, MAP_SHARED, fd, 0); close (fd); /* Write a random integer to memory-mapped area. */ sprintf((char*) file_memory, “%d/n”, random_range (-100, 100)); /* Release the memory (unnecessary because the program exits). */ munmap (file_memory, FILE_LENGTH); return 0; } Listing 5.6 (mmap-read.c) Read an Integer from a Memory-Mapped File, and Double It #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #define FILE_LENGTH 0x100 int main (int argc, char* const argv[]) { int fd; void* file_memory; int integer; /* Open the file. */ fd = open (argv[1], O_RDWR, S_IRUSR | S_IWUSR); /* Create the memory mapping. */ file_memory = mmap (0, FILE_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close (fd); /* Read the integer, print it out, and double it. */ sscanf (file_memory, “%d”, &integer); printf (“value: %d/n”, integer); sprintf ((char*) file_memory, “%d/n”, 2 * integer); /* Release the memory (unnecessary because the program exits). */ munmap (file_memory, FILE_LENGTH); return 0; } 你可以如下步骤运行, % ./mmap-write /tmp/integer-file % cat /tmp/integer-file 42 % ./mmap-read /tmp/integer-file value: 42 % cat /tmp/integer-file 84
5.3.3 文件的共享访问
不同的进程可以使用内存映射使用同一个关联文件来进程通信。指定 MAP_SHARED 这样你的任何改变将会写入到相关的文件中。
如果你不知定这个标志,Linux可能会只把改变写在内存中而不是立即反映到关联文件中。
你可以强制Linux把改变写入磁盘文件。这要使用 msync().
SYNOPSIS
#include <sys/mman.h>
int msync(void *addr, size_t length, int flags);
DESCRIPTION
addr //映射文件在内存中的起始地址。
length //映射区域的大小
flags //可以使用如下的标志:
MS_ASYNC//指定更新被安排,但是,调用立即返回,在返回前更新不一定被执行。
MS_SYNC //更新立即执行。在完成更新之前 msync调用阻塞。
MS_INVALIDATE //所有的其他文件映射无效,这样其他进程就可以看到改变后的数据。