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

進程和線程挪钓?

因為后面的知識涉及到進程,所以我們先來簡單了解一下進程和線程倚评。下面的內(nèi)容摘自iOS-線程&&進程的深入理解

進程基本概念

  • 進程就是一個正在運行的一個應(yīng)用程序
  • 每一個進度都是獨立的馏予,每一個進程均在專門且手保護的內(nèi)存空間內(nèi)
  • iOS是怎么管理自己的內(nèi)存的,見博客:iOS — 內(nèi)存分配與分區(qū)
  • 在Linux系統(tǒng)中,想要新開啟一個進程是一件非常簡單的事情只需要一句話:fork()呢岗,在fork()之后就會包含兩個進程蛹尝,此時可以根據(jù)返回的PID來判斷是子進程還是父進程
  • iOS中是一個非常封閉的系統(tǒng)突那,每一個App(一個進程)都有自己獨特的內(nèi)存和磁盤空間,別的App(進程)是不允許訪問的(越獄不在討論范圍)

常規(guī)文件操作

常規(guī)文件操作(read/write)有那幾個重要步驟:

  1. 進程發(fā)起讀文件請求
  2. 內(nèi)核通過查找進程文件符表早龟,定位到內(nèi)核已打開文件集上的文件信息,從而找到此文件的inode
  3. inode在address_space上查找要請求的文件頁是否已經(jīng)緩存在內(nèi)核頁高速緩沖中壹店。如果存在芝加,則直接返回這片文件頁的內(nèi)容
  4. 如果不存在,則通過inode定位到文件磁盤地址老赤,將數(shù)據(jù)從磁盤復(fù)制到內(nèi)核頁高速緩沖制市。之后再次發(fā)起讀頁面過程,進而將內(nèi)核頁高速緩沖中的數(shù)據(jù)發(fā)給用戶進程

需要注意的幾點:

  1. 常規(guī)文件操作為了提高讀寫效率和保護磁盤开财,使用了頁緩存機制误褪。由于頁緩存處在內(nèi)核空間兽间,不能被用戶進程直接尋址,所以需要將頁緩存中數(shù)據(jù)頁再次拷貝到內(nèi)存對應(yīng)的用戶空間中
  2. read/write是系統(tǒng)調(diào)用很耗時恤溶,如下圖帜羊,它首先將文件內(nèi)容從硬盤拷貝到內(nèi)核空間的一個緩沖區(qū),然后再將這些數(shù)據(jù)拷貝到用戶空間帐姻,實際上完成了兩次數(shù)據(jù)拷貝
  3. 如果兩個進程都對磁盤中的一個文件內(nèi)容進行訪問奶段,那么這個內(nèi)容在物理內(nèi)存中有三份:進程A的地址空間 + 進程B的地址空間 + 內(nèi)核頁高速緩沖空間
  4. 寫操作也是一樣,待寫入的buffer在內(nèi)核空間不能直接訪問扛伍,必須要先拷貝至內(nèi)核空間對應(yīng)的主存刺洒,再寫回磁盤中(延遲寫回)吼砂,也是需要兩次數(shù)據(jù)拷貝

關(guān)于內(nèi)核有疑問不懂的可以參考我的這篇文章Linux 內(nèi)核剖析,想了解更多l(xiāng)inux文件系統(tǒng)相關(guān)知識的可以參考這篇文章從內(nèi)核文件系統(tǒng)看文件讀寫過程因俐。

下面這個圖來自linux內(nèi)存映射mmap原理分析周偎,很形象的描述了整個進程訪問磁盤中文件的過程。

image

mmap內(nèi)存映射

同樣的我會放一個mmap映射過程圖,以求讓大家對mmap映射有更直觀理解蛉艾,圖片也還是來自linux內(nèi)存映射mmap原理分析

image

在內(nèi)存映射的過程中勿侯,并沒有實際的數(shù)據(jù)拷貝,文件沒有被載入內(nèi)存祭埂,只是邏輯上被放入了內(nèi)存兵钮,具體到代碼,就是建立并初始化了相關(guān)的數(shù)據(jù)結(jié)構(gòu)(struct address_space)航罗,這個過程有系統(tǒng)調(diào)用mmap()實現(xiàn)屁药,所以建立內(nèi)存映射的效率很高酿箭。

既然建立內(nèi)存映射沒有進行實際的數(shù)據(jù)拷貝,那么進程又怎么能最終直接通過內(nèi)存操作訪問到硬盤上的文件呢缔御?那就要看內(nèi)存映射之后的幾個相關(guān)的過程了妇蛀。

mmap()會返回一個指針ptr笤成,它指向進程邏輯地址空間中的一個地址炕泳,這樣以后上祈,進程無需再調(diào)用read或write對文件進行讀寫,而只需要通過ptr就能夠操作文件籽腕。但是ptr所指向的是一個邏輯地址纸俭,要操作其中的數(shù)據(jù),必須通過MMU將邏輯地址轉(zhuǎn)換成物理地址廊宪,如圖1中過程2所示女轿。這個過程與內(nèi)存映射無關(guān)蛉迹。

前面講過,建立內(nèi)存映射并沒有實際拷貝數(shù)據(jù)荐操,這時珍策,MMU在地址映射表中是無法找到與ptr相對應(yīng)的物理地址的,也就是MMU失敗屯耸,將產(chǎn)生一個缺頁中斷蹭劈,缺頁中斷的中斷響應(yīng)函數(shù)會在swap中尋找相對應(yīng)的頁面铺韧,如果找不到(也就是該文件從來沒有被讀入內(nèi)存的情況),則會通過mmap()建立的映射關(guān)系塔逃,從硬盤上將文件讀取到物理內(nèi)存中,如圖1中過程3所示鹏溯。這個過程與內(nèi)存映射無關(guān)淹仑。

如果在拷貝數(shù)據(jù)時肺孵,發(fā)現(xiàn)物理內(nèi)存不夠用,則會通過虛擬內(nèi)存機制(swap)將暫時不用的物理頁面交換到硬盤上吓肋,如圖1中過程4所示是鬼。這個過程也與內(nèi)存映射無關(guān)紫新。

mmap內(nèi)存映射的實現(xiàn)過程,總的來說可以分為三個階段:

  1. 進程啟動映射過程囤耳,并在虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域
  2. 調(diào)用內(nèi)核空間的系統(tǒng)調(diào)用函數(shù)mmap(不同于用戶空間函數(shù))偶芍,實現(xiàn)文件物理地址和進程虛擬地址的一一映射關(guān)系
  3. 進程發(fā)起對這片映射空間的訪問匪蟀,引發(fā)缺頁異常,實現(xiàn)文件內(nèi)容到物理內(nèi)存(主存)的拷貝

如果想了解每個階段更多詳細內(nèi)容铃剔,請看這里認真分析mmap:是什么 為什么 怎么用

mmap使用分析

這一部分來自蘋果官方開發(fā)文檔Mapping Files Into Memory

適合的場景

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

當(dāng)隨機訪問一個非常大的文件時净当,通常最好只映射文件的一小部分蕴潦。映射大文件的問題是文件會消耗活動內(nèi)存。如果文件足夠大忽冻,系統(tǒng)可能會被迫將其他部分的內(nèi)存分頁以加載文件萄传。將多個文件映射到內(nèi)存中會使這個問題更加復(fù)雜秀菱。

不適合的場景

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

代碼實現(xiàn)

這段代碼實現(xiàn)比較簡單猜年,源自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)
        ProcessFile(inPathName: filePath)
        let result = try? String(contentsOfFile: filePath, encoding: .utf8)
        print(result)

在iOS的應(yīng)用

具體在項目中怎么去使用mmap呢码倦?我推薦你看看以下的文章和代碼:

MMKV--基于 mmap 的 iOS 高性能通用 key-value 組件
iOS圖片加載速度極限優(yōu)化—FastImageCache解析
FastImageCache

之后我也會在一個開源項目中使用mmap袁稽,到時候會更加詳細的講實現(xiàn)的細節(jié)擒抛,老鐵來波關(guān)注吧歧沪。

最后

說實話如果沒有一些操作系統(tǒng)相關(guān)知識,很難完全弄明白整個過程暖夭。因為涉及到進程撵孤,用戶空間,內(nèi)存空間裕菠,邏輯地址奴潘,物理地址影钉,系統(tǒng)調(diào)用,中斷奈虾,磁盤I/O等一系列的知識。我曾嘗試畫圖以便能說的更清楚予权,但最后還是放棄了浪册,因為只會越說越復(fù)雜村象。如果有什么疑問可以留言,能回答的都會盡量回答躁劣。

參考文章:

iOS-線程&&進程的深入理解
Mapping Files Into Memory
linux內(nèi)存映射mmap原理分析
mmap實例及原理分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末账忘,一起剝皮案震驚了整個濱河市熙宇,隨后出現(xiàn)的幾起案子烫止,更是在濱河造成了極大的恐慌,老刑警劉巖期升,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件播赁,死亡現(xiàn)場離奇詭異吨铸,居然都是意外死亡诞吱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門沼瘫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耿戚,“玉大人,你說我怎么就攤上這事坛猪≡砉桑” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蘑辑。 經(jīng)常有香客問我洋魂,道長,這世上最難降的妖魔是什么刁标? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮谨垃,結(jié)果婚禮上硼控,老公的妹妹穿的比我還像新娘。我一直安慰自己匙隔,他們只是感情好熏版,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布撼短。 她就那樣靜靜地躺著,像睡著了一般喂柒。 火紅的嫁衣襯著肌膚如雪灾杰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天吠撮,我揣著相機與錄音讲竿,去河邊找鬼。 笑死题禀,一個胖子當(dāng)著我的面吹牛鞋诗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迈嘹,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼削彬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了秀仲?” 一聲冷哼從身側(cè)響起融痛,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎神僵,沒想到半個月后雁刷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡沛励,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了炮障。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片目派。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胁赢,靈堂內(nèi)的尸體忽然破棺而出企蹭,到底是詐尸還是另有隱情,我是刑警寧澤智末,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布谅摄,位于F島的核電站,受9級特大地震影響吹害,放射性物質(zhì)發(fā)生泄漏螟凭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一它呀、第九天 我趴在偏房一處隱蔽的房頂上張望螺男。 院中可真熱鬧棒厘,春花似錦、人聲如沸下隧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淆院。三九已至何乎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間土辩,已是汗流浹背支救。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拷淘,地道東北人各墨。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像启涯,于是被迫代替她去往敵國和親贬堵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容