前言
本篇文章我们来讲解一下内存映射。
一、内存映射概念
内存映射是一种将文件或其他设备映射到进程的虚拟内存空间的技术。它通过在进程的地址空间中创建一个映射区域,使得进程可以像访问内存一样直接访问文件或设备的内容。内存映射提供了一种高效的方式来进行文件 I/O 操作和共享内存数据。
在内存映射中,操作系统为进程创建了一个虚拟内存区域,该区域与实际的文件或设备建立了映射关系。当进程通过对该内存区域进行读写操作时,对应的文件或设备内容也会被读取或写入。
内存映射的主要优点包括:
1.直接访问:通过内存映射,进程可以像访问内存一样直接访问文件或设备的内容。这消除了传统的读取和写入文件的系统调用的开销,提高了读写性能。
2.共享内存:多个进程可以将同一个文件映射到各自的地址空间中,使得它们可以共享文件的内容。这种共享内存的方式可以用于进程间通信和数据共享。
3.简化文件 I/O 操作:通过内存映射,可以将文件的内容直接映射到内存中,从而省去了使用 read() 和 write() 等传统的文件 I/O 函数的步骤。这样就可以通过简单的内存操作来处理文件数据。
内存映射的实现通常涉及以下步骤:
1.打开文件或设备:首先,需要打开要映射的文件或设备,通常使用标准的文件 I/O 函数(如 open())来打开文件并获取文件描述符。
2.创建映射:使用操作系统提供的内存映射函数(如 mmap())来创建映射区域,将文件或设备的内容映射到进程的虚拟内存空间中。
3.访问数据:一旦内存映射建立,进程就可以通过对映射区域进行读取和写入操作,无需使用传统的文件 I/O 函数。
4.解除映射:最后,当进程不需要访问映射数据时,应该使用相应的函数(如 munmap())来解除内存映射。
需要注意的是,内存映射在操作系统层面进行,因此在不同的操作系统中,对内存映射的支持和实现方式可能会有所不同。
总结起来,内存映射是一种通过让文件或设备的内容直接映射到进程的虚拟内存空间中,实现高效文件 I/O 操作和共享内存数据的技术。它提供了直接访问性能高、共享数据的能力,并简化了对文件内容的访问操作。
二、mmap函数介绍
mmap(memory map)函数是用于在应用程序和内核之间建立内存映射的系统调用函数。它允许应用程序将一个文件或者匿名内存映射到其地址空间,从而实现对文件或内存区域的直接访问。
函数原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数说明:
addr:指定欲映射的首地址,通常设为0,表示由系统自己选择映射的地址。
length:指定映射的内存区域长度,以字节为单位。
prot:指定映射区域的保护方式(权限)。可以是PROT_NONE(无权限)、PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)等之一,也可以通过位掩码进行组合,例如PROT_READ | PROT_WRITE。
flags:用于指示映射类型和映射属性的标志位。常用的标志包括MAP_SHARED(与其他映射该文件的进程共享更新),MAP_PRIVATE(复制映射,对映射的修改不会写回文件),MAP_ANONYMOUS(创建匿名映射)等。
fd:要映射的文件描述符,如果映射的是匿名内存,则为-1。
offset:文件映射的偏移量,对于匿名映射来说通常为0。
返回值是映射区域的起始地址,如果映射失败则返回MAP_FAILED(通常是-1)。
通过mmap函数,应用程序可以将文件(如磁盘上的文件)或匿名内存映射到自己的虚拟地址空间,从而使得应用程序能够直接对内存区域进行读写操作,就像访问普通内存一样。
mmap函数在许多场景中都很常见,常用的用途包括:
1.文件 I/O:通过将文件映射到内存,可以使用内存操作来读写文件,避免了频繁的文件 I/O 操作。
2.共享内存:多个进程可以通过共享同一个文件的映射区域来实现进程间的通信和数据共享。
3.匿名映射:用于分配一片匿名的内存区域,可以用于存储临时数据或实现进程间的共享数据结构等。
4.需要注意的是,对映射区域的操作可能需要使用同步机制(如互斥锁、信号量等)来实现多个进程或线程之间的正确同步,以避免数据一致性问题。
总而言之,mmap函数是用于在应用程序和内核之间建立内存映射的系统调用函数,它允许应用程序直接访问映射的文件或内存区域,提供了更高效和方便的数据访问方式
三、ftruncate函数
ftruncate函数是一个用于改变文件大小的系统调用函数。它可以截断或扩展一个已打开文件的大小。该函数常用于更改文件的大小,以满足应用程序的需要。
函数原型如下:
int ftruncate(int fd, off_t length);
参数说明:
1.fd:文件描述符,指向一个已打开的文件。
2.length:指定文件的新大小,以字节为单位。
3.返回值是一个整数,表示函数执行的结果。如果成功,返回0;如果出现错误,返回-1,并设置errno变量来指示具体的错误原因。
ftruncate函数的作用是改变文件的大小,它有以下几种用法:
1.如果指定的长度length小于文件的当前大小,那么文件将被截断为指定的长度。文件内容将被删除,只有剩余的部分保留在文件中。
2.如果指定的长度length大于文件的当前大小,那么文件将被扩展为指定的大小。在扩展的部分,文件会使用空字节(0)来填充。
3.如果指定的长度length与文件的当前大小相等,文件大小将保持不变,没有任何改变。
4.需要注意的是,ftruncate函数只能用于常规文件,不能用于特殊的设备文件或管道。在使用ftruncate函数之前,必须先使用open或creat函数打开或创建文件,并获得有效的文件描述符。
四、mmap函数使用
下面我们使用ftruncate函数将一个文件大小扩展为100,然后使用mmap函数为这个文件创建一个映射区,那么操作这个映射区就相当于操作这个文件了。
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <sys/mman.h> int main(void) { int fd; char* p; int len; fd = open("1.txt", O_RDWR); if(fd == -1) { printf("open err\n"); return -1; } ftruncate(fd, 100); len = lseek(fd, 0, SEEK_END); p = mmap(0, len, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0); if(p == MAP_FAILED) { printf("mmap is err\n"); return -1; } strcpy(p, "Hello World\n"); munmap(p, len); return 0; }
使用cat 命令查看文件内容:
这里可以看到Hello World被成功的写入文件中了:
五、mmap函数中的MAP_SHARED和MAP_PRIVATE
MAP_SHARED和MAP_PRIVATE。这两个标志位影响着映射的行为和对映射内存的访问权限。
1.MAP_SHARED:
当使用MAP_SHARED标志位时,多个进程可以共享同一个映射区域。这意味着对于这个映射区域所做的修改会在所有共享该区域的进程之间可见。
当一个进程修改了共享区域中的数据后,这个修改会被立即写入到文件中,这样其他进程就可以读取到最新修改后的数据。
使用MAP_SHARED标志位时,对映射区域的修改相当于对原文件的修改。因此,该映射区域对所有进程都是可见的,包括对文件尺寸的修改。
这种共享映射适用于需要多个进程之间共享数据的场景,如进程间通信、共享内存等。
2.MAP_PRIVATE:
当使用MAP_PRIVATE标志位时,每个进程都会有一个独立的映射副本。修改这个副本中的数据不会影响到其他进程的映射或者原始文件。
对MAP_PRIVATE映射的修改只会影响到当前进程的映射视图,不会写回到原文件中。因此,其他进程或者同一进程的其他映射视图看不到这些修改。
使用MAP_PRIVATE标志位时,多个进程之间的数据修改是相互独立的,互不可见的。
这种私有映射适用于需要将文件内容作为只读数据加载到内存中,或者对映射区域进行修改而不希望影响其他进程或原文件的场景。
总结
mmap函数优点总结:
1.文件和内存的无缝连接:mmap函数将文件映射到内存中,使得文件的内容可以直接在内存中进行读写操作。这种无缝连接提供了高效的 I/O 操作方式,使得对文件的访问更加简单、灵活和高效。
2.延迟加载和部分加载:使用mmap函数映射文件时,文件的内容并不会立刻全部复制到内存中。而是采用了一种延迟加载的方式,只有在真正需要访问某个页面时,才会将该页面从磁盘加载到内存中。这种延迟加载的特性使得程序在启动时的初始化速度更快,同时也能够节省内存的消耗。
3.零拷贝操作:mmap函数提供了零拷贝的特性,即在文件和内存之间的数据传输时,可以避免不必要的数据拷贝。当使用mmap读取文件时,数据可以直接从文件系统缓存将数据复制到内存中,而无需经过中间缓冲区。这样可以显著提高文件读取的效率,减少数据拷贝的开销。
4.共享内存:mmap函数还可以用于进程间的共享内存通信。多个进程可以将同一个文件映射到它们的地址空间中,从而实现了共享数据的目的。这种共享内存的方式比传统的进程间通信方式(如管道、消息队列等)更加高效,并且不需要进行数据的拷贝,减少了进程间通信的开销。
5.内存映射文件缓存机制:操作系统会维护一个内存映射文件缓存机制,可以提高对映射文件的访问速度。当多个进程共享同一个文件时,它们可以共用该文件的缓存数据,从而加快文件的读取速度。这种缓存机制可以显著提高文件访问的效率,避免重复的磁盘 I/O。