iOS_bookmarkiOS精品文章iOS开发之常用技术点

iOS内存映射mmap详解

2018-06-05  本文已影响168人  小凉介

进程和线程?

因为后面的知识涉及到进程,所以我们先来简单了解一下进程和线程。下面的内容摘自iOS-线程&&进程的深入理解

进程基本概念

常规文件操作

常规文件操作(read/write)有那几个重要步骤:

  1. 进程发起读文件请求
  2. 内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的inode
  3. inode在address_space上查找要请求的文件页是否已经缓存在内核页高速缓冲中。如果存在,则直接返回这片文件页的内容
  4. 如果不存在,则通过inode定位到文件磁盘地址,将数据从磁盘复制到内核页高速缓冲。之后再次发起读页面过程,进而将内核页高速缓冲中的数据发给用户进程

需要注意的几点:

  1. 常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。由于页缓存处在内核空间,不能被用户进程直接寻址,所以需要将页缓存中数据页再次拷贝到内存对应的用户空间中
  2. read/write是系统调用很耗时,如下图,它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区,然后再将这些数据拷贝到用户空间,实际上完成了两次数据拷贝
  3. 如果两个进程都对磁盘中的一个文件内容进行访问,那么这个内容在物理内存中有三份:进程A的地址空间 + 进程B的地址空间 + 内核页高速缓冲空间
  4. 写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝

关于内核有疑问不懂的可以参考我的这篇文章Linux 内核剖析,想了解更多linux文件系统相关知识的可以参考这篇文章从内核文件系统看文件读写过程

下面这个图来自linux内存映射mmap原理分析,很形象的描述了整个进程访问磁盘中文件的过程。

image

mmap内存映射

同样的我会放一个mmap映射过程图,以求让大家对mmap映射有更直观理解,图片也还是来自linux内存映射mmap原理分析

image

在内存映射的过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存,具体到代码,就是建立并初始化了相关的数据结构(struct address_space),这个过程有系统调用mmap()实现,所以建立内存映射的效率很高。

既然建立内存映射没有进行实际的数据拷贝,那么进程又怎么能最终直接通过内存操作访问到硬盘上的文件呢?那就要看内存映射之后的几个相关的过程了。

mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无需再调用read或write对文件进行读写,而只需要通过ptr就能够操作文件。但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成物理地址,如图1中过程2所示。这个过程与内存映射无关。

前面讲过,建立内存映射并没有实际拷贝数据,这时,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中,如图1中过程3所示。这个过程与内存映射无关。

如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上,如图1中过程4所示。这个过程也与内存映射无关。

mmap内存映射的实现过程,总的来说可以分为三个阶段:

  1. 进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
  2. 调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系
  3. 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

如果想了解每个阶段更多详细内容,请看这里认真分析mmap:是什么 为什么 怎么用

mmap使用分析

这一部分来自苹果官方开发文档Mapping Files Into Memory

适合的场景

当随机访问一个非常大的文件时,通常最好只映射文件的一小部分。映射大文件的问题是文件会消耗活动内存。如果文件足够大,系统可能会被迫将其他部分的内存分页以加载文件。将多个文件映射到内存中会使这个问题更加复杂。

不适合的场景

代码实现

这段代码实现比较简单,源自Mapping Files Into Memory

import Foundation
import Darwin

func ProcessFile(inPathName: String) {
    
    var dataLength: size_t?
    var dataPtr: UnsafeMutableRawPointer?
    var start: UnsafeMutableRawPointer?
    
    if mapFile(inPathName: inPathName, outDataPtr: &dataPtr, outDataLength: &dataLength) {
        start = dataPtr
        dataPtr = dataPtr! + 3
        memcpy(dataPtr, "CCCC", 4)
        // Unmap files:
        munmap(start, 7)
    }
    
    
    
}


func mapFile(inPathName: String, outDataPtr: inout UnsafeMutableRawPointer?, outDataLength: inout size_t?) -> Bool {
    
    var fileDescriptor: Int32
    var statInfo = stat()
    
    outDataPtr = nil
    outDataLength = 0
    
    // Open the file
    fileDescriptor = open(inPathName, O_RDWR, 0)
    
    if fileDescriptor < 0 {
        return false
    }
    
    // We now know the file exists. Retrieve the file size.
    if fstat(fileDescriptor, &statInfo) != 0 {
        return false
    }else {
        ftruncate(fileDescriptor, statInfo.st_size+4)
        fsync(fileDescriptor)
        outDataPtr = mmap(nil, Int(statInfo.st_size+4), PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, fileDescriptor, 0)
        if outDataPtr == MAP_FAILED {
            return false
        }else{
            outDataLength = size_t(statInfo.st_size)
        }
    }
    
    // Now close the file. The kernel doesn’t use our file descriptor.
    close(fileDescriptor)
    
    return true
    
    
}
    let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
        let str = "AAA"
        let filePath = "\(path ?? "")/text.txt"
        try? str.write(toFile: filePath, atomically: true, encoding: .utf8)
        mapFile.processFile(pathName: filePath)
        let result = try? String(contentsOfFile: filePath, encoding: .utf8)
        print(result)

在iOS的应用

具体在项目中怎么去使用mmap呢?我推荐你看看以下的文章和代码:

MMKV--基于 mmap 的 iOS 高性能通用 key-value 组件
iOS图片加载速度极限优化—FastImageCache解析
FastImageCache

之后我也会在一个开源项目中使用mmap,到时候会更加详细的讲实现的细节,老铁来波关注吧。

最后

说实话如果没有一些操作系统相关知识,很难完全弄明白整个过程。因为涉及到进程,用户空间,内存空间,逻辑地址,物理地址,系统调用,中断,磁盘I/O等一系列的知识。我曾尝试画图以便能说的更清楚,但最后还是放弃了,因为只会越说越复杂。如果有什么疑问可以留言,能回答的都会尽量回答。

参考文章:

iOS-线程&&进程的深入理解
Mapping Files Into Memory
linux内存映射mmap原理分析
mmap实例及原理分析

上一篇下一篇

猜你喜欢

热点阅读