參考
介紹
除了標(biāo)準(zhǔn)的文件 IO枢舶,例如 open, read, write,內(nèi)核還提供接口允許應(yīng)用將文件 map 到內(nèi)存替久。使得內(nèi)存中的一個字節(jié)與文件中的一個字節(jié)一一對應(yīng)凉泄。
-
優(yōu)勢
- 讀寫文件避免了
read()
和write()
系統(tǒng)調(diào)用,也避免了數(shù)據(jù)的拷貝蚯根。 - 除了潛在的頁錯誤后众,讀寫 map 后的文件不引起系統(tǒng)調(diào)用或者上下文切換。就像訪問內(nèi)存一樣簡單。
- 多個進(jìn)程 map 同一個對象蒂誉,可以共享數(shù)據(jù)教藻。
- 可以直接使用指針來跳轉(zhuǎn)到文件某個位置,不必使用
lseek()
系統(tǒng)調(diào)用右锨。
- 讀寫文件避免了
-
劣勢
- 內(nèi)存浪費括堤。由于必須要使用整數(shù)頁的內(nèi)存。
- 導(dǎo)致難以找到連續(xù)的內(nèi)存區(qū)域
- 創(chuàng)建和維護(hù)映射和相關(guān)的數(shù)據(jù)結(jié)構(gòu)的額外開銷绍移。在大文件和頻繁訪問的文件中痊臭,這個開銷相比 read write 的 copy 開銷小。
使用方法
函數(shù)原型為:
#include <sys/mman.h>
void * mmap (void *addr,
size_t len,
int prot,
int flags,
int fd,
off_t offset);
addr
這個參數(shù)是建議地址(hint)登夫,沒有特別需求一般設(shè)為0。這個函數(shù)會返回一個實際 map 的地址允趟。len
文件長度恼策。prot
表明對這塊內(nèi)存的保護(hù)方式,不可與文件訪問方式?jīng)_突潮剪。
PROT_NONE
無權(quán)限涣楷,基本沒有用
PROT_READ
讀權(quán)限
PROT_WRITE
寫權(quán)限
PROT_EXEC
執(zhí)行權(quán)限flags
描述了映射的類型。
MAP_FIXED
開啟這個選項抗碰,則 addr 參數(shù)指定的地址是作為必須而不是建議狮斗。如果由于空間不足等問題無法映射則調(diào)用失敗。不建議使用弧蝇。
MAP_PRIVATE
表明這個映射不是共享的碳褒。文件使用 copy on write 機(jī)制映射,任何內(nèi)存中的改動并不反映到文件之中看疗。也不反映到其他映射了這個文件的進(jìn)程之中沙峻。如果只需要讀取某個文件而不改變文件內(nèi)容,可以使用這種模式两芳。
MAP_SHARED
和其他進(jìn)程共享這個文件摔寨。往內(nèi)存中寫入相當(dāng)于往文件中寫入。會影響映射了這個文件的其他進(jìn)程怖辆。與MAP_PRIVATE
沖突是复。fd
文件描述符。進(jìn)行 map 之后竖螃,文件的引用計數(shù)會增加淑廊。因此,我們可以在 map 結(jié)束后關(guān)閉 fd斑鼻,進(jìn)程仍然可以訪問它蒋纬。當(dāng)我們 unmap 或者結(jié)束進(jìn)程,引用計數(shù)會減少。offset
文件偏移蜀备,從文件起始算起关摇。
如果失敗,mmap 函數(shù)將返回 MAP_FAILED
碾阁。
頁面對齊
內(nèi)存擁有獨立權(quán)限的最小單位就是頁输虱。因此,mmap 的最小單位也是頁脂凶。addr
和 offset
參數(shù)都必須頁對齊宪睹,len
會被 roundup。被 roundup 的多余的內(nèi)存會以 \0
填充蚕钦。對這一部分的寫入操作不會影響文件亭病。我們可以通過如下方式獲取本機(jī)的頁面大小:
#include <unistd.h>
long page_size = sysconf(_SC_PAGESIZE);
代碼實現(xiàn)
因為項目需求并發(fā)寫入嘶居,為了提高性能罪帖,實現(xiàn)了一個可以并行寫入的mmap。
具體代碼可以查看我的Github邮屁。
遇到的問題
- 寫入時發(fā)生錯誤
bus error(core dump)
stackoverflow 大佬的原話:
You are creating a new zero sized file, you can't extend the file size with mmap. You'll get a bus error when you try to write outside the content of the file.
因此使用 lseek
先把文件擴(kuò)展到需要的大小整袁。
// solve the bus error problem:
// we should allocate space for the file first.
lseek(fd, size_lim_-1, SEEK_SET);
write(fd,"",1);
- 文件權(quán)限設(shè)置
int fd = open(file_path_.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0644);
打開的時候忘了加 0644 設(shè)置權(quán)限。
- 文件大小
由于文件最初利用 lseek 擴(kuò)張了一次佑吝,中間有大量的'\0'段坐昙。導(dǎo)致文件在驗證中出錯弹渔,而且打開緩慢畏鼓。
// resize the file to actual size
truncate(file_path_.c_str(), cur_pos_.load());
在析構(gòu)函數(shù)中增加 truncate 解決纠脾。