Why Use Memory Map ?
1. 内存映射以页面为单位,将文件内容映射到内存中。
2. 使用内存映射可以创建内存映射文件。内存映射文件的优点是我们不需要调用 read 、write
之类的I/O函数,只需用从内存映射区取、存数据,实际的 I/O
操作是在背后由内核执行的。很多时候,这样做可以简化我们的代码。当然并不是所有的文件都可以被映射,比如终端和套接字文件就不能被映射。
3. 使用内存映射可以实现进程间共享内存。(
以 MAP_SHARED 调用 mmap)
Functions
#include
void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off );
Returns: starting address of mapped region if OK,
MAP_FAILED on error
说明:
1. 参数
addr 指明 描述字
filedes 指定的文件在进程地址空间内的映射区的开始地址,必须是页面对齐的地址,通常设为 NULL ,让内核去选择开始地址。任何情况下,mmap 的返回值为内存映射区的开始地址。
2. 参数
len 指明 文件需要被映射的字节长度。
off 指明文件的偏移量。通常
off 设为 0 。
※
如果 len 不是页面的倍数,它将被扩大为页面的倍数。扩充的部分通常被系统置为 0 ,而且对其修改并不影响到文件。
※
off 同样必须是页面的倍数。通过 sysconf(_SC_PAGE_SIZE)可以获得页面的。大小。
3. 参数
prot 指明映射区的保护权限。通常有以下 4 种。通常是 PROT_READ | PROT_WRITE 。
PROT_READ 可读
PROT_WRITE 可写
PROT_EXEC 可执行
PROT_NONE 不能被访问
4. 参数
flag 指明映射区的属性。取值有以下几种。MAP_PRIVATE 与 MAP_SHARED 必选其一,MAP_FIXED 为可选项。
1)MAP_PRIVATE 指明对映射区数据的修改不会影响 真正的文件。
2)MAP_SHARED 指明对映射区数据的修改,多个共享该映射区的进程都可以看见,而且会反映到实际的文件。
3)MAP_FIXED 要求 mmap 的返回值必须等于
addr 。如果不指定 MAP_FIXED 并且
addr 不为 NULL ,则对
addr 的处理取决于具体实现。考虑到可移植性,
addr 通常设为 NULL ,不指定 MAP_FIXED。
5. 当 mmap 成功返回时,
filedes 就可以关闭,这并不影响创建的映射区。
#include
int munmap(void *addr, size_t len);
Returns: 0 if OK, -1 on error
说明:
1. 进程退出的时候,映射区会自动删除。不过当不再需要映射区时,可以调用 munmap 显式删除。当映射区删除后,后续对映射区的引用会生成 SIGSEGV 信号。
#include
int msync(void *addr, size_t len, int flags);
Returns: 0 if OK, -1 on error
说明:
1. 当映射区数据被修改时,内核会稍后将其更新到文件。但有时候为了确保修改能被反映到文件,可以调用 msync 函数来进行同步操作。
2. 参数
addr 和
len 通常引用整个映射区,当然也可以指定映射区的一部分。
3. 参数
flags 控制回写到文件的具体方式。MS_ASYNC 、MS_SYNC 必须指定其一。
1)MS_ASYNC ,只是将写操作排队,并不等待写操作完成就返回。
2)MS_SYNC ,等待写操作完成后才返回。
3)MS_INVALIDATE ,作废与实际文件内容不一致缓存页,有的实现则是作废整个映射区的缓存页。后续的引用将从文件获取数据。
我们知道,共享内存可以用在非相关(unrelated)进程间进行通信。而对于相关(related)进程间的通信,一些实现提供了基于内存映射的不同的技术。下面两种就是。
(SVR 4 ) /dev/zero Memory Mapping
1. 可以将伪设备 "/dev/zero" 作为参数传递给 mmap 而创建一个映射区。/dev/zero
的特殊在于,对于该设备文件所有的读操作都返回值为 0 的指定长度的字节流 ,任何写入的内容都被丢弃。我们的兴趣在于用它来创建映射区,用
/dev/zero 创建的映射区,其内容被初始为 0 。
2. 使用 /dev/zero 的优点在于,mmap创建映射区时,不需要一个时间存在的文件,伪文件 /dev/zero 就足够了。缺点是只能用在相关进程间。
相对于相关进程间的通信,使用线程间通信效率要更高一些。不管使用那种技术,对共享数据的访问都需要进行同步。
3. 关于 /dev/zero ,请参考
/dev/null 与 /dev/zero文件 (zz)
(4.4 BSD) Anonymous Memory Mapping
1. 匿名内存映射 与 使用 /dev/zero 类型,都不需要真实的文件。要使用匿名映射之需要向 mmap 传入 MAP_ANON 标志,并且 fd 参数 置为 -1 。
2. 所谓匿名,指的是映射区并没有通过 fd 与 文件路径名相关联。匿名内存映射用在有血缘关系的进程间。
3. 匿名内存映射的创建如下所示:
if ((area = mmap(0, SIZE, PROT_READ | PROT_WRITE,
MAP_ANON | MAP_SHARED, -1, 0)) == MAP_FAILED)
A Example From UNPv2
#include "unpipc.h"
struct shared {
sem_t mutex; /* the mutex: a Posix memory-based semaphore */
int count; /* and the counter */
} shared;
int
main(int argc, char **argv)
{
int fd, i, nloop;
struct shared *ptr;
if (argc != 3)
err_quit("usage: incr3
<#loops>");
nloop = atoi(argv[2]);
/* 4open file, initialize to 0, map into memory */
fd = Open(argv[1], O_RDWR | O_CREAT, FILE_MODE);
Write(fd, &shared, sizeof(struct shared));
ptr = Mmap(NULL, sizeof(struct shared), PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
Close(fd);
/* 4initialize semaphore that is shared between processes */
Sem_init(&ptr->mutex, 1, 1);
setbuf(stdout, NULL); /* stdout is unbuffered */ (※)
if (Fork() == 0) { /* child */
for (i = 0; i < nloop; i++) {
Sem_wait(&ptr->mutex);
printf("child: %d\n", ptr->count++);
Sem_post(&ptr->mutex);
}
exit(0);
}
/* 4parent */
for (i = 0; i < nloop; i++) {
Sem_wait(&ptr->mutex);
printf("parent: %d\n", ptr->count++);
Sem_post(&ptr->mutex);
}
exit(0);
}
※ 代码中setbuf(stdout, NULL) 被解释为 是为了防止父子进程的输出相互影响,交错出现。
不过,为什么输出缓冲区设为 0 , 父子进程间的输出就不影响了?不是很理解。
Referencing Memory-Mapping Objects
1. 对于映射区各区段的引用,其结果如下所示:
|-------------------------------------------| (file size)
remainder of page
|-------------------------------------------|-------------------------------|-------------------------------------| (mmap size)
\<- reference
OK ->\<-
SIGBUS ->\<- SIGSEGV
由于内存映射是以 页 为单位进行的,因而我们可以访问比实际文件大小还有大的区域,如上图所示那样。
2. 内核会跟踪所映射的文件的大小。我们总是能够引用当前文件大小之内,并且也在映射区之内的数据。也就是说我们可以利用 ftruncate 函数动态的更新的文件的大小,而由该文件映射的共享内存同时也会自动更新。
---------------------------------------------------------------------------------------------------------------------
小技巧:生成固定大小的文件
fd = Open(PATHNAME, O_RDWR | O_CREAT | O_TRUNC, FILE_MODE);
Lseek(fd, filesize-1, SEEK_SET);
Write(fd, "", 1);
阅读(11401) | 评论(1) | 转发(0) |