0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

malloc 申請內(nèi)存的兩種方式

科技綠洲 ? 來源:Linux開發(fā)架構(gòu)之路 ? 作者:Linux開發(fā)架構(gòu)之路 ? 2023-11-13 11:42 ? 次閱讀

我們知道m(xù)alloc() 并不是系統(tǒng)調(diào)用,也不是運算符,而是 C 庫里的函數(shù),用于動態(tài)分配內(nèi)存。

malloc 申請內(nèi)存的時候,會有兩種方式向操作系統(tǒng)申請堆內(nèi)存:

  • 方式一:通過 brk() 系統(tǒng)調(diào)用從堆分配內(nèi)存
  • 方式二:通過 mmap() 系統(tǒng)調(diào)用在文件映射區(qū)域分配內(nèi)存;

一、brk()系統(tǒng)調(diào)用

1、brk()的申請方式

一般如果用戶分配的內(nèi)存小于 128 KB,則通過 brk() 申請內(nèi)存。而brk()的實現(xiàn)的方式很簡單,就是通過 brk() 函數(shù)將堆頂指針向高地址移動,獲得新的內(nèi)存空間。如下圖:

圖片

malloc 通過 brk() 方式申請的內(nèi)存,free 釋放內(nèi)存的時候,并不會把內(nèi)存歸還給操作系統(tǒng),而是緩存在 malloc 的內(nèi)存池中,待下次使用,這樣就可以重復使用。

2、brk()系統(tǒng)調(diào)用的優(yōu)缺點

所以使用brk()方式的點很明顯:可以減少缺頁異常的發(fā)生,提高內(nèi)存訪問效率。

但它的缺點也同樣明顯:由于申請的內(nèi)存沒有歸還系統(tǒng),在內(nèi)存工作繁忙時,頻繁的內(nèi)存分配和釋放會造成內(nèi)存碎片。brk()方式之所以會產(chǎn)生內(nèi)存碎片,是由于brk通過移動堆頂?shù)奈恢脕矸峙鋬?nèi)存,并且使用完不會立即歸還系統(tǒng),重復使用,如果高地址的內(nèi)存不釋放,低地址的內(nèi)存是得不到釋放的。

正是由于使用brk()會出現(xiàn)內(nèi)存碎片,所以在我們申請大塊內(nèi)存的時候才會使用mmap()方式,mmap()是以頁為單位進行內(nèi)存分配和管理的,釋放后就直接歸還系統(tǒng)了,所以不會出現(xiàn)這種小碎片的情況。

3、brk()系統(tǒng)調(diào)用的優(yōu)化

一、Ptmalloc :malloc采用的是內(nèi)存池的管理方式,Ptmalloc 采用邊界標記法將內(nèi)存劃分成很多塊,從而對內(nèi)存的分配與回收進行管理。為了內(nèi)存分配函數(shù)malloc的高效性,ptmalloc會預先向操作系統(tǒng)申請一塊內(nèi)存供用戶使用,當我們申請和釋放內(nèi)存的時候,ptmalloc會將這些內(nèi)存管理起來,并通過一些策略來判斷是否將其回收給操作系統(tǒng)。這樣做的最大好處就是,使用戶申請和釋放內(nèi)存的時候更加高效,避免產(chǎn)生過多的內(nèi)存碎片。

二、Tcmalloc:Ptmalloc在性能上還是存在一些問題的,比如不同分配區(qū)(arena)的內(nèi)存不能交替使用,比如每個內(nèi)存塊分配都要浪費8字節(jié)內(nèi)存等等,所以一般傾向于使用第三方的malloc。

Tcmalloc是Google gperftools里的組件之一。全名是 thread cache malloc(線程緩存分配器)其內(nèi)存管理分為線程內(nèi)存和中央堆兩部分。

1.小塊內(nèi)部的分配:對于小塊內(nèi)存分配,其內(nèi)部維護了60個不同大小的分配器(實際源碼中看到的是86個),和ptmalloc不同的是,它的每個分配器的大小差是不同的,依此按8字節(jié)、16字節(jié)、32字節(jié)等間隔開。在內(nèi)存分配的時候,會找到最小符合條件的,比如833字節(jié)到1024字節(jié)的內(nèi)存分配請求都會分配一個1024大小的內(nèi)存塊。如果這些分配器的剩余內(nèi)存不夠了,會向中央堆申請一些內(nèi)存,打碎以后填入對應分配器中。同樣,如果中央堆也沒內(nèi)存了,就向中央內(nèi)存分配器申請內(nèi)存。

在線程緩存內(nèi)的60個分配器分別維護了一個大小固定的自由空間鏈表,直接由這些鏈表分配內(nèi)存的時候是不加鎖的。但是中央堆是所有線程共享的,在由其分配內(nèi)存的時候會加自旋鎖(spin lock)。

2.大內(nèi)存的分配:對于大內(nèi)存分配(大于8個分頁, 即32K),tcmalloc直接在中央堆里分配。中央堆的內(nèi)存管理是以分頁為單位的,同樣按大小維護了256個空閑空間鏈表,前255個分別是1個分頁、2個分頁到255個分頁的空閑空間,最后一個是更多分頁的小的空間。這里的空間如果不夠用,就會直接從系統(tǒng)申請了。

3.ptmalloc與tcmalloc的不足:都是針對小內(nèi)存分配和管理;對大塊內(nèi)存還是直接用了系統(tǒng)調(diào)用。應該盡量避免大內(nèi)存的malloc/new、free/delete操作。頻繁分配小內(nèi)存,例如:對bool、int、short進行new的時候,造成內(nèi)存浪費。

三、Jemalloc: jemalloc 是由 Jason Evans 在 FreeBSD 項目中引入的新一代內(nèi)存分配器。它是一個通用的malloc實現(xiàn),側(cè)重于減少內(nèi)存碎片和提升高并發(fā)場景下內(nèi)存的分配效率,其目標是能夠替代 malloc。下面是Jemalloc的兩個重要部分:

1.arena:arena 是 jemalloc 最重要的部分,內(nèi)存由一定數(shù)量的 arenas 負責管理。每個用戶線程都會被綁定到一個 arena 上,線程采用 round-robin 輪詢的方式選擇可用的 arena 進行內(nèi)存分配,為了減少線程之間的鎖競爭,默認每個 CPU 會分配 4 個 arena,各個 arena 所管理的內(nèi)存相互獨立。

struct arena_s {

	atomic_u_t		nthreads[2];
	tsdn_t		*last_thd;

	arena_stats_t		stats;  // arena的狀態(tài)

	ql_head(tcache_t)	tcache_ql;
	ql_head(cache_bin_array_descriptor_t)	cache_bin_array_descriptor_ql;
	malloc_mutex_t				tcache_ql_mtx;

	prof_accum_t		prof_accum;
	uint64_t		prof_accumbytes;

	atomic_zu_t		offset_state;

	atomic_zu_t		extent_sn_next;  // extent的序列號生成器狀態(tài)

	atomic_u_t		dss_prec;   

	atomic_zu_t		nactive;    // 激活的extents的page數(shù)量

	extent_list_t	large;      // 存放 large extent 的 extents

	malloc_mutex_t	large_mtx;  // large extent的鎖

	extents_t extents_dirty;    // 剛被釋放后空閑 extent 位于的地方

	extents_t extents_muzzy;    // extents_dirty 進行 lazy purge 后位于的地方,dirty - > muzzy

	extents_t extents_retained; // extents_muzzy 進行 decommit 或 force purge 后 extent 位于的地方,muzzy - > retained

	arena_decay_t	decay_dirty; // dirty -- > muzzy 

	arena_decay_t	decay_muzzy; // muzzy -- > retained 

	pszind_t		extent_grow_next;
	pszind_t		retain_grow_limit;
	malloc_mutex_t		extent_grow_mtx;

	extent_tree_t		extent_avail;     // heap,存放可用的 extent 元數(shù)據(jù)

	malloc_mutex_t		extent_avail_mtx; // extent_avail的鎖

	bin_t			bins[NBINS];      // 所有用于分配小內(nèi)存的 bin

	base_t			*base;            // 用于分配元數(shù)據(jù)的 base

	nstime_t		create_time;      // 創(chuàng)建時間
};

2.extent:管理 jemalloc 內(nèi)存塊(即用于用戶分配的內(nèi)存)的結(jié)構(gòu),每一個內(nèi)存塊大小可以是 N * page_size(4KB)(N >= 1)。每個 extent 有一個序列號(serial number)。一個 extent 可以用來分配一次 large_class 的內(nèi)存申請,但可以用來分配多次 small_class 的內(nèi)存申請。

struct extent_s {
    uint64_t		e_bits; // 8字節(jié)長,記錄多種信息

    void			*e_addr; // 管理的內(nèi)存塊的起始地址

    union {
		size_t	e_size_esn; // extent和序列號的大小
		size_t	e_bsize;    // 基本extent的大小
	};

    union {
		/* 
         * S位圖,當此 extent 用于分配 small_class 內(nèi)存時,用來記錄這個 extent 的分配情況,        
         * 此時每個 extent 的內(nèi)的小內(nèi)存稱為 region 
         */
		arena_slab_data_t	e_slab_data; 

		atomic_p_t		e_prof_tctx; // 一個計數(shù)器,用于large object
	};
}

二、mmap()系統(tǒng)調(diào)用

1、mmap基礎概念

mmap 是一種內(nèi)存映射文件的方法,即將一個文件或者其他對象映射到進程的地址空間,實現(xiàn)文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一映射關系。

實現(xiàn)這樣的映射關系后,進程就可以采用指針的方式讀寫操作這一段內(nèi)存,而系統(tǒng)會自動回寫臟頁面到對應的文件磁盤上,即完成了對文件的操作而不必調(diào)用read,write等系統(tǒng)調(diào)用函數(shù)。相反,內(nèi)核空間的這段區(qū)域的修改也直接反映用戶空間,從而可以實現(xiàn)不同進程的文件共享。如下圖所示:

圖片

由上圖可以看出,進程的虛擬地址空間,由多個虛擬內(nèi)存區(qū)域構(gòu)成。虛擬內(nèi)存區(qū)域是進程的虛擬地址空間中的一個同質(zhì)區(qū)間,即具有同樣特性的連續(xù)地址范圍。上圖中所示的text數(shù)據(jù)段、初始數(shù)據(jù)段、Bss數(shù)據(jù)段、堆、棧、內(nèi)存映射,都是一個獨立的虛擬內(nèi)存區(qū)域。而為內(nèi)存映射服務的地址空間處在堆棧之間的空余部分。

linux 內(nèi)核使用的vm_area_struct 結(jié)構(gòu)來表示一個獨立的虛擬內(nèi)存區(qū)域,由于每個不同質(zhì)的虛擬內(nèi)存區(qū)域功能和內(nèi)部機制不同;因此同一個進程使用多個vm_area_struct 結(jié)構(gòu)來分別表示不同類型的虛擬內(nèi)存區(qū)域。各個vm_area_struct 結(jié)構(gòu)使用鏈表或者樹形結(jié)構(gòu)鏈接,方便進程快速訪問。如下圖所示:

圖片

vm_area_struct 結(jié)構(gòu)中包含區(qū)域起始和終止地址以及其他相關信息,同時也包含一個vm_ops 指針,其內(nèi)部可引出所有針對這個區(qū)域可以使用的系統(tǒng)調(diào)用函數(shù)。這樣,進程對某一虛擬內(nèi)存區(qū)域的任何操作都需要的信息,都可以從vm_area_struct 中獲得。mmap函數(shù)就是要創(chuàng)建一個新的vm_area_struct結(jié)構(gòu) ,并將其與文件的物理磁盤地址相連。

2、mmap 內(nèi)存映射原理

mmap 內(nèi)存映射實現(xiàn)過程,總的來說可以分為三個階段:

(一)進程啟動映射過程,并在虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域

1、進程在用戶空間調(diào)用函數(shù)mmap ,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

2、在當前進程虛擬地址空間中,尋找一段空閑的滿足要求的連續(xù)的虛擬地址

3、為此虛擬區(qū)分配一個vm_area_struct 結(jié)構(gòu),接著對這個結(jié)構(gòu)各個區(qū)域進行初始化

4、將新建的虛擬區(qū)結(jié)構(gòu)(vm_area_struct)插入進程的虛擬地址區(qū)域鏈表或樹中

(二)調(diào)用內(nèi)核空間的系統(tǒng)調(diào)用函數(shù)mmap (不同于用戶空間函數(shù)),實現(xiàn)文件物理地址和進程虛擬地址的一一映射關系

5、為映射分配新的虛擬地址區(qū)域后,通過待映射的文件指針,在文件描述符表中找到對應的文件描述符,通過文件描述符,鏈接到內(nèi)核“已打開文集”中該文件結(jié)構(gòu)體,每個文件結(jié)構(gòu)體維護者和這個已經(jī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ù)建立頁表,即實現(xiàn)了文件地址和虛擬地址區(qū)域的映射關系。此時,這片虛擬地址并沒有任何數(shù)據(jù)關聯(lián)到主存中。

(三)進程發(fā)起對這片映射空間的訪問,引發(fā)缺頁異常,實現(xiàn)文件內(nèi)容到物理內(nèi)存(主存)的拷貝。

前兩個階段僅在于創(chuàng)建虛擬區(qū)間并完成地址映射,但是并沒有將任何文件數(shù)據(jù)拷貝至主存。真正的文件讀取是當進程發(fā)起讀或者寫操作時。

9、進程的讀寫操作訪問虛擬地址空間這一段映射地址后,通過查詢頁表,先這一段地址并不在物理頁面。因為目前只建立了映射,真正的硬盤數(shù)據(jù)還沒有拷貝到內(nèi)存中,因此引發(fā)缺頁異常。

10、缺頁異常進行一系列判斷,確定無法操作后,內(nèi)核發(fā)起請求掉頁過程。

11、調(diào)頁過程先在交換緩存空間中尋找需要訪問的內(nèi)存頁,,如果沒有則調(diào)用nopage函數(shù)把所缺的頁從磁盤裝入到主存中。

12、之后進程即可對這片主存進行讀或者寫的操作了,如果寫操作改變了內(nèi)容,一定時間后系統(tǒng)自動回寫臟頁面到對應的磁盤地址,也即完成了寫入到文件的過程。

注:修改過的臟頁面并不會立即更新回文件,而是有一段時間延遲,可以調(diào)用msync() 來強制同步,這樣所寫的內(nèi)容就能立即保存到文件里了。

3、mmap優(yōu)點

1、對文件的讀取操作跨過了頁緩存,減少了數(shù)據(jù)的拷貝次數(shù),用內(nèi)存讀寫取代了I/O讀寫,提高了讀取的效率。

2、實現(xiàn)了用戶空間和內(nèi)核空間的高校交互方式,兩空間的各自修改操作可以直接反映在映射的區(qū)域內(nèi),從而被對方空間及時捕捉。

3、提供進程間共享內(nèi)存及互相通信的方式。不管是父子進程還是無親緣關系進程,都可以將自身空間用戶映射到同一個文件或者匿名映射到同一片區(qū)域。從而通過各自映射區(qū)域的改動,打到進程間通信和進程間共享的目的。

同時,如果進程A和進程 B 都映射了區(qū)域C,當A第一次讀取C時候,通過缺頁從磁盤復制文件頁到內(nèi)存中,但當B再讀C的相同頁面時,雖然也會產(chǎn)生缺頁異常,但是不會從磁盤中復制文件過來,而是直接使用已經(jīng)保存再內(nèi)存中的文件數(shù)據(jù)。

4、適用場景

可用于實現(xiàn)高效的大規(guī)模數(shù)據(jù)傳輸。內(nèi)存空間不足,是制約大數(shù)據(jù)操作的一個方面,解決方案往往是借助于硬盤空間的協(xié)助,補充內(nèi)存的不足。但是進一步造成大量的文件I/O操作,極大影響效率。這個問題可以通過mmap映射很好地解決。換句話說,但凡需要磁盤空間代替內(nèi)存的時候,mmap都可以發(fā)揮功效。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)存
    +關注

    關注

    8

    文章

    2966

    瀏覽量

    73812
  • 操作系統(tǒng)

    關注

    37

    文章

    6684

    瀏覽量

    123140
  • 函數(shù)
    +關注

    關注

    3

    文章

    4277

    瀏覽量

    62323
  • malloc
    +關注

    關注

    0

    文章

    52

    瀏覽量

    62
收藏 人收藏

    評論

    相關推薦

    C語言malloc申請內(nèi)存時的碎片問題

    解決問題:malloc申請內(nèi)存的時候,內(nèi)存碎片問題會導致原本內(nèi)存大小足夠,卻申請
    發(fā)表于 08-06 16:58 ?1576次閱讀
    C語言<b class='flag-5'>malloc</b><b class='flag-5'>申請</b><b class='flag-5'>內(nèi)存</b>時的碎片問題

    可以在RTOS的任何位置使用malloc申請內(nèi)存了嗎?

    今天看了CubeIDE 1.7.0的release note ,里面寫的一條新特性是:Thread-safe malloc solution 這是否意味著我可以在RTOS的任何位置使用malloc申請
    發(fā)表于 04-03 07:23

    兩種采樣方式

    兩種采樣方式.....................................
    發(fā)表于 08-08 15:04

    為什么要使用 malloc()是動態(tài)內(nèi)存分配函數(shù)?

    1、相對于自動分配內(nèi)存,malloc()函數(shù)申請分配的內(nèi)存地址有什么不同?比如:int x[100] int * x = (int *)malloc
    發(fā)表于 06-26 16:41

    簡單的內(nèi)存管理方式

    這些內(nèi)存,這種方式和我們學習 C 語言時,使用 malloc 和 free 函數(shù)來申請內(nèi)存內(nèi)存
    發(fā)表于 01-14 17:17

    SQL語言的兩種使用方式

    SQL語言的兩種使用方式在終端交互方式下使用,稱為交互式SQL嵌入在高級語言的程序中使用,稱為嵌入式SQL―高級語言如C、Java等,稱為宿主語言嵌入式SQL的實現(xiàn)方式源程序(用主語言
    發(fā)表于 12-20 06:51

    程序內(nèi)存分布RW Size是否包含malloc申請的大小?

    程序內(nèi)存分布RW Size是否包含malloc申請的大小
    發(fā)表于 10-16 06:46

    逆變器的兩種電流型控制方式

    逆變器的兩種電流型控制方式 摘要:研究分析了逆變器的兩種雙環(huán)瞬時反饋控制方式——電流型準PWM控制方式和三態(tài)DPM
    發(fā)表于 07-10 11:21 ?3970次閱讀
    逆變器的<b class='flag-5'>兩種</b>電流型控制<b class='flag-5'>方式</b>

    Wincc如何與PLC進行通訊兩種常用的方式介紹

    西門子WINCC與SiemensPLC通訊連接有多種方式,下面介紹兩種常用的通訊方式。
    的頭像 發(fā)表于 02-17 09:27 ?3w次閱讀
    Wincc如何與PLC進行通訊<b class='flag-5'>兩種</b>常用的<b class='flag-5'>方式</b>介紹

    關于stm32 MCU申請動態(tài)內(nèi)存malloc的認識

    首先,malloc( )屬于標準C語言函數(shù),當然可以在單片機上使用,如STM32可以先在啟動文件中設置heap的大小,再使用動態(tài)內(nèi)存分配: Heap_Size EQU 0x00000200 \\也就
    發(fā)表于 11-18 16:21 ?14次下載
    關于stm32 MCU<b class='flag-5'>申請</b>動態(tài)<b class='flag-5'>內(nèi)存</b><b class='flag-5'>malloc</b>的認識

    記錄單片機使用malloc產(chǎn)生內(nèi)存泄露的問題及解決方法

    項目場景:單片機使用malloc產(chǎn)生內(nèi)存泄露的問題問題描述:bug1:創(chuàng)建了一個結(jié)構(gòu)體指針,通過malloc動態(tài)開辟內(nèi)存方式開辟了一段
    發(fā)表于 12-03 10:21 ?8次下載
    記錄單片機使用<b class='flag-5'>malloc</b>產(chǎn)生<b class='flag-5'>內(nèi)存</b>泄露的問題及解決方法

    malloc和free簡介及實現(xiàn)方式說明

    malloc 分配指定大小的內(nèi)存空間,返回一個指向該空間的指針。大小以字節(jié)為單位。返回 void* 指針,需要強制類型轉(zhuǎn)換后才能引用其中的值。 free 釋放一個由 malloc 所分配的內(nèi)存
    的頭像 發(fā)表于 05-14 09:56 ?4509次閱讀
    <b class='flag-5'>malloc</b>和free簡介及實現(xiàn)<b class='flag-5'>方式</b>說明

    在MATLAB/simulink中建模時的兩種不同實現(xiàn)方式

    導讀:本期文章主要介紹在MATLAB/simulink中建模時的兩種不同實現(xiàn)方式,一是直接用現(xiàn)成的文件庫中的模塊進行搭建,一是用Sfunction代碼實現(xiàn)。接下來以電壓型磁鏈觀測器
    的頭像 發(fā)表于 09-15 10:07 ?1810次閱讀

    MATLAB/simulink中兩種實現(xiàn)建模方式的優(yōu)勢

    導讀:本期文章主要介紹在MATLAB/simulink中建模時的兩種不同實現(xiàn)方式,一是直接用現(xiàn)成的文件庫中的模塊進行搭建,一是用Sfunction代碼實現(xiàn)。接下來以電壓型磁鏈觀測器
    的頭像 發(fā)表于 09-15 10:10 ?4876次閱讀

    什么是堆內(nèi)存?存儲方式是什么樣的?

    的存儲方式。 C 代碼中動態(tài)申請內(nèi)存申請函數(shù)是 malloc ,常見的內(nèi)存代碼如下圖所示:
    的頭像 發(fā)表于 06-22 10:29 ?1115次閱讀
    什么是堆<b class='flag-5'>內(nèi)存</b>?存儲<b class='flag-5'>方式</b>是什么樣的?