内存映射文件进行写文件和读文件有啥不同_Linux中的mmap映射 [二]
來看下上文介紹的mmap()的函數(shù)原型是怎樣的:
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);看起來參數(shù)好像比較多,但對照著下面這張圖看,應(yīng)該會清晰不少。
其中fd, offset和length都是用來描述要映射的文件區(qū)域的,"fd"是文件描述符,對于匿名映射,fd應(yīng)該是-1(如果是通過打開/dev/zero這個特殊的文件來創(chuàng)建匿名映射,則它也是有不為-1的正常fd值的)。"offset"是文件中映射的起始位置,"length"是映射的長度。如果訪問了超出映射的區(qū)域,則有可能觸發(fā)SIGSEGV異常(segmentation fault)。
為什么說是有可能呢?因為只有訪問的地址沒有落在進(jìn)程的任何VMAs(關(guān)于VMA請參考這篇文章)里,才應(yīng)該觸發(fā)SIGSEGV,如果超出映射的這段區(qū)域正好落在另一個VMA里,那就可以僥幸逃過SIGSEGV。其實這樣更糟糕,觸發(fā)SIGSEGV可以讓你及時知道事故的第一現(xiàn)場,如果繼續(xù)運行,然后在某個不可預(yù)知的地方崩潰,那這bug排查起來就困難了。
圖片來源 - 《Linux環(huán)境編程:從應(yīng)用到內(nèi)核》Linux是以page為單位管理內(nèi)存的,mmap也是以page為單位建立映射的,因此offset必須是按page size對齊的(不對齊的話就會映射失敗)。文件是有長度的,offset和offset+length的值都應(yīng)該小于被映射文件的長度。
然而,mmap()并不會對此作出檢查,所以有可能建立的映射區(qū)域并不完全在文件的長度范圍內(nèi)。比如一個文件的長度是4KB,而你映射了8KB,那么在訪問后面的這4KB內(nèi)存的時候,就會觸發(fā)SIGBUS異常,表明你訪問的這段內(nèi)存區(qū)域,沒有對應(yīng)的文件。
如果用戶給定的length不是按page size對齊的,那么內(nèi)核會填充一部分長度以保證對齊(可見,offset對齊是用戶自己保證的,length對齊是靠內(nèi)核來保證的)。比如文件長度是10KB,你映射了5KB,那么內(nèi)核會將其擴(kuò)充到8KB。如果你訪問下圖中5120字節(jié)到8191字節(jié)的這段內(nèi)存,因為既沒有超出映射的內(nèi)存區(qū)域,也沒有超出文件的長度,所以是不會觸發(fā)任何異常的。
你以為只要保證映射的區(qū)域完全落在文件的長度范圍內(nèi)就可以高枕無憂了么?事務(wù)都是變化的,被映射的文件也不例外,比如它可以通過truncate()/ftruncate()截斷,截斷之后文件的長度如果減小了(truncate也是可以增大的),然后你剛好訪問了被截斷的這段區(qū)域,依然會觸發(fā)SIGBUS。
映射區(qū)域的大小可以通過mremap()動態(tài)調(diào)整,事實上,glibc中的realloc()就是調(diào)用mremap()實現(xiàn)的。
"prot"是protection的意思,表示的是對內(nèi)存映射區(qū)域的保護(hù),包括PROT_READ(可讀),PROT_WRITE(可寫)和PROT_EXEC(可執(zhí)行)。還有一個很特殊的PROT_NONE,就是既不可讀也不可寫更不可執(zhí)行,啥操作都不可以,那映射出來干嗎?
普通場景下沒有用,不代表特殊場景下沒用,PROT_NONE可以用于實現(xiàn)防范攻擊的guard page。如果攻擊者訪問了某個guard page,就會觸發(fā)SIGSEV,作用和地雷是差不多的。在布了地雷的土地上,你不能種田,不能蓋房,但是可以防范敵人的入侵,只是,你得記住地雷放的位置,別自己踩著了。
prot屬性是可以通過mprotect()動態(tài)修改的(mprotect并不局限于操作由mmap映射的內(nèi)存區(qū)域,它可以操作任意區(qū)域的內(nèi)存),這篇文章介紹的JIT實現(xiàn)就是對此一個頗有意思的應(yīng)用。
"flags"用于指定映射是基于文件的還是匿名(MAP_ANONYMOUS)的,是共享的(MAP_SHARED)還是私有的(MAP_PRIVATE)。上文介紹過,共享映射和私有映射在寫操作上是有區(qū)別的,前者是直接寫,后者是COW,先copy再寫。
addr用于指定映射到的VMA的起始地址,這個地址也必須按page size對齊。映射是由內(nèi)核完成的,但進(jìn)程可以通過addr參數(shù)建議一個它認(rèn)為的最佳地址(沒有這種要求就設(shè)置addr為NULL),畢竟進(jìn)程最了解它自身的應(yīng)用場景嘛。
但這對內(nèi)核來說不是強(qiáng)制的,如果addr和addr+length之間的虛擬內(nèi)存空間恰好是可用的,那么內(nèi)核會滿足進(jìn)程的這一要求。如果flags中加上MAP_FIXED,那就是進(jìn)程要求必須映射到這個addr起始的區(qū)域,當(dāng)然,這會增加映射失敗的概率。
實際映射到的VMA起始地址保存在mmap()的返回值中。
同read()和write()一樣,要獲得mmap()中的參數(shù)fd,自然是要先open()一下,而調(diào)用open()的時候是需要設(shè)置對文件的讀寫權(quán)限的,比如O_RDONLY(只讀),O_RDWD(可讀寫),mmap()中prot和flags的設(shè)置需要和進(jìn)程打開文件時的屬性設(shè)置保持一致。
上文提到,mmap()的實現(xiàn)主要是在進(jìn)程虛擬地址空間創(chuàng)建了一個VMA,而一個VMA是有VM_READ, VM_WRITE, VM_EXEC, VM_SHARED等諸多屬性的。如果是通過mmap創(chuàng)建的VMA,則這個VMA的屬性就是由mmap()中prot和flags參數(shù)傳遞進(jìn)去的。
參考:
《Linux環(huán)境編程:從應(yīng)用到內(nèi)核》第11.4節(jié)
原創(chuàng)文章,轉(zhuǎn)載請注明出處。
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的内存映射文件进行写文件和读文件有啥不同_Linux中的mmap映射 [二]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vuex 存储刷新_vuex 存储数据
- 下一篇: asp点击链接数字加1代码_Asp.Ne