Bootstrap

Linux系统编程-内存映射相关操作详解

        内存映射I/O(Memory-mapped I/O)是一种通过将文件的一部分或整个文件映射到进程的虚拟地址空间中来进行文件访问的技术。它允许程序直接在内存中操作文件数据,而不需要通过传统的readwrite系统调用来进行IO操作,这种方法通常比传统IO更高效,尤其在处理大文件时效率更高。这样一来,进程就可以通过简单的内存访问操作(读取或写入内存)来访问文件的内容,而不需要频繁地调用readwrite进行IO操作。

1.mmap()

mmap() 为调用进程创建一片内存映射。
原型:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数说明:
addr:映射的起始地址,通常设置为NULL,由系统选择合适的地址。
length:映射区域的长度,一般为文件的大小或者所需映射的字节数。
prot:映射区域的保护模式,可以是以下之一或其组合:
    PROT_READ:允许读取映射区域的数据。
    PROT_WRITE:允许写入映射区域的数据。
    PROT_EXEC:允许执行映射区域中的代码。
    PROT_NONE:映射区域不可访问。
flags:映射标志,可以是以下之一或其组合:
    MAP_SHARED:映射区域对所有映射到该文件的进程都可见,对映射区域的修改会被同步到文件中。
    MAP_PRIVATE:创建一个私有的映射,对映射区域的修改不会影响到原文件。
    MAP_FIXED:要求内核将映射区域映射到精确的addr指定的地址处(仅当addr非NULL时有效)。
    MAP_ANONYMOUS:映射一个匿名的内存区域,不与任何文件关联。
fd:要映射的文件的文件描述符,通过open()函数打开。
offset:文件中映射起始位置的偏移量,通常为0。
返回值:
成功时,返回映射区域的起始地址;
失败时,返回MAP_FAILED,并设置errno以指示错误类型。

使用步骤

  1. 打开文件:使用open()打开要映射的文件,并获取文件描述符。
  2. 映射文件:使用mmap()将文件映射到进程的地址空间中。
  3. 操作内存映射区域:通过返回的指针操作内存映射区域,进行读取、写入或其他操作。
  4. 同步修改(可选):如果需要,使用msync()手动同步修改到文件。
  5. 解除映射:使用munmap()解除映射关系,释放资源。

代码示例:

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

#define FILE_SIZE 4096 // 文件大小,这里假设为4KB

int main() {
    int fd;
    char *addr;

    // 打开文件
    fd = open("mmap_example.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 拓展文件大小到 FILE_SIZE
    if (ftruncate(fd, FILE_SIZE) == -1) {
        perror("ftruncate");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 映射文件到内存
    addr = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 关闭文件描述符,不影响内存映射
    close(fd);

    // 将一些数据写入内存映射区域
    const char *msg = "Hello, Memory Mapping!";
    strncpy(addr, msg, strlen(msg));

    // 读取并打印映射区域的内容
    printf("Content in memory mapped region: %s\n", addr);

    // 解除映射
    if (munmap(addr, FILE_SIZE) == -1) {
        perror("munmap");
        exit(EXIT_FAILURE);
    }

    return 0;
}

代码解释:

  • openftruncate:打开文件并确保文件大小为指定大小,这里是4KB。
  • mmap:将文件映射到进程的地址空间,返回映射后的地址指针addr
    • PROT_READ | PROT_WRITE指定映射区域可读写。
    • MAP_SHARED表示多个进程可以共享该映射。
  • close(fd):关闭文件描述符,但不影响内存映射的存在。
  • 写入数据:通过strcpy或类似的操作,向addr指向的内存区域写入数据。
  • 读取并打印数据:直接读取映射区域的数据,并打印输出。
  • munmap:解除内存映射关系,释放资源。

2.munmap()

munmap()用于解除通过mmap()建立的内存映射关系,释放对应的地址空间资源。

原型:
int munmap(void *addr, size_t length);
参数说明:
addr:要解除映射的起始地址,即之前通过mmap()返回的映射地址。
length:映射区域的长度,即之前通过mmap()传递的映射长度。
返回值:
成功时,返回0。
失败时,返回-1,并设置errno来指示具体的错误原因。

使用步骤

  1. 确定解除映射的地址和长度:通过mmap()成功返回的地址和长度确定要解除映射的范围。
  2. 调用munmap():传入映射的起始地址和长度,以释放对应的内存映射资源。

示例代码:

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

#define FILE_SIZE 4096 // 文件大小,这里假设为4KB

int main() {
    int fd;
    char *addr;

    // 打开文件
    fd = open("mmap_example.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 拓展文件大小到 FILE_SIZE
    if (ftruncate(fd, FILE_SIZE) == -1) {
        perror("ftruncate");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 映射文件到内存
    addr = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 关闭文件描述符,不影响内存映射
    close(fd);

    // 将数据写入内存映射区域
    const char *msg = "Hello, Memory Mapping!";
    strncpy(addr, msg, strlen(msg));

    // 同步修改到文件
    if (msync(addr, FILE_SIZE, MS_SYNC) == -1) {
        perror("msync");
        munmap(addr, FILE_SIZE);
        exit(EXIT_FAILURE);
    }

    // 解除映射
    if (munmap(addr, FILE_SIZE) == -1) {
        perror("munmap");
        exit(EXIT_FAILURE);
    }

    return 0;
}

3.msync()

msync()用于将指定内存区域的修改同步到对应的文件中,确保数据持久化到存储设备。

原型:
int msync(void *addr, size_t length, int flags);
参数说明:
addr:要同步到文件的内存区域的起始地址,即通过mmap()返回的映射地址。
length:要同步的内存区域的长度,即之前通过mmap()传递的映射长度。
flags:控制同步的行为,可以是以下值之一:
    MS_ASYNC:异步写入。表示请求立即返回,数据会被延迟写入磁盘。
    MS_SYNC:同步写入。表示数据同步到磁盘后,msync()才会返回。
    MS_INVALIDATE:使指定范围的数据失效。在写入之前,先使对应文件系统缓存中的数据无效。
返回值:
成功时,返回0。
失败时,返回-1,并设置errno来指示具体的错误原因。

使用步骤

  1. 确定要同步的内存地址和长度:通过mmap()成功返回的地址和长度确定要同步到文件的范围。
  2. 调用msync():传入映射的起始地址、长度和适当的同步标志,以将内存中的数据同步到文件系统。

示例代码:

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

#define FILE_SIZE 4096 // 文件大小,这里假设为4KB

int main() {
    int fd;
    char *addr;

    // 打开文件
    fd = open("mmap_example.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 拓展文件大小到 FILE_SIZE
    if (ftruncate(fd, FILE_SIZE) == -1) {
        perror("ftruncate");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 映射文件到内存
    addr = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 关闭文件描述符,不影响内存映射
    close(fd);

    // 将数据写入内存映射区域
    const char *msg = "Hello, Memory Mapping!";
    strncpy(addr, msg, strlen(msg));

    // 同步修改到文件
    if (msync(addr, FILE_SIZE, MS_SYNC) == -1) {
        perror("msync");
        munmap(addr, FILE_SIZE);
        exit(EXIT_FAILURE);
    }

    // 解除映射
    if (munmap(addr, FILE_SIZE) == -1) {
        perror("munmap");
        exit(EXIT_FAILURE);
    }

    return 0;
}

;