Docker原理解讀
782
2025-03-31
@[toc]
管道
匿名管道
在shell中管道用 “|” 表示。
可以理解為內(nèi)存中的一個(gè)緩沖區(qū),用于將某個(gè)進(jìn)程的數(shù)據(jù)流導(dǎo)入,由某一個(gè)進(jìn)程導(dǎo)出,實(shí)現(xiàn)通信。
這種管道稱為“匿名管道”,用完即毀。
匿名管道是特殊的文件,只存在于內(nèi)存之中,不存在于文件系統(tǒng)中。
匿名管道用于有血緣關(guān)系的進(jìn)程間通信。
這種血緣關(guān)系不一定要是父子關(guān)系,在 shell 里面執(zhí)行 A | B命令的時(shí)候,A 進(jìn)程和 B 進(jìn)程都是 shell 創(chuàng)建出來的子進(jìn)程,A 和 B 之間不存在父子關(guān)系,它倆的父進(jìn)程都是 shell。
如果要用管道進(jìn)行無血緣關(guān)系之間的進(jìn)程通信,用FIFO有名管道。
局勢到這里已經(jīng)很清楚了,管道具有:“召之即來,揮之即去,且不占文件系統(tǒng)位置”的特性,適合用于shell中的“一次性博弈”。
如果是“長期博弈”,它不行,得往后看。
使用管道注意以下四種情況(假設(shè)都是阻塞I/O操作):
1、兩個(gè)寫端都被阻塞。read會(huì)返回0。
2、兩個(gè)讀端都被阻塞。這時(shí)有進(jìn)程向管道的寫端write,那么該進(jìn)程會(huì)收到信號(hào)SIGPIPE,通常會(huì)導(dǎo)致進(jìn)程異常終止。
3、正常開放,但是只讀不寫。讀完了就開始阻塞。
4、正常開放,但是只寫不讀。管道寫滿了就阻塞了。
管道PIPE_BUF說明
可以使用linux的ulimit -a來查看系統(tǒng)限制。
POSIX.1要求PIPE_BUF至少為512字節(jié)。(在Linux上,PIPE_BUF為4096字節(jié))。在實(shí)踐中取決于文件描述符是否為非阻塞(O_NONBLOCK),管道中是否有多個(gè)寫入器,以及n要寫入的字節(jié)數(shù):
情況1:O_NONBLOCK disabled, n <= PIPE_BUF 所有n個(gè)字節(jié)都以原子方式寫入; write可能會(huì)阻止如果沒有空間來立即寫入n個(gè)字節(jié)。 情況2:O_NONBLOCK enabled, n <= PIPE_BUF 如果有空間向管道寫入n個(gè)字節(jié),則立即write成功,寫入全部n個(gè)字節(jié); 否則失敗,并將errno設(shè)置為EAGAIN。 情況3:O_NONBLOCK disabled, n > PIPE_BUF 寫入是非原子化的:write的數(shù)據(jù)可能會(huì)被寫入與其他進(jìn)程交錯(cuò); write阻塞,直到寫入了n個(gè)字節(jié)。 情況4:O_NONBLOCK enabled, n > PIPE_BUF 如果管道已滿,則寫入失敗,并將errno設(shè)置為EAGAIN。 否則,可能會(huì)寫入1到n個(gè)字節(jié)(即可能發(fā)生“部分寫入”;調(diào)用者應(yīng)該檢查write(2)的返回值以查看實(shí)際寫入的字節(jié)數(shù)),并且可以將這些字節(jié)與其他進(jìn)程寫入。
有名管道
通信可以是雙向的,并且父子關(guān)系不是必需的,當(dāng)建立了一個(gè)有名管道后,多個(gè)進(jìn)程都可用它通信。
一旦創(chuàng)建,它們表現(xiàn)為文件系統(tǒng)的典型文件。通過系統(tǒng)調(diào)用 mkfifo(),可以創(chuàng)建 FIFO,通過系統(tǒng)調(diào)用 open()、read()、write()和close(),可以操作 FIFO。
雖然 FIFO 允許雙向通信,但只允許半雙工傳輸
數(shù)據(jù)在同一時(shí)間內(nèi)只能按一個(gè)方向傳輸,程序不能以O(shè)_RDWR(讀寫)模式打開FIFO文件進(jìn)行讀寫操作,因?yàn)槿缫粋€(gè)管道以讀/寫方式打開,進(jìn)程就會(huì)讀回自己的輸出。如果數(shù)據(jù)要在兩個(gè)方向上傳輸,那么通常使用兩個(gè) FIFO。
與 UNIX 系統(tǒng)相比,Windows 系統(tǒng)的有名管道通信機(jī)制更加豐富。
對于以只讀方式(O_RDONLY)打開的FIFO文件,如果open調(diào)用是阻塞的(即第二個(gè)參數(shù)為O_RDONLY),除非有一個(gè)進(jìn)程以寫方式打開同一個(gè)FIFO,否則它不會(huì)返回;如果open調(diào)用是非阻塞的的(即第二個(gè)參數(shù)為O_RDONLY | O_NONBLOCK),則即使沒有其他進(jìn)程以寫方式打開同一個(gè)FIFO文件,open調(diào)用將成功并立即返回。
對于以只寫方式(O_WRONLY)打開的FIFO文件,如果open調(diào)用是阻塞的(即第二個(gè)參數(shù)為O_WRONLY),open調(diào)用將被阻塞,直到有一個(gè)進(jìn)程以只讀方式打開同一個(gè)FIFO文件為止;如果open調(diào)用是非阻塞的(即第二個(gè)參數(shù)為O_WRONLY | O_NONBLOCK),open總會(huì)立即返回,但如果沒有其他進(jìn)程以只讀方式打開同一個(gè)FIFO文件,open調(diào)用將返回-1,并且FIFO也不會(huì)被打開。
有一種情況是:一個(gè)FIFO文件,有多個(gè)進(jìn)程同時(shí)向同一個(gè)FIFO文件寫數(shù)據(jù),而只有一個(gè)讀FIFO進(jìn)程在同一個(gè)FIFO文件中讀取數(shù)據(jù)時(shí),會(huì)發(fā)生數(shù)據(jù)塊的相互交錯(cuò)。不同進(jìn)程向一個(gè)FIFO讀進(jìn)程發(fā)送數(shù)據(jù)是很普通的情況。這個(gè)問題的解決方法,就是讓寫操作的原子化。
有名管道,但當(dāng)涉獵,感覺不上不下的,使用起來沒有匿名管道簡單,傳輸效率又沒有共享內(nèi)存快,內(nèi)存吧又沒mmap大,滯后性又不如消息隊(duì)列,信號(hào)機(jī)制更不用說了。
消息隊(duì)列
==一發(fā)一存一消費(fèi)==
拋去那些已經(jīng)成名的消息隊(duì)列,
1、消息隊(duì)列是內(nèi)核地址空間中的內(nèi)部鏈表,通過Linux內(nèi)核在不同的進(jìn)程間傳遞消息。 2、消息順序的發(fā)送到消息隊(duì)列中,并以幾種不同的方式從隊(duì)列中獲取。 3、內(nèi)核中的消息隊(duì)列是==通過IPC標(biāo)識(shí)符來進(jìn)行區(qū)別==的,不同消息隊(duì)列之間是==互相獨(dú)立==的。 4、每個(gè)消息隊(duì)列中的消息又構(gòu)成一個(gè)獨(dú)立的鏈表。 消息隊(duì)列不適合比較大數(shù)據(jù)的傳輸,因?yàn)樵趦?nèi)核中每個(gè)消息體都有一個(gè)最大長度的限制,同時(shí)所有隊(duì)列所包含的全部消息體的總長度也是有上限。在 Linux 內(nèi)核中,會(huì)有兩個(gè)宏定義 MSGMAX 和 MSGMNB,它們以字節(jié)為單位,分別定義了一條消息的最大長度和一個(gè)隊(duì)列的最大長度。
消息隊(duì)列的優(yōu)勢還是很明顯的,實(shí)現(xiàn)了業(yè)務(wù)層面的異步操作。
目前,MQ 的應(yīng)用場景非常多,大家能倒背如流的是:系統(tǒng)解耦、異步通信和流量削峰。除此之外,還有延遲通知、最終一致性保證、順序消息、流式處理等等。
關(guān)于消息隊(duì)列的部分會(huì)在后面專門寫有名消息隊(duì)列的博客里續(xù)寫。
共享內(nèi)存
1、共享內(nèi)存是在多個(gè)進(jìn)程之間共享內(nèi)存區(qū)域的一種進(jìn)程間的通信方式。
2、它是在多個(gè)進(jìn)程間通過對指定內(nèi)存段進(jìn)行映射實(shí)現(xiàn)內(nèi)存共享的。
3、這是IPC最快捷的方式,因?yàn)樗鼪]有中間商賺差價(jià)。
4、多個(gè)進(jìn)程間共享的是同一塊==物理空間==,僅僅是掛載地址不同而已,因此不需要進(jìn)行復(fù)制,可以直接使用這段空間。
代碼操作
到了共享內(nèi)存,不像前面兩個(gè),是時(shí)候來代碼操作了。
#include
注意:內(nèi)核是以頁為單位分配內(nèi)存,當(dāng)size參數(shù)的值不是系統(tǒng)內(nèi)存頁長的整數(shù)倍時(shí),系統(tǒng)會(huì)分配給進(jìn)程最小的可以滿足size長的頁數(shù),但是最后一頁的剩余部分內(nèi)存是不可用的。
key_t ftok(const char *pathname, int proj_id); 創(chuàng)建IPC通訊時(shí)所必需的ID值。 pathname:指定已經(jīng)存在的文件名,一般是當(dāng)前目錄 proj_id:子序列號(hào),正整數(shù),差不多大小就好了,別亂設(shè)。 返回值:ID值,大小是文件的索引節(jié)點(diǎn)號(hào)前加上子序列號(hào),例如:pathname索引節(jié)點(diǎn)號(hào)為0x101010,proj_id為0x32,則ID為0x32101010。
void *shmat(int shmid, const void *shmaddr, int shmflg); shmid:共享內(nèi)存標(biāo)識(shí)符,shmget() 的返回值。 shmaddr:共享內(nèi)存映射地址(若為 NULL 則由系統(tǒng)自動(dòng)指定),推薦使用 NULL。 shmflg:共享內(nèi)存段的訪問權(quán)限和映射條件( 通常為 0 ),具體取值如下: 0:共享內(nèi)存具有可讀可寫權(quán)限。 SHM_RDONLY:只讀。 SHM_RND:(shmaddr 非空時(shí)才有效) 函數(shù)執(zhí)行成功返回共享內(nèi)存的首地址,失敗返回–1。shmat函數(shù)成功執(zhí)行會(huì)將shm_id段的shmid_ds結(jié)構(gòu)的shm_nattch計(jì)數(shù)器的值加1。
使用函數(shù)shmat將一個(gè)存在的共享內(nèi)存段連接到本進(jìn)程空間,函數(shù)中參數(shù)shm_id指定要引入的共享內(nèi)存,參數(shù)addr與flag組合說明要引入的地址值,通常只有2種用法,addr為0,表明讓內(nèi)核來決定第1個(gè)可以引入的位置。addr非零,并且flag中指定SHM_RND,則此段引入到addr所指向的位置(此操作不推薦使用,因?yàn)椴粫?huì)只對一種硬件上運(yùn)行應(yīng)用程序,為了程序的通用性推薦使用第1種方法),在flag參數(shù)中可以指定要引入的方式(讀寫方式指定)。
注:fork后子進(jìn)程繼承已連接的共享內(nèi)存地址。exec后該子進(jìn)程與已連接的共享內(nèi)存地址自動(dòng)脫離(detach)。進(jìn)程結(jié)束后,已連接的共享內(nèi)存地址會(huì)自動(dòng)脫離(detach)
/* 將共享內(nèi)存和當(dāng)前進(jìn)程分離(僅僅是斷開聯(lián)系并不刪除共享內(nèi)存,相當(dāng)于讓之前的指向此共享內(nèi)存的指針,不再指向)。*/ #include
參數(shù)addr是調(diào)用shmat函數(shù)的返回值,函數(shù)執(zhí)行成功返回0,并將該共享內(nèi)存的shmid_ds結(jié)構(gòu)的shm_nattch計(jì)數(shù)器減1,失敗返回–1。
#include
注:IPC_RMID僅是將共享內(nèi)存標(biāo)記為刪除。如果共享內(nèi)存僅有當(dāng)前進(jìn)程連接,則直接刪除。如果還有其他進(jìn)程,則會(huì)將當(dāng)前進(jìn)程從連接中刪除,并且將共享內(nèi)存標(biāo)識(shí)符改為0,表明其為PRIVATE狀態(tài),阻止其他進(jìn)程再連接共享內(nèi)存,等待其他進(jìn)程斷開連接后刪除共享內(nèi)存。
struct shmid_ds { struct ipc_perm msg_perm; //后面寫 size_t shm_segsz; //段大小,以字節(jié)為單位 time_t shm_atime; //最后掛載時(shí)間 time_t shm_dtime; //最后卸載時(shí)間 time_t shm_ctime; //最后修改時(shí)間 pid_t shm_cpid; //建立者 pid_t shm_lpid; //最后一個(gè)at/dt操作的進(jìn)程PID shmatt_t shm_nattch; //現(xiàn)掛載數(shù)量 ··· } //下面這個(gè)是關(guān)鍵 struct ipc_perm { key_t key; //鍵值 uid_t uid; gid_t gid; //用戶GID uid_t cuid; gid_t cgid; //建立者GID unsigned short mode;//權(quán)限 unsigned short seq; //序列號(hào) }
寫端示例:
/* study.cpp 寫端代碼 */ #include
讀端:
/* main.c 讀端代碼 */ #include
并發(fā)場景下的共享內(nèi)存
它沒有提供進(jìn)程間同步和互斥的功能。所以,共享內(nèi)存通常是要與信號(hào)量結(jié)合使用。
文件映射
虛擬空間 && 虛擬內(nèi)存
from:https://www.zhihu.com/question/48161206
看到有的地方將虛擬空間和虛擬內(nèi)存混在一起講了,還是先把這個(gè)捋清楚。還有虛擬內(nèi)存?那數(shù)據(jù)寫下去不是“拳拳到肉”嗎?那我4G內(nèi)核怎么寫40G數(shù)據(jù)進(jìn)去啊?
虛擬地址 != 虛擬內(nèi)存 虛擬內(nèi)存 == 不存在的
在開發(fā)中沒有意義。開發(fā)中只有虛擬空間的概念,進(jìn)程看到的所有地址組成的空間,就是虛擬空間。虛擬空間是某個(gè)進(jìn)程對分配給它的所有物理地址(已經(jīng)分配的和將會(huì)分配的)的重新映射。
“mmap的作用,在應(yīng)用這一層,==是讓你把文件的某一段,當(dāng)作內(nèi)存一樣來訪問==。內(nèi)核和驅(qū)動(dòng)如何實(shí)現(xiàn)的,性能高不高這些問題,這層語義上沒有承諾。你基于功能決定怎么用它就好了,少胡思亂想。”
虛擬空間可以很大,但不表示物理內(nèi)存也需要很大。每個(gè)進(jìn)程有自己的虛擬空間(切換進(jìn)程的時(shí)候切換MMU的翻譯表即可),這些虛擬空間可以映射到物理內(nèi)存的不同或者相同的位置。示意如下:
Linux執(zhí)行一個(gè)程序,這個(gè)程序在磁盤上,為了執(zhí)行這個(gè)程序,需要把程序加載到內(nèi)存中,這時(shí)采用的就是mmap,mmap讓虛擬空間和文件的內(nèi)容組成的空間(我這里稱為文件空間)對應(yīng),類似這樣:
上面展示的是mmap之后的效果,但文件的內(nèi)容在磁盤上是不能被CPU訪問的,所以當(dāng)CPU真的在這個(gè)地址上發(fā)起讀寫執(zhí)行等操作時(shí),OS會(huì)進(jìn)入異常,異常中會(huì)調(diào)用文件系統(tǒng)把一頁或者多頁的文件內(nèi)容加載到物理內(nèi)存中,這會(huì)變成這樣:
mmap內(nèi)存映射原理
from:https://blog.csdn.net/qq_33611327/article/details/81738195
mmap將一個(gè)文件或者其它對象映射進(jìn)內(nèi)存。文件被映射到多個(gè)頁上,如果文件的大小不是所有頁的大小之和,最后一個(gè)頁不被使用的空間將會(huì)清零。
mmap的特點(diǎn)是==按需調(diào)頁==。最開始只申請vma,并不調(diào)真正的頁。當(dāng)對某些頁進(jìn)行引用的時(shí)候,會(huì)引起一個(gè)缺頁中斷,再將頁面調(diào)入到內(nèi)存當(dāng)中,這樣避免了對內(nèi)存的浪費(fèi)。
函數(shù)原型也沒有shm那么多,當(dāng)然比后邊那個(gè)socket也是更少的了。
mmap內(nèi)存映射的實(shí)現(xiàn)過程,總的來說可以分為三個(gè)階段:
(一)進(jìn)程啟動(dòng)映射過程,并在虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域
1、進(jìn)程在用戶空間調(diào)用庫函數(shù)mmap, 原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 2、在當(dāng)前進(jìn)程的虛擬地址空間中,尋找一段空閑的滿足要求的連續(xù)的虛擬地址 3、為此虛擬區(qū)分配一個(gè)vm_area_struct結(jié)構(gòu),接著對這個(gè)結(jié)構(gòu)的各個(gè)域進(jìn)行了初始化 4、將新建的虛擬區(qū)結(jié)構(gòu)(vm_area_struct)插入進(jìn)程的虛擬地址區(qū)域鏈表或樹中
(二)調(diào)用內(nèi)核空間的系統(tǒng)調(diào)用函數(shù)mmap(不同于用戶空間函數(shù)),實(shí)現(xiàn)文件物理地址和進(jìn)程虛擬地址的一一映射關(guān)系
5、為映射分配了新的虛擬地址區(qū)域后,通過待映射的文件指針,在文件描述符表中找到對應(yīng)的文件描述符, 通過文件描述符,鏈接到內(nèi)核“已打開文件集”中該文件的文件結(jié)構(gòu)體(struct file),每個(gè)文件結(jié)構(gòu)體維護(hù)著和這個(gè)已打開文件相關(guān)各項(xiàng)信息。 6、通過該文件的文件結(jié)構(gòu)體,鏈接到file_operations模塊,調(diào)用內(nèi)核函數(shù)mmap, 其原型為:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用戶空間庫函數(shù)。 7、內(nèi)核mmap函數(shù)通過虛擬文件系統(tǒng)inode模塊定位到文件磁盤物理地址。 8、通過remap_pfn_range函數(shù)建立頁表,即實(shí)現(xiàn)了文件地址和虛擬地址區(qū)域的映射關(guān)系。此時(shí),這片虛擬地址并沒有任何數(shù)據(jù)關(guān)聯(lián)到主存中。
(三)進(jìn)程發(fā)起對這片映射空間的訪問,引發(fā)缺頁異常,實(shí)現(xiàn)文件內(nèi)容到物理內(nèi)存(主存)的拷貝
注:前兩個(gè)階段僅在于創(chuàng)建虛擬區(qū)間并完成地址映射,但是并沒有將任何文件數(shù)據(jù)的拷貝至主存。真正的文件讀取是當(dāng)進(jìn)程發(fā)起讀或?qū)懖僮鲿r(shí)。
9、進(jìn)程的讀或?qū)懖僮髟L問虛擬地址空間這一段映射地址,通過查詢頁表,發(fā)現(xiàn)這一段地址并不在物理頁面上。 因?yàn)槟壳爸唤⒘说刂酚成洌嬲挠脖P數(shù)據(jù)還沒有拷貝到內(nèi)存中,因此引發(fā)缺頁異常。 10、缺頁異常進(jìn)行一系列判斷,確定無非法操作后,內(nèi)核發(fā)起請求調(diào)頁過程。 11、調(diào)頁過程先在交換緩存空間(swap cache)中尋找需要訪問的內(nèi)存頁,如果沒有則調(diào)用nopage函數(shù)把所缺的頁從磁盤裝入到主存中。 12、之后進(jìn)程即可對這片主存進(jìn)行讀或者寫的操作,如果寫操作改變了其內(nèi)容,一定時(shí)間后系統(tǒng)會(huì)自動(dòng)回寫臟頁面到對應(yīng)磁盤地址,也即完成了寫入到文件的過程。 注:修改過的臟頁面并不會(huì)立即更新回文件中,而是有一段時(shí)間的延遲,可以調(diào)用msync()來強(qiáng)制同步, 這樣所寫的內(nèi)容就能立即保存到文件里了。
mmap優(yōu)點(diǎn)
由上文討論可知,mmap優(yōu)點(diǎn)共有一下幾點(diǎn):
1、對文件的讀取操作跨過了頁緩存,減少了數(shù)據(jù)的拷貝次數(shù),用內(nèi)存讀寫取代I/O讀寫,提高了文件讀取效率。
2、實(shí)現(xiàn)了用戶空間和內(nèi)核空間的高效交互方式。兩空間的各自修改操作可以直接反映在映射的區(qū)域內(nèi),從而被對方空間及時(shí)捕捉。
3、提供進(jìn)程間共享內(nèi)存及相互通信的方式。不管是父子進(jìn)程還是無親緣關(guān)系的進(jìn)程,都可以將自身用戶空間映射到同一個(gè)文件或匿名映射到同一片區(qū)域。從而通過各自對映射區(qū)域的改動(dòng),達(dá)到進(jìn)程間通信和進(jìn)程間共享的目的。
同時(shí),如果進(jìn)程A和進(jìn)程B都映射了區(qū)域C,當(dāng)A第一次讀取C時(shí)通過缺頁從磁盤復(fù)制文件頁到內(nèi)存中;但當(dāng)B再讀C的相同頁面時(shí),雖然也會(huì)產(chǎn)生缺頁異常,但是不再需要從磁盤中復(fù)制文件過來,而可直接使用已經(jīng)保存在內(nèi)存中的文件數(shù)據(jù)。
4、可用于實(shí)現(xiàn)高效的大規(guī)模數(shù)據(jù)傳輸。內(nèi)存空間不足,是制約大數(shù)據(jù)操作的一個(gè)方面,解決方案往往是借助硬盤空間協(xié)助操作,補(bǔ)充內(nèi)存的不足。但是進(jìn)一步會(huì)造成大量的文件I/O操作,極大影響效率。這個(gè)問題可以通過mmap映射很好的解決。換句話說,但凡是需要用磁盤空間代替內(nèi)存的時(shí)候,mmap都可以發(fā)揮其功效。
函數(shù)原型
#include
成功執(zhí)行時(shí),mmap()返回被映射區(qū)的指針,munmap()返回0。失敗時(shí),mmap()返回MAP_FAILED[其值為(void *)-1],munmap返回-1。errno被設(shè)為以下的某個(gè)值
EACCES:訪問出錯(cuò) EAGAIN:文件已被鎖定,或者太多的內(nèi)存已被鎖定 EBADF:fd不是有效的文件描述詞 EINVAL:一個(gè)或者多個(gè)參數(shù)無效 ENFILE:已達(dá)到系統(tǒng)對打開文件的限制 ENODEV:指定文件所在的文件系統(tǒng)不支持內(nèi)存映射 ENOMEM:內(nèi)存不足,或者進(jìn)程已超出最大內(nèi)存映射數(shù)量 EPERM:權(quán)能不足,操作不允許 ETXTBSY:已寫的方式打開文件,同時(shí)指定MAP_DENYWRITE標(biāo)志 SIGSEGV:試著向只讀區(qū)寫入 SIGBUS:試著訪問不屬于進(jìn)程的內(nèi)存區(qū)
mmap()系統(tǒng)調(diào)用使得進(jìn)程之間通過映射同一個(gè)普通文件實(shí)現(xiàn)共享內(nèi)存。普通文件被映射到進(jìn)程地址空間后,進(jìn)程可以像訪問普通內(nèi)存一樣對文件進(jìn)行訪問,不必再調(diào)用read(),write()等操作。
注:實(shí)際上,mmap()系統(tǒng)調(diào)用并不是完全為了用于共享內(nèi)存而設(shè)計(jì)的。它本身提供了不同于一般對普通文件的訪問方式,進(jìn)程可以像讀寫內(nèi)存一樣對普通文件的操作。而Posix或System V的共享內(nèi)存IPC則純粹用于共享目的,當(dāng)然mmap()實(shí)現(xiàn)共享內(nèi)存也是其主要應(yīng)用之一。
mmap使用細(xì)節(jié)
1、使用mmap需要注意的一個(gè)關(guān)鍵點(diǎn)是,mmap映射區(qū)域大小必須是物理頁大小(page_size)的整倍數(shù)(32位系統(tǒng)中通常是4k字節(jié))。原因是,內(nèi)存的最小粒度是頁,而進(jìn)程虛擬地址空間和內(nèi)存的映射也是以頁為單位。為了匹配內(nèi)存的操作,mmap從磁盤到虛擬地址空間的映射也必須是頁。
2、內(nèi)核可以跟蹤被內(nèi)存映射的底層對象(文件)的大小,進(jìn)程可以合法的訪問在當(dāng)前文件大小以內(nèi)又在內(nèi)存映射區(qū)以內(nèi)的那些字節(jié)。也就是說,如果文件的大小一直在擴(kuò)張,只要在映射區(qū)域范圍內(nèi)的數(shù)據(jù),進(jìn)程都可以合法得到,這和映射建立時(shí)文件的大小無關(guān)。具體情形參見“情形三”。
3、映射建立之后,即使文件關(guān)閉,映射依然存在。因?yàn)橛成涞氖谴疟P的地址,不是文件本身,和文件句柄無關(guān)。同時(shí)可用于進(jìn)程間通信的有效地址空間不完全受限于被映射文件的大小,因?yàn)槭前错撚成洹?/p>
在上面的知識(shí)前提下,我們下面看看如果大小不是頁的整倍數(shù)的具體情況:
情形一:一個(gè)文件的大小是5000字節(jié),mmap函數(shù)從一個(gè)文件的起始位置開始,映射5000字節(jié)到虛擬內(nèi)存中。
分析:因?yàn)閱挝晃锢眄撁娴拇笮∈?096字節(jié),雖然被映射的文件只有5000字節(jié),但是對應(yīng)到進(jìn)程虛擬地址區(qū)域的大小需要滿足整頁大小,因此mmap函數(shù)執(zhí)行后,實(shí)際映射到虛擬內(nèi)存區(qū)域8192個(gè) 字節(jié),5000~8191的字節(jié)部分用零填充。
此時(shí):
(1)讀/寫前5000個(gè)字節(jié)(0~4999),會(huì)返回操作文件內(nèi)容。
(2)讀字節(jié)5000~8191時(shí),結(jié)果全為0。寫5000~8191時(shí),進(jìn)程不會(huì)報(bào)錯(cuò),但是所寫的內(nèi)容不會(huì)寫入原文件中 。
(3)讀/寫8192以外的磁盤部分,會(huì)返回一個(gè)SIGSECV錯(cuò)誤。
情形二:一個(gè)文件的大小是5000字節(jié),mmap函數(shù)從一個(gè)文件的起始位置開始,映射15000字節(jié)到虛擬內(nèi)存中,即映射大小超過了原始文件的大小。
分析:由于文件的大小是5000字節(jié),和情形一一樣,其對應(yīng)的兩個(gè)物理頁。那么這兩個(gè)物理頁都是合法可以讀寫的,只是超出5000的部分不會(huì)體現(xiàn)在原文件中。由于程序要求映射15000字節(jié),而文件只占兩個(gè)物理頁,因此8192字節(jié)~15000字節(jié)都不能讀寫,操作時(shí)會(huì)返回異常。
此時(shí):
(1)進(jìn)程可以正常讀/寫被映射的前5000字節(jié)(0~4999),寫操作的改動(dòng)會(huì)在一定時(shí)間后反映在原文件中。
(2)對于5000~8191字節(jié),進(jìn)程可以進(jìn)行讀寫過程,不會(huì)報(bào)錯(cuò)。但是內(nèi)容在寫入前均為0,另外,寫入后不會(huì)反映在文件中。
(3)對于8192~14999字節(jié),進(jìn)程不能對其進(jìn)行讀寫,會(huì)報(bào)SIGBUS錯(cuò)誤。
(4)對于15000以外的字節(jié),進(jìn)程不能對其讀寫,會(huì)引發(fā)SIGSEGV錯(cuò)誤。
情形三:一個(gè)文件初始大小為0,使用mmap操作映射了1000*4K的大小,即1000個(gè)物理頁大約4M字節(jié)空間,mmap返回指針ptr。
分析:如果在映射建立之初,就對文件進(jìn)行讀寫操作,由于文件大小為0,并沒有合法的物理頁對應(yīng),如同情形二一樣,會(huì)返回SIGBUS錯(cuò)誤。
但是如果,每次操作ptr讀寫前,先增加文件的大小,那么ptr在文件大小內(nèi)部的操作就是合法的。例如,文件擴(kuò)充4096字節(jié),ptr就能操作ptr ~ [ (char)ptr + 4095]的空間。只要文件擴(kuò)充的范圍在1000個(gè)物理頁(映射范圍)內(nèi),ptr都可以對應(yīng)操作相同的大小。
這樣,方便隨時(shí)擴(kuò)充文件空間,隨時(shí)寫入文件,不造成空間浪費(fèi)
性能?
都說不要胡思亂想了。
“
mmap之后,再有讀操作不會(huì)經(jīng)過系統(tǒng)調(diào)用,在 LRU 比較最近使用的頁的時(shí)候不占優(yōu)勢;
于是,普通讀情況下(排除反復(fù)讀之類的文藝與2B讀操作),read() 通常會(huì)比 mmap() 來得更快。
”
信號(hào)
累了,下次吧。
好吧,其實(shí)就是不會(huì)。
socket
后面會(huì)專門再整理一篇socket的,這里只是聲明一下。
任務(wù)調(diào)度
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。