• mmap使用测试


    mmap使用测试

    Linux系统调用mmap()api说明,这个系统函数在频繁读写文件是很高效。

    mmap在调用进程内开辟一块内存空间,将文件(或文件部分)内容映射到调用的进程的虚拟空间中。进程通过操作这块mmap开辟的虚拟内存,就相当于直接操作文件本身了,其余的细节都由kernel,cpu中的mcu负责完成。这样与传统的I/O操作相比,不仅省去了写入I/O缓存,再有I/O写出的次数,还更加稳定可靠。

    这篇文章主要尝试使用mmap进行频繁写文件操作尝试。

    函数原型

    void *mmap(void addr[.length], size_t length, int prot, int flags, int fd, off_t offset);
    
    • 1

    描述

    mmap()函数会在调用它进程的虚拟内存空间中创建一个映射。创建的映射开始地址是通过函数参数addr进行设定。参数length表示这个映射的长度(这个值必须大于0)。

    如果addr的值是NULL,kernel会决定映射在虚拟内存中的创建起始地址,这也是创建映射最便捷的方式。

    如果addr的不是NULL,kernel会根据addr值所表明的位置作为映射的起始地址(参数addr值不一定是最终的映射创建地址,kernel将其作为一个参考值,在其周边选择一个页边界地址开始创建映射。这个边界地址总是要大于**/proc/sys/vm/mmap_min_addr**的值)并尝试创建映射。

    如果另一个映射已经存在于需要的地址上,kernel会选择在另一个新地址上创建映射,这个起始地址可能依赖于addr也可能于之无关,不依赖于这个参数地址值。

    函数最终的返回值就是最终创建映射的起始地址。

    fd所表示的文件句柄,一个文件映射的内容从文件的offset开始,长度length个字节。offset必须是一个内存页大小的倍数(sysconf(_SC_PAGE_SIZE))。

    mmap()调用返回后,文件描述器fd在不检查映射的有效性的前提下可以快速关闭。

    参数prot描述的是映射的内存保护级别(这个值不能与文件的打开模式冲突)。

    prot参数值:

    • PROT_EXEC 页可执行
    • PROC_READ 页可读
    • PROC_WRITE 页可写
    • PROC_NONE 页不能存取

    flags参数确定映射的更新对于映射同一区域的其他进程是否可见,以及更新是否传递到基础文件。flags可以有以下值:

    • MAP_SHARED

      共享映射。映射的更新对映射到同一块区域(内存)的其他进程也可见,并且(在文件支持的映射的情况下)被传递到基础文件。

    • MAP_SHARED_INVALIDATE (Linux 4.15后)

      这个值的结果与 MAP_SHARED 一样,只是MAP_SHARED会忽略flags未知的flag值。

    • MAP_PRIVATE

      创建写时复制的私有映射。映射的更新对于映射同一文件的其他进程是不可见的,并且不会传递到基础文件。在mmap()调用后,对文件做出的修改在映射区域是否可见没有明确。

    除此外,0或其他的flag值可以使用。

    **munmap()**系统调用删除指定地址范围的映射,并导致对该范围内地址的进一步引用生成无效的内存引用。进程终止时,区域也会自动取消映射。另一方面,关闭文件并不能取消内存映射。

    返回值

    mmap()在成功映射之后会返回映射的指针。一旦出错,返回MAP_FAILED(值是 (void *) -1)值。并且 errno 会被设置以提示这个错误。

    munmap()成功执行后返回 0。一旦出错,函数返回 -1,并且 errno 会被设置以提示这个错误(可能是EINVAL)。

    使用示例

    在使用mmap进行文件映射时,可以整个文件全部映射到虚拟内存中,也可以部分内容映射。

    测试文件大小是101MB。

    整体映射

    整个文件一次性映射到进程的虚拟内存。对于超大文件,kernel会自行将文件映射到不同位置。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    
    void WriteContentWhole(const string &dest_file, const string &msg) {
    
      string input_text = move(msg);
    
      // 要写入的内容长度
      int input_text_length = input_text.size();
      cout << "预期要写入的文本长度(bytes): " << input_text_length << endl;
    
      // 判断输入的内容最后是否是\n,若不是\n,在输入的文本后添加\n。
      if (input_text.at(input_text_length - 1) != '\n') {
        char buff[1]{'\n'};
        input_text.append(buff);
        input_text_length = input_text.size();
      }
    
      string path(dest_file);
      int fd = open(path.c_str(), O_RDWR | O_APPEND, 777);
      if (fd == -1) {
        cout << "文件打开失败" << endl;
        return;
      }
    
      struct stat st;
      if (fstat(fd, &st) == -1) {
        cout << "file stat error" << endl;
        return;
      }
    
      off_t new_file_size = 0;
      off_t origin_file_size = st.st_size; // 文件大小
    
      // 读取文件最后一个字符,判断是否是\n符号
      if (lseek(fd, -1, SEEK_END) == -1) {
        perror("无法定位到文件末尾");
        return;
      }
      char buffer[1]{0};
      int ret = read(fd, buffer, 1);
      if (ret == -1) {
        perror("无法读取文件最后一个字符");
        return;
      }
    
      if ((ret == sizeof(buffer)) && (buffer[0] != '\n')) {
        cout << "文件最后一个字符不是'\\n'" << endl;
        char buff[1]{'\n'};
        input_text.insert(0, buff);
        input_text_length = input_text.size();
      } else {
        cout << "文件最后一个字符是'\\n'" << endl;
      }
    
      cout << "最终要写入的文本长度(bytes): " << input_text_length << endl;
      // 设置最终写入内容后的新的文件大小
      new_file_size = origin_file_size + input_text_length;
    
      int result = ftruncate(fd, new_file_size);
      if (result == -1) {
        perror("无法设置文件大小");
        return;
      }
      char *mptr = static_cast(
          mmap(0, new_file_size, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0));
    
      // 文件映射完成后,关闭文件
      close(fd);
    
      if (mptr == MAP_FAILED) {
        cout << "文件内存映射失败," << strerror(errno) << endl;
        exit(1);
      }
    
      strncpy(mptr + origin_file_size, input_text.c_str(), input_text_length);
    
      munmap(mptr, new_file_size);
    }
    
    int main() {
    
      WriteContentWhole("/home/xacsz/dependencies.txt", u8"整个文件进行mmap映射");
    
      cout << "Completed ..." << endl;
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    部分映射

    部分映射及将文件的一部分映射到虚拟内存空间中。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    void WriteContent(const string &dest_file, const string &msg) {
    
      string input_text = move(msg);
    
      // 要写入的内容长度
      int input_text_length = input_text.size();
      cout << "预期要写入的文本长度(bytes): " << input_text_length << endl;
    
      // 判断输入的内容最后是否是\n,若不是\n,在输入的文本后添加\n。
      // 这段if判断知识判断输入的内容最后一个是否包含回车换行符。可以忽略
      if (input_text.at(input_text_length - 1) != '\n') {
        char buff[1]{'\n'};
        input_text.append(buff);
        input_text_length = input_text.size();
      }
    
      string path(dest_file);
      int fd = open(path.c_str(), O_RDWR | O_APPEND, 777);
      if (fd == -1) {
        cout << "文件打开失败" << endl;
        return;
      }
    
      struct stat st;
      if (fstat(fd, &st) == -1) {
        cout << "file stat error" << endl;
        return;
      }
    
      off_t new_file_size = 0;
      off_t origin_file_size = st.st_size; // 文件大小
    
      // 读取文件最后一个字符,判断是否是\n符号
      if (lseek(fd, -1, SEEK_END) == -1) {
        perror("无法定位到文件末尾");
        return;
      }
      char buffer[1]{0};
      int ret = read(fd, buffer, 1);
      if (ret == -1) {
        perror("无法读取文件最后一个字符");
        return;
      }
    
      if ((ret == sizeof(buffer)) && (buffer[0] != '\n')) {
        cout << "文件最后一个字符不是'\\n'" << endl;
        char buff[1]{'\n'};
        input_text.insert(0, buff);
        input_text_length = input_text.size();
      } else {
        cout << "文件最后一个字符是'\\n'" << endl;
      }
    
      cout << "最终要写入的文本长度(bytes): " << input_text_length << endl;
      // 设置最终写入内容后的新的文件大小
      new_file_size = origin_file_size + input_text_length;
    
      int result = ftruncate(fd, new_file_size);
      if (result == -1) {
        perror("无法设置文件大小");
        return;
      }
    
      // 系统页大小
      const long kPageSize = sysconf(_SC_PAGE_SIZE);
      cout << "内存页大小: " << kPageSize << endl;
    
      // 固定映射内存大小是2M,对文件进行偏移设置,每次将文件指针确认到文件末尾
      const size_t mapping_size = kPageSize * 1024 / 2; // 2M映射大小
      // 文件偏移
      off_t file_offset = 0;
      // 将文件索引移动到最后一个映射大小的首位
      while (file_offset + mapping_size <= origin_file_size) {
        file_offset += mapping_size;
      }
      size_t remaining_size = origin_file_size - file_offset;
      cout << "原始文件大小: " << origin_file_size << ",文件偏移: "
           << file_offset << ",原始文件剩于字节数: " << remaining_size
           << ",映射内存大小: " << mapping_size << ",新文件大小: "
           << new_file_size << endl;
    
      // mmap参数:
      //    0 的位置可以传入nullptr,然后由kernel来确定在哪个位置开始开辟虚拟内存空间
      //    mapping_size 位置传入系统页(sysconf(_SC_PAGE_SIZE))的整数倍,此段代码中2M大小
      //    PROT_WRITE | PROT_READ 设定了这段内段的访问方式,这里是可读写
      //    MAP_SHARED 表明这段内存可以被映射相同文件的其他进程可见
      //    fd 要映射的文件的句柄
      //    file_offset 文件索引的偏移位置,由于映射到内存(与内存页对应),因此这里传入的值是内存页大小的倍数
      char *mptr = static_cast(mmap(0, mapping_size, PROT_WRITE | PROT_READ,
                                            MAP_SHARED, fd, file_offset));
    
      // 文件映射完成后,关闭文件
      close(fd);
    
      if (mptr == MAP_FAILED) {
        cout << "文件内存映射失败," << strerror(errno) << endl;
        exit(1);
      }
    
      // 遍历要写入内容的位置。
      int start_index_written = origin_file_size - file_offset;
      cout << "开始写入的文件偏移位置:" << start_index_written << endl;
    
      strncpy(mptr + start_index_written, input_text.c_str(), input_text_length);
    
      munmap(mptr, mapping_size);
    }
    
    int main() {
    
      WriteContent("/home/xacsz/dependencies.txt", u8"世界就是这么庞大!");
      
      cout << "Completed ..." << endl;
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126

    所有不对的地方,欢迎讨论。

  • 相关阅读:
    JDBC入门和API详解
    netapp fas2220更换硬盘的记录
    java多线程应用场景
    生成树协议 STP(spanning-tree protocol)
    贪吃蛇代码
    RabbitMQ——死信队列
    k8s kubernetes 1.23.6 + flannel公网环境安装
    CompletableFuture 方法总结
    Redis 中两个字段排序
    python 图像处理(一阶梯度图像和角度图像)
  • 原文地址:https://blog.csdn.net/snowgeneral/article/details/133131427