mmap机制
mmap
在 Linux 中,”mmap” 通常指的是内存映射(Memory-Mapped)机制。
mmap
是一个系统调用,它允许应用程序将文件或设备的内容映射到进程的地址空间。这样,文件或设备的内容就可以像访问普通内存一样进行读写操作,而无需使用传统的文件读写系统调用(如read()
和write()
)。
mmap
机制的本质是提供一种高效的文件访问方式,通过以下几个方面实现:
直接内存访问:
通过将文件内容映射到进程的虚拟内存,应用程序可以直接通过内存地址访问文件数据,这避免了传统文件 I/O 调用中的数据复制步骤。延迟加载:
mmap
创建的内存映射是按需加载的,这意味着文件的内容只有在实际访问时才会被加载到物理内存中。这可以提高对大文件的处理效率,并减少内存的使用。共享内存:
mmap
可以用于创建共享内存区域,允许多个进程访问同一块内存。这是进程间通信(IPC)的一种方式,也可以用于共享文件的内容。页缓存(Page Cache):
使用mmap
映射的文件数据会被操作系统的页缓存管理,这意味着频繁访问的数据可以保留在缓存中,从而加快访问速度。同时,对映射内存的修改可以延迟写回到文件,直到必要时才进行,这可以减少磁盘 I/O 操作。虚拟内存管理:
mmap
依赖于操作系统的虚拟内存管理机制。当进程访问映射的内存区域时,如果对应的数据尚未加载到物理内存,操作系统会自动处理缺页中断(page fault),将数据从磁盘加载到内存中。
mmap
系统调用通常用于需要高效文件访问的场景,如数据库管理系统、文件编辑器、多媒体应用程序等。它也是实现内存映射文件和匿名映射(不与任何文件关联的内存区域)的基础。总的来说,
mmap
机制的本质是通过将文件或设备内容映射到虚拟内存,提供一种高效、灵活的内存访问和文件操作方式,同时充分利用操作系统的虚拟内存和页缓存功能。
传统IO
传统的 I/O 操作,通常指的是使用系统调用如
read()
和write()
进行文件读写。这些操作涉及到用户空间和内核空间之间的数据拷贝。以下是传统 I/O 操作中数据拷贝的一般过程:读操作 (
read()
)
用户空间请求:
应用程序执行read()
系统调用,请求从文件中读取数据。它提供了一个指向用户空间缓冲区的指针,用于存储从文件中读取的数据。上下文切换:
系统调用导致 CPU 从用户模式切换到内核模式,因为文件 I/O 操作是由操作系统内核管理的。内核空间缓冲:
内核将数据从存储设备(如硬盘)读取到内核空间的缓冲区。这通常涉及到文件系统的操作和可能的磁盘 I/O。数据拷贝:
一旦数据在内核缓冲区准备好,内核会将数据从内核空间拷贝到用户空间提供的缓冲区。这个拷贝过程涉及到 CPU 和内存总线的使用。返回用户空间:
数据拷贝完成后,系统调用返回,CPU 从内核模式切换回用户模式,应用程序继续执行。写操作 (
write()
)
用户空间请求:
应用程序执行write()
系统调用,请求将数据写入文件。它提供了一个指向用户空间缓冲区的指针,该缓冲区包含要写入文件的数据。上下文切换:
类似于读操作,执行系统调用导致 CPU 从用户模式切换到内核模式。数据拷贝:
内核将数据从用户空间的缓冲区拷贝到内核空间的缓冲区。内核空间缓冲:
数据在内核缓冲区中可能会暂时存储,直到内核准备好将数据写入存储设备。写入存储设备:
内核将数据从内核空间缓冲区写入存储设备。这可能会立即发生,也可能会延迟,取决于缓冲策略和 I/O 调度。返回用户空间:
系统调用完成后,CPU 从内核模式切换回用户模式,应用程序继续执行。在这两种情况下,数据在用户空间和内核空间之间的拷贝是必要的,因为操作系统为了安全和稳定性的考虑,不允许用户空间程序直接访问内核空间资源。这种数据拷贝机制是传统 I/O 操作的一个主要性能瓶颈,因为它涉及到额外的 CPU 使用和内存带宽消耗。
为了减少这种开销,Linux 提供了零拷贝(Zero-Copy)技术,如
sendfile()
和splice()
系统调用,它们可以在不需要将数据拷贝到用户空间的情况下,直接在内核空间中移动数据。这些技术可以显著提高 I/O 性能,特别是在处理大量数据的网络和文件操作中。
Android Binder 机制
Android 的 Binder 机制确实使用了
mmap
技术,但不是完全依赖于它来实现。Binder 是 Android 中用于进程间通信(IPC)的一种高效机制,它允许应用程序和服务之间进行远程过程调用(RPC)。Binder 机制涉及到一系列复杂的步骤,包括内核驱动、用户空间的库和mmap
系统调用。Binder 机制的关键组成部分:
Binder 驱动:
Binder 机制在内核层面由一个特殊的 Binder 驱动支持。这个驱动负责管理 Binder 通信协议,处理进程间的数据传输和同步。Service Manager:
Android 系统中的 Service Manager 是 Binder 通信的中心节点,它管理着系统中所有的服务和它们的 Binder 引用。Binder 代理和引用:
当一个进程需要与另一个进程中的对象通信时,它通过 Binder 代理(Proxy)来发送请求。Binder 引用(Reference)是在远程进程中实际对象的代表。mmap 在 Binder 中的使用:
mmap
在 Binder 通信中主要用于创建共享内存区域,这个区域被称为 Binder 内存池(Binder Memory Pool)。当一个进程使用 Binder 机制进行 IPC 时,它会通过mmap
将 Binder 驱动提供的一块内存映射到自己的地址空间。这样,进程可以将要传输的数据放入这块共享内存中,而不是通过读写系统调用来传输数据。使用共享内存池的好处是:
- 减少了数据在用户空间和内核空间之间的拷贝次数,因为数据可以直接在共享内存中进行读写。
- 提高了 IPC 的效率,因为 Binder 驱动可以直接访问这块共享内存,而不需要额外的拷贝操作。
总的来说,Binder 机制确实使用了
mmap
来创建共享内存区域,这有助于提高 Android 系统中 IPC 的效率。然而,Binder 的实现远不止于此,它是一个包含内核驱动、用户空间库和多个组件的复杂系统。
mmap 共享内存
mmap
系统调用在 Unix-like 系统中,包括 Linux 和 Android,可以用来实现共享内存。共享内存是一种允许两个或多个进程共享一块内存区域的机制,这样,一个进程对这块内存的修改可以被其他进程看到,从而实现进程间通信(IPC)。以下是
mmap
实现共享内存的基本步骤:创建共享内存区域
创建或打开一个文件:
通常,共享内存是通过映射一个文件来实现的。这个文件可以是一个实际存在的文件,也可以是一个特殊的匿名文件(在 Linux 中,这可以通过指定/dev/zero
或使用MAP_ANONYMOUS
标志来创建)。映射文件到内存:
使用mmap
系统调用将文件映射到进程的地址空间。在调用mmap
时,需要指定MAP_SHARED
标志,这样,对映射区域的修改会反映到底层文件中,并且可以被其他映射了同一文件的进程看到。
1 void* addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);在这个例子中,
length
是映射区域的大小,fd
是文件描述符,0
是从文件的哪个偏移开始映射。访问共享内存
读写内存:
映射成功后,进程可以像访问普通内存一样对这块区域进行读写操作。对这块内存的修改会自动同步到映射的文件中。共享给其他进程:
其他进程也可以通过mmap
同一个文件到它们自己的地址空间,使用相同的length
、fd
和MAP_SHARED
标志。这样,这些进程就可以看到由任何一个进程所做的修改。同步和解除映射
同步更改:
使用msync
系统调用可以将内存映射区域的更改同步回文件,确保数据的一致性。解除映射:
当进程不再需要共享内存时,它可以使用munmap
系统调用来解除映射。注意事项
- 使用共享内存时,需要注意同步问题。多个进程同时写入同一块内存可能会导致数据竞争和不一致。因此,通常需要使用某种形式的同步机制,如互斥锁(mutexes)、信号量(semaphores)等。
- 在某些系统中,
mmap
还可以用于创建共享匿名内存,这种内存不与任何文件关联,而是直接在内核中分配。这可以通过指定MAP_ANONYMOUS
标志(同时fd
设置为-1
)来实现。通过
mmap
实现的共享内存是一种高效的 IPC 机制,因为它避免了数据在用户空间和内核空间之间的拷贝,同时提供了一种直观的内存访问方式。
mmap 创建共享内存
mmap
创建的共享内存映射实际上涉及到内核态和用户态两个层面:
内核态:
当你调用mmap
时,内核会设置虚拟内存区域,并将这块区域映射到文件或匿名内存。这个映射过程是在内核态完成的,因为它需要操作系统内核来管理虚拟内存和页表。如果映射的是一个文件,内核还会负责将文件内容加载到内存中(通常是通过页缓存),以及处理缺页中断等。用户态:
一旦内核设置好了内存映射,映射的内存区域就会出现在进程的用户态地址空间中。进程可以像访问普通内存一样直接读写这块区域。对于进程来说,这就像是在用户态操作内存,但实际上背后的页映射和物理内存管理是由内核处理的。因此,可以说
mmap
创建的共享内存映射是由内核管理的,但它提供给用户态进程直接访问的接口。这种设计允许进程以高效的方式共享数据,同时仍由内核负责底层的内存管理和同步。