標籤:
最近在用java中的ByteBuffer,一直不明所以,尤其是對MappedByteBuffer使用的記憶體映射這個概念雲裡霧裡。
於是首先補了實體記憶體、虛擬記憶體、分頁檔、交換區的只是:小科普——實體記憶體、分頁檔、交換區和虛擬記憶體
然後閱讀了ByteBuffer的文章:ByteBuffer使用和實現以及檔案記憶體映射。
Bytebuffer分為兩種:間接地和直接的,所謂直接就是指MappedByteBuffer,直接使用記憶體映射(java的話就意味著在JVM之外分配虛擬位址空間);而間接的ByteBuffer是在JVM的堆上面的,說白了就是管理一群byte數組的封裝。
這裡面最核心最關鍵的也即是記憶體映射了,下面的內容來自這篇文章:記憶體對應檔原理探索
記憶體映射:
原理
首先,“映射”這個詞,就和數學課上說的“一一映射”是一個意思,就是建立一種一一對應關係,在這裡主要是只 硬碟上檔案 的位置與進程 邏輯地址空間 中一塊大小相同的地區之間的一一對應,1中過程1所示。這種對應關係純屬是邏輯上的概念,物理上是不存在的,原因是進程的邏輯地址空間本身就是不存在的。在記憶體映射的過程中,並沒有實際的資料拷貝,檔案沒有被載入記憶體,只是邏輯上被放入了記憶體,具體到代碼,就是建立並初始化了相關的資料結構(struct address_space),這個過程有系統調用mmap()實現,所以建立記憶體映射的效率很高。
圖1.記憶體映射原理
既然建立記憶體映射沒有進行實際的資料拷貝,那麼進程又怎麼能最終直接通過記憶體操作訪問到硬碟上的檔案呢?那就要看記憶體映射之後的幾個相關的過程了。
mmap()會返回一個指標ptr,它指向進程邏輯地址空間中的一個地址,這樣以後,進程無需再調用read或write對檔案進行讀寫,而只需要通過ptr就能夠操作檔案。但是ptr所指向的是一個邏輯地址,要操作其中的資料,必須通過MMU將邏輯地址轉換成物理地址,1中過程2所示。這個過程與記憶體映射無關。
前面講過,建立記憶體映射並沒有實際拷貝資料,這時,MMU在地址映射表中是無法找到與ptr相對應的物理地址的,也就是MMU失敗,將產生一個缺頁中斷,缺頁中斷的中斷響應函數會在swap中尋找相對應的頁面,如果找不到(也就是該檔案從來沒有被讀入記憶體的情況),則會通過mmap()建立的映射關係,從硬碟上將檔案讀取到實體記憶體中,1中過程3所示。這個過程與記憶體映射無關。
如果在拷貝資料時,發現實體記憶體不夠用,則會通過虛擬記憶體機制(swap)將暫時不用的物理頁面交換到硬碟上,1中過程4所示。這個過程也與記憶體映射無關。
效率
從代碼層面上看,從硬碟上將檔案讀入記憶體,都要經過檔案系統進行資料拷貝,並且資料拷貝操作是由檔案系統和硬體驅動實現的,理論上來說,拷貝資料的效率是一樣的。但是通過記憶體映射的方法訪問硬碟上的檔案,效率要比read和write系統調用高,這是為什麼呢?原因是read()是系統調用,其中進行了資料拷貝,它首先將檔案內容從硬碟拷貝到核心空間的一個緩衝區,2中過程1,然後再將這些資料拷貝到使用者空間,2中過程2,在這個過程中,實際上完成了 兩次資料拷貝 ;而mmap()也是系統調用,如前所述,mmap()中沒有進行資料拷貝,真正的資料拷貝是在缺頁中斷處理時進行的,由於mmap()將檔案直接映射到使用者空間,所以中斷處理函數根據這個映射關係,直接將檔案從硬碟拷貝到使用者空間,只進行了 一次資料拷貝 。因此,記憶體映射的效率要比read/write效率高。
圖2.read系統調用原理
MappedByteBuffer以及ByteBufer的底層原理