iOS文件內(nèi)存映射詳解(mmap)

文件內(nèi)存映射(mmap)之前看過很多文章提及到,但是都沒有寫iOS中具體的實(shí)現(xiàn),只是都說對(duì)于大文件讀寫效率比較高等。所以作者就專門研究了以下mmap技術(shù),并且實(shí)現(xiàn)了一下

mmap

文件映射是將文件的磁盤扇區(qū)映射到進(jìn)程的虛擬內(nèi)存空間的過程。一旦被映射,您的應(yīng)用程序就會(huì)訪問這個(gè)文件,就好像它完全駐留在內(nèi)存中一樣(<font color = 'red'>不占用內(nèi)存,使用的是虛擬內(nèi)存</font>)。當(dāng)您從映射的文件指針讀取數(shù)據(jù)時(shí),將在適當(dāng)?shù)臄?shù)據(jù)中的內(nèi)核頁面并將其返回給您的應(yīng)用程序。

疑問

那大家就會(huì)想了,既然不消耗內(nèi)存,那豈不是都用mmap就行了,這樣多好啊,又不占內(nèi)存。其實(shí)不然,并不是所有的場景都適合使用mmap的

適合的場景

  • 您有一個(gè)很大的文件,其內(nèi)容您想要隨機(jī)訪問一個(gè)或多個(gè)時(shí)間。
  • 您有一個(gè)小文件,它的內(nèi)容您想要立即讀入內(nèi)存并經(jīng)常訪問。這種技術(shù)最適合那些大小不超過幾個(gè)虛擬內(nèi)存頁的文件。(頁是地址空間的最小單位,虛擬頁和物理頁的大小是一樣的,通常為4KB。)
  • 您需要在內(nèi)存中緩存文件的特定部分。文件映射消除了緩存數(shù)據(jù)的需要,這使得系統(tǒng)磁盤緩存中的其他數(shù)據(jù)空間更大。

當(dāng)隨機(jī)訪問一個(gè)非常大的文件時(shí),通常最好只映射文件的一小部分。映射大文件的問題是文件會(huì)消耗活動(dòng)內(nèi)存。如果文件足夠大,系統(tǒng)可能會(huì)被迫將其他部分的內(nèi)存分頁以加載文件。將多個(gè)文件映射到內(nèi)存中會(huì)使這個(gè)問題更加復(fù)雜。

不適合的場景

  • 您希望從開始到結(jié)束的順序從頭到尾讀取一個(gè)文件。
  • 這個(gè)文件有幾百兆字節(jié)或者更大。將大文件映射到內(nèi)存中會(huì)快速地填充內(nèi)存,并可能導(dǎo)致分頁,這將抵消首先映射文件的好處。對(duì)于大型順序讀取操作,禁用磁盤緩存并將文件讀入一個(gè)小內(nèi)存緩沖區(qū)。
  • 該文件大于可用的連續(xù)虛擬內(nèi)存地址空間。對(duì)于64位應(yīng)用程序來說,這不是什么問題,但是對(duì)于32位應(yīng)用程序來說,這是一個(gè)問題。
  • 該文件位于可移動(dòng)驅(qū)動(dòng)器上。
  • 該文件位于網(wǎng)絡(luò)驅(qū)動(dòng)器上。

實(shí)現(xiàn)

這個(gè)代碼實(shí)現(xiàn)的功能就是首先讀取存儲(chǔ)在我們沙盒的文件,然后在該文件的上繼續(xù)寫入數(shù)據(jù)(追加數(shù)據(jù))

#import "ViewController.h"
#import <sys/mman.h>
#import <sys/stat.h>

int MapFile( char * inPathName, void ** outDataPtr, size_t * outDataLength );

void ProcessFile( char * inPathName )
{
    size_t dataLength;
    void * dataPtr;
    void *start;
    if( MapFile( inPathName, &dataPtr, &dataLength ) == 0 )
    {
        start = dataPtr;
        dataPtr = dataPtr+3;
        memcpy(dataPtr, "CCCC", 4);
        // Unmap files:
        munmap(start, 7);
    }
}
// MapFile

// Exit:    outDataPtra     pointer to the mapped memory region
//          outDataLength   size of the mapped memory region
//          return value    an errno value on error (see sys/errno.h)
//                          or zero for success
//
int MapFile( char * inPathName, void ** outDataPtr, size_t * outDataLength )
{
    int outError;
    int fileDescriptor;
    struct stat statInfo;

    // Return safe values on error.
    outError = 0;
    *outDataPtr = NULL;
    *outDataLength = 0;

    // Open the file.
    fileDescriptor = open( inPathName, O_RDWR, 0 );
    if( fileDescriptor < 0 )
    {
        outError = errno;
    }
    else
    {
        // We now know the file exists. Retrieve the file size.
        if( fstat( fileDescriptor, &statInfo ) != 0 )
        {
            outError = errno;
        }
        else
        {
            ftruncate(fileDescriptor, statInfo.st_size+4);//增加文件大小
            fsync(fileDescriptor);//刷新文件
            *outDataPtr = mmap(NULL,
                               statInfo.st_size+4,
                               PROT_READ|PROT_WRITE,
                               MAP_FILE|MAP_SHARED,
                               fileDescriptor,
                               0);
            if( *outDataPtr == MAP_FAILED )
            {
                outError = errno;
            }
            else
            {
                // On success, return the size of the mapped file.
                *outDataLength = statInfo.st_size;
            }
        }

        // Now close the file. The kernel doesn’t use our file descriptor.
        close( fileDescriptor );
    }

    return outError;
}

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextView *mTV;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    NSString *str = @"AAA";
    NSError *error;
    NSString *filePath = [NSString stringWithFormat:@"%@/text.txt",path];
    [str writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
    if (error) {
        NSLog(@"%@",error);
    }
    ProcessFile(filePath.UTF8String);
    NSString *result = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    self.mTV.text = result;
}
@end

最重要的就是2個(gè)函數(shù):mmap()munmap()

  • mmap()

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

start:映射區(qū)的開始地址,設(shè)置為0時(shí)表示由系統(tǒng)決定映射區(qū)的起始地址。
length:映射區(qū)的長度。//長度單位是 以字節(jié)為單位,不足一內(nèi)存頁按一內(nèi)存頁處理
prot:期望的內(nèi)存保護(hù)標(biāo)志,不能與文件的打開模式?jīng)_突。是以下的某個(gè)值,可以通過or運(yùn)算合理地組合在一起
PROT_EXEC //頁內(nèi)容可以被執(zhí)行
PROT_READ //頁內(nèi)容可以被讀取
PROT_WRITE //頁可以被寫入
PROT_NONE //頁不可訪問
flags:指定映射對(duì)象的類型,映射選項(xiàng)和映射頁是否可以共享。它的值可以是一個(gè)或者多個(gè)以下位的組合體
fd:有效的文件描述詞。一般是由open()函數(shù)返回,其值也可以設(shè)置為-1,此時(shí)需要指定flags參數(shù)中的MAP_ANON,表明進(jìn)行的是匿名映射。
off_toffset:被映射對(duì)象內(nèi)容的起點(diǎn)。

這里的參數(shù)我們要重點(diǎn)關(guān)注3個(gè)lengthprot、flags
length代表了我們可以操作的內(nèi)存大?。?br> prot代表我們對(duì)文件的操作權(quán)限。這里傳入了讀寫權(quán)限,而且注意要與open()保持一致,所以open()函數(shù)傳入了O_RDWR可讀寫權(quán)限;。
flags要寫MAP_FILE|MAP_SHARED,我一開始只寫了MAP_FILE,能讀,但是不能寫。

  • munmap()

int munmap(void* start,size_t length);

這里對(duì)原來文件追加寫入數(shù)據(jù)要注意一點(diǎn),讀取原來文件之后,我們只有原來文件大小的可寫區(qū)域。例如以上例子原文件中是AAA,這時(shí)我們要寫入CCCC,做覆蓋寫入的話我們只能寫入CCC。所以要要對(duì)文件進(jìn)行追加寫入的話,必須提前增加文件的大小即調(diào)用ftruncate()sync(),增加了4位了,最終才能使CCCC順利寫入

源碼和博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)自認(rèn)真分析mmap:是什么 為什么 怎么用 閱讀目錄mmap基礎(chǔ)概念mmap內(nèi)存映射原理mmap和常規(guī)文件操作的...
    扎Zn了老Fe閱讀 893評(píng)論 0 3
  • Linux進(jìn)程通信實(shí)現(xiàn)機(jī)制有很多,也有各自優(yōu)缺點(diǎn)和適用場景,關(guān)于她們之間的對(duì)比,等各種通信機(jī)制一一介紹后,再來一個(gè)...
    batbattle閱讀 4,211評(píng)論 3 13
  • UNIX網(wǎng)絡(luò)編程第二卷進(jìn)程間通信對(duì)mmap函數(shù)進(jìn)行了說明。該函數(shù)主要用途有三個(gè):1、將一個(gè)普通文件映射到內(nèi)存中,通...
    宇文黎琴閱讀 3,740評(píng)論 0 4
  • mmap基礎(chǔ)概念 mmap是一種內(nèi)存映射文件的方法,即將一個(gè)文件或者其它對(duì)象映射到進(jìn)程的地址空間,實(shí)現(xiàn)文件磁盤地址...
    dequal閱讀 1,390評(píng)論 0 1
  • 導(dǎo)讀:史玉柱曾經(jīng)說過有夢(mèng)想的人都是實(shí)干家,都是有著用不完的精力的偏執(zhí)狂! 下面是他自訴成功之道! 一、忍受孤獨(dú) 孤...
    i十年i閱讀 387評(píng)論 0 0

友情鏈接更多精彩內(nèi)容