當前位置:首頁 » 操作系統 » mmaplinux

mmaplinux

發布時間: 2023-01-20 09:06:02

『壹』 在linux系統下使用內存技術,檢測堆越界錯誤

一般使用c或cpp編程時,堆棧越界訪問(read/write)往往會引起很多意想不到的錯誤,比如延後的進程崩潰等。因此,如果有一種方法,可以讓越界訪問立即觸發系統錯誤(讓進程拋出異常而終止,再生成coremp文件),就可以立即檢測出內存越界行為,並將對這種隱藏的錯誤,及時作出反應,以免在生產環境下造成更大的損失。

我們知道,在windows系統下面,我們可以使用VirtualAlloc系列函數,通過申請2頁內存,並設置某頁的保護參數(比如,可讀,可寫等),就可以實現類似的保護機制。這樣,當我們對新增加的類(數據結構),就可以重載operator new/delete,將類的邊界設置到一頁的邊緣,再將相鄰頁設置為不可讀不可寫。這樣就能有效監測堆越界讀寫問題。而且可以,設置某個編譯宏,比如PROTECT_CLASSX。演示代碼如下:

在linux下,則需要藉助mmap和mprotect來實現這個機制。具體步驟如下,首先用mmap使用PROT_NONE映射一個特殊文件,比如/dev/zero(或者使用MAP_ANONYMOUS),然後再用mprotect提交內存。上面的例子,可以繼續使用,但是只列出來核心的代碼,什麼重載操作符就不寫了,另外,內存映射文件j句柄最好用內存池來hold,最後在close掉。演示代碼只說明大致用法,並不能直接拿來用。

下面補充mprotect的用法:

再把mmap函數的用法示例如下:

『貳』 Android中mmap原理及應用簡析

mmap是Linux中常用的系統調用API,用途廣泛,Android中也有不少地方用到,比如匿名共享內存,Binder機制等。本文簡單記錄下Android中mmap調用流程及原理。mmap函數原型如下:

幾個重要參數

返回值是void *類型,分配成功後,被映射成虛擬內存地址。

mmap屬於系統調用,用戶控制項間接通過swi指令觸發軟中斷,進入內核態(各種環境的切換),進入內核態之後,便可以調用內核函數進行處理。 mmap->mmap64->__mmap2->sys_mmap2-> sys_mmap_pgoff ->do_mmap_pgoff

而 __NR_mmap在系統函數調用表中對應的減值如下:

通過系統調用,執行swi軟中斷,進入內核態,最終映射到call.S中的內核函數:sys_mmap2

sys_mmap2最終通過sys_mmap_pgoff在內核態完成後續邏輯。

sys_mmap_pgoff通過宏定義實現

進而調用do_mmap_pgoff:

get_unmapped_area用於為用戶空間找一塊內存區域,

current->mm->get_unmapped_area一般被賦值為arch_get_unmapped_area_topdown,

先找到合適的虛擬內存(用戶空間),幾經周轉後,調用相應文件或者設備驅動中的mmap函數,完成該設備文件的mmap,至於如何處理處理虛擬空間,要看每個文件的自己的操作了。

這里有個很關鍵的結構體

它是文件驅動操作的入口,在open的時候,完成file_operations的綁定,open流程跟mmap類似

先通過get_unused_fd_flags獲取個未使用的fd,再通過do_file_open完成file結構體的創建及初始化,最後通過fd_install完成fd與file的綁定。

重點看下path_openat:

拿Binder設備文件為例子,在注冊該設備驅動的時候,對應的file_operations已經注冊好了,

open的時候,只需要根根inode節點,獲取到file_operations既可,並且,在open成功後,要回調file_operations中的open函數

open後,就可以利用fd找到file,之後利用file中的file_operations *f_op調用相應驅動函數,接著看mmap。

Binder機制中mmap的最大特點是一次拷貝即可完成進程間通信 。Android應用在進程啟動之初會創建一個單例的ProcessState對象,其構造函數執行時會同時完成binder mmap,為進程分配一塊內存,專門用於Binder通信,如下。

第一個參數是分配地址,為0意味著讓系統自動分配,流程跟之前分子類似,先在用戶空間找到一塊合適的虛擬內存,之後,在內核空間也找到一塊合適的虛擬內存,修改兩個控制項的頁表,使得兩者映射到同一塊物力內存。

Linux的內存分用戶空間跟內核空間,同時頁表有也分兩類,用戶空間頁表跟內核空間頁表,每個進程有一個用戶空間頁表,但是系統只有一個內核空間頁表。而Binder mmap的關鍵是:也更新用戶空間對應的頁表的同時也同步映射內核頁表,讓兩個頁表都指向同一塊地址,這樣一來,數據只需要從A進程的用戶空間,直接拷貝拷貝到B所對應的內核空間,而B多對應的內核空間在B進程的用戶空間也有相應的映射,這樣就無需從內核拷貝到用戶空間了。

binder_update_page_range完成了內存分配、頁表修改等關鍵操作:

可以看到,binder一次拷貝的關鍵是,完成內存的時候,同時完成了內核空間跟用戶空間的映射,也就是說,同一份物理內存,既可以在用戶空間,用虛擬地址訪問,也可以在內核空間用虛擬地址訪問。

普通文件的訪問方式有兩種:第一種是通過read/write系統調訪問,先在用戶空間分配一段buffer,然後,進入內核,將內容從磁碟讀取到內核緩沖,最後,拷貝到用戶進程空間,至少牽扯到兩次數據拷貝;同時,多個進程同時訪問一個文件,每個進程都有一個副本,存在資源浪費的問題。

另一種是通過mmap來訪問文件,mmap()將文件直接映射到用戶空間,文件在mmap的時候,內存並未真正分配,只有在第一次讀取/寫入的時候才會觸發,這個時候,會引發缺頁中斷,在處理缺頁中斷的時候,完成內存也分配,同時也完成文件數據的拷貝。並且,修改用戶空間對應的頁表,完成到物理內存到用戶空間的映射,這種方式只存在一次數據拷貝,效率更高。同時多進程間通過mmap共享文件數據的時候,僅需要一塊物理內存就夠了。

共享內存是在普通文件mmap的基礎上實現的,其實就是基於tmpfs文件系統的普通mmap,有機會再分析,不再啰嗦。

Android中mmap原理及應用簡析

僅供參考,歡迎指正

『叄』 Linux - 用戶態內存映射 和 內核態內存映射

操作系統的內存管理,主要分為三個方面。
第一,物理內存的管理,相當於會議室管理員管理會議室。
第二,虛擬地址的管理,也即在項目組的視角,會議室的虛擬地址應該如何組織。
第三,虛擬地址和物理地址如何映射,也即會議室管理員如果管理映射表。

那麼虛擬地址和物理地址如何映射呢?

每一個進程都有一個列表vm_area_struct,指向虛擬地址空間的不同的內存塊,這個變數的名字叫mmap。

其實內存映射不僅僅是物理內存和虛擬內存之間的映射,還包括將文件中的內容映射到虛擬內存空間。這個時候,訪問內存空間就能夠訪問到文件裡面的數據。而僅有物理內存和虛擬內存的映射,是一種特殊情況。

如果我們要申請小塊內存,就用brk。brk函數之前已經解析過了,這里就不多說了。如果申請一大塊內存,就要用mmap。對於堆的申請來講,mmap是映射內存空間到物理內存。

另外,如果一個進程想映射一個文件到自己的虛擬內存空間,也要通過mmap系統調用。這個時候mmap是映射內存空間到物理內存再到文件。可見mmap這個系統調用是核心,我們現在來看mmap這個系統調用。

用戶態的內存映射機制包含以下幾個部分。

物理內存根據NUMA架構分節點。每個節點裡面再分區域。每個區域裡面再分頁。

物理頁面通過夥伴系統進行分配。分配的物理頁面要變成虛擬地址讓上層可以訪問,kswapd可以根據物理頁面的使用情況對頁面進行換入換出。

對於內存的分配需求,可能來自內核態,也可能來自用戶態。

對於內核態,kmalloc在分配大內存的時候,以及vmalloc分配不連續物理頁的時候,直接使用夥伴系統,分配後轉換為虛擬地址,訪問的時候需要通過內核頁表進行映射。

對於kmem_cache以及kmalloc分配小內存,則使用slub分配器,將夥伴系統分配出來的大塊內存切成一小塊一小塊進行分配。

kmem_cache和kmalloc的部分不會被換出,因為用這兩個函數分配的內存多用於保持內核關鍵的數據結構。內核態中vmalloc分配的部分會被換出,因而當訪問的時候,發現不在,就會調用do_page_fault。

對於用戶態的內存分配,或者直接調用mmap系統調用分配,或者調用malloc。調用malloc的時候,如果分配小的內存,就用sys_brk系統調用;如果分配大的內存,還是用sys_mmap系統調用。正常情況下,用戶態的內存都是可以換出的,因而一旦發現內存中不存在,就會調用do_page_fault。

『肆』 [轉]淺談Linux下的零拷貝機制

維基上是這么描述零拷貝的:零拷貝描述的是CPU不執行拷貝數據從一個存儲區域到另一個存儲區域的任務,這通常用於通過網路傳輸一個文件時以減少CPU周期和內存帶寬。

減少甚至完全避免不必要的CPU拷貝,從而讓CPU解脫出來去執行其他的任務
減少內存帶寬的佔用
通常零拷貝技術還能夠減少用戶空間和操作系統內核空間之間的上下文切換

從Linux系統上看,除了引導系統的BIN區,整個內存空間主要被分成兩個部分: 內核空間(Kernel space) 用戶空間(User space) 。「用戶空間」和「內核空間」的空間、操作許可權以及作用都是不一樣的。
內核空間是Linux自身使用的內存空間,主要提供給程序調度、內存分配、連接硬體資源等程序邏輯使用;
用戶空間則是提供給各個進程的主要空間。用戶空間不具有訪問內核空間資源的許可權,因此如果應用程序需要使用到內核空間的資源,則需要通過系統調用來完成:從用戶空間切換到內核空間,然後在完成相關操作後再從內核空間切換回用戶空間。

① 直接 I/O:對於這種數據傳輸方式來說,應用程序可以直接訪問硬體存儲,操作系統內核只是輔助數據傳輸。這種方式依舊存在用戶空間和內核空間的上下文切換,但是硬體上的數據不會拷貝一份到內核空間,而是直接拷貝至了用戶空間,因此直接I/O不存在內核空間緩沖區和用戶空間緩沖區之間的數據拷貝。

② 在數據傳輸過程中,避免數據在用戶空間緩沖區和系統內核空間緩沖區之間的CPU拷貝,以及數據在系統內核空間內的CPU拷貝。本文主要討論的就是該方式下的零拷貝機制。

③ -on-write(寫時復制技術):在某些情況下,Linux操作系統的內核空間緩沖區可能被多個應用程序所共享,操作系統有可能會將用戶空間緩沖區地址映射到內核空間緩存區中。當應用程序需要對共享的數據進行修改的時候,才需要真正地拷貝數據到應用程序的用戶空間緩沖區中,並且對自己用戶空間的緩沖區的數據進行修改不會影響到其他共享數據的應用程序。所以,如果應用程序不需要對數據進行任何修改的話,就不會存在數據從系統內核空間緩沖區拷貝到用戶空間緩沖區的操作。

下面我們通過一個Java非常常見的應用場景:將系統中的文件發送到遠端(該流程涉及:磁碟上文件 ——> 內存(位元組數組) ——> 傳輸給用戶/網路)來詳細展開傳統I/O操作和通過零拷貝來實現的I/O操作。

① 發出read系統調用:導致用戶空間到內核空間的上下文切換(第一次上下文切換)。通過DMA引擎將文件中的數據從磁碟上讀取到內核空間緩沖區(第一次拷貝: hard drive ——> kernel buffer)。
② 將內核空間緩沖區的數據拷貝到用戶空間緩沖區(第二次拷貝: kernel buffer ——> user buffer),然後read系統調用返回。而系統調用的返回又會導致一次內核空間到用戶空間的上下文切換(第二次上下文切換)。
③ 發出write系統調用:導致用戶空間到內核空間的上下文切換(第三次上下文切換)。將用戶空間緩沖區中的數據拷貝到內核空間中與socket相關聯的緩沖區中(即,第②步中從內核空間緩沖區拷貝而來的數據原封不動的再次拷貝到內核空間的socket緩沖區中。)(第三次拷貝: user buffer ——> socket buffer)。
④ write系統調用返回,導致內核空間到用戶空間的再次上下文切換(第四次上下文切換)。通過DMA引擎將內核緩沖區中的數據傳遞到協議引擎(第四次拷貝: socket buffer ——> protocol engine),這次拷貝是一個獨立且非同步的過程。

Q: 你可能會問獨立和非同步這是什麼意思?難道是調用會在數據被傳輸前返回?
A: 事實上調用的返回並不保證數據被傳輸;它甚至不保證傳輸的開始。它只是意味著將我們要發送的數據放入到了一個待發送的隊列中,在我們之前可能有許多數據包在排隊。除非驅動器或硬體實現優先順序環或隊列,否則數據是以先進先出的方式傳輸的。

總的來說,傳統的I/O操作進行了4次用戶空間與內核空間的上下文切換,以及4次數據拷貝。其中4次數據拷貝中包括了2次DMA拷貝和2次CPU拷貝。

Q: 傳統I/O模式為什麼將數據從磁碟讀取到內核空間緩沖區,然後再將數據從內核空間緩沖區拷貝到用戶空間緩沖區了?為什麼不直接將數據從磁碟讀取到用戶空間緩沖區就好?
A: 傳統I/O模式之所以將數據從磁碟讀取到內核空間緩沖區而不是直接讀取到用戶空間緩沖區,是為了減少磁碟I/O操作以此來提高性能。因為OS會根據局部性原理在一次read()系統調用的時候預讀取更多的文件數據到內核空間緩沖區中,這樣當下一次read()系統調用的時候發現要讀取的數據已經存在於內核空間緩沖區中的時候只要直接拷貝數據到用戶空間緩沖區中即可,無需再進行一次低效的磁碟I/O操作(注意:磁碟I/O操作的速度比直接訪問內存慢了好幾個數量級)。

Q: 既然系統內核緩沖區能夠減少磁碟I/O操作,那麼我們經常使用的BufferedInputStream緩沖區又是用來幹啥的?
A: BufferedInputStream的作用是會根據情況自動為我們預取更多的數據到它自己維護的一個內部位元組數據緩沖區中,這樣做能夠減少系統調用的次數以此來提供性能。

總的來說內核空間緩沖區的一大用處是為了減少磁碟I/O操作,因為它會從磁碟中預讀更多的數據到緩沖區中。而BufferedInputStream的用處是減少「系統調用」。

DMA(Direct Memory Access) ———— 直接內存訪問 :DMA是允許外設組件將I/O數據直接傳送到主存儲器中並且傳輸不需要CPU的參與,以此將CPU解放出來去完成其他的事情。
而用戶空間與內核空間之間的數據傳輸並沒有類似DMA這種可以不需要CPU參與的傳輸工具,因此用戶空間與內核空間之間的數據傳輸是需要CPU全程參與的。所有也就有了通過零拷貝技術來減少和避免不必要的CPU數據拷貝過程。

① 發出sendfile系統調用,導致用戶空間到內核空間的上下文切換(第一次上下文切換)。通過DMA引擎將磁碟文件中的內容拷貝到內核空間緩沖區中(第一次拷貝: hard drive ——> kernel buffer)。然後再將數據從內核空間緩沖區拷貝到內核中與socket相關的緩沖區中(第二次拷貝: kernel buffer ——> socket buffer)。
② sendfile系統調用返回,導致內核空間到用戶空間的上下文切換(第二次上下文切換)。通過DMA引擎將內核空間socket緩沖區中的數據傳遞到協議引擎(第三次拷貝: socket buffer ——> protocol engine)

總的來說,通過sendfile實現的零拷貝I/O只使用了2次用戶空間與內核空間的上下文切換,以及3次數據的拷貝。其中3次數據拷貝中包括了2次DMA拷貝和1次CPU拷貝。

Q: 但通過是這里還是存在著一次CPU拷貝操作,即,kernel buffer ——> socket buffer。是否有辦法將該拷貝操作也取消掉了?
A: 有的。但這需要底層操作系統的支持。從Linux 2.4版本開始,操作系統底層提供了scatter/gather這種DMA的方式來從內核空間緩沖區中將數據直接讀取到協議引擎中,而無需將內核空間緩沖區中的數據再拷貝一份到內核空間socket相關聯的緩沖區中。

從Linux 2.4版本開始,操作系統底層提供了帶有scatter/gather的DMA來從內核空間緩沖區中將數據讀取到協議引擎中。這樣一來待傳輸的數據可以分散在存儲的不同位置上,而不需要在連續存儲中存放。那麼從文件中讀出的數據就根本不需要被拷貝到socket緩沖區中去,只是需要將緩沖區描述符添加到socket緩沖區中去,DMA收集操作會根據緩沖區描述符中的信息將內核空間中的數據直接拷貝到協議引擎中。

① 發出sendfile系統調用,導致用戶空間到內核空間的上下文切換(第一次上下文切換)。通過DMA引擎將磁碟文件中的內容拷貝到內核空間緩沖區中(第一次拷貝: hard drive ——> kernel buffer)。
② 沒有數據拷貝到socket緩沖區。取而代之的是只有相應的描述符信息會被拷貝到相應的socket緩沖區當中。該描述符包含了兩方面的信息:a)kernel buffer的內存地址;b)kernel buffer的偏移量。
③ sendfile系統調用返回,導致內核空間到用戶空間的上下文切換(第二次上下文切換)。DMA gather 根據socket緩沖區中描述符提供的位置和偏移量信息直接將內核空間緩沖區中的數據拷貝到協議引擎上(第二次拷貝: kernel buffer ——> protocol engine),這樣就避免了最後一次CPU數據拷貝。

總的來說,帶有DMA收集拷貝功能的sendfile實現的I/O只使用了2次用戶空間與內核空間的上下文切換,以及2次數據的拷貝,而且這2次的數據拷貝都是非CPU拷貝。這樣一來我們就實現了最理想的零拷貝I/O傳輸了,不需要任何一次的CPU拷貝,以及最少的上下文切換。

在linux2.6.33版本之前 sendfile指支持文件到套接字之間傳輸數據,即in_fd相當於一個支持mmap的文件,out_fd必須是一個socket。但從linux2.6.33版本開始,out_fd可以是任意類型文件描述符。所以從linux2.6.33版本開始sendfile可以支持「文件到文件」和「文件到套接字」之間的數據傳輸。

Q: 對於上面的第三點,如果我們需要對數據進行操作該怎麼辦了?
A: Linux提供了mmap零拷貝來實現我們的需求。

mmap(內存映射)是一個比sendfile昂貴但優於傳統I/O的方法。

① 發出mmap系統調用,導致用戶空間到內核空間的上下文切換(第一次上下文切換)。通過DMA引擎將磁碟文件中的內容拷貝到內核空間緩沖區中(第一次拷貝: hard drive ——> kernel buffer)。
② mmap系統調用返回,導致內核空間到用戶空間的上下文切換(第二次上下文切換)。接著用戶空間和內核空間共享這個緩沖區,而不需要將數據從內核空間拷貝到用戶空間。因為用戶空間和內核空間共享了這個緩沖區數據,所以用戶空間就可以像在操作自己緩沖區中數據一般操作這個由內核空間共享的緩沖區數據。
③ 發出write系統調用,導致用戶空間到內核空間的上下文切換(第三次上下文切換)。將數據從內核空間緩沖區拷貝到內核空間socket相關聯的緩沖區(第二次拷貝: kernel buffer ——> socket buffer)。
④ write系統調用返回,導致內核空間到用戶空間的上下文切換(第四次上下文切換)。通過DMA引擎將內核空間socket緩沖區中的數據傳遞到協議引擎(第三次拷貝: socket buffer ——> protocol engine)

總的來說,通過mmap實現的零拷貝I/O進行了4次用戶空間與內核空間的上下文切換,以及3次數據拷貝。其中3次數據拷貝中包括了2次DMA拷貝和1次CPU拷貝。

FileChannel中大量使用了我們上面所提及的零拷貝技術。
FileChannel的map方法會返回一個MappedByteBuffer。MappedByteBuffer是一個直接位元組緩沖器,該緩沖器的內存是一個文件的內存映射區域。map方法底層是通過mmap實現的,因此將文件內存從磁碟讀取到內核緩沖區後,用戶空間和內核空間共享該緩沖區。
MappedByteBuffer內存映射文件是一種允許Java程序直接從內存訪問的一種特殊的文件。我們可以將整個文件或者整個文件的一部分映射到內存當中,那麼接下來是由操作系統來進行相關的頁面請求並將內存的修改寫入到文件當中。我們的應用程序只需要處理內存的數據,這樣可以實現非常迅速的I/O操作。

只讀模式來說,如果程序試圖進行寫操作,則會拋出ReadOnlyBufferException異常

讀寫模式表明,對結果對緩沖區所做的修改將最終廣播到文件。但這個修改可能會也可能不會被其他映射了相同文件程序可見。

私有模式來說,對結果緩沖區的修改將不會被廣播到文件並且也不會對其他映射了相同文件的程序可見。取而代之的是,它將導致被修改部分緩沖區獨自拷貝一份到用戶空間。這便是OS的「 on write」原則。

如果操作系統底層支持的話transferTo、transferFrom也會使用相關的零拷貝技術來實現數據的傳輸。所以,這里是否使用零拷貝必須依賴於底層的系統實現。

轉自: https://www.jianshu.com/p/e76e3580e356

熱點內容
安卓在哪裡找游戲 發布:2025-07-04 22:15:25 瀏覽:241
路由器訪問光貓 發布:2025-07-04 22:07:47 瀏覽:897
資料庫顯示語句 發布:2025-07-04 22:04:30 瀏覽:740
編程課道具 發布:2025-07-04 22:04:02 瀏覽:844
華為手機不是安卓什麼時候可以更新米加小鎮 發布:2025-07-04 22:01:37 瀏覽:785
飢荒伺服器搭建視頻 發布:2025-07-04 21:48:38 瀏覽:523
github上傳文件夾 發布:2025-07-04 21:29:22 瀏覽:1003
php課程學習中心 發布:2025-07-04 21:29:16 瀏覽:298
win7加密文件夾如何解密 發布:2025-07-04 21:25:24 瀏覽:555
為啥系統緩存的垃圾多呢 發布:2025-07-04 21:15:45 瀏覽:952