mmap使用测试

2023-09-21 12:19:17

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);

描述

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 <fcntl.h>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

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<char *>(
      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;
}

部分映射

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

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

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<char *>(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;
}

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

更多推荐

79、SpringBoot 整合 R2DBC --- R2DBC 就是 JDBC 的 反应式版本, R2DBC 是 JDBC 的升级版。

★何谓R2DBCR2DBC就是JDBC的反应式版本,R2DBC是JDBC的升级版。R2DBC是ReactiveRelationalDatabaseConnectivity(关系型数据库的响应式连接)的缩写反应式的就是类似于消息发布者和订阅者,有消息就进行推送。R2DBC中DAO接口中方法的返回值是Flux或Mono因此

嵌入式笔试面试刷题(day5 IIC详解)

文章目录前言一、IIC需要几根线分别是什么线二、IIC优势三、IIC可以挂载多少个从设备,主设备1.从设备数量2.主设备数量四、IIC是全双工还是半双工五、SDA和SCL为什么配置为上拉开漏输出模式1.为什么要配置为开漏输出不能是推挽输出a.实现线与功能b.保护设备不会被短路2.上拉电阻的作用a.确保空闲状态保持高电平

Java synchronized关键字常见面试题

1、什么是线程同步,为什么需要线程同步?线程同步是一种机制,用于控制多个线程对共享资源的访问,以防止并发问题。它需要确保在同一时刻只有一个线程可以访问共享资源,以避免数据竞争和不一致性。2、请解释Java中synchronized关键字的作用和用法。synchronized关键字用于实现线程同步。它可以应用于方法或代码

0021Java程序设计-SSM框架图书管理系统

文章目录摘要目录系统设计开发环境摘要伴随着时代的进步,以及科学技术的不断发展,越来越多的人会从图书馆中借阅书籍,从而获得新的知识。但是,传统的图书管理方法有着很多不便之处,而且还容易丢失,因此,很有必要设计出一个易于操作,具有高便捷性,还可以提高工作效率的图书管理系统。该系统采用了Java技术,采用了计算机技术,取代了

python经典百题之请问他多少岁

题目:有5个人坐在一起,问第五个人多少岁?他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第三个人,又说比第2人大两岁。问第2个人,说比第一个人大两岁。最后问第一个人,他说是10岁。请问第五个人多大?方法1:迭代deffind_fifth_person_age_iterative():age=10#第一个

Multitor:一款带有负载均衡功能的多Tor实例创建工具

关于MultitorMultitor是一款带有负载均衡功能的多Tor实例创建工具,Multitor的主要目的是以最快的速度完成大量Tor进程的初始化,并将大量实例应用到我们日常使用的程序中,例如Web浏览器和聊天工具等等。除此之外,在该工具的帮助下,我们还可以在进行渗透测试和对基础设施安全性进行审计时提高隐蔽性和匿名性

4、wireshark使用教程

文章目录一、wireshark简介二、环境三、wireshark抓包三、wireshark过滤器使用一、wireshark简介Wireshark是使用最广泛的一款「开源抓包软件」,常用来检测网络问题、攻击溯源、或者分析底层通信机制。Wireshark抓包原理:单机情况:电脑直连互联网的单机环境。Wireshark直接抓

HR对职业发展进行思考

如果你还没有职业发展方面的思考,请不要怪自己,这很正常。没有谁是一开始就会如此清晰理性地对职业发展进行思考。笔者对职业发展有关话题进行系统性的思考,得益于两本书:第一本是哈佛大学泰勒·本-沙哈尔教授的《幸福的方法》,第二本书是美国学者比尔·博内特和戴夫·伊万斯写的《斯坦福大学人生设计课》,这两本书给了笔者很大的启发,并

【无标题】

未来,该算法可以在低延迟视频编码领域得到广泛应用。例如,该算法可以用于实时视频传输、视频会议、视频监控等场景,以提高视频编码的效率和质量。此外,该算法还可以扩展到其他编码平台,如H.264/AVC、VP9等,以满足不同应用场景的需求。关于每个图和公式的位置,以下是简要说明:图1:位于第2页,显示了所提出的速率控制算法的

MySQL备份与恢复

目录一、数据库备份二、数据备份的重要性三、MySQL完全备份优缺点实例:完全备份MySQL物理冷备份及恢复完全备份指定库中的部分表完全备份MySQL,服务器中所有的库完全备份一个或多个完的库(包括其中所有的表)查看备份文件MySQL完全恢复恢复数据库恢复数据表MySQL增量备份基于位置恢复基于时间点恢复一、数据库备份备

MySQL安装(1)

安装环境:Win1064位软件版本:MySQL5.7.24解压版一、下载点开下面的链接:https://downloads.mysql.com/archives/community/选择选择和自己系统位数相对应的版本点击右边的Download,此时会进到另一个页面,同样在接近页面底部的地方找到如下图所示的位置:不用理会

热文推荐