天天看點

輕松突破檔案IO瓶頸:記憶體映射mmap技術

作者:嵌入式Linux核心

一、mmap基礎概念

mmap 即 memory map,也就是記憶體映射。mmap 是一種記憶體映射檔案的方法,即将一個檔案或者其它對象映射到程序的位址空間,實作檔案磁盤位址和程序虛拟位址空間中一段虛拟位址的一一對映關系。實作這樣的映射關系後,程序就可以采用指針的方式讀寫操作這一段記憶體,而系統會自動回寫髒頁面到對應的檔案磁盤上,即完成了對檔案的操作而不必再調用 read、write 等系統調用函數。相反,核心空間對這段區域的修改也直接反映使用者空間,進而可以實作不同程序間的檔案共享。

如下圖所示:

輕松突破檔案IO瓶頸:記憶體映射mmap技術

mmap的作用,在應用這一層,是讓你把檔案的某一段,當作記憶體一樣來通路。将檔案映射到實體記憶體,将程序虛拟空間映射到那塊記憶體。這樣,程序不僅能像通路記憶體一樣讀寫檔案,多個程序映射同一檔案,還能保證虛拟空間映射到同一塊實體記憶體,達到記憶體共享的作用。

mmap 具有如下的特點:

  1. mmap 向應用程式提供的記憶體通路接口是記憶體位址連續的,但是對應的磁盤檔案的 block 可以不是位址連續的;
  2. mmap 提供的記憶體空間是虛拟空間(虛拟記憶體),而不是實體空間(實體記憶體),是以完全可以配置設定遠遠大于實體記憶體大小的虛拟空間(例如 16G 記憶體主機配置設定 1000G 的 mmap 記憶體空間);
  3. mmap 負責映射檔案邏輯上一段連續的資料(實體上可以不連續存儲)映射為連續記憶體,而這裡的檔案可以是磁盤檔案、驅動假造出的檔案(例如 DMA 技術)以及裝置;
  4. mmap 由作業系統負責管理,對同一個檔案位址的映射将被所有線程共享,作業系統確定線程安全以及線程可見性;
  5. mmap 的設計很有啟發性。基于磁盤的讀寫機關是 block(一般大小為 4KB),而基于記憶體的讀寫機關是位址(雖然記憶體的管理與配置設定機關是 4KB)。

換言之,CPU 進行一次磁盤讀寫操作涉及的資料量至少是 4KB,但是進行一次記憶體操作涉及的資料量是基于位址的,也就是通常的 64bit(64 位作業系統)。mmap 下程序可以采用指針的方式進行讀寫操作,這是值得注意的。

二. 虛拟記憶體?虛拟空間?

其實是一個概念,前一篇對于這個詞沒有确切的定義,現在定義一下:

虛拟空間就是程序看到的所有位址組成的空間,虛拟空間是某個程序對配置設定給它的所有實體位址(已經配置設定的和将會配置設定的)的重新映射。

而虛拟記憶體,為啥叫虛拟記憶體,是因為它就不是真正的記憶體,是假的,因為它是由位址組成的空間,是以在這裡,使用虛拟空間這個詞更加确切和易懂。(不過虛拟記憶體這個詞也不算錯)

虛拟空間原理

實體記憶體

首先,實體位址實際上也不是連續的,通常是包含作為主存的DRAM和IO寄存器

輕松突破檔案IO瓶頸:記憶體映射mmap技術

以前的CPU(如X86)是為IO劃分單獨的位址空間,是以不能用直接通路記憶體的方式(如指針)IO,隻能用專門的方法(in/read/out/write)諸如此類。現在的CPU利用PCI總線将IO寄存器映射到實體記憶體,是以出現了基于記憶體通路的IO。還有一點補充的,就如同程序空間有一塊核心空間一樣,實體記憶體也會有極小一部分是不能通路的,為核心所用。

三個總線

這裡再補充下三個總線的知識,即:位址總線、資料總線、控制總線

  • 位址總線,用來傳輸位址
  • 資料總線,用來傳輸資料
  • 控制總線,用來傳輸指令

比如CPU通過控制總線發送讀取指令,同時用位址總線發送要讀取的資料虛位址,經過MMU後到記憶體

記憶體通過資料總線将資料傳輸給CPU。虛拟位址的空間和指令集的位址長度有關,不一定和實體位址長度一緻,比如現在的64位處理器,從VA角度看來,可以通路64位的位址,但位址總線長度隻有48位,是以你可以通路一個位于2^52這個位置的位址。

虛拟記憶體位址轉換(虛位址轉實位址)

上面已經明确了虛拟記憶體是虛拟空間,即位址的集合這一概念。基于此,來說說原理。

如果還記得作業系統課程裡面提到的虛位址,那麼這個虛位址就是虛拟空間的位址了,虛位址通過轉換得到實位址,轉換方式課程内也講得很清楚,虛位址頭部包含了頁号(段位址和段大小,看存儲模式:頁存儲、段存儲,段頁式),剩下部分是偏移量,經過MMU轉換成實位址。

輕松突破檔案IO瓶頸:記憶體映射mmap技術

存儲方式

輕松突破檔案IO瓶頸:記憶體映射mmap技術

虛拟位址頭部為頁号通過查詢頁表得到實體頁号,假設一頁時1K,那麼頁号*偏移量就得到實體位址

輕松突破檔案IO瓶頸:記憶體映射mmap技術

虛拟位址頭部為段号,段表中找到段基位址加上偏移量得到實位址

輕松突破檔案IO瓶頸:記憶體映射mmap技術

二、mmap原理

mmap函數建立一個新的vm_area_struct結構,并将其與檔案/裝置的實體位址相連。

vm_area_struct:

linux使用vm_area_struct來表示一個獨立的虛拟記憶體區域,一個程序可以使用多個vm_area_struct來表示不用類型的虛拟記憶體區域(如堆,棧,代碼段,MMAP區域等)。

vm_area_struct結構中包含了區域起始位址。同時也包含了一個vm_opt指針,其内部可引出所有針對這個區域可以使用的系統調用函數。進而,程序可以通過vm_area_struct擷取操作這段記憶體區域所需的任何資訊。

程序通過vma操作記憶體,而vma與檔案/裝置的實體位址相連,系統自動回寫髒頁面到對應的檔案磁盤上(或寫入到裝置位址空間),實作記憶體映射檔案。

輕松突破檔案IO瓶頸:記憶體映射mmap技術

[核心資料領取,](https://docs.qq.com/doc/DTmFTc29xUGdNSnZ2)

[Linux核心源碼學習位址。](https://ke.qq.com/course/4032547?flowToken=1044435)

記憶體映射檔案的原理:

首先建立虛拟區間并完成位址映射,此時還沒有将任何檔案資料拷貝至主存。當程序發起讀寫操作時,會通路虛拟位址空間,通過查詢頁表,發現這段位址不在實體頁上,因為隻建立了位址映射,真正的資料還沒有拷貝到記憶體,是以引發缺頁異常。缺頁異常經過一系列判斷,确定無非法操作後,核心發起請求調頁過程。

最終會調用nopage函數把所缺的頁從檔案在磁盤裡的位址拷貝到實體記憶體。之後程序便可以對這片主存進行讀寫,如果寫操作修改了内容,一定時間後系統會自動回寫髒頁面到對應的磁盤位址,完成了寫入到檔案的過程。另外,也可以調用msync()來強制同步,這樣所寫的記憶體就能立刻儲存到檔案中。

mmap記憶體映射的實作過程,總的來說可以分為三個階段:

(一)程序啟動映射過程,并在虛拟位址空間中為映射建立虛拟映射區域

  • 程序在使用者空間調用庫函數mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
  • 在目前程序的虛拟位址空間中,尋找一段空閑的滿足要求的連續的虛拟位址
  • 為此虛拟區配置設定一個vm_area_struct結構,接着對這個結構的各個域進行了初始化
  • 将建立的虛拟區結構(vm_area_struct)插入程序的虛拟位址區域連結清單或樹中

(二)調用核心空間的系統調用函數mmap(不同于使用者空間函數),實作檔案實體位址和程序虛拟位址的一一映射關系

  • 為映射配置設定了新的虛拟位址區域後,通過待映射的檔案指針,在檔案描述符表中找到對應的檔案描述符,通過檔案描述符,連結到核心“已打開檔案集”中該檔案的檔案結構體(struct file),每個檔案結構體維護着和這個已打開檔案相關各項資訊。
  • 通過該檔案的檔案結構體,連結到file_operations子產品,調用核心函數mmap,其原型為:int mmap(struct file *filp, struct vm_area_struct *vma),不同于使用者空間庫函數。
  • 核心mmap函數通過虛拟檔案系統inode子產品定位到檔案磁盤實體位址。
  • 通過remap_pfn_range函數建立頁表,即實作了檔案位址和虛拟位址區域的映射關系。此時,這片虛拟位址并沒有任何資料關聯到主存中。

(三)程序發起對這片映射空間的通路,引發缺頁異常,實作檔案内容到實體記憶體(主存)的拷貝

注:前兩個階段僅在于建立虛拟區間并完成位址映射,但是并沒有将任何檔案資料的拷貝至主存。真正的檔案讀取是當程序發起讀或寫操作時。

  • 程序的讀或寫操作通路虛拟位址空間這一段映射位址,通過查詢頁表,發現這一段位址并不在實體頁面上。因為目前隻建立了位址映射,真正的硬碟資料還沒有拷貝到記憶體中,是以引發缺頁異常。
  • 缺頁異常進行一系列判斷,确定無非法操作後,核心發起請求調頁過程。
  • 調頁過程先在交換緩存空間(swap cache)中尋找需要通路的記憶體頁,如果沒有則調用nopage函數把所缺的頁從磁盤裝入到主存中。
  • 之後程序即可對這片主存進行讀或者寫的操作,如果寫操作改變了其内容,一定時間後系統會自動回寫髒頁面到對應磁盤位址,也即完成了寫入到檔案的過程。

注:修改過的髒頁面并不會立即更新回檔案中,而是有一段時間的延遲,可以調用msync()來強制同步, 這樣所寫的内容就能立即儲存到檔案裡了。

三、mmap 的 I/O 模型

mmap 也是一種零拷貝技術,其 I/O 模型如下圖所示:、

輕松突破檔案IO瓶頸:記憶體映射mmap技術
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)           
輕松突破檔案IO瓶頸:記憶體映射mmap技術

mmap 技術有如下特點:

  1. 利用 DMA 技術來取代 CPU 來在記憶體與其他元件之間的資料拷貝,例如從磁盤到記憶體,從記憶體到網卡;
  2. 使用者空間的 mmap file 使用虛拟記憶體,實際上并不占據實體記憶體,隻有在核心空間的 kernel buffer cache 才占據實際的實體記憶體;
  3. mmap() 函數需要配合 write() 系統調動進行配合操作,這與 sendfile() 函數有所不同,後者一次性代替了 read() 以及 write();是以 mmap 也至少需要 4 次上下文切換;
  4. mmap 僅僅能夠避免核心空間到使用者空間的全程 CPU 負責的資料拷貝,但是核心空間内部還是需要全程 CPU 負責的資料拷貝;

利用 mmap() 替換 read(),配合 write() 調用的整個流程如下:

  1. 使用者程序調用 mmap(),從使用者态陷入核心态,将核心緩沖區映射到使用者緩存區;
  2. DMA 控制器将資料從硬碟拷貝到核心緩沖區(可見其使用了 Page Cache 機制);
  3. mmap() 傳回,上下文從核心态切換回使用者态;
  4. 使用者程序調用 write(),嘗試把檔案資料寫到核心裡的套接字緩沖區,再次陷入核心态;
  5. CPU 将核心緩沖區中的資料拷貝到的套接字緩沖區;
  6. DMA 控制器将資料從套接字緩沖區拷貝到網卡完成資料傳輸;
  7. write() 傳回,上下文從核心态切換回使用者态。

通過mmap實作的零拷貝I/O進行了4次使用者空間與核心空間的上下文切換,以及3次資料拷貝;其中3次資料拷貝中包括了2次DMA拷貝和1次CPU拷貝

四、mmap 的優勢

1.簡化使用者程序程式設計

在使用者空間看來,通過 mmap 機制以後,磁盤上的檔案仿佛直接就在記憶體中,把通路磁盤檔案簡化為按位址通路記憶體。這樣一來,應用程式自然不需要使用檔案系統的 write(寫入)、read(讀取)、fsync(同步)等系統調用,因為現在隻要面向記憶體的虛拟空間進行開發。

但是,這并不意味着我們不再需要進行這些系統調用,而是說這些系統調用由作業系統在 mmap 機制的内部封裝好了。

(1)基于缺頁異常的懶加載

出于節約實體記憶體以及 mmap 方法快速傳回的目的,mmap 映射采用懶加載機制。具體來說,通過 mmap 申請 1000G 記憶體可能僅僅占用了 100MB 的虛拟記憶體空間,甚至沒有配置設定實際的實體記憶體空間。當你通路相關記憶體位址時,才會進行真正的 write、read 等系統調用。CPU 會通過陷入缺頁異常的方式來将磁盤上的資料加載到實體記憶體中,此時才會發生真正的實體記憶體配置設定。

(2)資料一緻性由 OS 確定

當發生資料修改時,記憶體出現髒頁,與磁盤檔案出現不一緻。mmap 機制下由作業系統自動完成記憶體資料落盤(髒頁回刷),使用者程序通常并不需要手動管理資料落盤。

讀寫效率提高:避免核心空間到使用者空間的資料拷貝

簡而言之,mmap 被認為快的原因是因為建立了頁到使用者程序的虛位址空間映射,以讀取檔案為例,避免了頁從核心空間拷貝到使用者空間。

3.避免隻讀操作時的 swap 操作

虛拟記憶體帶來了種種好處,但是一個最大的問題在于所有程序的虛拟記憶體大小總和可能大于實體記憶體總大小,是以當作業系統實體記憶體不夠用時,就會把一部分記憶體 swap 到磁盤上。

在 mmap 下,如果虛拟空間沒有發生寫操作,那麼由于通過 mmap 操作得到的記憶體資料完全可以通過再次調用 mmap 操作映射檔案得到。但是,通過其他方式配置設定的記憶體,在沒有發生寫操作的情況下,作業系統并不知道如何簡單地從現有檔案中(除非其重新執行一遍應用程式,但是代價很大)恢複記憶體資料,是以必須将記憶體 swap 到磁盤上。

4.節約記憶體

由于使用者空間與核心空間實際上共用同一份資料,是以在大檔案場景下在實際實體記憶體占用上有優勢。

4. mmap 不是銀彈

mmap 不是銀彈,這意味着 mmap 也有其缺陷,在相關場景下的性能存在缺陷:

  1. 由于 MMAP 使用時必須實作指定好記憶體映射的大小,是以 mmap 并不适合變長檔案;
  2. 如果更新檔案的操作很多,mmap 避免兩态拷貝的優勢就被攤還,最終還是落在了大量的髒頁回寫及由此引發的随機 I/O 上,是以在随機寫很多的情況下,mmap 方式在效率上不一定會比帶緩沖區的一般寫快;
  3. 讀/寫小檔案(例如 16K 以下的檔案),mmap 與通過 read 系統調用相比有着更高的開銷與延遲;同時 mmap 的刷盤由系統全權控制,但是在小資料量的情況下由應用本身手動控制更好;
  4. mmap 受限于作業系統記憶體大小:例如在 32-bits 的作業系統上,虛拟記憶體總大小也就 2GB,但由于 mmap 必須要在記憶體中找到一塊連續的位址塊,此時你就無法對 4GB 大小的檔案完全進行 mmap,在這種情況下你必須分多塊分别進行 mmap,但是此時位址記憶體位址已經不再連續,使用 mmap 的意義大打折扣,而且引入了額外的複雜性;

5. mmap 的适用場景

mmap 的适用場景實際上非常受限,在如下場合下可以選擇使用 mmap 機制:

  1. 多個線程以隻讀的方式同時通路一個檔案,這是因為 mmap 機制下多線程共享了同一實體記憶體空間,是以節約了記憶體;
  2. mmap 非常适合用于程序間通信,這是因為對同一檔案對應的 mmap 配置設定的實體記憶體天然多線程共享,并可以依賴于作業系統的同步原語;
  3. mmap 雖然比 sendfile 等機制多了一次 CPU 全程參與的記憶體拷貝,但是使用者空間與核心空間并不需要資料拷貝,是以在正确使用情況下并不比 sendfile 效率差;

6.mmap使用細節

  1. 使用mmap需要注意的一個關鍵點是,mmap映射區域大小必須是實體頁大小(page_size)的整倍數(32位系統中通常是4k位元組)。原因是,記憶體的最小粒度是頁,而程序虛拟位址空間和記憶體的映射也是以頁為機關。為了比對記憶體的操作,mmap從磁盤到虛拟位址空間的映射也必須是頁。
  2. 核心可以跟蹤被記憶體映射的底層對象(檔案)的大小,程序可以合法的通路在目前檔案大小以内又在記憶體映射區以内的那些位元組。也就是說,如果檔案的大小一直在擴張,隻要在映射區域範圍内的資料,程序都可以合法得到,這和映射建立時檔案的大小無關。
  3. 映射建立之後,即使檔案關閉,映射依然存在。因為映射的是磁盤的位址,不是檔案本身,和檔案句柄無關。同時可用于程序間通信的有效位址空間不完全受限于被映射檔案的大小,因為是按頁映射。

在上面的知識前提下,我們下面看看如果大小不是頁的整倍數的具體情況:

情形一:一個檔案的大小是5000位元組,mmap函數從一個檔案的起始位置開始,映射5000位元組到虛拟記憶體中。

分析:因為機關實體頁面的大小是4096位元組,雖然被映射的檔案隻有5000位元組,但是對應到程序虛拟位址區域的大小需要滿足整頁大小,是以mmap函數執行後,實際映射到虛拟記憶體區域8192個 位元組,5000~8191的位元組部分用零填充。映射後的對應關系如下圖所示:

輕松突破檔案IO瓶頸:記憶體映射mmap技術

此時: (1)讀/寫前5000個位元組(0~4999),會傳回操作檔案内容。 (2)讀位元組50008191時,結果全為0。寫50008191時,程序不會報錯,但是所寫的内容不會寫入原檔案中 。 (3)讀/寫8192以外的磁盤部分,會傳回一個SIGSECV錯誤。情形二:一個檔案的大小是5000位元組,mmap函數從一個檔案的起始位置開始,映射15000位元組到虛拟記憶體中,即映射大小超過了原始檔案的大小。

分析:由于檔案的大小是5000位元組,和情形一一樣,其對應的兩個實體頁。那麼這兩個實體頁都是合法可以讀寫的,隻是超出5000的部分不會展現在原檔案中。由于程式要求映射15000位元組,而檔案隻占兩個實體頁,是以8192位元組~15000位元組都不能讀寫,操作時會傳回異常。如下圖所示:

輕松突破檔案IO瓶頸:記憶體映射mmap技術

此時: (1)程序可以正常讀/寫被映射的前5000位元組(0~4999),寫操作的改動會在一定時間後反映在原檔案中。 (2)對于5000~8191位元組,程序可以進行讀寫過程,不會報錯。但是内容在寫入前均為0,另外,寫入後不會反映在檔案中。 (3)對于8192~14999位元組,程序不能對其進行讀寫,會報SIGBUS錯誤。 (4)對于15000以外的位元組,程序不能對其讀寫,會引發SIGSEGV錯誤。情形三:一個檔案初始大小為0,使用mmap操作映射了10004K的大小,即1000個實體頁大約4M位元組空間,mmap傳回指針ptr。

分析:如果在映射建立之初,就對檔案進行讀寫操作,由于檔案大小為0,并沒有合法的實體頁對應,如同情形二一樣,會傳回SIGBUS錯誤。但是如果,每次操作ptr讀寫前,先增加檔案的大小,那麼ptr在檔案大小内部的操作就是合法的。例如,檔案擴充4096位元組,ptr就能操作ptr ~ [ (char)ptr + 4095]的空間。隻要檔案擴充的範圍在1000個實體頁(映射範圍)内,ptr都可以對應操作相同的大小。這樣,友善随時擴充檔案空間,随時寫入檔案,不造成空間浪費。

五、mmap映射

在記憶體映射的過程中,并沒有實際的資料拷貝,檔案沒有被載入記憶體,隻是邏輯上被放入了記憶體,具體到代碼,就是建立并初始化了相關的資料結構(struct address_space),這個過程由系統調用mmap()實作,是以建立記憶體映射的效率很高。 既然建立記憶體映射沒有進行實際的資料拷貝,那麼程序又怎麼能最終直接通過記憶體操作通路到硬碟上的檔案呢?那就要看記憶體映射之後的幾個相關的過程了。 mmap()會傳回一個指針ptr,它指向程序邏輯位址空間中的一個位址,這樣以後,程序無需再調用read或write對檔案進行讀寫,而隻需要通過ptr就能夠操作檔案。

但是ptr所指向的是一個邏輯位址,要操作其中的資料,必須通過MMU将邏輯位址轉換成實體位址,這個過程與記憶體映射無關。 前面講過,建立記憶體映射并沒有實際拷貝資料,這時,MMU在位址映射表中是無法找到與ptr相對應的實體位址的,也就是MMU失敗,将産生一個缺頁中斷,缺頁中斷的中斷響應函數會在swap中尋找相對應的頁面,如果找不到(也就是該檔案從來沒有被讀入記憶體的情況),則會通過mmap()建立的映射關系,從硬碟上将檔案讀取到實體記憶體中,如圖1中過程3所示。這個過程與記憶體映射無關。 如果在拷貝資料時,發現實體記憶體不夠用,則會通過虛拟記憶體機制(swap)将暫時不用的實體頁面交換到硬碟上,這個過程也與記憶體映射無關。mmap記憶體映射的實作過程:

  1. 程序啟動映射過程,并在虛拟位址空間中為映射建立虛拟映射區域
  2. 調用核心空間的系統調用函數mmap(不同于使用者空間函數),實作檔案實體位址和程序虛拟位址的一一映射關系
  3. 程序發起對這片映射空間的通路,引發缺頁異常,實作檔案内容到實體記憶體(主存)的拷貝

适合的場景

  • 您有一個很大的檔案,其内容您想要随機通路一個或多個時間
  • 您有一個小檔案,它的内容您想要立即讀入記憶體并經常通路。這種技術最适合那些大小不超過幾個虛拟記憶體頁的檔案。(頁是位址空間的最小機關,虛拟頁和實體頁的大小是一樣的,通常為4KB。)
  • 您需要在記憶體中緩存檔案的特定部分。檔案映射消除了緩存資料的需要,這使得系統磁盤緩存中的其他資料空間更大 當随機通路一個非常大的檔案時,通常最好隻映射檔案的一小部分。映射大檔案的問題是檔案會消耗活動記憶體。如果檔案足夠大,系統可能會被迫将其他部分的記憶體分頁以加載檔案。将多個檔案映射到記憶體中會使這個問題更加複雜。

不适合的場景

  • 您希望從開始到結束的順序從頭到尾讀取一個檔案
  • 這個檔案有幾百兆位元組或者更大。将大檔案映射到記憶體中會快速地填充記憶體,并可能導緻分頁,這将抵消首先映射檔案的好處。對于大型順序讀取操作,禁用磁盤緩存并将檔案讀入一個小記憶體緩沖區
  • 該檔案大于可用的連續虛拟記憶體位址空間。對于64位應用程式來說,這不是什麼問題,但是對于32位應用程式來說,這是一個問題
  • 該檔案位于可移動驅動器上
  • 該檔案位于網絡驅動器上

示例代碼

//
//  ViewController.m
//  TestCode
//
//  Created by zhangdasen on 2020/5/24.
//  Copyright © 2020 zhangdasen. All rights reserved.
//

#import "ViewController.h"
#import <sys/mman.h>
#import <sys/stat.h>
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"test.data"];
    NSLog(@"path: %@", path);
    NSString *str = @"test str2";
    [str writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
    
    ProcessFile(path.UTF8String);
    NSString *result = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"result:%@", result);
}


int MapFile(const char * inPathName, void ** outDataPtr, size_t * outDataLength, size_t appendSize)
{
    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 + appendSize);
            fsync(fileDescriptor);
            *outDataPtr = mmap(NULL,
                               statInfo.st_size + appendSize,
                               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;
}


void ProcessFile(const char * inPathName)
{
    size_t dataLength;
    void * dataPtr;
    char *appendStr = " append_key2";
    int appendSize = (int)strlen(appendStr);
    if( MapFile(inPathName, &dataPtr, &dataLength, appendSize) == 0) {
        dataPtr = dataPtr + dataLength;
        memcpy(dataPtr, appendStr, appendSize);
        // Unmap files
        munmap(dataPtr, appendSize + dataLength);
    }
}
@end           

繼續閱讀