mmap

存储映射

存储映射I/O(Memory-mapped I/O)使一个磁盘文件与存储空间中的一个缓冲区相映射. 于是当从缓冲区中取数据, 就相当于读文件中的相应字节. 与此类似, 将数据存入缓冲区, 则相应的字节就自动写入文件. 这样, 就可在不适用read和write函数的情况下, 使用地址(指针)完成I/O操作

使用这种方法, 首先应通知内核, 将一个指定文件映射到存储区域中. 这个映射工作可以通过mmap函数来实现

作用: 将磁盘文件的数据映射到内存, 用户通过修改内存就能修改磁盘文件

mmap创建内存映射
  

匿名映射

通过使用我们发现, 使用映射区来完成文件读写操作十分方便, 父子进程间通信也较容易. 但缺陷是, 每次创建映射区一定要依赖一个文件才能实现. 通常为了建立映射区要open一个temp文件, 创建好了再unlink, close掉, 比较麻烦. 可以直接使用匿名映射来代替. 其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定

使用MAP_ANONYMOUS (或MAP_ANON), 如: int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

需注意的是, MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏. 在类Unix系统中如无该宏定义, 可使用如下两步来完成匿名映射区的建立

fd = open("/dev/zero", O_RDWR);
p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);

mmap无血缘关系进程间通信

实质上mmap是内核借助文件帮我们创建了一个映射区, 多个进程之间利用该映射区(需借助文件)完成数据传递. 由于内核空间多进程共享, 因此无血缘关系的进程间也可以使用mmap来完成通信. 只要设置相应的标志位参数flags即可. 若想实现共享, 当然应该使用MAP_SHARED了

基础API

mmap

void *mmap(
	void *addr,		// 建立映射区首地址, 由linux内核指定, 传NULL 
	size_t length, 	// 映射区的大小, 4k的整数倍, 不能为0, 一般文件有多大length就为多大
	int prot, 		// 映射区的权限, PROT_READ(映射区  必须  要有读权限), PROT_WRITE
	int flags,		// 标志位参数, 	MAP_SHARED(数据同步到磁盘), MAP_PRIVATE(数据不会同步到磁盘), 有血缘关系通信需是MAP_SHARED
	int fd, 		// 文件描述符, 	要映射文件对应的fd(open得来的)
	off_t offset	// 映射文件的偏移量, 	映射时文件指针的偏移量, 必须是4k的整数倍, 一般为0
);

返回值: 成功, 映射区首地址; 失败, MAP_FAILED

思考问题
如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?
  不能对ptr做操作, 可以复制一份, 对复制的指针进行操作
如果open是O_RDONLY, mmap时prot参数指定PROT_READ|PROT_WRITE会怎样
  mmap调用失败, open文件指定权限应该大于等于mmap第三个参数prot指定的权限
如果文件偏移量为1000会怎样
  必须是4096的整数倍
如果不检测mmap的返回值会怎么样
  没什么影响
mmap什么情况下会调用失败
  第二个参数length=0; 第三个参数没有指定PROT_READ; 第五个参数fd对应的open权限必须大于port权限; offset必须是4096的整数倍
可以open的时候O_CREAT一个新文件来创建映射区吗
  可以, 需要做文件拓展(lseek, truncate)
mmap后关闭文件描述符, 对mmap映射有无影响
  没有影响
对ptr越界操作会怎样
  段错误

munmap

int munmap(void *addr, size_t length);

参数:
  addr: mmap的返回值
  length: mmap的第二个参数

进程间通信, 不阻塞, 数据直接才内存中处理,
  有血缘关系,
    父子进程共享内存映射区, 可以创建匿名映射区, 可以不需要磁盘文件进行通信
  没有血缘关系
    不能使用匿名映射的方式, 只能借助磁盘文件创建映射区
    A(a.c) B(b.c)
    a.c: int fd1 = open("XXX"); void *ptr = mmap(,,,fd1, 0); 对映射区(ptr)进行读写操作
    b.c: int fd2 = open("hello"); void ptr* = mmap(,,,fd2, 0); 对映射区(ptr)进行写操作

示例程序

利用内存映射区读文件

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>

int main(int argc, const char* argv[]) {
    // 打开一个文件
    int fd = open("english.txt", O_RDWR);
    int len = lseek(fd, 0, SEEK_END);
    // 创建内存映射区
    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("error");
        exit(1);
    }

    printf("%s", (char*)ptr);

    // 释放内存映射区
    munmap(ptr, len);
    close(fd);

    return 0;
}

MAP_PRIVATE与MAP_SHARED测试

父子进程共享:

  1. 打开的文件
  2. mmap建立的映射区(但必须要使用MAP_SHARED)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(int argc, const char* argv[]) {
    // 打开一个文件
    int fd = open("english.txt", O_RDWR);
    int len = lseek(fd, 0, SEEK_END);
    
    // 通信测试
    //void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);    // MAP_PRIVATE, 父子进程不可通信
    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);   // MAP_SHARED, 父子进程可通信
    if (ptr == MAP_FAILED) {
        perror("error");
        exit(1);
    }
    close(fd);

    //printf("%s", (char*)ptr);

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork error");
        exit(1);
    }
    if (pid > 0) {
        // 父进程写数据
        strcpy((char*)ptr, "haha, hehe");
        // 回收
        wait(NULL);
    }
    else if (pid == 0) {
        // 读数据
        printf("%s\n", (char*)ptr);
    }

    // 释放内存映射区
    //ptr++;
    int ret = munmap(ptr, len);
    if (ret == -1) {
        perror("mmap error");
        exit(1);
    }

    return 0;
}

有血缘关系匿名映射区通信

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(int argc, const char* argv[]) {
    // 创建匿名内存映射区
    int len = 4096;

    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);      // MAP_SHARED可通信
    //void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);   // MAP_PRIVATE, 不可通信
    if (ptr == MAP_FAILED) {
        perror("error");
        exit(1);
    }

    //printf("%s", (char*)ptr);

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork error");
        exit(1);
    }
    if (pid > 0) {
        // 父进程写数据
        strcpy((char*)ptr, "haha");
        // 回收
        wait(NULL);
    }
    else if (pid == 0) {
        // 读数据
        printf("%s\n", (char*)ptr);
    }

    // 释放内存映射区
    //ptr++;
    int ret = munmap(ptr, len);
    if (ret == -1) {
        perror("mmap error");
        exit(1);
    }

    return 0;
}

无血缘关系利用内存映射区通信

读端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>

int main(int argc, const char *argv[]) {
    int fd = open("temp", O_RDWR | O_CREAT, 0664);
    ftruncate(fd, 4096);
    int len = lseek(fd, 0, SEEK_END);

    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    while (1) {
        sleep(1);
        printf("%s\n", (char*)ptr+1024);
    }

    // 释放
    int ret = munmap(ptr, len);
    if (ret == -1) {
        perror("munmap");
        exit(1);
    }

    return 0;
}

写端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>

int main(int argc, const char *argv[]) {
    int fd = open("temp", O_RDWR | O_CREAT, 0664);

    void *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    while (1) {
        char *p = (char *)ptr;
        p += 1024;
        strcpy(p, "haha, I'm fine ~\n");
        sleep(2);
    }

    int ret = munmap(ptr, 4096);
    if (ret == -1) {
        perror("munmap");
        exit(1);
    }

    return 0;
}
posted @ 2019-04-19 21:21  张飘扬  阅读(2649)  评论(0编辑  收藏  举报